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>
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>
- 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>
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>