feat: global install (#71), tmux (#65), CI + tests; folds #66 #73

Merged
dan-claude-bot merged 7 commits from feat/multiuser-hosts into main 2026-07-18 00:24:38 +00:00
5 changed files with 97 additions and 17 deletions
Showing only changes of commit aad576a86a - Show all commits

View file

@ -3,6 +3,30 @@
History before 0.5.0 lives in git and in [drill/RUNS.md](drill/RUNS.md),
which records not just what changed but what each drill run proved.
## Unreleased
### Fixed
- **`box setup-host` finishes in one run** (#63). When it had to add you to
`incus-admin` it stopped there and told you to re-login and re-run — an
`exit 0` that reported success having built none of the stack: no `boxnet`,
no ACL, no `box-net` profile, no firewall. It now re-execs itself under
`sg incus-admin` and completes in that one invocation. The membership check
was also asking the wrong question: `id -nG "$USER"` reads the group
database, which lists the group the moment `usermod` returns, so a
same-session re-run passed the check with credentials that still lacked the
group and died further down on a bare permission error from `incus`. Argless
`id -nG` asks the process what it actually holds.
### Changed
- **`install.sh` runs the host setup itself** (#64) — it printed a warning and
left you a command to run, so the install reported success and `box new`
failed on a host with no Incus. Since `setup-host` is idempotent, doing this
on every install is also how an upgraded host picks up stack changes.
`BOX_SKIP_SETUP_HOST=1` opts out; if setup fails, the install still stands
and says what to re-run.
## 0.5.0 — 2026-07-15
The release the project was renamed in: the repo is `heavy-duty/box`, matching

View file

@ -41,13 +41,18 @@ curl -fsSL https://raw.githubusercontent.com/heavy-duty/box/main/install.sh | ba
```
Installs the tree to `~/.local/share/box` and links `box` onto your
`PATH`. Re-run any time to upgrade — upgrading from a pre-0.4.0 install also
retires the old `claudebox` symlink. (No `git clone` needed.)
`PATH`, then runs the host setup below for you (it may ask for `sudo`; set
`BOX_SKIP_SETUP_HOST=1` to opt out and run it yourself). Re-run any time to
upgrade — upgrading re-applies the host stack, and from a pre-0.4.0 install
also retires the old `claudebox` symlink. (No `git clone` needed.)
## One-time host setup (Ubuntu 24.04 / Debian 13)
The installer already does this. Run it directly to set up a host you
installed with `BOX_SKIP_SETUP_HOST=1`, or to re-apply the stack by hand:
```sh
box setup-host # run twice if it adds you to incus-admin (re-login between)
box setup-host # one run is enough
```
Idempotent. Installs Incus and creates the isolation stack: the `boxnet` NAT

View file

@ -387,10 +387,11 @@ EOF
Prepare this host to mint boxes — one time. Installs Incus and builds the
isolation stack: the boxnet NAT bridge (resolver pinned), the box-isolate
ACL, the box-net profile, and the firewall rules, all re-applied at boot.
Idempotent — safe to re-run after a box upgrade to pick up stack changes.
Idempotent — safe to re-run after a box upgrade to pick up stack changes;
install.sh runs it for you, so this is for re-applying by hand.
If it has to add you to the incus-admin group it will say so and exit; log
back in (or 'sg incus-admin') and run it again.
One run is enough. If it has to add you to the incus-admin group it re-runs
itself under that group — no re-login, no second invocation.
box setup-host
EOF

View file

@ -3,17 +3,39 @@
# the box-net profile. Idempotent. Ubuntu 24.04 / Debian 13.
set -euo pipefail
here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
self="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
here="$(dirname "$(dirname "$self")")"
if ! command -v incus >/dev/null; then
sudo apt-get update
sudo apt-get install -y incus
fi
if ! id -nG "$USER" | grep -qw incus-admin; then
# Group membership is a property of THIS PROCESS's credentials, not of the group
# database — and the two disagree for exactly as long as it matters here.
# 'id -nG "$USER"' names a user, so it reads /etc/group and reports incus-admin
# the instant usermod returns; the running shell's own credentials still lack
# it, because supplementary groups are fixed at login. So the old check passed
# on a same-session re-run, sailed into the incus calls below, and died on a
# permission error that named neither the group nor the re-login. Argless
# 'id -nG' asks the process what it actually holds, which is what incus checks
# when it opens /var/lib/incus/unix.socket.
if ! id -nG | grep -qw incus-admin; then
sudo usermod -aG incus-admin "$USER"
echo "NOTE: added $USER to incus-admin — re-login (or 'sg incus-admin') and re-run."
exit 0
# Then finish the job rather than adjourning it. Exiting 0 here was a
# success-shaped no-op: no boxnet, no ACL, no box-net profile, no firewall —
# and the burden of knowing that on the reader of a NOTE (#63). 'sg' runs us
# again with the new group in our credentials, no re-login, one invocation.
# The guard makes that at most one hop: if sg somehow lands without the
# group, we fail loudly instead of forking forever.
if [ -z "${BOX_SETUP_HOST_REEXEC:-}" ]; then
echo "added $USER to incus-admin — re-running under the new group (no re-login needed)"
export BOX_SETUP_HOST_REEXEC=1
exec sg incus-admin -c "$(printf '%q ' bash "$self" "$@")"
fi
echo "ERROR: still not in incus-admin after usermod + sg." >&2
echo " log out and back in, then re-run: box setup-host" >&2
exit 1
fi
# Storage pool + base config (safe to re-run: skipped once the pool exists).

View file

@ -84,16 +84,44 @@ case ":$PATH:" in
;;
esac
# --- environment check -----------------------------------------------------
if ! command -v incus >/dev/null 2>&1; then
warn "incus was not found. box needs Incus on the host."
warn " run the one-time host setup: $DEST/host/setup-host.sh"
fi
# Record WHAT was installed, so a caller can assert it got what it asked for.
# Without this, an installer invoked with stale env vars (the CLAUDEBOX_* names
# retired in 0.5.0) silently falls back to the defaults and installs main —
# and the caller drills the wrong tree, believing it drilled its branch.
# Written BEFORE host setup: this records the install, which has now happened,
# and it must not hinge on whether the host stack came up.
printf '%s@%s\n' "$REPO" "$REF" > "$DEST/INSTALLED_FROM"
log "done ($REPO@$REF) — try: box new --name test"
# --- host setup ------------------------------------------------------------
# The installer finishes the job (#64). Telling the user to go run setup-host
# was a step that read as optional and failed later as mysterious: the install
# reports success, 'box' is on PATH, and 'box new' dies on a host with no
# Incus, no boxnet, no profile. setup-host is idempotent by design, so doing
# this on EVERY install is also how an upgraded host picks up stack changes —
# the isolation fixes that ship as new firewall rules land when the tool that
# claims them lands, instead of waiting on someone to re-run a command.
# BOX_SKIP_SETUP_HOST=1 opts out: CI, image builds, a host set up by hand.
setup_ok=""
if [ -n "${BOX_SKIP_SETUP_HOST:-}" ]; then
log "skipping host setup (BOX_SKIP_SETUP_HOST is set) — run it yourself: box setup-host"
elif [ "$(id -u)" -ne 0 ] && ! command -v sudo >/dev/null 2>&1; then
warn "host setup needs root and sudo was not found."
warn " run this as root to finish: $DEST/host/setup-host.sh"
else
log "running one-time host setup (installs Incus + the isolation stack; may ask for sudo)"
# </dev/null because under 'curl … | bash' this script IS stdin: a child that
# reads stdin eats the installer's own remaining lines. sudo is unaffected —
# it prompts on /dev/tty, so an interactive host can still authenticate.
if bash "$DEST/host/setup-host.sh" </dev/null; then
setup_ok=1
else
warn "host setup did not complete — box is installed, the host is not ready."
warn " fix the error above and re-run: box setup-host"
fi
fi
if [ -n "$setup_ok" ]; then
log "done ($REPO@$REF) — try: box new --name test"
else
log "done ($REPO@$REF) — finish with 'box setup-host', then: box new --name test"
fi