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
|
|
|
# The claude template — a thin, creds-free seed (#81): Debian 13, the
|
|
|
|
|
# 'claude' user, tmux and rig. What the box BECOMES — the Claude Code CLI,
|
|
|
|
|
# docker, node, the agent-context file with its #80 guard — is rig's job:
|
|
|
|
|
# box auto-runs 'rig bootstrap claude' after mint (heavy-duty/rig#31).
|
feat!: claudebox becomes box — the Claude box is one template among several
The tool underneath was already generic: a thin, honest wrapper over
Incus. What was Claude-specific was welded on — one image, one profile,
one cloud-init file, one hardcoded 'sudo -u claude'. The weld is now a
template.
The mechanic: 'box new' stamps the template's identity onto the
instance (user.box=1, user.box.template, user.box.user); shell/exec/
tmux read the user back off the instance, and 'incus copy' carries
user.* keys (audit B2), so a clone knows what it is without consulting
the template. Templates are box.env (parsed against a strict allowlist,
never sourced — no key for a network exists, on purpose) plus a
verbatim cloud-init. Every template launches with the shared box-net
profile: the isolated NIC and root disk, nothing template-controlled —
resources land per-instance from box.env, overridable via BOX_CPU/
BOX_MEMORY/BOX_DISK (which is also how the drill shrinks boxes on a
small host now that profile edits can't).
The three open calls, taken as recommended: clean cut at 0.4.0 (no
claudebox shim; the installer retires the old symlink); default
template = claude (muscle memory survives); repo stays heavy-duty/
claudebox, binary is box.
Compat is the tag, not the name: resolve_box and list honor the legacy
user.claudebox=1 forever, and the legacy tag maps to the claude user —
a pre-rename box lists, shells, clones, unchanged.
Deliberate divergence from #17's table: the host-stack resource names
(claudenet, claude-isolate, nft tables, claudebox-firewall.*) are NOT
renamed — they are host-internal, invisible to users, and renaming
them breaks every provisioned host for zero user-visible gain.
claude-dev is no longer created; setup-host creates box-net, teardown
removes both.
Closes #17
2026-07-14 14:22:50 +00:00
|
|
|
# KEY="value" only. Parsed against an allowlist, never sourced; there is no
|
|
|
|
|
# key for a network or a security flag, on purpose — the shared box-net
|
|
|
|
|
# profile is the placement contract and no template can weaken it.
|
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_USER must match the user user-data.yaml creates (the duplication is
|
|
|
|
|
# deliberate and by hand) — and it is the tenant user the rig role converges
|
|
|
|
|
# (rig dies loudly if the seed did not create it).
|
|
|
|
|
BOX_DESCRIPTION="Claude Code on Debian 13, creds-free — box mints, rig converges"
|
feat!: claudebox becomes box — the Claude box is one template among several
The tool underneath was already generic: a thin, honest wrapper over
Incus. What was Claude-specific was welded on — one image, one profile,
one cloud-init file, one hardcoded 'sudo -u claude'. The weld is now a
template.
The mechanic: 'box new' stamps the template's identity onto the
instance (user.box=1, user.box.template, user.box.user); shell/exec/
tmux read the user back off the instance, and 'incus copy' carries
user.* keys (audit B2), so a clone knows what it is without consulting
the template. Templates are box.env (parsed against a strict allowlist,
never sourced — no key for a network exists, on purpose) plus a
verbatim cloud-init. Every template launches with the shared box-net
profile: the isolated NIC and root disk, nothing template-controlled —
resources land per-instance from box.env, overridable via BOX_CPU/
BOX_MEMORY/BOX_DISK (which is also how the drill shrinks boxes on a
small host now that profile edits can't).
The three open calls, taken as recommended: clean cut at 0.4.0 (no
claudebox shim; the installer retires the old symlink); default
template = claude (muscle memory survives); repo stays heavy-duty/
claudebox, binary is box.
Compat is the tag, not the name: resolve_box and list honor the legacy
user.claudebox=1 forever, and the legacy tag maps to the claude user —
a pre-rename box lists, shells, clones, unchanged.
Deliberate divergence from #17's table: the host-stack resource names
(claudenet, claude-isolate, nft tables, claudebox-firewall.*) are NOT
renamed — they are host-internal, invisible to users, and renaming
them breaks every provisioned host for zero user-visible gain.
claude-dev is no longer created; setup-host creates box-net, teardown
removes both.
Closes #17
2026-07-14 14:22:50 +00:00
|
|
|
BOX_IMAGE="images:debian/13/cloud"
|
|
|
|
|
BOX_USER="claude"
|
|
|
|
|
BOX_CPU="4"
|
|
|
|
|
BOX_MEMORY="8GiB"
|
|
|
|
|
BOX_DISK="60GiB"
|
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_BOOTSTRAP_ROLE="claude"
|