feat(seed): route seeded provider keys to provider.env for zero-touch boot

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>
This commit is contained in:
Sam & Claude 2026-06-22 08:56:46 +02:00
parent 343ba35536
commit ba2f09f290
4 changed files with 61 additions and 18 deletions

View file

@ -42,23 +42,35 @@ just double-clicks "Join Hive" and the fetch runs automatically.
| Join Hive | `live/operator-session/clawdie-join-hive.sh` | Desktop launcher: checks for creds → prompts or auto-fetches | | Join Hive | `live/operator-session/clawdie-join-hive.sh` | Desktop launcher: checks for creds → prompts or auto-fetches |
| Seed README | `live/operator-session/clawdie-live-seed.README.txt` | Operator-facing seed partition instructions | | Seed README | `live/operator-session/clawdie-live-seed.README.txt` | Operator-facing seed partition instructions |
## Remaining work: delete the click ## Zero-touch: direct keys on the seed (implemented)
The seed partition path reduces onboarding to ONE double-click. Making it The click only existed to trigger the Vaultwarden round-trip. For a
zero-touch requires: personalized stick that already holds the real provider keys, that round-trip
is unnecessary — and the boot ordering already favors zero-touch:
1. **xdg autostart entry** — on first login, if BW\_\* creds are present in - `clawdie_live_seed` runs as root, `BEFORE: LOGIN`.
provider.env AND `colibri_daemon` is not yet fully provisioned, auto-run - `colibri_daemon` runs `REQUIRE: LOGIN` — strictly after.
`clawdie-vault-fetch --write-env <provider.env>` and restart the daemon.
2. **First-boot guard** — run once per boot only (not on every login). A So the importer now merges the active agent's direct provider keys (everything
sentinel file (`/var/db/clawdie/vault-fetched`) prevents re-triggering. except `BW_*`) into the daemon's `provider.env` as well as the operator's
`~/.env`. The daemon starts afterward, finds `DEEPSEEK_API_KEY`, and
auto-spawns the agent (`COLIBRI_AUTOSPAWN_PI=YES`) — no click, no vault
round-trip, no typing. `BW_*` still route to `~/.config/vault-bootstrap.env`
for operators who prefer the vault-fetch path.
3. **START-HERE.txt update** — remove the "add your 3 BW secrets" section This makes the **personalized seed** the onboarding primitive:
when the seed partition is present. Show a confirmation instead:
"Secrets loaded from seed partition. Colibri is starting."
Estimated: ~30 lines of shell. - The image stays generic and publishable; secrets are never baked in.
- The seed (FAT32 `CLAWDIESEED`) is the personalization layer and stays
physical/offline — `env` (direct keys + optional `TAILSCALE_AUTH_KEY`),
`harness.toml`, `soul/`, `ssh/authorized_keys`, optional `/shred` to wipe
keys off the stick after first import.
- The seed can be generated by an agent (e.g. Hermes) that already holds the
soul and key material, written straight onto the mounted partition.
This supersedes the earlier xdg-autostart "delete the click" plan: it removes
the click for free without a first-login sentinel or a network dependency at
first boot.
## Related: one-secret path (future) ## Related: one-secret path (future)

View file

@ -55,6 +55,11 @@ LIVE SEED
Seed partition label: Seed partition label:
CLAWDIESEED CLAWDIESEED
If this stick was seeded with provider keys, there is nothing to do: the
agent's keys were loaded before the daemon started, so Colibri auto-spawns
the agent on boot. Check with:
colibri status
Readable operator guide: Readable operator guide:
/usr/local/share/clawdie-iso/seed/README.txt /usr/local/share/clawdie-iso/seed/README.txt

View file

@ -54,6 +54,12 @@ SEED_VALID_HARNESSES="pi zot local"
# Vaultwarden bootstrap creds are routed out of .env into this file (relative to # Vaultwarden bootstrap creds are routed out of .env into this file (relative to
# the agent home) so clawdie-vault-fetch can consume them. # the agent home) so clawdie-vault-fetch can consume them.
SEED_VAULT_BOOTSTRAP_REL=".config/vault-bootstrap.env" SEED_VAULT_BOOTSTRAP_REL=".config/vault-bootstrap.env"
# colibri_daemon reads provider keys from this file (rc.conf
# colibri_daemon_provider_env), NOT the operator's ~/.env. The active agent's
# direct provider keys are merged here too so the daemon auto-spawns at boot
# from a seeded stick with no operator action (zero-touch provisioning). The
# importer runs as root before LOGIN, so it can write this root-owned file.
SEED_PROVIDER_ENV="${SEED_PROVIDER_ENV:-/usr/local/etc/colibri/provider.env}"
_seed_log() { _seed_log() {
printf '%s %s\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$1" >>"${SEED_LOG}" 2>/dev/null || true printf '%s %s\n' "$(date '+%Y-%m-%dT%H:%M:%S')" "$1" >>"${SEED_LOG}" 2>/dev/null || true
@ -261,6 +267,14 @@ _seed_activate_agent() {
if [ -f "${_dir}/env" ]; then if [ -f "${_dir}/env" ]; then
_seed_split_env "${_dir}/env" "${_stage}" _seed_split_env "${_dir}/env" "${_stage}"
_seed_merge_env "${_stage}/.app.env" "${_home}/.env" "${_user}" _seed_merge_env "${_stage}/.app.env" "${_home}/.env" "${_user}"
# Feed the daemon too: colibri_daemon reads provider.env, not ~/.env.
# Routing the active agent's provider keys here lets a seeded stick boot
# straight into an auto-spawned agent — no Join Hive click, no vault
# round-trip. Lands root-owned 0600 (the importer is root, pre-LOGIN).
if [ -s "${_stage}/.app.env" ]; then
_seed_merge_env "${_stage}/.app.env" "${SEED_PROVIDER_ENV}" root
_seed_log "merged active-agent provider keys -> ${SEED_PROVIDER_ENV}"
fi
if [ -s "${_stage}/.boot.env" ]; then if [ -s "${_stage}/.boot.env" ]; then
_seed_merge_env "${_stage}/.boot.env" "${_home}/${SEED_VAULT_BOOTSTRAP_REL}" "${_user}" _seed_merge_env "${_stage}/.boot.env" "${_home}/${SEED_VAULT_BOOTSTRAP_REL}" "${_user}"
_seed_log "routed Vaultwarden bootstrap creds -> ${_home}/${SEED_VAULT_BOOTSTRAP_REL}" _seed_log "routed Vaultwarden bootstrap creds -> ${_home}/${SEED_VAULT_BOOTSTRAP_REL}"

View file

@ -40,14 +40,26 @@ LAYER 2 — PER-AGENT DIRECTORIES
Create one directory per agent. THE DIRECTORY NAME IS THE AGENT NAME. Create one directory per agent. THE DIRECTORY NAME IS THE AGENT NAME.
Inside it, any of these are honored: Inside it, any of these are honored:
/<agent>/env Plaintext KEY=VALUE lines. Merged into the /<agent>/env Plaintext KEY=VALUE lines. Keys you list
agent's .env (mode 0600). Keys you list
replace existing values; keys you omit are replace existing values; keys you omit are
preserved. Blank/`#` lines are ignored. preserved. Blank/`#` lines are ignored.
Typical contents: provider API keys Routing for the ACTIVE agent:
(ANTHROPIC_API_KEY=..., ZAI_API_KEY=...), - Provider API keys and toggles
or the Vaultwarden bootstrap (DEEPSEEK_API_KEY=..., OPENROUTER_API_KEY=...,
(BW_CLIENTID/BW_CLIENTSECRET/BW_PASSWORD). COLIBRI_AUTOSPAWN_PI=YES, ...) are merged
into BOTH the agent's ~/.env AND the daemon's
/usr/local/etc/colibri/provider.env (mode
0600). Because the importer runs before the
daemon starts, a seeded provider key makes
colibri_daemon auto-spawn the agent on first
boot with NO operator action — fully
zero-touch. No Vaultwarden round-trip needed.
- Vaultwarden bootstrap creds
(BW_CLIENTID/BW_CLIENTSECRET/BW_PASSWORD) are
routed to ~/.config/vault-bootstrap.env for
clawdie-vault-fetch — use these only if you
want the vault-fetch path instead of direct
keys.
The Vaultwarden endpoint is baked into the The Vaultwarden endpoint is baked into the
image; do not put it on the seed unless you image; do not put it on the seed unless you
are deliberately overriding it. are deliberately overriding it.