forked from heavy-duty/box
#71 — run as root, box installs once to /opt/box (world-readable) with a /usr/local/bin shim, so every operator on a shared host has `box` on PATH instead of `command not found`. Per-user ($HOME/.local) install is byte-identical to before; BOX_HOME/BOX_BIN still override. A per-user tree under /root is 0700 and unreadable to everyone else — the whole fleet got `command not found` — so the root branch lands in a system location and chmod -R a+rX's it, guarded on root. This is the path rig installs box through (rig#24's `box` role). #65 — `box tmux` runs `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. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d0a14a18a5
commit
9849021b6a
4 changed files with 71 additions and 2 deletions
22
CHANGELOG.md
22
CHANGELOG.md
|
|
@ -5,8 +5,30 @@ which records not just what changed but what each drill run proved.
|
|||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- **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
|
||||
tree under `/root` is `0700` and unreadable to everyone else — the whole fleet
|
||||
got `command not found` — so the root branch lands in a system location and
|
||||
`chmod -R a+rX`'s it (read for files, +search on dirs), guarded on root. This
|
||||
unblocks "rig installs box" (rig#24's `box` role).
|
||||
- **CI + a test suite** — `.github/workflows/ci.yml` (a `check` job: globstar
|
||||
`shellcheck -x` over `bin/* **/*.sh`, then `bash test/cli.sh`) and `test/cli.sh`,
|
||||
dependency-free and runnable by a non-root user with no Incus. It exercises the
|
||||
`install.sh` DEST/BINDIR branch functionally (both tiers + `BOX_HOME`/`BOX_BIN`
|
||||
overrides), the CLI contract, and grep-guards the daemon-gated invariants and
|
||||
tmux in every template — the box was the repo with "no tests and no CI".
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`box tmux` works on every template** (#65) — `box tmux` runs
|
||||
`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`).
|
||||
|
||||
- **`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`,
|
||||
|
|
|
|||
19
README.md
19
README.md
|
|
@ -64,6 +64,25 @@ A version-aware upgrade that migrates boxes instead of asking you to is
|
|||
(CI, images), `BOX_YES=1` answers every prompt yes and `BOX_SKIP_SETUP_HOST=1`
|
||||
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:
|
||||
|
||||
- **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
|
||||
install, every operator on the host runs the same `box`. This is the fleet
|
||||
path: [rig](https://github.com/heavy-duty/rig)'s `box` role
|
||||
([rig#24](https://github.com/heavy-duty/rig/issues/24)) installs box once at
|
||||
host bootstrap ([#71](https://github.com/heavy-duty/box/issues/71)).
|
||||
- **As a normal user → per-user.** The tree goes to `~/.local/share/box` and
|
||||
the symlink to `~/.local/bin` — the solo path, unchanged. Nobody else needs
|
||||
to run your box.
|
||||
|
||||
`BOX_HOME` / `BOX_BIN` override the destination on either path. A per-user
|
||||
install under `/root` would be `0700` and unreadable to everyone else — which
|
||||
is exactly the bug the root branch fixes.
|
||||
|
||||
## One-time host setup (Ubuntu 24.04 / Debian 13)
|
||||
|
||||
The installer already does this. Run it directly to set up a host you
|
||||
|
|
|
|||
22
install.sh
22
install.sh
|
|
@ -10,8 +10,20 @@ set -euo pipefail
|
|||
|
||||
REPO="${BOX_REPO:-heavy-duty/box}"
|
||||
REF="${BOX_REF:-main}"
|
||||
# Root installs GLOBALLY, non-root installs per-user. box's install tree is
|
||||
# EXECUTED by other users (the multi-user host path: rig installs box once, every
|
||||
# incus-group operator runs it) — unlike rig, which is root-only and can hide in
|
||||
# /root. So a root install must land in a system location, not $HOME: /root is
|
||||
# 0700, so a $HOME/.local tree there is unreadable to everyone else and the whole
|
||||
# fleet gets 'command not found' (#71). /opt/box is the world-readable system
|
||||
# tree; /usr/local/bin is already on every login PATH. BOX_HOME/BOX_BIN still win.
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
DEST="${BOX_HOME:-/opt/box}"
|
||||
BINDIR="${BOX_BIN:-/usr/local/bin}"
|
||||
else
|
||||
DEST="${BOX_HOME:-$HOME/.local/share/box}"
|
||||
BINDIR="${BOX_BIN:-$HOME/.local/bin}"
|
||||
fi
|
||||
|
||||
log() { printf 'box-install: %s\n' "$*"; }
|
||||
warn() { printf 'box-install: WARNING: %s\n' "$*" >&2; }
|
||||
|
|
@ -96,6 +108,16 @@ mv "$EXTRACTED" "$DEST"
|
|||
|
||||
chmod +x "$DEST/bin/box"
|
||||
|
||||
# A global (root) install is run by OTHER users, but mv preserves the tarball's
|
||||
# root:root ownership and GitHub's archives carry no world bits on some paths — so
|
||||
# without this, a non-root caller cannot even traverse into $DEST to reach bin/box.
|
||||
# Root owns the tree, nobody else writes it, everybody reads it. a+rX: read on
|
||||
# files, +search (x) on directories only. Guarded on root so the per-user install
|
||||
# stays byte-identical to before.
|
||||
if [ "$(id -u)" -eq 0 ]; then
|
||||
chmod -R a+rX "$DEST"
|
||||
fi
|
||||
|
||||
# --- put box on PATH -------------------------------------------------------
|
||||
mkdir -p "$BINDIR"
|
||||
ln -sf "$DEST/bin/box" "$BINDIR/box"
|
||||
|
|
|
|||
|
|
@ -7,3 +7,9 @@ users:
|
|||
sudo: "ALL=(ALL) NOPASSWD:ALL"
|
||||
lock_passwd: true
|
||||
package_update: false
|
||||
# The one package a blank box still needs: 'box tmux' runs 'tmux new-session'
|
||||
# INSIDE the box, and a bare Debian image ships no tmux — without this it fails
|
||||
# with "tmux: command not found" (#65). cloud-init refreshes the apt lists on its
|
||||
# own whenever 'packages' is non-empty, so package_update stays false here.
|
||||
packages:
|
||||
- tmux
|
||||
|
|
|
|||
Loading…
Reference in a new issue