colibri/packaging/mother/MOTHER-SETUP.md
Sam & Claude e941fdd494
Some checks failed
CI / rust (pull_request) Has been cancelled
CI / markdown (pull_request) Has been cancelled
CI / port (pull_request) Has been cancelled
CI / agent-jail-pkgs (pull_request) Has been cancelled
mother: rename usb_nodes→hive_nodes (+node_type), harden setup/register
Review pass on the mother MCP infra:

- Rename usb_nodes → hive_nodes: a node is any host that joined the hive
  (live-usb/disk/vps/mother), not just a USB boot. Add a first-class
  node_type column (live-usb|disk|vps|mother|unknown). The schema migrates an
  existing osa DB in place (ALTER TABLE + ALTER SEQUENCE, guarded by
  to_regclass) and ADD COLUMN IF NOT EXISTS for already-renamed tables — data
  preserved, idempotent. FKs/trigger/indexes follow.
- node-register-mcp: accepts + validates node_type, UPSERTs into hive_nodes.
  Add ON_ERROR_STOP=1 (psql otherwise exits 0 on SQL error → false success)
  and fold stderr into the captured result so failures are reported.
- setup-mother.sh: apply schema BEFORE granting on its tables (fresh installs
  had no tables when grants ran); pipe the schema via stdin so the postgres
  user need not read the repo checkout; locate pg_hba via SHOW hba_file (was
  hardcoded) and PREPEND the peer rule (pg_hba is first-match); grants target
  hive_nodes/hive_nodes_id_seq.
- build-colibri.sh: fast-forward a checked-out branch to origin so it builds
  current upstream code, not a stale local copy.

Validated: prettier + sh -n green. Schema migration/UPSERT to be exercised on
osa (no local postgres server here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 09:45:39 +02:00

6 KiB

Mother Node MCP Setup

What this is

The mother node (OSA) runs the Colibri MCP host for USB operator node coordination. USB nodes send hardware profiles via SSH → MCP → PostgreSQL. This directory contains the scripts and schema for that infrastructure.

Files

File Purpose
setup-mother.sh Idempotent deploy — run once as root, safe to re-run
mother_schema.sql PostgreSQL schema (hive_nodes, build_queue, audit_log + triggers)
node-register-mcp MCP tool: receive hw-probe JSON → UPSERT into hive_nodes
geodesic-dome-mcp MCP tool: geodesic dome wireframe + structural BOM
build-colibri.sh MCP tool: build a colibri crate (branch-allowlisted)
colibri-mcp-ssh SSH forced-command wrapper (allowlisted: "", "tools" only)

Architecture

USB node                       Mother (osa)
────────                       ────────────
clawdie-hw-probe               PostgreSQL (mother_hive)
  │                              ▲
  ▼                              │
colibri-daemon                  │ INSERT / UPSERT
  │ (external MCP)               │
  ▼                              │
ssh colibri@mother (no cmd)      │
  │ (mother-mcp key)             │
  ▼                              │
colibri-mcp-ssh ◄─ authorized_keys command="..."
  │  allowlist: "" or "tools"
  ▼
colibri-mcp ──► node-register-mcp ──► psql -v :'variable' (heredoc)
                  (parameterized UPSERT, no shell interpolation of SQL)

The USB's colibri-mcp spawns ssh -i ~/.ssh/mother-mcp colibri@mother with NO remote command. SSH is restricted via command= + restrict to run only colibri-mcp-ssh, which delegates to colibri-mcp in stdio MCP mode. The node_register tool on mother receives the hw-probe JSON and UPSERTs it into PostgreSQL using psql -v :'variable' quoting in a heredoc — the JSON blob is a bound variable, never interpolated into SQL.

Security properties

  • colibri-mcp-ssh: allowlists SSH_ORIGINAL_COMMAND to "" (stdio MCP mode) or "tools" (one-shot discovery). All other values are rejected. This prevents callers from passing arbitrary colibri-mcp flags through the forced-command boundary.
  • build-colibri.sh: branch is validated against an allowlist (main, semver v* tags, plus COLIBRI_BUILD_ALLOW_BRANCHES colon-separated extras). Features are validated against ^[A-Za-z0-9_,-]+$. Cargo args use a shell array — no unquoted string concatenation.
  • node-register-mcp: psql -v :'variable' quoting in a heredoc (not -c). The JSON blob is dollar-quoted by psql; shell never interpolates it into SQL.
  • mother-mcp key: NOT baked into images, NOT reused for Forgejo. Lives on seed partition (CLAWDIESEED/colibri/ssh/mother-mcp). One key per trust domain — blast radius is MCP-over-SSH only.

Setup (one-time)

On mother, as root:

# Build colibri from current main first, then:
cd /home/clawdie/ai/colibri
sudo ./packaging/mother/setup-mother.sh

# The script prints the mother-mcp private key — copy it for USB nodes.

What setup-mother.sh does

  1. Installs colibri binaries from target/release
  2. Installs MCP server scripts
  3. jq-merges mother servers into /usr/local/etc/colibri/external-mcp.json
  4. Creates colibri OS user + SSH authorized_keys with command= wrapper
  5. Generates mother-mcp keypair (prints private key for seed placement)
  6. Configures PostgreSQL peer auth for colibri on mother_hive
  7. Runs mother_schema.sql (idempotent)
  8. Updates colibri daemon env (COLIBRI_AUTOSPAWN, COLIBRI_MCP_EXTERNAL_CALL)
  9. Restarts colibri_daemon

Idempotent — running again is a no-op.

Key management

The mother-mcp key is NOT baked into images. It lives:

  • On mother: /var/db/colibri/.ssh/mother-mcp + .pub (auto-generated by setup-mother.sh)
  • On USB nodes: placed on the seed partition at CLAWDIESEED/colibri/ssh/mother-mcp (the live-seed importer picks it up at first boot and installs it at ~/.ssh/mother-mcp)

One key per trust domain. The mother-mcp key is not reused for Forgejo or other services — its blast radius is limited to MCP over SSH.

USB-side external-mcp.json

The USB node registers mother as an external MCP server. Note: NO remote command — the forced-command wrapper handles entry:

{
  "servers": {
    "mother": {
      "command": "ssh",
      "args": [
        "-i", "/home/clawdie/.ssh/mother-mcp",
        "-o", "StrictHostKeyChecking=accept-new",
        "colibri@100.72.229.63"
      ],
      "env": {}
    }
  }
}

The colibri-daemon spawner pipes JSON-RPC on stdin/stdout. SSH connects with an empty remote command; the forced-command wrapper starts colibri-mcp in stdio MCP mode automatically.

Verification

# On mother: check the external MCP tools are live
colibri-mcp tools 2>&1 | grep external

# On mother: test node_register with a sample hw-probe
sudo clawdie-hw-probe 2>/dev/null | \
  jq '{jsonrpc:"2.0",method:"tools/call",id:1,
       params:{name:"node_register",
               arguments:{hostname:"test-node",hw_profile:.}}}' | \
  /usr/local/bin/node-register-mcp

# Check PostgreSQL
sudo -u colibri psql -d mother_hive -c \
  "SELECT hostname, status, capabilities FROM hive_nodes;"

# From a USB node with the mother-mcp key:
ssh colibri@mother tools | grep node_register

Adding a USB node

  1. Copy the mother-mcp private key to the USB node's seed partition (CLAWDIESEED/colibri/ssh/mother-mcp)
  2. On the USB, install external-mcp.json as shown above
  3. On the USB, install clawdie-hw-probe from clawdie-iso
  4. Restart colibri_daemon — autospawn runs hw-probe and registers the node with mother

See docs/USB-MOTHER-MCP-CONNECTION.md in clawdie-iso for the full USB-side procedure.