## 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 ✓
43 lines
2.3 KiB
SQL
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';
|