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:03:41 +00:00
|
|
|
#cloud-config
|
|
|
|
|
# A thin, creds-free, server-class seed (#81): the 'ops' user, tmux (#65),
|
|
|
|
|
# and rig — nothing that joins a tailnet or admits credentials, no docker,
|
|
|
|
|
# no sshd config, no keys. The server posture comes from
|
refactor(templates): the tenant seeds carry rig's -box family suffix
rig is growing a second family of roles, and once a 'staging' role can mean
either a fleet machine or a box tenant, the bare name stops naming anything.
rig's answer is a suffix on the role (heavy-duty/rig#76): '-server' for fleet
machines, '-box' for box tenants. box's answer is that a template keeps being
named for the role it converges, so the tenant templates move with it:
claude -> claude-box codex -> codex-box
grok -> grok-box staging -> staging-box
Templates are the only surface that spells a rig role out loud
(BOX_BOOTSTRAP_ROLE, auto-run at mint since #81), so a directory that says one
thing and a role key that says another is a trap with a 15-minute fuse: it
mints clean and dies at convergence. Renamed with 'git mv' so the history of
each seed follows it.
'blank' keeps its name. It seeds no tenant role and sets no
BOX_BOOTSTRAP_ROLE, so it has nothing to agree with — renaming it would only
churn the default template's name for symmetry's sake.
Two namespaces move apart here and only one of them moved: the template name
and the role are now claude-box, while the seed USER stays 'claude' — that is
the user rig's role converges and the one 'box shell' lands in. test/cli.sh
pins the pair per tenant rather than each half alone, because a later rename
that moves one and forgets the other mints a box whose role dies looking for a
user nobody created. drill.sh keeps its bare box NAMES ('codex', 'grok' — what
the pre-flight banner announces and what teardown deletes) and only moves the
--template it passes.
The mint-time hints in cmd_new match both spellings of user.box.template, and
that is not an alias for the role: 'rig bootstrap claude' is gone and nothing
here softens the cut. The stamp is a fact about an INSTANCE, written at its own
mint time and carried forward by every clone; refusing the old spelling would
cut nothing over and only drop the login hint on boxes that predate today —
the same reason user.claudebox is honored everywhere else. migrate-host.sh
stamps re-homed legacy boxes claude-box, the name the template has today, so a
re-homed box looks like a fresh mint rather than a fossil.
Ordered AFTER rig's rename, and that is not a preference. The seeds install rig
from RIG_REPO/RIG_REF, defaulting to heavy-duty/rig@main and unpinned until
rig#32's releases, so these templates ask whatever main happens to be for
'rig bootstrap claude-box'. Against a pre-rename rig that role does not exist
and cmd_new refuses to call the box ready. Merged in the other order the window
closes instead of opening: rig's cut is hard, with no aliases, so the day it
lands every unmerged box seed naming a bare role is the broken one.
Closes #123
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:01:29 +00:00
|
|
|
# 'rig bootstrap staging-box' (heavy-duty/rig#31), which box auto-runs after
|
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:03:41 +00:00
|
|
|
# mint; the tailnet workload join holds a pre-auth key and stays
|
2026-07-20 00:07:00 +00:00
|
|
|
# operator-run ('box shell' → 'sudo rig bootstrap workload-server'), exactly as
|
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:03:41 +00:00
|
|
|
# #69 designed it — box never sees the key.
|
|
|
|
|
users:
|
|
|
|
|
- name: ops
|
|
|
|
|
shell: /bin/bash
|
|
|
|
|
sudo: "ALL=(ALL) NOPASSWD:ALL"
|
|
|
|
|
lock_passwd: true
|
|
|
|
|
package_update: true
|
|
|
|
|
# tmux: 'box tmux' runs 'tmux new-session' INSIDE the box (#65) — and the
|
|
|
|
|
# operator babysits the workload join through it.
|
|
|
|
|
# curl + ca-certificates: the rig installer below rides them, and a bare
|
|
|
|
|
# cloud image is not guaranteed to ship either.
|
|
|
|
|
packages:
|
|
|
|
|
- tmux
|
|
|
|
|
- curl
|
|
|
|
|
- ca-certificates
|
|
|
|
|
runcmd:
|
|
|
|
|
# Preinstall rig so the box can converge — and re-converge — via
|
refactor(templates): the tenant seeds carry rig's -box family suffix
rig is growing a second family of roles, and once a 'staging' role can mean
either a fleet machine or a box tenant, the bare name stops naming anything.
rig's answer is a suffix on the role (heavy-duty/rig#76): '-server' for fleet
machines, '-box' for box tenants. box's answer is that a template keeps being
named for the role it converges, so the tenant templates move with it:
claude -> claude-box codex -> codex-box
grok -> grok-box staging -> staging-box
Templates are the only surface that spells a rig role out loud
(BOX_BOOTSTRAP_ROLE, auto-run at mint since #81), so a directory that says one
thing and a role key that says another is a trap with a 15-minute fuse: it
mints clean and dies at convergence. Renamed with 'git mv' so the history of
each seed follows it.
'blank' keeps its name. It seeds no tenant role and sets no
BOX_BOOTSTRAP_ROLE, so it has nothing to agree with — renaming it would only
churn the default template's name for symmetry's sake.
Two namespaces move apart here and only one of them moved: the template name
and the role are now claude-box, while the seed USER stays 'claude' — that is
the user rig's role converges and the one 'box shell' lands in. test/cli.sh
pins the pair per tenant rather than each half alone, because a later rename
that moves one and forgets the other mints a box whose role dies looking for a
user nobody created. drill.sh keeps its bare box NAMES ('codex', 'grok' — what
the pre-flight banner announces and what teardown deletes) and only moves the
--template it passes.
The mint-time hints in cmd_new match both spellings of user.box.template, and
that is not an alias for the role: 'rig bootstrap claude' is gone and nothing
here softens the cut. The stamp is a fact about an INSTANCE, written at its own
mint time and carried forward by every clone; refusing the old spelling would
cut nothing over and only drop the login hint on boxes that predate today —
the same reason user.claudebox is honored everywhere else. migrate-host.sh
stamps re-homed legacy boxes claude-box, the name the template has today, so a
re-homed box looks like a fresh mint rather than a fossil.
Ordered AFTER rig's rename, and that is not a preference. The seeds install rig
from RIG_REPO/RIG_REF, defaulting to heavy-duty/rig@main and unpinned until
rig#32's releases, so these templates ask whatever main happens to be for
'rig bootstrap claude-box'. Against a pre-rename rig that role does not exist
and cmd_new refuses to call the box ready. Merged in the other order the window
closes instead of opening: rig's cut is hard, with no aliases, so the day it
lands every unmerged box seed naming a bare role is the broken one.
Closes #123
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 00:01:29 +00:00
|
|
|
# 'rig bootstrap staging-box'. @RIG_REPO@/@RIG_REF@ are the pin point (#81):
|
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:03:41 +00:00
|
|
|
# box substitutes them at mint from the RIG_REPO/RIG_REF environment
|
|
|
|
|
# (default heavy-duty/rig @ main — unpinned, tracking main, the same
|
|
|
|
|
# honest edge as rig's own unpinned box install, until rig#32 ships a
|
|
|
|
|
# release flow). The pin covers both the installer fetched AND the tree
|
|
|
|
|
# it installs, so a branch under review is testable end to end.
|
2026-07-18 20:20:49 +00:00
|
|
|
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
|
|
|
|
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
|
|
|
|
# measured live: the mint died with "HOME: unbound variable" without it.
|
|
|
|
|
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|