Users can connect their own local-hardware nodes into a fleet. Each node runs a
new Rust daemon that dials home over an outbound WebSocket, reports host health,
and runs commands we send.
Backend:
- migrations/0018_fleet_nodes.sql: nodes + node_health tables + agent_containers
(node_id, workspace_id) index. cm-domain NodeId.
- cm-db repo/nodes.rs: create/auth/list+health/get/heartbeat/set_status/delete
(unchecked sqlx, no .sqlx regen).
- cm-api fleet.rs NodeHub: live daemon channels (node_id→sender) + the WS channel
runner (heartbeat→DB upsert, exec request/response framing). routes/nodes.rs:
POST /pair, GET /nodes, SSE /nodes/live, POST /{id}/exec-test, DELETE /{id},
WS /nodes/agent (token-auth). Wired into AppState + router.
Daemon (new crate crates/bins/clawmates-node):
- sysinfo host metrics (cpu/mem/pressure/swap/disk/load/containers), outbound WSS
dial + reconnect, heartbeat loop, exec command handling, tailscale-ip probe.
install.sh convenience installer.
Frontend:
- Fleet sidebar item + FleetOverview + LocalHardware node-health cards (live via
/api/nodes, 3s poll) + ConnectHostWizard (install → verify connection →
exec-test). InfraStage dispatches fleet/local; default selection = fleet.
Deferred: P1 (BYO Tailscale + network metrics), P2 (RemoteDriver + placement so
agents actually run on connected nodes).
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
37 lines
1.9 KiB
SQL
37 lines
1.9 KiB
SQL
-- Fleet: user-connected local-hardware nodes. Each node runs the clawmates-node
|
|
-- daemon, which dials home over an outbound control channel to report host health
|
|
-- and accept workload-placement commands. (Phase 2 of the multi-node node-pool;
|
|
-- agent_containers.node_id references the chosen node, 'local' by default.)
|
|
CREATE TABLE nodes (
|
|
id UUID PRIMARY KEY,
|
|
workspace_id UUID NOT NULL REFERENCES workspaces (id) ON DELETE CASCADE,
|
|
name TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending', -- pending | online | offline | draining
|
|
token TEXT NOT NULL UNIQUE, -- shared secret the daemon presents on the channel
|
|
agent_version TEXT,
|
|
tailscale_ip TEXT,
|
|
last_seen TIMESTAMPTZ,
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
|
);
|
|
CREATE INDEX nodes_workspace_idx ON nodes (workspace_id);
|
|
|
|
-- Latest host-health snapshot per node (upserted by the daemon heartbeat).
|
|
CREATE TABLE node_health (
|
|
node_id UUID PRIMARY KEY REFERENCES nodes (id) ON DELETE CASCADE,
|
|
captured_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
cpu_pct DOUBLE PRECISION NOT NULL DEFAULT 0, -- 0..100
|
|
mem_total BIGINT NOT NULL DEFAULT 0, -- bytes
|
|
mem_used BIGINT NOT NULL DEFAULT 0,
|
|
mem_pressure DOUBLE PRECISION NOT NULL DEFAULT 0, -- 0..1 (used/total, or PSI if available)
|
|
swap_used BIGINT NOT NULL DEFAULT 0,
|
|
disk_total BIGINT NOT NULL DEFAULT 0,
|
|
disk_free BIGINT NOT NULL DEFAULT 0,
|
|
load1 DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
load5 DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
load15 DOUBLE PRECISION NOT NULL DEFAULT 0,
|
|
container_count INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
-- Placement lookups (which containers run on which node, per workspace).
|
|
CREATE INDEX agent_containers_node_idx ON agent_containers (node_id, workspace_id);
|