Layer 1 — rc.d ordering:
- Add tailscaled to colibri_daemon REQUIRE so the daemon doesn't start
before the tailscale daemon is running.
Layer 2 — autospawn hook:
- After agent spawn, if clawdie-hw-probe was collected, read
external-mcp.json to detect a 'mother' server entry.
- If configured, SSH to mother and call node_register via colibri-mcp
with 3 retries / 5s backoff (tailscale auth can lag).
- Runs in a detached tokio task so SSH retries never block the daemon.
The probe data is already collected at autospawn time and passed to
the agent via CLAWDIE_HW_PROFILE; this addition closes the loop by
actually sending it to the mother node as a best-effort side effect.
Sam & Claude
Per the no-real-100.x-IPs-in-git policy: env.example now ships
COLIBRI_BRIDGE_LISTEN_ADDR=TAILSCALE_IP_REQUIRED (operator fills in via
tailscale ip -4 at deploy time), and the README uses placeholders/commands
instead of literal addresses for both domedog and hermes.
Linux peer of packaging/freebsd/colibri_bridge.in: bridge the colibri-daemon
control-plane Unix socket to TCP 9190 on the Tailscale interface so mesh hosts
can reach the control plane.
- colibri-bridge.service: systemd unit running socat under sandboxing, BindsTo
the daemon, freebind so it can bind the tailnet IP before tailscaled is up.
- colibri-bridge.env.example: tunables (systemd parallel to the rc.d sysrc vars).
- colibri-bridge.nft: nftables ruleset for hosts WITHOUT ufw.
- README: install steps + the verified domedog host facts (tailnet IP, ufw
default-deny posture, 8443=CloudPanel and its public exposure) + open
questions for the cross-host (hermes) review.
Network gate already applied on domedog: `ufw allow in on tailscale0 to any
port 9190 proto tcp`. The systemd unit is proposed pending the hermes review.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three bugs in the FreeBSD rc.d script:
- colibri_bridge_health() had an empty body — health check did nothing
- tcolibri_bridge_health — stray 't' prefix, typo
- Stray closing/opening braces scrambled colibri_bridge_status()
into two detached blocks, breaking the pgrep + nc check
health now delegates to status; status runs the full health check
(pgrep for socat + nc smoke to the socket).
Replace the real default 100.72.229.63 with TAILSCALE_IP_REQUIRED.
The operator must now set the listen address explicitly in rc.conf
before the service will start. The prestart guard fails with a clear
error message if the placeholder is still present.
This ensures no real Tailscale IPs leak into the git history or
shipped config files. Per MULTI-AGENT-HOST-PLAN Phase 5 acceptance.
Adds colibri_daemon_require_secured knob (default NO). When enabled, the
daemon refuses to autospawn an agent until /var/db/colibri/.secured exists.
This interlock pairs with the clawdie-iso firstboot password gate (#139):
the gate writes .secured after the operator sets passwords, the daemon
reads it to gate autospawn + node_register.
Must run AFTER the provider.env block — otherwise COLIBRI_AUTOSPAWN=YES
from provider.env would override the NO set here. Defaults to NO so
deployed/disk hosts (which never run the firstboot gate) are unaffected.
Paired with: clawdie-iso PR #139 (force-root-password-on-first-boot).
The rc.d drops privilege via su -m, which preserves the environment from
/etc/rc (HOME=/). Without an explicit ZOT_HOME, zot resolves to
/.local/state/zot — missing any AGENTS.md installed by the seed importer.
Pin ZOT_HOME to /var/db/colibri/.local/state/zot. The seed importer
(clawdie-iso) targets this same path, so AGENTS.md placed on the seed
reaches the autospawned zot's global slot.
- §2: list colibri-mcp instead of colibri-test-agent (matches preflight at
build.sh:335 — test-agent is optional, gated by COLIBRI_STAGE_TEST_AGENT)
- §3: name the specific binaries preflight checks
- Notes: add Node.js (npm) to host toolchain requirements — build_and_stage_docs
needs node+npm, and the handoff should match REQUIREMENTS.md
geodesic-dome-mcp imports numpy + PIL at module load (not stdlib-only, as
#178 incorrectly stated). A present python3 therefore proves nothing — the
preflight would pass on a host missing numpy/Pillow and the tool would fail
only when the MCP host first invokes it.
- setup-mother.sh: add a 'python3 -c "import numpy, PIL"' check after the
python3-exists check, with a pkg install py311-numpy py311-pillow hint.
- MOTHER-SETUP.md: correct the prereq from 'stdlib only, no pip' to
'python3 + numpy + Pillow'.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three reinforcing changes so the next agent's mother setup lands instead
of failing late:
- setup-mother.sh: fail-fast preflight for python3 (geodesic-dome-mcp is a
python3 script that otherwise installs fine and fails only when invoked).
- MOTHER-SETUP.md: new Prerequisites section — python3 on PATH, and the
COLIBRI_AUTOSPAWN_RPC_PROMPT boot decision (set = auto-spawn agent on
boot; unset = quiet token-free boot).
- FREEBSD-BUILD-LANE-HANDOFF.md: pointer to MOTHER-SETUP.md/setup-mother.sh
so the mother docs are discoverable from the build-lane entry point.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
An ordered first-run checklist for deploying on osa (or any new mother),
covering the things that can only be validated against a live PostgreSQL +
FreeBSD host:
- build 0.12 on FreeBSD from current main + ci-checks (Linux binaries won't run)
- record any pre-existing node-register before install
- post-install integrity: installed node-register is the hardened hive_nodes
version (grep -c "E'" == 0; grep hive_nodes > 0) — not the injectable copy
- schema migrated in place (usb_nodes renamed, not duplicated; node_type present)
- peer auth works; pg_hba peer rule present AND precedes generic local rules
- external-mcp has all three servers (jq-merge preserved existing)
- SSH forced-command wrapper rejects non-allowlisted commands
- daemon env + service live; key hygiene (private key → seed only)
Captures the operational risks flagged during the mother-infra review.
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>
Class I icosahedron subdivision, perspective rendering, and
complete bill of materials analysis. Reports:
- Strut lengths + counts (grouped by size)
- Connector types (3/4/5/6-way junctions)
- Triangle areas (grouped, with totals)
- Material BOM (glass, polycarbonate, insulated)
- Cost + weight estimates per material
- Full or half-sphere modes
No Blender, no GPU — pure Python + numpy + Pillow.
Honor an optional pkg-list-jails path/URL argument, allow comments inside the agent-jail section, and apply Prettier to docs/README.md. This preserves the cross-repo gate for the jq addition.\n\nValidation: ./scripts/check-format.sh; cargo fmt --check; ./packaging/freebsd/port/check-cargo-crates.sh; ./packaging/freebsd/check-agent-jail-pkgs.sh /home/clawdie/ai/clawdie-iso/packages/pkg-list-jails.txt; sh -n packaging/freebsd/agent-jail-bootstrap.sh packaging/freebsd/mother-sync-hive-keys.sh; cargo check -p colibri-daemon -p colibri-client -p colibri-mcp.
Mother side of the vault-mediated hive key exchange (direction B — agents call
mother). Pulls the hive-pubkey-* items agents publish to Vaultwarden and rebuilds
the colibri user's authorized_keys, each entry restricted to the MCP command
(command="colibri-mcp",restrict,no-pty,no-*-forwarding).
- Rebuild, not append: deleting an agent's vault item revokes it next run.
- Fail-safe: a vault/login failure leaves authorized_keys untouched.
- Atomic write (mktemp + mv); colibri-owned 0600.
- Tunable via PROVIDER_ENV / COLIBRI_HOME / COLIBRI_USER / MCP_COMMAND
(mother = osa for now; a dedicated host is a config change).
- Cron-driven (sample line in the header). Uses the bitwarden-cli-vault skill's
session + authorized_keys-rebuild patterns.
sh -n clean; parse/rebuild core tested (filters non-key items, strips key
comments, applies the restriction wrapper).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MCP tooling parses colibri-mcp / external MCP JSON-RPC output with jq, so
agent jails need it on PATH. Add jq to the pinned PKGS list (host must have it
in the pkg cache, like the other pinned packages). Mirrors the matching entry
in clawdie-iso pkg-list-jails.txt.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
#136 moved staging from /var/run/colibri-stage to
/home/clawdie/.cache/colibri/stage. Bastille creates the jail's
/home/clawdie as root:wheel, so the daemon (running as clawdie)
couldn't create staging directories there. chown after binary
copy ensures the daemon owns its home directory inside the jail.
Closes#135. The daemon stages per-spawn launch.sh/env.sh under the jail root;
the previous location /var/run/colibri-stage is root-owned, so the daemon
(running as clawdie) could not create per-spawn subdirs there — the second
jail-spawn EACCES, worked around in #134 by pre-creating the dir in
agent-jail-bootstrap.sh.
Move the default staging root to the daemon user's home,
/home/clawdie/.cache/colibri/stage, which clawdie owns by construction of the
jail account. create_dir_all now succeeds with no privileged pre-creation step,
and /home is persistent (unlike a tmpfs /var/run). The path is overridable via
COLIBRI_JAIL_STAGE_DIR, matching the daemon's other env-configurable paths.
- spawner.rs: const → staged_jail_run_dir() resolver; updated unit test.
- agent-jail-bootstrap.sh: drop the now-unnecessary install -d staging block
and DAEMON_USER var (the #134 workaround).
- docs: update jailed-spawn design + truss analysis to the new location.
clippy clean; spawner suite green (21 tests); sh -n clean; touched docs pass
the markdown gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second root cause of the jail-spawn EACCES (found via truss, docs PR #132):
for staged spawns the daemon writes launch.sh/env.sh under
<jail_root>/var/run/colibri-stage/<stage_id>/, but nothing created
/var/run/colibri-stage. The daemon runs as clawdie and cannot mkdir under
root-owned /var/run, so staging failed with Permission denied.
agent-jail-bootstrap.sh now pre-creates the dir owned by the daemon user
(0700), replacing the runtime `chmod 777` workaround — durable across jail
rebuilds and not world-writable (staged files are sourced as shell, so a
world-writable staging dir would be a privilege footgun). DAEMON_USER is
overridable, defaulting to clawdie.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes#122. Creates packaging/freebsd/clawdie-npm-profile.sh as
the single source for npm PATH + npm config. The agent-jail
bootstrap installs it with NPM_PREFIX baked in, replacing the
inline heredoc. The clawdie-iso build.sh installs the same file.
Before: two divergent heredocs, different filenames, different
prefixes. Now: one file, both environments, parameterized prefix.
Root cause of the recurring "pi/bw not found in jail" bug: the npm-global-on-PATH
fix was solved canonically in the clawdie-iso image (/etc/profile.d/clawdie.sh,
all login shells), but the agent jail is a separate environment that never reused
it — a fresh Bastille jail doesn't inherit the image's profile.d, and the
bootstrap set no PATH. PR #120 band-aided it with a hardcoded append to one
user's ~/.profile (sh-only, drifts from NPM_PREFIX).
Replace that band-aid with the same mechanism the image uses, scoped to the jail:
- write one managed /etc/profile.d/clawdie-npm.sh derived from NPM_PREFIX
- source it from /etc/profile (covers all sh/bash login shells, system-wide),
idempotently
- delete the per-user ~/.profile append from #120
Now the PATH content lives in a single file tied to NPM_PREFIX, so it can't miss
shells or drift from the prefix. Follow-up (not here): hoist the snippet into one
shared file installed by both clawdie-iso and the jail bootstrap, so a future new
environment can't re-grow this.
Verified: sh -n clean; smoke test — snippet expands NPM_PREFIX (keeps $PATH
literal), /etc/profile sources it, append is idempotent, sourced shell resolves
the npm-global bin onto PATH.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Closes the gap left after #70/PR #81: the agent-jail package set is hand-synced
across two repos (this bootstrap's PKGS= and clawdie-iso pkg-list-jails.txt
"# agent-jail" section) with nothing catching future drift.
- check-agent-jail-pkgs.sh: pure POSIX sh; extracts PKGS= here, fetches the
clawdie-iso list over HTTP (ISO_PKG_LIST_URL overridable), diffs the two sets,
reports the delta, exits non-zero on mismatch.
- ci.yml: new `agent-jail-pkgs` job runs it on every push/PR.
Same shape as the CARGO_CRATES drift check. Verified: green in sync (5 pkgs);
negative test flags missing packages and exits 1; ci.yml valid YAML.
Single-sided (fires on colibri CI); the clawdie-iso list is fetched from main.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Version: unify colibri with the Clawdie release version 0.11.0 (matches
clawdie-iso ISO_VERSION). Cargo.toml 0.0.1 -> 0.11.0, Cargo.lock refreshed,
port DISTVERSION 0.0.1 -> 0.11.0, port README example tag v0.11.0.
License: relicense all 12 crates from AGPL-3.0-only to MIT, matching the rest of
the project (layered-soul is MIT; nothing was BSD-3). Add a LICENSE file with
the same MIT text + holder (clawdie, 2026). Port: LICENSE=MIT + LICENSE_FILE.
Validation: CARGO_CRATES drift check green (346); markdown gate clean; no AGPL
references remain. Edition stays 2021 (2024 migration is a separate tested task).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Make the canonical sysutils/colibri port install its rc.d services so
`pkg install colibri` registers them — the one functional bit the (now-retiring)
clawdie-iso port duplicate carried. Poudriere builds in a clean jail that only
sees the port dir, so the rc.d templates live in files/ (mirrored from the
canonical packaging/freebsd/ copies).
- files/colibri_daemon.in, files/colibri_bridge.in (rc.d templates)
- do-install: INSTALL_SCRIPT both into PREFIX/etc/rc.d/ (binary path
PREFIX/bin/colibri-daemon already matches the daemon rc.d expectation)
- pkg-plist: add the two etc/rc.d entries
- README: document files/ + that this is the single canonical port
Validation: rc.d sh -n clean; CARGO_CRATES drift check green (346); markdown
gate clean. Port remains poudriere-build-unproven until the first mother-build run.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a top-of-file usage banner showing how to run it: `sh ...` / `./...`.
It is a POSIX shell wrapper that calls python3 internally. Comment-only.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to #109 (which generated the 346-crate CARGO_CRATES block). Make that
list self-maintaining so it can't silently drift from the source deps:
- check-cargo-crates.sh: parses Cargo.lock (registry crates only; skips the 13
workspace-local crates and any git deps) and diffs against the Makefile's
CARGO_CRATES block. Reports MISSING / STALE, exits non-zero on drift. No
network, pure tomllib — runs on any host. Independently confirms #109's list
is complete and correct (346/346 in sync).
- ci.yml: new `port` job (python:3.12) runs the check on every push/PR, so a
dependency change that forgets `make cargo-crates` fails CI.
- Makefile: replace the stale "Empty in this draft" comment (CARGO_CRATES is now
populated) with accurate regenerate/verify guidance.
- README: CARGO_CRATES is committed now (only distinfo is build-host-generated);
document the checker and trim the build steps.
Verified: checker green at 346 crates; both drift directions (missing/stale)
detected in negative tests; ci.yml is valid YAML; port README prettier-clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Source-of-truth for the FreeBSD port built by pkg.clawdie.si. Grounded in the
actual workspace (not the runbook's stale assumptions):
- license AGPL-3.0-only (the build-server doc had said MIT — wrong).
- ships 6 binaries: clawdie, colibri, colibri-daemon, colibri-mcp,
colibri-test-agent, colibri-tui. Builds only the 5 crates that own them
(colibri-client yields colibri + colibri-test-agent); no GUI deps pulled in.
colibri-probe / colibri-runtime-inventory (dev tools) are not installed.
- USES=cargo; source from Forgejo archive (tagged release vX.Y.Z), extracts to
colibri/. post-extract drops rust-toolchain.toml so the clean jail uses the
ports lang/rust instead of a pinned rustup channel.
- distinfo and the CARGO_CRATES list are build-host-generated (make makesum /
make cargo-crates) — documented in README, deliberately not committed (no
fabricated checksums).
Recipe lines tab-indented; .for/.endfor at column 0. Not yet test-built against
a live ports tree — first real run is on mother-build.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Correct the raw socket spawn example to use the bootstrapped colibri-test-agent in the jail instead of the default remote-provider agent binary. Use a harmless FIRST_PROOF_KEY item for the throwaway collection.\n\nAlso convert packaging/freebsd/colibri-agent-loop.md to normal Markdown so the repository formatting gate passes.\n\nChecks: ./scripts/check-format.sh; git diff --check
- provider.env.example: template with BW_CLIENTID, BW_CLIENTSECRET,
BW_PASSWORD fields and install instructions
- colibri_daemon.in: add provider.env to one-time setup steps
so it's never lost on a fresh deploy
The live file at /usr/local/etc/colibri/provider.env (0600) is never
committed — only the template.
Tighten agent-jail-bootstrap.sh per review of #96:
- pin each package to the host's EXACT installed version (pkg query '%v' ->
install name-version from the host's mounted cache); fail loudly if the host
lacks it, instead of pulling a different version into the jail
- set -eu; validate jail name ([A-Za-z0-9_-], non-empty) so it can't escape the
bastille jails root; assert the jail root exists before touching it
- guard every host source (binaries, npm modules) so a missing source fails
clearly rather than producing a half-bootstrapped jail
Relies on the existing host pkg-cache reachability from the jail (offline install).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>