forked from heavy-duty/box
fix: install pinned drill inputs by commit
This commit is contained in:
parent
45c29d6dbc
commit
e1e4fc77db
10 changed files with 83 additions and 47 deletions
|
|
@ -49,13 +49,15 @@ By default that installs the **latest release** — the installer resolves the
|
|||
release tag off GitHub's `releases/latest` redirect (no API, no token) and
|
||||
downloads exactly that tree, so two operators running it get the same box.
|
||||
If the resolution fails it says so and stops — it never silently hands out
|
||||
`main`. `BOX_REF` picks another channel (a set ref is tried as a tag first,
|
||||
then as a branch — [#83](https://github.com/heavy-duty/box/issues/83)):
|
||||
`main`. `BOX_REF` picks another channel: a named ref is tried as a tag first,
|
||||
then as a branch ([#83](https://github.com/heavy-duty/box/issues/83)), while a
|
||||
full 40-hex commit SHA selects GitHub's immutable commit archive:
|
||||
|
||||
```sh
|
||||
curl -fsSL .../install.sh | bash # the latest release (default)
|
||||
curl -fsSL .../install.sh | BOX_REF=0.6.0 bash # pin a release
|
||||
curl -fsSL .../install.sh | BOX_REF=main bash # the development tip
|
||||
curl -fsSL .../install.sh | BOX_REF=<full-commit-sha> bash # exact tree
|
||||
```
|
||||
|
||||
(A dev tree's `VERSION` carries a `-dev` suffix, so it lands beside your
|
||||
|
|
|
|||
32
bin/box
32
bin/box
|
|
@ -1096,36 +1096,48 @@ load_template() {
|
|||
T_DISK="${disk:-${BOX_DISK:-${T_DISK:-60GiB}}}"
|
||||
}
|
||||
|
||||
# The ONE substitution a template gets — user-data.yaml is otherwise passed to
|
||||
# Incus verbatim. The tenant seeds preinstall rig, which inverts the rig→box
|
||||
# install edge (rig#28: rig installs box on hosts; now box guests install rig),
|
||||
# and that edge needs a pin point (#81): the seed carries @RIG_REPO@ /
|
||||
# @RIG_REF@ tokens, resolved here from the mint environment — RIG_REPO
|
||||
# (default heavy-duty/rig) and RIG_REF (default main). Both directions track
|
||||
# main unpinned today, said honestly (the same treatment rig#29 gave box's own
|
||||
# unpinned install) until a release flow exists (rig#32 / #83). The values are
|
||||
# The ONE substitution a template gets — @RIG_INSTALL@ — while user-data.yaml
|
||||
# is otherwise passed to Incus verbatim. The tenant seeds preinstall rig, which
|
||||
# inverts the rig→box install edge (rig#28: rig installs box on hosts; now box
|
||||
# guests install rig), and that edge needs a pin point (#81). The renderer
|
||||
# resolves the mint environment's RIG_REPO (default heavy-duty/rig) and RIG_REF
|
||||
# (default main) into the whole installer command. Named refs keep the normal
|
||||
# tag/branch channel; a drill-pinned commit carries its immutable archive as
|
||||
# RIG_INSTALL_SOURCE (#152). Both values are
|
||||
# allowlist-validated BEFORE touching the YAML: they land inside a runcmd
|
||||
# shell line, so a quote, a space or a newline smuggled through the
|
||||
# environment must die on the host, never execute in the guest. bash's =~
|
||||
# anchors to the whole string — a multi-line value cannot sneak one clean
|
||||
# line past it the way a line-oriented grep would.
|
||||
# The rig pin, resolved from the mint environment, in ONE place: render_userdata
|
||||
# substitutes it into the seed, and the mint stamp (#103) records it onto the
|
||||
# builds it into the seed, and the mint stamp (#103) records it onto the
|
||||
# instance. Two spellings of the same default would eventually disagree, and a
|
||||
# stamp that disagrees with the seed is worse than no stamp at all.
|
||||
rig_repo() { printf '%s\n' "${RIG_REPO:-heavy-duty/rig}"; }
|
||||
rig_ref() { printf '%s\n' "${RIG_REF:-main}"; }
|
||||
|
||||
render_userdata() {
|
||||
local f="$1" repo data ref
|
||||
local f="$1" repo data ref install
|
||||
repo="$(rig_repo)"; ref="$(rig_ref)"
|
||||
[[ "$repo" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] \
|
||||
|| die "RIG_REPO must look like owner/repo: $repo"
|
||||
[[ "$ref" =~ ^[A-Za-z0-9._/-]+$ ]] \
|
||||
|| die "RIG_REF must be a plain ref name (letters, digits, . _ / -): $ref"
|
||||
if [[ "$ref" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
# rig's installer currently resolves named tags/branches. A release drill
|
||||
# pins the moving ref before mint, so hand that installer the immutable
|
||||
# commit archive as a local source instead of asking it to resolve again
|
||||
# inside the guest (#152).
|
||||
printf -v install '%s' \
|
||||
"rig_source=/tmp/rig-$ref.tar.gz; rig_installer=/tmp/rig-install-$ref.sh; curl -fsSL https://github.com/$repo/archive/$ref.tar.gz -o \"\$rig_source\" && curl -fsSL https://raw.githubusercontent.com/$repo/$ref/install.sh -o \"\$rig_installer\" && HOME=/root RIG_REPO=\"$repo\" RIG_REF=\"$ref\" RIG_INSTALL_SOURCE=\"\$rig_source\" bash \"\$rig_installer\"; rig_rc=\$?; rm -f \"\$rig_source\" \"\$rig_installer\"; exit \"\$rig_rc\""
|
||||
else
|
||||
printf -v install 'curl -fsSL https://raw.githubusercontent.com/%s/%s/install.sh | HOME=/root RIG_REPO="%s" RIG_REF="%s" bash' \
|
||||
"$repo" "$ref" "$repo" "$ref"
|
||||
fi
|
||||
data="$(cat "$f")"
|
||||
data="${data//@RIG_REPO@/$repo}"
|
||||
data="${data//@RIG_REF@/$ref}"
|
||||
data="${data//@RIG_INSTALL@/$install}"
|
||||
printf '%s\n' "$data"
|
||||
}
|
||||
|
||||
|
|
|
|||
35
install.sh
35
install.sh
|
|
@ -24,10 +24,11 @@ set -euo pipefail
|
|||
# downloading — for CI and the drill, so what lands is the code under review.
|
||||
|
||||
REPO="${BOX_REPO:-heavy-duty/box}"
|
||||
# Three install channels, one knob (#83): BOX_REF unset installs the LATEST
|
||||
# Four install channels, one knob (#83, #152): BOX_REF unset installs the LATEST
|
||||
# RELEASE (the tag resolved from GitHub's releases/latest redirect, below);
|
||||
# BOX_REF=<tag> pins a release; BOX_REF=<branch> (say, main) is the dev
|
||||
# channel. A set ref is tried as a tag first, then as a branch.
|
||||
# channel; and a full 40-hex commit SHA is fetched immutably. A named ref is
|
||||
# tried as a tag first, then as a branch.
|
||||
REF="${BOX_REF:-}"
|
||||
# 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
|
||||
|
|
@ -232,17 +233,27 @@ else
|
|||
fi
|
||||
INSTALLED_FROM="$REPO@$REF"
|
||||
log "installing box from $REPO@$REF"
|
||||
# A ref is a TAG first (the pinned-release channel), a branch second (the
|
||||
# dev channel, BOX_REF=main) — and the fallback only exists for a ref the
|
||||
# OPERATOR named: a resolved latest tag has no branch to fall through to.
|
||||
URL="https://github.com/$REPO/archive/refs/tags/$REF.tar.gz"
|
||||
log "downloading $URL"
|
||||
if ! curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz"; then
|
||||
[ -n "${BOX_REF:-}" ] || die "failed to download $URL"
|
||||
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
|
||||
log "no tag '$REF' — trying it as a branch: $URL"
|
||||
if [[ "$REF" =~ ^[0-9a-f]{40}$ ]]; then
|
||||
# The drill resolves moving candidate refs before it mutates a host, then
|
||||
# installs the exact object it measured (#152). GitHub's commit archive is
|
||||
# outside refs/{tags,heads}; guessing either namespace makes every SHA 404.
|
||||
URL="https://github.com/$REPO/archive/$REF.tar.gz"
|
||||
log "downloading immutable commit $URL"
|
||||
curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \
|
||||
|| die "failed to download it as either — '$REF' is neither a tag nor a branch of $REPO"
|
||||
|| die "failed to download commit $REF from $REPO"
|
||||
else
|
||||
# A named ref is a TAG first (the pinned-release channel), a branch second
|
||||
# (the dev channel, BOX_REF=main). The fallback only exists for a ref the
|
||||
# OPERATOR named: a resolved latest tag has no branch to fall through to.
|
||||
URL="https://github.com/$REPO/archive/refs/tags/$REF.tar.gz"
|
||||
log "downloading $URL"
|
||||
if ! curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz"; then
|
||||
[ -n "${BOX_REF:-}" ] || die "failed to download $URL"
|
||||
URL="https://github.com/$REPO/archive/refs/heads/$REF.tar.gz"
|
||||
log "no tag '$REF' — trying it as a branch: $URL"
|
||||
curl -fsSL "$URL" -o "$TMPDIR/box.tar.gz" \
|
||||
|| die "failed to download it as either — '$REF' is neither a tag nor a branch of $REPO"
|
||||
fi
|
||||
fi
|
||||
|
||||
log "extracting archive"
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -30,4 +30,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
|
|
@ -31,4 +31,4 @@ runcmd:
|
|||
# HOME=/root: cloud-init runs runcmd as root but with NO $HOME in the
|
||||
# environment, and the rig installer (set -u) reads $HOME for its DEST —
|
||||
# measured live: the mint died with "HOME: unbound variable" without it.
|
||||
- curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | HOME=/root RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash
|
||||
- '@RIG_INSTALL@'
|
||||
|
|
|
|||
36
test/cli.sh
36
test/cli.sh
|
|
@ -266,15 +266,28 @@ check "render_userdata: the pin's defaults came with it (guards the grep)" 0 "he
|
|||
check "render_userdata: the extracted function is valid bash" 0 "" bash -n "$RUFN"
|
||||
|
||||
SEED="$(mktemp)"
|
||||
printf '#cloud-config\nruncmd:\n - curl -fsSL https://raw.githubusercontent.com/@RIG_REPO@/@RIG_REF@/install.sh | RIG_REPO="@RIG_REPO@" RIG_REF="@RIG_REF@" bash\n' > "$SEED"
|
||||
printf "#cloud-config\nruncmd:\n - '@RIG_INSTALL@'\n" > "$SEED"
|
||||
# shellcheck disable=SC2016 # $0/$1 expand in the child shell, by design
|
||||
rud() { # rud [VAR=val ...] — render the fixture seed through the real function
|
||||
env "$@" bash -c 'die() { echo "box: $*" >&2; exit 1; }; . "$0"; render_userdata "$1"' "$RUFN" "$SEED"
|
||||
}
|
||||
check "render_userdata: defaults pin heavy-duty/rig" 0 "githubusercontent.com/heavy-duty/rig/main/install.sh" rud
|
||||
check "render_userdata: defaults feed the installer's own env too" 0 'RIG_REPO="heavy-duty/rig" RIG_REF="main"' rud
|
||||
check "render_userdata: the rig install pins HOME=/root (runcmd has no \$HOME)" 0 "HOME=/root " rud
|
||||
check "render_userdata: RIG_REPO/RIG_REF override at mint" 0 "dan-claude-bot/rig/feat/bootstrap-roles/install.sh" \
|
||||
rud RIG_REPO=dan-claude-bot/rig RIG_REF=feat/bootstrap-roles
|
||||
RIG_COMMIT=89abcdef0123456789abcdef0123456789abcdef
|
||||
check "render_userdata: a rig commit downloads its immutable archive" 0 "archive/$RIG_COMMIT.tar.gz" \
|
||||
rud RIG_REPO=heavy-duty/rig RIG_REF="$RIG_COMMIT"
|
||||
# $rig_source belongs to the rendered guest shell.
|
||||
# shellcheck disable=SC2016
|
||||
check "render_userdata: a rig commit feeds the archive to the installer" 0 'RIG_INSTALL_SOURCE="$rig_source"' \
|
||||
rud RIG_REPO=heavy-duty/rig RIG_REF="$RIG_COMMIT"
|
||||
# Expansion belongs to the bash -c fixture.
|
||||
# shellcheck disable=SC2016
|
||||
check "render_userdata: a named rig ref keeps the normal channel" 1 "" \
|
||||
bash -c 'die() { exit 1; }; RIG_REPO=heavy-duty/rig RIG_REF=main; . "$1"; render_userdata "$2" | grep -q RIG_INSTALL_SOURCE' \
|
||||
_ "$RUFN" "$SEED"
|
||||
# shellcheck disable=SC2016 # $0/$1 expand in the child shells, by design
|
||||
check "render_userdata: no token survives the render" 1 "" \
|
||||
bash -c 'env bash -c "die() { echo box: \$*; exit 1; }; . \"\$0\"; render_userdata \"\$1\"" "$1" "$2" | grep -q @RIG_' _ "$RUFN" "$SEED"
|
||||
|
|
@ -326,26 +339,13 @@ for d in "$ROOT"/templates/*/; do
|
|||
# The thin-template contract (#81), both halves per template:
|
||||
#
|
||||
# THE SEED — a template that names a tenant role (BOX_BOOTSTRAP_ROLE) must
|
||||
# preinstall rig carrying BOTH pin tokens, on the installer URL and on the
|
||||
# installer's own env, or the pin is a half-truth: a mint would fetch one
|
||||
# ref's installer and install another ref's tree.
|
||||
# delegate its rig install to render_userdata. That one renderer owns both
|
||||
# the named-ref channel and the immutable-commit archive channel.
|
||||
# ------------------------------------------------------------------------
|
||||
trole="$(tpl "$ROOT" "$t" | sed -n 's/.*ROLE=\([^ ]*\).*/\1/p')"
|
||||
if [ -n "$trole" ]; then
|
||||
check "template '$t': the seed installs rig (role '$trole')" 0 "" \
|
||||
grep -q 'install.sh' "$d/user-data.yaml"
|
||||
# shellcheck disable=SC2016 # $1 expands in the child shell, by design
|
||||
check "template '$t': the rig install carries the @RIG_REPO@ pin token" 0 "" \
|
||||
bash -c 'grep "install.sh" "$1" | grep -q "@RIG_REPO@/@RIG_REF@"' _ "$d/user-data.yaml"
|
||||
# shellcheck disable=SC2016
|
||||
check "template '$t': the pin reaches the installer's env too" 0 "" \
|
||||
bash -c 'grep "install.sh" "$1" | grep -q "RIG_REPO=\"@RIG_REPO@\" RIG_REF=\"@RIG_REF@\""' _ "$d/user-data.yaml"
|
||||
# HOME=/root: a scar found live — cloud-init's runcmd has no $HOME and
|
||||
# rig's installer (set -u) dies on it (rig#39). The pin must survive
|
||||
# every seed rewrite.
|
||||
# shellcheck disable=SC2016
|
||||
check "template '$t': the rig install pins HOME=/root (runcmd has no \$HOME)" 0 "" \
|
||||
bash -c 'grep "install.sh" "$1" | grep -q "HOME=/root "' _ "$d/user-data.yaml"
|
||||
check "template '$t': the seed installs rig through the renderer (role '$trole')" 0 "" \
|
||||
grep -q "^[[:space:]]*- '@RIG_INSTALL@'\$" "$d/user-data.yaml"
|
||||
fi
|
||||
# ------------------------------------------------------------------------
|
||||
# THE ABSENCE — no tenant content in ANY template, ever again. Everything a
|
||||
|
|
|
|||
|
|
@ -109,6 +109,16 @@ check "dev channel: tries the tag first" 0 "refs/tags/main.tar.gz" head -1 "$L3"
|
|||
check "dev channel: then downloads the branch" 0 "" \
|
||||
grep -qF "archive/refs/heads/main.tar.gz" "$L3"
|
||||
|
||||
COMMIT_REF=0123456789abcdef0123456789abcdef01234567
|
||||
H5="$WORK/h5"; B5="$WORK/b5"; L5="$WORK/c5.log"
|
||||
check "commit channel: installs an immutable full SHA" 0 "installing box from heavy-duty/box@$COMMIT_REF" \
|
||||
ninst "$H5" "$B5" BOX_REF="$COMMIT_REF" FAKE_CURL_LOG="$L5" \
|
||||
FAKE_SERVE_URL="https://github.com/heavy-duty/box/archive/$COMMIT_REF.tar.gz"
|
||||
check "commit channel: downloads the commit archive directly" 0 "archive/$COMMIT_REF.tar.gz" cat "$L5"
|
||||
check "commit channel: never guesses tag or branch namespaces" 1 "" grep -Eq 'refs/(tags|heads)' "$L5"
|
||||
check "commit channel: records the immutable source" 0 "heavy-duty/box@$COMMIT_REF" \
|
||||
cat "$H5/versions/9.9.9/INSTALLED_FROM"
|
||||
|
||||
H4="$WORK/h4"; B4="$WORK/b4"; L4="$WORK/c4.log"
|
||||
check "resolution failure: names the latest-release probe" 1 "could not resolve the latest release" \
|
||||
ninst "$H4" "$B4" FAKE_CURL_RC=6 FAKE_CURL_LOG="$L4"
|
||||
|
|
@ -122,6 +132,7 @@ check "unknown ref names both attempted channels" 1 "neither a tag nor a branch"
|
|||
check "README documents the latest-release channel" 0 "" grep -qF 'latest release' "$ROOT/README.md"
|
||||
check "README documents the pinned channel" 0 "" grep -qF 'BOX_REF=0.6.0' "$ROOT/README.md"
|
||||
check "README documents the dev channel" 0 "" grep -qF 'BOX_REF=main' "$ROOT/README.md"
|
||||
check "README documents the immutable commit channel" 0 "" grep -qF 'BOX_REF=<full-commit-sha>' "$ROOT/README.md"
|
||||
|
||||
echo "---"
|
||||
echo "$PASS passed, $FAIL failed"
|
||||
|
|
|
|||
Loading…
Reference in a new issue