feat: thin templates — box mints, rig converges (#81) #88

Merged
dan-claude-bot merged 5 commits from feat/thin-templates into main 2026-07-18 21:08:44 +00:00

5 commits

Author SHA1 Message Date
dan-claude-bot
fb1f5e6f98 test(cli): pin the HOME=/root scar so a seed rewrite cannot drop it silently
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:52:15 +00:00
dan-claude-bot
24e67a3c2b fix(templates): pin HOME=/root on the seed's rig install — runcmd has no $HOME
Measured live on the first e2e mint: cloud-init runs runcmd as root but
with NO $HOME in the environment, and rig's installer (set -u) reads
$HOME for its DEST — the mint died with 'HOME: unbound variable'. The
seed line now pins HOME=/root explicitly, with the scar documented next
to it in every tenant template.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:51:46 +00:00
dan-claude-bot
3a7a9f6c6b docs: the thin-template story — box mints, rig converges (#81)
README: the templates section tells the split — thin seeds (user, tmux,
rig), the creds-free tenant role auto-run at mint, the operator-run
workload join, and the RIG_REPO/RIG_REF pin point with the honest unpinned
note (both directions of the rig<->box edge track main until rig#32/#83).
The #80 guard note is cross-referenced as living once, in rig's roles.

box-design.md: a layering section (why rig roles and not cloud-init:
convergent, re-runnable, effective-state-asserted vs a first-boot
one-shot), and the announce section now says who renders the context file.

CHANGELOG: staging template, BOX_BOOTSTRAP_ROLE + auto-run, the pin point
under Added; the tenant-content move under Changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:51:46 +00:00
dan-claude-bot
a8d0f64e48 test(cli): the seed contract, driven — and the absence, fail-closed (#81)
- render_userdata extracted and DRIVEN: defaults pin heavy-duty/rig@main on
  both the installer URL and the installer's env; RIG_REPO/RIG_REF override
  at mint; a shell-shaped repo, a spaced ref and a newline-smuggled repo all
  die on the host (bash =~ anchors the whole string — one clean line cannot
  sneak past it the way a line-oriented grep -q lets it).
- BOX_BOOTSTRAP_ROLE through the real parser: round-trips, and a
  shell-shaped value dies at the gate.
- Per-template sweep grows the #81 contract: BOX_USER matches the user the
  cloud-init actually creates; a role-bearing seed installs rig carrying
  BOTH pin tokens; and absence greps over EFFECTIVE cloud-init lines
  (comments may name what they refuse — #69's idiom) prove no agent CLI, no
  docker, no tailscale/authkey/ssh, no write_files heredocs, in ANY
  template. Want-exit 1: re-adding tenant content goes red.
- cmd_new pinned: user-data reaches Incus through render_userdata; the
  auto-run orders after the cloud-init wait and sits under the
  T_BOOTSTRAP_ROLE guard; the failure path names the re-run; the workload
  join is printed, never exec'd; no template names a creds-holding role.
- staging's boot demands pinned to the file; blank proven roleless and
  rig-less.

283 passed, 0 failed (main: 213); shellcheck -x clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:51:27 +00:00
dan-claude-bot
f4f25016d6 feat(templates): thin creds-free seeds — box mints, rig converges (#81)
The tenant content that lived in claude/codex/grok's cloud-init — agent CLI
installs, docker, node, the per-template agent-context heredocs — moves to
rig's bootstrap roles (rig#31), where it is convergent, idempotent and
testable end to end. What remains per template is a thin seed: the tenant
user, tmux (#65), and rig preinstalled — nothing that joins a tailnet or
admits credentials.

- BOX_BOOTSTRAP_ROLE: a template names the creds-free rig role cmd_new
  auto-runs inside the guest after cloud-init settles. The value is a role
  NAME by allowlist — anything shell-shaped dies at parse time, on the
  host. A failed role leaves the box up and names the re-run.
- render_userdata: the seed's ONE substitution. @RIG_REPO@/@RIG_REF@
  resolve from the mint environment (default heavy-duty/rig @ main —
  unpinned, the honest rig#29 treatment, until rig#32's releases); values
  are whole-string-validated before touching the YAML, because they land
  inside a runcmd shell line.
- templates/staging: the re-cut of #69's layering — user ops,
  BOX_REQUIRE_VM=1, BOX_AUTOSTART=1, role staging. The tailnet workload
  join holds a key and stays operator-run; cmd_new prints it as the next
  step and box never sees the key.
- blank stays a box with nobody home: no rig, no role, nothing auto-runs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 20:51:27 +00:00