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>
114 lines
4.6 KiB
PL/PgSQL
114 lines
4.6 KiB
PL/PgSQL
-- Mother hive schema. Idempotent: safe to re-run (setup-mother.sh applies it).
|
|
--
|
|
-- A "node" is any host that has joined the hive and reports a hardware
|
|
-- profile — live-USB, disk-installed FreeBSD, a Linux box, a VPS, or the
|
|
-- mother itself. The boot/provisioning medium is an attribute (node_type),
|
|
-- not the table's identity.
|
|
|
|
-- Migration: usb_nodes → hive_nodes (rename in place; preserves data + the
|
|
-- owning sequence). Runs only on hosts created under the old name; a no-op on
|
|
-- fresh installs and on already-migrated hosts.
|
|
DO $$
|
|
BEGIN
|
|
IF to_regclass('public.usb_nodes') IS NOT NULL
|
|
AND to_regclass('public.hive_nodes') IS NULL THEN
|
|
ALTER TABLE usb_nodes RENAME TO hive_nodes;
|
|
IF to_regclass('public.usb_nodes_id_seq') IS NOT NULL THEN
|
|
ALTER SEQUENCE usb_nodes_id_seq RENAME TO hive_nodes_id_seq;
|
|
END IF;
|
|
END IF;
|
|
END $$;
|
|
|
|
CREATE TABLE IF NOT EXISTS hive_nodes (
|
|
id SERIAL PRIMARY KEY,
|
|
hostname TEXT NOT NULL UNIQUE,
|
|
-- Provisioning medium: live-usb | disk | vps | mother | unknown.
|
|
node_type TEXT NOT NULL DEFAULT 'unknown',
|
|
last_seen TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
first_seen TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
freebsd_version JSONB,
|
|
hw_profile JSONB NOT NULL,
|
|
capabilities JSONB,
|
|
status TEXT NOT NULL DEFAULT 'offline',
|
|
tags TEXT[] DEFAULT '{}',
|
|
last_cap_sync TIMESTAMPTZ
|
|
);
|
|
-- Add node_type to tables migrated from the old usb_nodes definition.
|
|
ALTER TABLE hive_nodes ADD COLUMN IF NOT EXISTS node_type TEXT NOT NULL DEFAULT 'unknown';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_nodes_status ON hive_nodes (status);
|
|
CREATE INDEX IF NOT EXISTS idx_nodes_last_seen ON hive_nodes (last_seen DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_nodes_type ON hive_nodes (node_type);
|
|
CREATE INDEX IF NOT EXISTS idx_nodes_cap_has_gpu ON hive_nodes ((capabilities->>'has_gpu'));
|
|
|
|
CREATE TABLE IF NOT EXISTS build_queue (
|
|
id SERIAL PRIMARY KEY,
|
|
node_id INTEGER REFERENCES hive_nodes(id),
|
|
crate TEXT NOT NULL DEFAULT 'colibri-daemon',
|
|
branch TEXT NOT NULL DEFAULT 'main',
|
|
release BOOLEAN NOT NULL DEFAULT true,
|
|
status TEXT NOT NULL DEFAULT 'queued',
|
|
priority INTEGER NOT NULL DEFAULT 0,
|
|
queued_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
started_at TIMESTAMPTZ,
|
|
completed_at TIMESTAMPTZ,
|
|
binary_path TEXT,
|
|
binary_size BIGINT,
|
|
commit_sha TEXT,
|
|
duration_s INTEGER,
|
|
error_log TEXT
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_build_status ON build_queue (status, priority DESC, queued_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS audit_log (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
event_ts TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
event_type TEXT NOT NULL,
|
|
node_id INTEGER REFERENCES hive_nodes(id),
|
|
build_id INTEGER REFERENCES build_queue(id),
|
|
details JSONB
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_audit_ts ON audit_log (event_ts DESC);
|
|
|
|
CREATE OR REPLACE FUNCTION derive_capabilities()
|
|
RETURNS trigger AS $$
|
|
DECLARE
|
|
ram INTEGER;
|
|
drivers TEXT;
|
|
wifi JSONB;
|
|
caps JSONB := '{}'::JSONB;
|
|
BEGIN
|
|
ram := COALESCE((NEW.hw_profile->>'ram_gb')::INTEGER, 0);
|
|
drivers := NEW.hw_profile->>'gpu_driver';
|
|
wifi := NEW.hw_profile->'wifi';
|
|
IF NEW.hw_profile->'gpu' IS NOT NULL AND jsonb_array_length(NEW.hw_profile->'gpu') > 0 THEN
|
|
caps := caps || '{"has_gpu": true}'::JSONB;
|
|
IF drivers ILIKE '%amdgpu%' THEN
|
|
caps := caps || '{"gpu_vendor": "amd", "vulkan_compute": true}'::JSONB;
|
|
ELSIF drivers ILIKE '%nvidia%' THEN
|
|
caps := caps || '{"gpu_vendor": "nvidia"}'::JSONB;
|
|
END IF;
|
|
ELSE
|
|
caps := caps || '{"has_gpu": false, "cpu_only": true}'::JSONB;
|
|
END IF;
|
|
IF caps->>'has_gpu' = 'true' AND caps->>'gpu_vendor' = 'nvidia' THEN
|
|
caps := caps || '{"can_run_local_llm": true}'::JSONB;
|
|
IF ram >= 64 THEN caps := caps || '{"max_model": "13b-q4"}'::JSONB;
|
|
ELSIF ram >= 32 THEN caps := caps || '{"max_model": "7b-q4"}'::JSONB;
|
|
END IF;
|
|
ELSIF ram >= 16 THEN
|
|
caps := caps || '{"can_run_local_llm": true, "max_model": "3b"}'::JSONB;
|
|
END IF;
|
|
IF wifi IS NOT NULL AND jsonb_array_length(wifi) > 0 THEN
|
|
caps := caps || '{"has_wifi": true}'::JSONB;
|
|
END IF;
|
|
NEW.capabilities := caps;
|
|
NEW.last_cap_sync := now();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS trg_derive_capabilities ON hive_nodes;
|
|
CREATE TRIGGER trg_derive_capabilities
|
|
BEFORE INSERT OR UPDATE OF hw_profile ON hive_nodes
|
|
FOR EACH ROW EXECUTE FUNCTION derive_capabilities();
|