Files
GoClaw/drizzle/0006_swarm_nodes.sql
bboxwtf c57d694236 feat(phase21): real Docker Swarm management — live nodes, services, tasks, host shell, agent deployment
## What's implemented

### Go Gateway — New /api/swarm/* endpoints (handlers.go + docker/client.go + db.go)
- GET  /api/swarm/info          — swarm state, manager address, join tokens
- GET  /api/swarm/nodes         — live node list (hostname, IP, CPU, RAM, role, labels)
- POST /api/swarm/nodes/{id}/label        — add/update node label
- POST /api/swarm/nodes/{id}/availability — set node availability (active|pause|drain)
- GET  /api/swarm/services       — all swarm services with replica counts
- POST /api/swarm/services/create — deploy a new agent as a swarm service
- GET  /api/swarm/services/{id}/tasks  — tasks per service (which node runs which replica)
- POST /api/swarm/services/{id}/scale  — scale replicas
- GET  /api/swarm/join-token    — worker/manager join command with token + manager addr
- POST /api/swarm/shell         — execute commands on the HOST via nsenter PID 1

### Docker client (client.go)
- ListServices, GetService, ScaleService, ListServiceTasks, CreateAgentService
- AddNodeLabel, UpdateNodeAvailability (patch node spec via Docker API)
- ExecOnHost (nsenter -t 1 → falls back to container scope)

### DB persistence (db.go)
- UpsertSwarmNodes — stores live node state to swarmNodes table
- UpsertSwarmTokens / GetSwarmTokens — persist join tokens
- Startup goroutine in main.go syncs tokens to DB on gateway start

### Node.js tRPC wrappers (routers.ts + gateway-proxy.ts)
- nodes.swarmInfo, nodes.list, nodes.services, nodes.serviceTasks
- nodes.scaleService, nodes.joinToken, nodes.execShell
- nodes.addNodeLabel, nodes.setAvailability, nodes.deployAgentService

### Frontend — Nodes.tsx (complete rewrite)
- Real swarm overview cards (nodes, managers, services, running tasks)
- Join token cards with copy button for worker & manager tokens
- Node cards with inline availability selector (active/pause/drain) + add-label form
- Services table with Scale dialog + Tasks drawer (replica → node mapping)
- Deploy Agent dialog (image, replicas, env vars, published port)
- Host Shell tab with command history and quick-command buttons

### docker-compose.yml
- gateway now runs with privileged: true + pid: host
  → nsenter can access the host PID namespace for real host-level shell execution

## Verified end-to-end
- GET /api/swarm/info returns manager addr + join tokens ✓
- GET /api/swarm/nodes returns node wsm (2 cores, 3.9 GB) ✓
- POST /api/swarm/services/create → deployed goclaw-test-agent (2 replicas) ✓
- GET /api/swarm/services/{id}/tasks returns task list with nodeId ✓
- POST /api/swarm/services/{id}/scale → scale to 0 ✓
- POST /api/swarm/shell {command:'docker node ls'} → real host output ✓
- tRPC chain: browser → control-center → gateway → docker.sock ✓
2026-03-21 17:23:32 +00:00

43 lines
2.3 KiB
SQL

-- ─── GoClaw Phase 21: Real Docker Swarm Management ────────────────────────────
-- swarmNodes: persistent record of each node in the swarm
-- Stores the advertise address, join tokens, role, labels,
-- and custom domain/port mapping so the UI can show connection info.
CREATE TABLE IF NOT EXISTS `swarmNodes` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`nodeId` VARCHAR(64) NOT NULL UNIQUE, -- Docker node ID
`hostname` VARCHAR(128) NOT NULL,
`role` ENUM('manager','worker') NOT NULL DEFAULT 'worker',
`state` ENUM('ready','down','disconnected') NOT NULL DEFAULT 'ready',
`availability` ENUM('active','pause','drain') NOT NULL DEFAULT 'active',
`advertiseAddr` VARCHAR(128), -- IP:port used by swarm
`domain` VARCHAR(256), -- optional custom domain
`sshPort` INT DEFAULT 22,
`labels` JSON,
`engineVersion` VARCHAR(64),
`cpuCores` INT DEFAULT 0,
`memTotalMB` BIGINT DEFAULT 0,
`isManager` TINYINT(1) DEFAULT 0,
`isLeader` TINYINT(1) DEFAULT 0,
`lastSeenAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`createdAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX `swarmNodes_role_idx` (`role`),
INDEX `swarmNodes_state_idx` (`state`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- swarmTokens: stores join tokens (manager + worker) for the swarm
-- Only one row (the current swarm). Updated by the Go gateway on startup.
CREATE TABLE IF NOT EXISTS `swarmTokens` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`managerToken` TEXT,
`workerToken` TEXT,
`managerAddr` VARCHAR(128), -- IP:2377 to join as manager/worker
`updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Add serviceId + replicas columns to agents so each agent maps to a swarm service
ALTER TABLE `agents`
ADD COLUMN IF NOT EXISTS `serviceId` VARCHAR(128) DEFAULT NULL COMMENT 'Docker Swarm service ID',
ADD COLUMN IF NOT EXISTS `serviceName` VARCHAR(128) DEFAULT NULL COMMENT 'Docker Swarm service name',
ADD COLUMN IF NOT EXISTS `replicas` INT DEFAULT 1 COMMENT 'Desired replica count';