The baked mother key (build/mother-ssh-key) puts a private key in the image,
which only works for a non-published personalized stick. The offline FAT32
seed is the correct home for per-node secrets.
Teach the importer to install outbound SSH client material from an agent's
ssh/ dir into the agent home:
- config -> ~/.ssh/config (0600)
- known_hosts* -> ~/.ssh/known_hosts* (0644, merged + de-duped)
- <name>.pub -> ~/.ssh/<name>.pub (0644)
- <name> -> ~/.ssh/<name> (0600, any other file = private key)
authorized_keys stays inbound-only via _seed_install_authorized_keys.
This closes the 'without manual key exchange' gap: known_hosts pins mother's
host key so the first node->mother connect does not prompt, and the private
client key rides on the offline seed instead of the base image — so the
published image stays secret-free. Supersedes the baked-key path (#112),
which can retire once this is validated on hardware.
Verified offline (CLAWDIE_SEED_TEST): correct perms (key 0600, pub/known_hosts
0644, config 0600, .ssh 0700) and idempotent known_hosts merge across re-runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The trigger copies osa-mother-2026 from the build host into any ISO
as long as the key file exists (which it does permanently on OSA).
A BUILD_CHANNEL=release build would embed the private key into a
publicly hosted image = mother compromise.
Add a fail-closed guard: release builds exit with an error before
copying the key. Dev builds (including personalized sticks) are
unaffected.
Copies /home/clawdie/.ssh/osa-mother-2026 (ed25519) from the build
host into the ISO at /home/clawdie/.ssh/osa-mother-2026 (0600).
Public key is in mother's authorized_keys. Lets the live USB node
SSH straight into the mother server without manual key exchange.
Skipped silently if the key file doesn't exist on the build host.
The live seed importer merged the active agent's provider keys into the
operator ~/.env, but colibri_daemon reads /usr/local/etc/colibri/provider.env
(rc.conf colibri_daemon_provider_env). So a personalized seed carrying real
provider keys never reached the daemon and no agent auto-spawned.
Route the active agent's non-BW_* keys into provider.env (0600 root) in
addition to ~/.env. The importer runs as root BEFORE LOGIN and colibri_daemon
REQUIREs LOGIN, so the daemon starts after the keys land and auto-spawns the
agent on first boot — no Join Hive click, no Vaultwarden round-trip, no typing.
This makes a personalized seed the zero-touch onboarding primitive: the image
stays generic/publishable, the FAT32 seed is the (offline) personalization
layer. BW_* still route to vault-bootstrap.env for the vault-fetch path.
Docs: seed README, START-HERE, and ONBOARDING-SIMPLIFICATION updated to
describe the direct-keys path (supersedes the xdg-autostart plan).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
clawdie-ai (TypeScript) is being phased out in favor of the colibri
(Rust) control plane. Remove its shallow git checkout from
/home/clawdie/ai/ on the ISO. The build manifest still records
clawdie-ai provenance; skills import and release gate checks are
unaffected — this only drops the source snapshot.
Also update the ai/README.txt to note the phase-out.
Documents the proven end-to-end chain: seed importer (rc.d BEFORE LOGIN)
→ BW creds in provider.env → clawdie-vault-fetch → colibri_daemon restart.
The only remaining click is the 'Join Hive' desktop launcher; the seed
partition already reduces onboarding from 3 typed secrets to one
double-click. True zero-touch requires ~30 lines of shell (xdg autostart).
Also records the zot extensions verdict: onboarding panel rejected
(chicken-and-egg), guard deferred (zot is not OOTB runtime), MCP bridge
kept as the first extension worth building (gated on colibri#143).
Stages the publish-time doc bump from 0.10.0 to 0.11.0: artifact filenames and
osa download/verify URLs (FLASHING, README, TESTING, BUILD, iso-publish skill),
the ISO product-version claims (README, BUILD), and enriches the existing
CHANGELOG [0.11.0] entry with this cycle's operator-facing ISO merges
(Join Hive vault provisioning, Tailscale auto-join, Mother MCP link, jq).
Left untouched: --clawdie-version examples (clawdie-ai namespace) and the
[0.10.0] CHANGELOG history. HOLD until the 0.11.0 image is built + hosted —
the download URLs 404 until then.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
build.cfg already targets ISO_VERSION 0.11.0; the runbook still said 0.10.0.
Scoped to the release-cutting doc only — download-URL docs (FLASHING/README/
TESTING) stay at 0.10.0 until 0.11.0 is actually published, and CHANGELOG
history is untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bumps the zot agent tag the image ships, consistently across build.cfg,
the preflight hint (build.sh), the staging hint (stage-zot-iso.sh), and the
live-rebuild doc. Continues work started by Codex (chore/zot-0.2.42-pin).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Update the ISO default Zot pin and build docs from v0.2.29 to v0.2.42 so the next image stages the current rebuilt FreeBSD zot binary instead of recording a mismatched checkout/binary pair. Also refresh the release runbook's 0.11.0 examples.\n\nValidation: ./scripts/check-format.sh; sh -n build.sh scripts/stage-zot-iso.sh; BUILD_CHANNEL=dev build.cfg default check; git diff --check.
The spawner uses stdin(Stdio::null()); zot's --json and rpc modes
both require input. Pi's --mode json is autonomous. Document the
blocker inline so nobody tries the pi→zot config flip without the
driver. Ref: colibri issue zot-rpc-driver + ADR-agent-harness-consolidation.md.
PR #102 wired the standalone tailscale-auth-key vault item, but the
out-of-the-box path (no baked key) could not actually start the service:
- clawdie-tailscale-up kept required_files=<keyfile>, which onestart still
enforces; the keyfile is absent on the OOTB image. Removed it — the start
function already returns 0 when neither provider.env nor the keyfile carries
a key, so the guard is redundant.
- join-hive called `service ... start`: refused because the service defaults to
enable=NO without a baked key, and it lacked root. Now `mdo -u root service
... onestart` (root + bypass rcvar).
- join-hive's post-join cleanup ran `sed ... provider.env/d` — a stray /d on the
file path made it error. Dropped it; the rc.d strips the key on success.
- join-hive interpolated the key into `sh -c "..."` argv (visible in ps). Now
piped via stdin.
Also keep provider.env at 0600 after the rc.d rewrite (it still holds BW_*).
Validated: sh -n on both scripts, ./scripts/check-format.sh clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- join-hive.sh: sed -i '' '/^TAILSCALE_AUTH_KEY=*** → .../d' (delete was missing)
- tailscale-up: grep -v pattern aligned to match any value, not literal ***
- Both files pass sh -n
Adds step [2b] to join-hive: if bw is available and the node is not
yet on Tailscale, fetch the tailscale-auth-key item from Vaultwarden,
write TAILSCALE_AUTH_KEY to provider.env, and trigger tailscale-up.
- Handles both naming variants (tailscale-auth-key / tailscale_auth_key)
- One-shot: key removed from provider.env after successful join
- tailscale-up now reads from provider.env first, legacy key file as fallback
- Graceful: no vault item → clear message, no break
Move jq into the agent-jail section so the package list mirrors Colibri's agent-jail-bootstrap baseline, and apply Prettier to pulled markdown drift.\n\nValidation: ./scripts/check-format.sh; sh -n build.sh scripts/stage-colibri-iso.sh live/operator-session/clawdie-join-hive.sh live/operator-session/clawdie-enable-mother.sh live/operator-session/colibri-live-rebuild; ./scripts/test-release-gate.sh; git diff --check.
Vault-mediated key exchange (direction B — we call mother). After ensuring the
colibri SSH identity, enable-mother now upserts the pubkey into Vaultwarden as
`hive-pubkey-<hostname>` (via bw, run as root so it can read the BW_* bootstrap
creds from provider.env). Mother's mother-sync-hive-keys rebuilds its
authorized_keys from these items, so no operator copy-paste between machines.
The printed pubkey + restricted command= line remain as a manual fallback when
the vault publish is unavailable. Uses the bitwarden-cli-vault skill's
session+upsert pattern. sh -n clean; embedded JSON/id-extraction tested.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Track C's enable-mother overwrote external-mcp.json with a single mother
server. Use jq to merge the mother entry into the existing registry so other
configured servers are preserved, written atomically (mktemp in same dir + mv).
This is the concrete consumer that makes jq a real dependency of the MCP path;
fails loudly if jq is absent.
(Re-applied: the original commit was lost to a branch-recreation race when #97
merged at the packages-only commit.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MCP tooling needs jq on PATH to parse colibri-mcp / external MCP JSON-RPC
output. Add it to the live operator image (where the auto-spawned Pi and
colibri-mcp run) and to the jail package union. The jail entry mirrors the
matching addition in Colibri's agent-jail-bootstrap.sh (kept in sync per the
list header).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per Hermes' review of the cred-capture flow: after the daemon restart that
loads the pulled keys, poll colibri status (up to 10s) for a live agent so the
operator sees confirmation that the Pi auto-spawn actually came up — instead of
just "daemon restarted". Prints "Pi agent is live." or a check hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Workstream C of the next ISO rebuild.
C1 — Auto-spawn lit up out of the box:
provider.env now ships COLIBRI_AUTOSPAWN_PI="YES", so colibri#137 fires on
the booted image once a DeepSeek key is present (pulled by Join Hive, A).
C2 — External MCP registry staged:
/usr/local/etc/colibri/external-mcp.json shipped as {"servers":{}} at the
path colibri-mcp reads by default. Empty = mother off by default.
C3 — Opt-in "Enable Mother Link" (clawdie-enable-mother + desktop entry):
Direction is "our Pi calls mother's tools" — colibri-mcp dials OUT to mother
over SSH-stdio and proxies mother's tools to the Pi via its external-call
path. The toggle:
- provisions an SSH identity for the colibri service account
(/var/db/colibri/.ssh — the daemon and its Pi run as `colibri`),
- writes the mother entry into external-mcp.json (ssh -i <key> ... mother),
- upserts COLIBRI_MCP_EXTERNAL_CALL=1 into provider.env,
- restarts the daemon and prints colibri's pubkey to authorize on mother.
provider.env.sample documents the new toggles. sh -n clean on all scripts;
the empty default and the emitted mother entry validate as JSON and match the
ExternalMcpRegistry {servers:{command,args,env}} shape.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Workstream A of the next ISO rebuild. The booted XFCE image's "Join Hive"
flow now collects the 3 Vaultwarden bootstrap values and pulls the provider
keys, instead of only warning when they are missing.
Step [2/4] now:
- If provider.env lacks BW_*, prompts for BW_CLIENTID/BW_CLIENTSECRET/
BW_PASSWORD (secret + password read with echo off) and upserts them into
provider.env (root-owned 0600). Entering nothing skips — manual floor intact.
- Then runs clawdie-vault-fetch against provider.env (as bootstrap and as
--write-env target) to pull DEEPSEEK_API_KEY (and other agent-secrets), and
restarts colibri_daemon so it loads the new keys — which triggers the Pi
auto-spawn (colibri#137).
Secrets never appear in process arguments: values stay in shell variables and a
0600 temp under ~/.cache/clawdie; provider.env is read/written via mdo. The
upsert preserves the endpoint line and other keys (verified: special characters
in the secret/password survive, no duplicate BW_* lines).
provider.env stays the single secret store — the daemon's vault provisioning and
the existing provider_env_has_bw_creds check already assume that.
sh -n clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The product-version scheme was only captured in scattered build.cfg/build.sh
comments and agent memory. Promote it to contributor-visible guidance:
- ISO_VERSION is an explicit product version in build.cfg (0.11.0, unified
with Colibri); no-version builds fail fast; image name = codename + version.
- Component versions are provenance in build-manifest.json (version_scheme
"product"), not the image identity.
- BUILD_CHANNEL dev|release; release gate (build.sh:check_release_gate)
requires clean staged trees so the manifest fully describes the artifact.
Matches shipped code; no behavior change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PR #92 wired the ISO to the shared clawdie-npm-profile.sh but hardcoded
${SCRIPT_DIR}/../colibri and had no existence guard. Every other colibri
consumer in build.sh resolves through resolve_colibri_paths (default
/home/clawdie/ai/colibri, honoring COLIBRI_REPO), so the hardcoded path
diverged from the real build-host layout and ignored the override; a
missing file let cat fail silently into a half-written snippet.
Now: resolve via resolve_colibri_paths and preflight the file with a
clear error pointing at COLIBRI_REPO, matching preflight_colibri_artifacts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes colibri#122. The ISO now installs the canonical
clawdie-npm-profile.sh from colibri (same file agent-jail-bootstrap
uses). The ISO-specific clawdie.sh sources it rather than
duplicating the npm PATH + npm config lines.