forked from heavy-duty/box
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
28 lines
1.3 KiB
YAML
28 lines
1.3 KiB
YAML
# The placement contract. Every box, whatever its template, launches with
|
|
# exactly this profile: the isolated NIC and the root disk — and NOTHING a
|
|
# template controls. Resources (limits.*) are per-instance, stamped from the
|
|
# template's box.env at mint time; they do not belong here, because a profile
|
|
# a template could point away from is a network a template could escape.
|
|
name: box-net
|
|
description: The box placement contract — isolated NIC + root disk, nothing else
|
|
config: {}
|
|
devices:
|
|
eth0:
|
|
type: nic
|
|
network: claudenet
|
|
name: eth0
|
|
# Boxes must not reach each other. This is the mechanism that actually does
|
|
# it: the kernel bridge's port-isolation flag, which stops two isolated
|
|
# ports exchanging frames at L2.
|
|
#
|
|
# It is not an ACL rule, and it cannot be. Incus ACLs are L3/L4, and two
|
|
# boxes on one bridge are on the same L2 segment — their frames are switched
|
|
# between ports and never traverse the netfilter path an ACL lives on. That
|
|
# is why the ACL's drop on 10.0.0.0/8 (which contains claudenet) and its
|
|
# default ingress drop BOTH looked airtight while box→box was wide open: a
|
|
# live probe found box A's SYN arriving at box B and B answering with a RST.
|
|
security.port_isolation: "true"
|
|
root:
|
|
type: disk
|
|
pool: default
|
|
path: /
|