chore: bump version for cleanness

This commit is contained in:
Dan 2026-07-18 14:39:45 +01:00
parent 014d662bd1
commit 77599ab3ee
3 changed files with 28 additions and 24 deletions

View file

@ -5,6 +5,8 @@ which records not just what changed but what each drill run proved.
## Unreleased
## 0.6.0 — 2026-07-18
### Added
- **The restricted tier: multi-user hosts** (#74, redesigning #72) — an admin
@ -12,7 +14,7 @@ which records not just what changed but what each drill run proved.
hardened `boxnet`, seeing nobody else's; `box revoke <user>` takes it back
(`--purge` deletes their world, and asserts the absence). The tier rides
incus-user, whose defaults miss box's contract three measured ways (Debian
13 / Incus 6.0.4): a private *unhardened* NAT bridge per user, snapshots
13 / Incus 6.0.4): a private _unhardened_ NAT bridge per user, snapshots
blocked, the `box-net` profile invisible — so grant is an idempotent
convergence: project narrowed to `boxnet` **and only boxnet** (listing the
private bridge too, the obvious fix, would keep an unhardened network one
@ -29,7 +31,7 @@ which records not just what changed but what each drill run proved.
`multiuser.sh --container`), so every PR proves the tier's semantics
against a live daemon, not a mock. The VM trust boundary itself remains a
real-hardware ritual, like the full drill.
- **Global / root install** (#71) — run as root, box installs *once* to
- **Global / root install** (#71) — run as root, box installs _once_ to
`/opt/box` (world-readable) with the `box` symlink on `/usr/local/bin`, so
every operator on a shared host runs the same tree. Per-user installs are
unchanged (`$HOME/.local`); `BOX_HOME`/`BOX_BIN` still override. A per-user
@ -53,7 +55,7 @@ which records not just what changed but what each drill run proved.
restricted user; fixed for every tier, and the rehearsal + a grep-guard in
`test/cli.sh` now hold it.
- **`box tmux` works on every template** (#65) — `box tmux` runs
`tmux new-session` *inside* the box, but the templates did not install tmux, so
`tmux new-session` _inside_ the box, but the templates did not install tmux, so
it failed with `tmux: command not found`. `tmux` is now in each template's
cloud-init package list (`blank`/`claude`/`codex`/`grok`).
@ -103,11 +105,11 @@ which records not just what changed but what each drill run proved.
It now asserts the post-install stack in-group before touching the host, and
runs `setup-host` exactly once more — after the clean, which deliberately
unsets `dns.mode` and so has to be converged back. `DRILL_OWNS_SETUP=1`
hands sequencing back to the drill. Pre-setup tripwires now read *before*
hands sequencing back to the drill. Pre-setup tripwires now read _before_
`install.sh`, since that is what triggers setup now.
- **`install.sh` asks, sets up the host, and no-ops on re-run** (#64) — it now
prompts *"Install box?"*, then on a fresh host installs the tree and asks a
second question, *"Set up this machine as a box host now?"*, running the whole
prompts _"Install box?"_, then on a fresh host installs the tree and asks a
second question, _"Set up this machine as a box host now?"_, running the whole
isolation stack if you say yes (previously it only printed a warning and left
you a command, so the install reported success and `box new` died on a host
with no Incus). Prompts read `/dev/tty`, since under `curl | bash` the script
@ -141,7 +143,7 @@ installer retires — but nothing current carries the old name.
is per-port, `--list`/`--remove` manage it, and `box info` shows open
exposures — a box with a hole says so.
- **Inline resource overrides on `new`** — `--cpu <n> --memory <size>
--disk <size>` (#57). Resolution most-specific-first: flag > `BOX_CPU` /
--disk <size>` (#57). Resolution most-specific-first: flag > `BOX_CPU` /
`BOX_MEMORY` / `BOX_DISK` environment (the scripting form) > template
`box.env` > defaults. Values pass to Incus verbatim; resources are all a
flag can touch. `--from` refuses them — a clone carries its source's

View file

@ -8,7 +8,7 @@ destroying it loses nothing you didn't push.
**Strictly creds-free.** A box ships with everything installed and **no**
credentials — no agent token, no git PAT, nothing. You authenticate
interactively *inside* the box. The tool never stores or injects a secret. That
interactively _inside_ the box. The tool never stores or injects a secret. That
means there's nothing shared or committed, so it's safe for multiple operators
out of the box.
@ -23,6 +23,8 @@ runbook that the box's coding agent reads and acts on — there is no `install`
step and no host-run setup. See [docs/box-design.md](docs/box-design.md) for the
design rationale.
> **0.6.0**: multi-user support.
> **0.5.0**: two new templates (`codex`, `grok`), `box expose` — a
> loopback-only door to a box port, for seeing a dev server — and the host
> lifecycle as first-class verbs: `box setup-host`, `box teardown-host`, and
@ -52,7 +54,7 @@ rebuild the stack under your boxes. Upgrading is therefore explicit: uninstall
what you have and install fresh. Preserve any boxes first — `box down <box>`,
copy out anything you need (a portable `box export` is
[#70](https://github.com/heavy-duty/box/issues/70)), then `box rm <box>`
(which deletes the box *and* its snapshots) — then:
(which deletes the box _and_ its snapshots) — then:
```sh
rm -rf ~/.local/share/box ~/.local/bin/box # uninstall
@ -67,7 +69,7 @@ declines the host-setup step.
### Global vs per-user install
Where box lands depends on **who runs the installer**, because on a shared host
box's tree is *executed by other users* — so it cannot hide in one user's home:
box's tree is _executed by other users_ — so it cannot hide in one user's home:
- **As root → global.** The tree goes to `/opt/box` (world-readable) and the
`box` symlink to `/usr/local/bin` (already on every login `PATH`). One
@ -113,11 +115,11 @@ socket is all-or-nothing — `incus-admin` group members own every instance on
the machine — so box layers a second tier on
[incus-user](https://linuxcontainers.org/incus/docs/main/projects/):
| tier | who | what they hold |
|---|---|---|
| **admin** | root, or the `incus-admin` group | everything: all boxes, the stack, `setup-host`, `expose`, `grant` |
| **restricted** | the `incus` group | their **own** boxes only, on the same hardened network |
| none | everyone else | no socket, nothing |
| tier | who | what they hold |
| -------------- | -------------------------------- | ----------------------------------------------------------------- |
| **admin** | root, or the `incus-admin` group | everything: all boxes, the stack, `setup-host`, `expose`, `grant` |
| **restricted** | the `incus` group | their **own** boxes only, on the same hardened network |
| none | everyone else | no socket, nothing |
An admin hands the tier out per user, and takes it back:
@ -132,7 +134,7 @@ box revoke dev1 --purge # ...or end their sessions and delete everything the
`grant` is an idempotent convergence, not a flag flip, because incus-user's
defaults miss box's contract three ways (measured on Debian 13 / Incus 6.0.4,
see [the plan doc](docs/plans/2026-07-18-restricted-tier.md)): it pins each
user to a private *unhardened* NAT bridge, it blocks snapshots, and it cannot
user to a private _unhardened_ NAT bridge, it blocks snapshots, and it cannot
see the `box-net` profile. Granting rewires all three: the user's project is
restricted to `boxnet` **and only boxnet** — the hardened network is not their
default placement but the only one their certificate can express — snapshots
@ -175,12 +177,12 @@ claude # if the repo has .box/, the agent reads it and
No coding agent is special — each is one template among several, and adding
another is just another directory. What ships today:
| Template | What's in it |
| --- | --- |
| Template | What's in it |
| -------- | --------------------------------------------------------- |
| `blank` | Bare Debian 13 — same isolation, no tooling. The default. |
| `claude` | Claude Code, creds-free — where this project started |
| `codex` | OpenAI Codex CLI, creds-free |
| `grok` | xAI Grok CLI, creds-free |
| `claude` | Claude Code, creds-free — where this project started |
| `codex` | OpenAI Codex CLI, creds-free |
| `grok` | xAI Grok CLI, creds-free |
A template is a directory under `templates/`: a `box.env` (image, user,
resources — parsed against a strict allowlist, never sourced) and a
@ -303,7 +305,7 @@ command surface is a table.
## Isolation
The contract: **a box reaches the public internet and nothing else.** Not the
host, not your LAN, not another box, not even another box's *name*. What
host, not your LAN, not another box, not even another box's _name_. What
enforces it, layer by layer:
- **Dedicated NAT bridge** `boxnet`, IPv6 off. Every rule below is
@ -311,7 +313,7 @@ enforces it, layer by layer:
contract, not a default.
- **`box-isolate` ACL** — drops all egress to private space (RFC1918,
CGNAT, link-local), with a single carve-out to the gateway so DNS works.
- **Sibling isolation, at L2** — two boxes on one bridge are *switched*,
- **Sibling isolation, at L2** — two boxes on one bridge are _switched_,
never routed, so no L3 rule can separate them (learned the hard way; see
below). `security.port_isolation` on every box NIC plus an nft
bridge-family drop mean box A cannot exchange frames with box B at all.

View file

@ -1 +1 @@
0.5.0
0.6.0