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>
88 lines
3.8 KiB
Bash
Executable file
88 lines
3.8 KiB
Bash
Executable file
#!/bin/sh
|
|
# node-register — MCP tool: register a hive node's hardware profile in PostgreSQL.
|
|
#
|
|
# A node is any host that joined the hive (live-usb, disk, vps, mother), keyed
|
|
# by hostname. Accepts a JSON-RPC tools/call request on stdin, UPSERTs the
|
|
# hw_profile into mother_hive.hive_nodes, and returns the result to stdout. The
|
|
# derive_capabilities() trigger auto-computes has_gpu, gpu_vendor,
|
|
# can_run_local_llm, has_wifi, etc. on INSERT/UPDATE.
|
|
#
|
|
# Expected input:
|
|
# {"jsonrpc":"2.0","method":"tools/call","id":1,"params":{"name":"node_register","arguments":{"hostname":"clawdie-node","node_type":"live-usb","hw_profile":{...}}}}
|
|
#
|
|
# Output on success:
|
|
# {"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"{\"registered\":true,\"hostname\":\"clawdie-node\",\"capabilities\":{...}}"}]}}
|
|
#
|
|
# Security: psql :'variable' substitution expands to a safely single-quoted SQL
|
|
# string literal — no shell interpolation touches the JSON blobs.
|
|
#
|
|
# PostgreSQL access: peer auth for the 'colibri' OS user. The operator must
|
|
# run once as postgres (or setup-mother.sh does it):
|
|
# CREATE ROLE colibri WITH LOGIN;
|
|
# GRANT CONNECT ON DATABASE mother_hive TO colibri;
|
|
# GRANT INSERT, UPDATE ON hive_nodes TO colibri;
|
|
# GRANT USAGE ON SEQUENCE hive_nodes_id_seq TO colibri;
|
|
set -eu
|
|
|
|
DB="mother_hive"
|
|
|
|
# Read JSON-RPC from stdin
|
|
INPUT=$(cat)
|
|
ID=$(echo "$INPUT" | jq -r '.id // "1"')
|
|
HOSTNAME=$(echo "$INPUT" | jq -r '.params.arguments.hostname // ""')
|
|
NODE_TYPE=$(echo "$INPUT" | jq -r '.params.arguments.node_type // "unknown"')
|
|
HW_PROFILE=$(echo "$INPUT" | jq -c '.params.arguments.hw_profile // {}')
|
|
|
|
# node_type is a small enum; validate so a bad value can't land an odd row.
|
|
case "$NODE_TYPE" in
|
|
live-usb|disk|vps|mother|unknown) ;;
|
|
*) NODE_TYPE="unknown" ;;
|
|
esac
|
|
|
|
if [ -z "$HOSTNAME" ]; then
|
|
printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"missing required argument: hostname"}}\n' "$ID"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$HW_PROFILE" = "{}" ]; then
|
|
printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"missing required argument: hw_profile"}}\n' "$ID"
|
|
exit 1
|
|
fi
|
|
|
|
# Pass the JSON blobs via psql -v with :'variable' quoting. :'variable'
|
|
# expands to a safely single-quoted SQL string literal (backslashes, quotes,
|
|
# and newlines inside the JSON are escaped), so untrusted input cannot break
|
|
# out of the literal — no shell interpolation ever touches the SQL.
|
|
# NOTE: a heredoc (not -c) is required — psql only expands :'variable' in SQL
|
|
# read from stdin / -f, not from -c.
|
|
# ON_ERROR_STOP=1 makes psql exit non-zero on a SQL error; without it psql
|
|
# would continue past a failed statement and exit 0, hiding failures. stderr is
|
|
# folded into RESULT so the error branch can report what went wrong.
|
|
RESULT=$(psql -d "$DB" -tA -v ON_ERROR_STOP=1 \
|
|
-v hostname="$HOSTNAME" -v node_type="$NODE_TYPE" -v hw_profile="$HW_PROFILE" 2>&1 <<'PSQL'
|
|
BEGIN;
|
|
INSERT INTO hive_nodes (hostname, node_type, hw_profile, status, last_seen)
|
|
VALUES (:'hostname', :'node_type', (:'hw_profile')::jsonb, 'online', now())
|
|
ON CONFLICT (hostname) DO UPDATE
|
|
SET node_type = EXCLUDED.node_type,
|
|
hw_profile = EXCLUDED.hw_profile,
|
|
status = 'online',
|
|
last_seen = now();
|
|
SELECT json_build_object(
|
|
'registered', true,
|
|
'hostname', hostname,
|
|
'node_type', node_type,
|
|
'capabilities', capabilities
|
|
) FROM hive_nodes WHERE hostname = :'hostname';
|
|
COMMIT;
|
|
PSQL
|
|
) || {
|
|
printf '{"jsonrpc":"2.0","id":%s,"error":{"code":-1,"message":"psql failed: %s"}}\n' \
|
|
"$ID" "$(printf '%s' "$RESULT" | sed 's/"/\\"/g' | tr '\n' ' ')"
|
|
exit 1
|
|
}
|
|
|
|
# RESULT is the JSON object from json_build_object (one line).
|
|
# Escape double quotes for JSON embedding in the MCP text field.
|
|
printf '{"jsonrpc":"2.0","id":%s,"result":{"content":[{"type":"text","text":"%s"}]}}\n' \
|
|
"$ID" "$(printf '%s' "$RESULT" | sed 's/"/\\"/g')"
|