Commit graph

24 commits

Author SHA1 Message Date
d9c3d5680d fix(drill): per-name cleanup, hatch-aware diagnostics, full teardown sweep
Run 2's five cascade FAILs traced to one line: 'incus delete -f a b c'
aborts at the first missing name, so an interrupted run's boxes survived
into the next run — stale 'archive' broke the rename, stale 'peer' broke
the clone (UNIQUE constraint), and the un-renamed 'drill' survived
teardown. Deletion is now one name at a time, and teardown sweeps every
name the drill can have left, whatever branch a partial run took.

The claude-in-box diagnostics also read the wrong line: the escape hatch
prints its 'claudebox: incus exec …' announce before the output, and
'head -1' captured the announce. Diagnostics now filter it, and probe
whether the binary itself runs by full path. Docker's D-phase note gains
a dockerd systemctl readout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 22:22:50 +00:00
9112f9e446 fix: make claude reachable from non-interactive shells in the box
'claudebox exec box -- claude --version' — the exact invocation the help
promises — failed on the first live drill: the binary lands in
~/.local/bin, but cloud-init only exported PATH in .bashrc/.zshrc, which
the non-interactive shell behind exec never reads (the box's login shell
is zsh, so even sudo -i reads neither). A symlink in /usr/local/bin is
on every shell's PATH, interactive or not. Found by the drill (#15).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 22:22:50 +00:00
Daniel Marin
5c1912a555
Merge pull request #19 from claude-hdb/drill/first-run-fixes
fix: first-run drill defects — pipefail false FAILs, exec-pty hang, DHCP race
2026-07-13 23:07:32 +01:00
4ea758fa81 fix: three first-run drill defects — pipefail false FAILs, exec-pty hang, DHCP race
The first live run (issue #15) hit all three: (1) 'set -o pipefail'
turned every 'refusal 2>&1 | grep -q' check into a false FAIL — the
refusals exit 1/2 by design, and 'grep -q' SIGPIPEs the left side on
early match; the pipeline verdict must be grep's alone, so pipefail is
gone with a comment explaining why. (2) The in-box background listener
inherited the exec pty, so 'incus exec' waited on it forever — the run
hung at C7 for 10+ minutes; the listener is now a helper with </dev/null
on the child and a timeout on the client. (3) eth0_ip raced DHCP — the
agent answers before the address exists — so the sibling probe never
ran; it now retries for 30s.

Also: the claude-in-box check now distinguishes 'installed but not on
exec's PATH' (a repo bug — the help promises that exact invocation)
from 'not installed', and dumps cloud-init status when it is the latter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 22:05:32 +00:00
Daniel Marin
de671b0731
Merge pull request #18 from claude-hdb/drill/rehearsal
test: add an end-to-end drill against a real Incus
2026-07-13 22:34:43 +01:00
aba7015e53 test: fold the #15 audit into the drill — sibling probes and a hardening rehearsal
The drill covered the CLI surface and basic egress but never probed the
one claim everything hinges on: it only ever had one box running, so
sibling isolation — #12's central, never-reproduced finding — went
untested. Phase C now clones a peer and probes box-to-box reachability
(with a listener, so "refused" cannot masquerade as "dropped"), DNS
enumeration, IPv6-off, and host-to-box inbound. A new phase D applies
the exact changes #16 proposes (dns.mode=none, NIC filtering, @internal)
and watches what breaks — a FAIL there is a design veto caught before
the code is written. The run ends with an answers block to paste into
issue #15.

Also: defaults now install heavy-duty/claudebox@main (the old default
pointed at a fork branch that stopped moving when #13 merged), the
version check compares against the installed VERSION file instead of a
hardcoded 0.3.0, and the blind sleep after start became an agent poll.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:30:47 +00:00
69eff180d4 fix: setup-host installs nftables when neither nft nor UFW exists
A stock Debian 13 cloud image ships neither, so the no-UFW firewall path
died on its first nft call — on exactly the kind of fresh cloud host the
tool targets. Found by the drill; the drill's manual workaround becomes a
regression tripwire.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:30:47 +00:00
c1ca981f46 test: add an end-to-end drill against a real Incus
The repo has no tests and no CI, and everything in #9/#10/#13 was verified
against a stubbed incus. The interesting failures aren't in the bash — they're
in what Incus actually does, which is exactly what a stub gets wrong. So the
drill runs the real thing on a host you can format.

  A. Incus semantics — the assumptions claudebox is built on, probed directly:
     the user.claudebox=1 tag read (now on the path of every box command in
     #13), the list filter, the CSV shape, the state string, whether rename
     really refuses a running instance, the snapshot-list field order.
  B. The claudebox surface — mint, list, info, snapshot, clone-from-snapshot-of-
     a-renamed-box, rename, the escape hatch, the rm guard, the CLI contract —
     and the boundary: an instance claudebox did NOT mint has down/rm/hatch
     aimed at it, must refuse all three, and must still be standing after.
  C. Isolation — from inside a real box: public egress works, box → host is
     blocked, box → RFC1918 is dropped.

Destructive by design, and it says so before it starts: it asks for consent
(--yes to skip), and refuses outright with no TTY. Without /dev/kvm it reports
that the VM trust boundary was NOT validated rather than passing on a container.
2026-07-13 21:15:56 +00:00
Daniel Marin
0982a2d36f
Merge pull request #13 from claude-hdb/refactor/command-table
feat: make the command surface a table, add `rename` and an escape hatch
2026-07-13 21:56:45 +01:00
7d33ad8a00 feat: make the command surface a table, add rename and an escape hatch
Every incus verb is a candidate feature request, and wrapping them one at a
time grows a worse incus. This lands the rule instead: claudebox owns a command
when it must enforce an invariant incus cannot see — the user.claudebox=1
boundary, the isolation stack, or the creds-free snapshot workflow. Everything
else is incus's job, and now has a door.

- CMDS table: one row per command, carrying synopsis, preconditions, summary,
  action and success message. Dispatch AND help are rendered from it, so the
  help can no longer describe a command that doesn't exist — the drift that
  produced #8 is now impossible, not merely fixed.
- rename, via a table row: it needs the box stopped (incus won't rename a
  running instance), so it says so instead of leaking an incus error.
- `claudebox incus <box> -- <args...>`: the escape hatch. Box resolved and
  tag-checked, rest passed to incus verbatim, {} substituted, command echoed.
  Warns when it can move a box off the isolation stack.
- The boundary is now ENFORCED, not assumed: every box-taking command resolves
  through the user.claudebox=1 tag, so claudebox will not stop, rename or delete
  an instance it didn't mint.
- The rule, written into docs/claudebox-design.md.

Closes #11
2026-07-13 20:49:46 +00:00
Daniel Marin
f2dab00549
Merge pull request #10 from claude-hdb/fix/cli-help-and-contract
fix: standard help, honest flags, and an `rm` that asks first
2026-07-13 21:43:14 +01:00
Daniel Marin
754f1ca56b
Merge pull request #9 from claude-hdb/feat/list-command
feat: add `claudebox list`, with snapshot labels
2026-07-13 21:42:59 +01:00
8636d1abcb fix: standard help, honest flags, and an rm that asks first
The help was `sed -n '2,9p' "$0"` — the script scraping its own header comment
by line number — with no sections, no per-command help, no `-h`, and no
`--version`. Worse, parts of the interface it described weren't real: `--force`
was parsed and never read, so `claudebox rm work` force-deleted a running box
with no confirmation while advertising a flag that implied the bare form was
the careful one.

- usage()/help_cmd(): NAME/USAGE/COMMANDS/OPTIONS/EXAMPLES/EXIT STATUS, plus a
  page per command. `claudebox help <cmd>` and `claudebox <cmd> --help`.
- `-h`/`--help` anywhere; `--version`/`-V` (VERSION file).
- Unknown command → stderr, exit 2, and a did-you-mean by edit distance.
- Unknown options are rejected, not swallowed as positionals: `snapshot work
  --labl x` no longer names the snapshot "--labl". `--` still passes everything
  through for exec.
- `--force` is real: `rm` confirms on a TTY, refuses without one unless forced.
  BEHAVIOR CHANGE — a scripted `claudebox rm` now needs `--force`.
- Exit 2 for usage errors, 1 for runtime failures; accurate per-command usage
  strings (`exec` no longer omits `-- <cmd...>`).
- shellcheck is clean: SC2034 (dead `force`) and SC2015 both gone.

Closes #8
2026-07-13 20:41:21 +00:00
5ce2a0ae8d feat: add claudebox list and claudebox info
`status` was the only lister: misnamed, silently ignoring the `<box>` its own
help advertised, and unable to show a snapshot label — the one thing the
README's snapshot→clone flow requires you to know. Typing the obvious
`claudebox list` printed the help and exited 0.

- `list` — a table of your boxes (name, state, type, snapshot count).
- `info <box>` — detail, the snapshot labels, and the `--from` line to clone one.
- `list <box>` is a wrong guess we can answer: it points at `info`.
- `--json` passthrough; `--remote` honored; a real error on an unknown box.
- `status` stays as a deprecated alias for `list` so muscle memory keeps working.

Reads are filtered by the `user.claudebox=1` tag, so an Incus instance
claudebox didn't mint is never reported on or touched.

Closes #7
2026-07-13 20:40:17 +00:00
Daniel Marin
188c8b0f95
Add MIT License to the project 2026-07-11 21:33:51 +01:00
Daniel Marin
00e5ef524e
Merge pull request #5 from claude-hdb/fix/preinstall-gh-cli
fix: preinstall the GitHub CLI in boxes
2026-07-11 12:25:40 +01:00
1abd776850 fix: preinstall the GitHub CLI in boxes
The README's auth flow tells operators to run `gh auth login`, but the
cloud-init package set never installed gh — a fresh box couldn't follow
its own instructions. Debian 13 ships gh natively, so a plain packages
entry suffices.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:21:30 +00:00
Daniel Marin
34158aeafa
Merge pull request #4 from claude-hdb/feat/drop-name-prefix
feat: drop the claude- instance-name prefix
2026-07-10 18:11:30 +01:00
d956614269 feat: drop the claude- instance-name prefix; tag boxes instead
Instance names now equal the box name (claudebox new --name work -> 'work',
not 'claude-work'). To still tell claudebox's instances apart from any other
incus instances, tag each on creation with user.claudebox=1 and filter status
+ teardown by that tag instead of a name prefix. Clones (new --from) inherit
the tag.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 17:09:45 +00:00
Daniel Marin
0628ac81b6
Merge pull request #2 from claude-hdb/fix/install-public-canonical
fix: install from the public canonical repo
2026-07-10 17:00:21 +01:00
e175c416c3 fix: install from the public canonical heavy-duty/claudebox
The fork claude-hdb/claudebox is private, so an anonymous 'curl | bash' against
it 401s. Point the installer default and the README one-liner at the public
canonical repo so the install flow works without auth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:08:18 +00:00
Daniel Marin
95ba6abbe2
Merge pull request #1 from claude-hdb/import-claudebox
Import claudebox: creds-free, trust-less Claude Code VMs
2026-07-10 16:01:39 +01:00
0c9971ecf8 Import claudebox: creds-free, trust-less Claude Code VMs
A CLI that mints trust-less, network-isolated Incus VMs with Claude Code
installed. Boxes are strictly creds-free — the operator logs into Claude
interactively inside; authenticated state is reused via snapshots. The tool
knows nothing about projects; a repo ships an optional agent-facing .claudebox/
runbook that Claude reads.

- bin/claudebox: new/shell/exec/snapshot/restore/down/start/rm/status; creds-free
  'new' (fresh launch or clone via --from <src>[/<snap>]).
- cloud-init: global ~/.claude/CLAUDE.md self-describing the box + .claudebox/ runbook.
- install.sh: curl-pipe-bash installer.
- host/: Incus isolation stack (claudenet + claude-isolate ACL + claude-dev
  profile + firewall).
- docs/: design + .claudebox/ convention.

Initial canonical import (prototyped separately; re-homed onto the heavy-duty fork).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 15:00:36 +00:00
Daniel Marin
ec41ade3fc
Create README.md 2026-07-10 15:53:40 +01:00