diff --git a/CHANGELOG.md b/CHANGELOG.md index 2631e0b..7d3245b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ on the way to cutting its first release, and this file starts there. ### Changed +- `bootstrap --host yes` installs a pinned box release instead of `main` (#103) - Release and labels machinery is consumed from heavy-duty/ceremony@0.1.0 by reference — the workflows shrink to caller stubs, the guard scripts and their tests move upstream (#112, ceremony#13) ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc6d760..38b4a68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,9 @@ heavy-duty/ceremony's machinery, consumed by reference: is the doctrine, `.github/workflows/release.yml` here is the ≤20-line caller pinning it, and the guards run in `ci.yml` from the same pin. Bare `X.Y.Z` tags, no `v`; the tag's source tarball is the package -`install.sh` downloads — rig ships no other artifact. +`install.sh` downloads — rig ships no other artifact. Each release deliberately +bumps and drills the `BOX_RELEASE` pin in `commands/bootstrap.sh`; it must never +float to a moving ref. What stays rig's is the **drill** — the real-hardware gate before the handoff of a release PR, run by `drill/drill.sh` (#105): `rig bootstrap` diff --git a/README.md b/README.md index cd6c7c4..abe895a 100644 --- a/README.md +++ b/README.md @@ -358,23 +358,17 @@ box is present) and **opt-out** (`RIG_SKIP_BOX_INSTALL=1`, plus a graceful skip with a manual-command pointer when curl or the network is missing — box is the host *extra*, so a failed box install never aborts a bootstrap that otherwise succeeded). Source is pinnable with `BOX_REPO` / `BOX_REF` (default -`heavy-duty/box@main`). If `/dev/kvm` is absent, rig warns (a host that exists to +`heavy-duty/box@0.9.0`). If `/dev/kvm` is absent, rig warns (a host that exists to run VMs should have it) but does not fail — the shape is rehearsed in containers, which legitimately lack it. (The world-readable global install path — box under `/opt/box` readable by every non-root user — depends on box PR #71; until that merges box's root install lands in `/root`.) -> **The box install is unpinned — on purpose, and out loud.** `coolify install` -> demands a version pin; the box step tracks a moving `heavy-duty/box@main`. -> Not because box self-updates (it doesn't — it has Coolify's shape, not the -> runner's) but because there is nothing to pin *to*: box cuts no tags and no -> releases, and its installer resolves `refs/heads/` — branches only — so -> a `BOX_REF=v0.5.0` would 404 even if the tag existed. Issue #12's call was -> that silently tracking `main` on the box that runs the agents is the option -> not to pick — hence this paragraph. `BOX_REPO` / `BOX_REF` are the pin -> points the day box cuts a tag (or you point at a frozen branch of your own -> fork); `RIG_SKIP_BOX_INSTALL=1` opts out entirely for a host whose box you -> manage by hand. +> **The box install is release-pinned.** A rig release carries one box release +> pin, so two machines bootstrapped from the same rig install the same box. +> `BOX_REPO` / `BOX_REF` remain explicit overrides for development and +> pre-release drills; `RIG_SKIP_BOX_INSTALL=1` opts out entirely for a host +> whose box you manage by hand. `dev-server` is the closed-door VM-hosting shape — `tag:local`, box CLI installed as above, operators entering as themselves (`--root-door open` turns it into the diff --git a/commands/bootstrap.sh b/commands/bootstrap.sh index b39554b..e310b2f 100755 --- a/commands/bootstrap.sh +++ b/commands/bootstrap.sh @@ -671,7 +671,9 @@ fi # hardening + the tailnet, and box is the host EXTRA, so a failed box install # must never abort a bootstrap that otherwise fully succeeded. # -# PIN POINTS: BOX_REPO / BOX_REF override the source (default heavy-duty/box@main). +# PIN POINTS: BOX_REPO / BOX_REF override the source (default +# heavy-duty/box@0.9.0). BOX_RELEASE is bumped deliberately when rig releases, +# after the pinned combination has passed the release drill. # BOX_YES=1 makes box's installer non-interactive AND keeps setup-host (so the # Incus stack is actually built, not just the CLI dropped on PATH). # @@ -684,10 +686,11 @@ fi # on box PR #71. Until that merges, box's root install lands in /root and non-root # users cannot reach it, so this step is only fully correct once box#71 is merged. if [ "$HOST" = "yes" ]; then + BOX_RELEASE=0.9.0 BOX_REPO="${BOX_REPO:-heavy-duty/box}" - BOX_REF="${BOX_REF:-main}" + BOX_REF="${BOX_REF:-$BOX_RELEASE}" BOX_INSTALL_URL="https://raw.githubusercontent.com/${BOX_REPO}/${BOX_REF}/install.sh" - BOX_MANUAL="curl -fsSL ${BOX_INSTALL_URL} | BOX_YES=1 bash" + BOX_MANUAL="curl -fsSL ${BOX_INSTALL_URL} | BOX_YES=1 BOX_REF=${BOX_REF} bash" if [ "${RIG_SKIP_BOX_INSTALL:-}" = "1" ]; then log "RIG_SKIP_BOX_INSTALL=1 — skipping box install; to prepare Incus by hand later: ${BOX_MANUAL}" elif ! command -v curl >/dev/null 2>&1; then @@ -701,7 +704,7 @@ if [ "$HOST" = "yes" ]; then # A curl failure (no network) fails the pipe under pipefail and lands in the # else — a warning, never an abort: box is the host extra, the OS+tailnet core # is already done. - if curl -fsSL "$BOX_INSTALL_URL" | BOX_YES=1 bash; then + if curl -fsSL "$BOX_INSTALL_URL" | BOX_YES=1 BOX_REF="$BOX_REF" bash; then # Don't trust the exit code — prove the effective state (issue #12). An # installer can exit 0 having done less than it claims: box's setup-host # is written for a sudo-capable user, and one of its paths exits 0 after diff --git a/test/cli.sh b/test/cli.sh index d4796b7..5c61692 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -165,10 +165,28 @@ check "bootstrap: box install is guarded on host=yes" 0 "" \ grep -qxE 'if \[ "\$HOST" = "yes" \]; then' "$ROOT/commands/bootstrap.sh" # It runs box's OWN global installer with BOX_YES=1 (non-interactive AND keeps # setup-host, so box builds Incus rather than only dropping the CLI on PATH). +# shellcheck disable=SC2016 check "bootstrap: box install runs box's installer non-interactively" 0 "" \ - grep -q "BOX_YES=1 bash" "$ROOT/commands/bootstrap.sh" -# Pin points: BOX_REPO / BOX_REF override the source, default heavy-duty/box@main. -check "bootstrap: box source is pinnable, defaults to heavy-duty/box@main" 0 "" \ + grep -q 'BOX_YES=1 BOX_REF="$BOX_REF" bash' "$ROOT/commands/bootstrap.sh" +# The default is a released semver pin carried in rig's tree, never a moving +# branch. BOX_REF remains an override so explicit main and release-branch refs +# still work for development and pre-release drills. +box_release="$(sed -n 's/^[[:space:]]*BOX_RELEASE=//p' "$ROOT/commands/bootstrap.sh")" +check "bootstrap: box default is a released semver pin, not a moving ref" 0 "" \ + grep -qxE '[0-9]+\.[0-9]+\.[0-9]+' <<<"$box_release" +# shellcheck disable=SC2016 +check "bootstrap: BOX_REF overrides the released default" 0 "" \ + grep -qF 'BOX_REF="${BOX_REF:-$BOX_RELEASE}"' "$ROOT/commands/bootstrap.sh" +# Fetching the installer at BOX_REF is only the first pin: box's installer +# independently resolves what it installs, so the ref must cross the pipe too. +# shellcheck disable=SC2016 +check "bootstrap: box install passes BOX_REF through the installer pipe" 0 "" \ + grep -qF 'BOX_YES=1 BOX_REF="$BOX_REF" bash' "$ROOT/commands/bootstrap.sh" +# The same pinned command is operators' recovery path on every skip/failure. +# shellcheck disable=SC2016 +check "bootstrap: manual box install carries the pinned ref" 0 "" \ + grep -qF 'BOX_YES=1 BOX_REF=${BOX_REF} bash' "$ROOT/commands/bootstrap.sh" +check "bootstrap: box repository remains pinnable" 0 "" \ grep -qF 'BOX_REPO:-heavy-duty/box' "$ROOT/commands/bootstrap.sh" # Opt-out for rehearsals / offline / hand-managed hosts. check "bootstrap: box install honors RIG_SKIP_BOX_INSTALL opt-out" 0 "" \ @@ -187,7 +205,8 @@ check "bootstrap: rig never apt-installs incus (box owns the daemon)" 1 "" \ # $MARKER_TMP is a literal we grep for in the script — single quotes intended. # shellcheck disable=SC2016 box_marker_at="$(grep -n 'install -m 0644 "$MARKER_TMP"' "$ROOT/commands/bootstrap.sh" | head -n1 | cut -d: -f1)" -box_install_at="$(grep -n 'BOX_YES=1 bash' "$ROOT/commands/bootstrap.sh" | grep -v 'BOX_MANUAL=' | tail -n1 | cut -d: -f1)" +# shellcheck disable=SC2016 +box_install_at="$(grep -n 'BOX_YES=1 BOX_REF="$BOX_REF" bash' "$ROOT/commands/bootstrap.sh" | tail -n1 | cut -d: -f1)" check "bootstrap: box install runs after the role marker write" \ 0 "" test "${box_marker_at:-999999}" -lt "${box_install_at:-0}" # On the skip/failure paths, keep pointing operators at the manual command so a