install.sh resolves releases and archive URLs through RIG_HOST (default GitHub), using Forgejo's single /archive/<ref>.tar.gz form and the same /releases/latest redirect grammar. Bootstrap's box fetch gets BOX_HOST with forge-aware raw-file URLs. Refusal hints and coolify Documentation= stop pointing only at GitHub. Closes #111
269 lines
13 KiB
Bash
269 lines
13 KiB
Bash
#!/usr/bin/env bash
|
|
# Rig's own half of the release surface (#32; trimmed in ceremony#13's
|
|
# conversion): latest-tag resolution and the installer's three channels.
|
|
# The machinery halves — changelog extraction, the arming rule,
|
|
# monotonicity, the drill gate, the workflow-shape pins — moved to
|
|
# heavy-duty/ceremony, which tests them in its own test/; what stays is
|
|
# everything that drives rig's install.sh and bin/. Dependency-free and
|
|
# NETWORK-FREE — wherever the code under test would call curl, the curl on
|
|
# PATH is a stub this harness wrote. Run: bash test/release.sh
|
|
# Deliberately no `set -e` — the harness asserts on failing commands.
|
|
set -u
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
cd "$ROOT" || exit 1
|
|
PASS=0 FAIL=0
|
|
|
|
# check <desc> <want_exit> <want_substr> <cmd...>
|
|
# Runs cmd, asserts exit code and (if non-empty) that combined output
|
|
# contains want_substr.
|
|
check() {
|
|
local desc="$1" want="$2" substr="$3"; shift 3
|
|
local out rc
|
|
out="$("$@" 2>&1)"; rc=$?
|
|
if [ "$rc" -ne "$want" ]; then
|
|
echo "FAIL: $desc — exit $rc, wanted $want"
|
|
printf '%s\n' "$out" | sed 's/^/ /'
|
|
FAIL=$((FAIL + 1)); return
|
|
fi
|
|
if [ -n "$substr" ] && ! printf '%s' "$out" | grep -qF -e "$substr"; then
|
|
echo "FAIL: $desc — output missing '$substr'"
|
|
printf '%s\n' "$out" | sed 's/^/ /'
|
|
FAIL=$((FAIL + 1)); return
|
|
fi
|
|
echo "ok: $desc"; PASS=$((PASS + 1))
|
|
}
|
|
|
|
WORK="$(mktemp -d)"
|
|
FAKEHOME="$WORK/home"; mkdir -p "$FAKEHOME"
|
|
|
|
|
|
# --- the installer's ref logic, extracted ------------------------------------
|
|
# install.sh must stay a single curl|bash file, so its channel functions live
|
|
# inline; extract them here and drive them for real (the valid_version awk
|
|
# idiom from test/cli.sh), against a stub curl — never the network.
|
|
RL="$WORK/installer-fns.sh"
|
|
awk '/^resolve_latest_tag\(\) \{/,/^\}/' "$ROOT/install.sh" > "$RL"
|
|
awk '/^ref_candidate_urls\(\) \{/,/^\}/' "$ROOT/install.sh" >> "$RL"
|
|
awk '/^install_script_url\(\) \{/,/^\}/' "$ROOT/install.sh" >> "$RL"
|
|
check "installer fns extracted (guards the awk)" 0 "redirect_url" cat "$RL"
|
|
|
|
STUB="$WORK/stub"; mkdir -p "$STUB"
|
|
cat > "$STUB/curl" <<'CURL'
|
|
#!/usr/bin/env bash
|
|
# The harness's curl — never the network. Scripted via env:
|
|
# CURL_STUB_FAIL nonempty -> every call exits 22 (curl's HTTP error)
|
|
# CURL_STUB_REDIRECT what -w %{redirect_url} answers (the HEAD probe)
|
|
# CURL_STUB_OK substring a download URL must carry to succeed
|
|
# CURL_STUB_TARBALL copied to -o's target on a successful download
|
|
# CURL_STUB_LOG every URL asked for, one per line, appended
|
|
set -u
|
|
out="" url="" probe=0
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
-o) out="$2"; shift 2 ;;
|
|
-w) probe=1; shift 2 ;;
|
|
-*) shift ;;
|
|
*) url="$1"; shift ;;
|
|
esac
|
|
done
|
|
if [ -n "${CURL_STUB_LOG:-}" ]; then printf '%s\n' "$url" >> "$CURL_STUB_LOG"; fi
|
|
if [ -n "${CURL_STUB_FAIL:-}" ]; then exit 22; fi
|
|
if [ "$probe" -eq 1 ]; then printf '%s' "${CURL_STUB_REDIRECT:-}"; exit 0; fi
|
|
case "$url" in
|
|
*"${CURL_STUB_OK:-/__nothing_succeeds__/}"*) cp "${CURL_STUB_TARBALL:?}" "${out:?}"; exit 0 ;;
|
|
*) exit 22 ;;
|
|
esac
|
|
CURL
|
|
chmod +x "$STUB/curl"
|
|
|
|
rlt() { # rlt [VAR=val ...] — resolve_latest_tag under the stub curl
|
|
# The single-quoted $1 is the inner bash's positional, not this shell's.
|
|
# shellcheck disable=SC2016
|
|
env PATH="$STUB:$PATH" "$@" bash -c 'set -euo pipefail
|
|
. "$1"; resolve_latest_tag heavy-duty/rig' _ "$RL"
|
|
}
|
|
check "resolve: a releases/tag redirect yields the tag" 0 "0.1.0" \
|
|
rlt CURL_STUB_REDIRECT=https://github.com/heavy-duty/rig/releases/tag/0.1.0
|
|
# A repo with NO releases redirects to /releases (measured live against
|
|
# heavy-duty/rig itself) — that must fail, never invent a ref.
|
|
check "resolve: the no-releases redirect (/releases) fails" 1 "" \
|
|
rlt CURL_STUB_REDIRECT=https://github.com/heavy-duty/rig/releases
|
|
check "resolve: no redirect at all fails" 1 "" rlt
|
|
check "resolve: a tagless releases/tag/ redirect fails" 1 "" \
|
|
rlt CURL_STUB_REDIRECT=https://github.com/heavy-duty/rig/releases/tag/
|
|
check "resolve: a failing curl fails (network down is not a channel)" 1 "" \
|
|
rlt CURL_STUB_FAIL=1
|
|
|
|
rcu_line() { # rcu_line <n> [VAR=val ...] — the nth candidate URL for an explicit ref
|
|
local n="$1"; shift
|
|
# shellcheck disable=SC2016
|
|
env "$@" bash -c 'set -euo pipefail
|
|
. "$1"; ref_candidate_urls acme/widgets 1.2.3 | sed -n "${2}p"' _ "$RL" "$n"
|
|
}
|
|
check "candidates: refs/tags first — the pin outranks a same-named branch" 0 \
|
|
"https://github.com/acme/widgets/archive/refs/tags/1.2.3.tar.gz" rcu_line 1
|
|
check "candidates: refs/heads is the fallback" 0 \
|
|
"https://github.com/acme/widgets/archive/refs/heads/1.2.3.tar.gz" rcu_line 2
|
|
# RIG_HOST selects the forge grammar (#111): non-GitHub hosts emit one bare
|
|
# /archive/<ref>.tar.gz form (Forgejo), never the refs/{tags,heads}/ pair.
|
|
check "candidates: Forgejo host emits the bare archive URL" 0 \
|
|
"https://forgejo.example/acme/widgets/archive/1.2.3.tar.gz" \
|
|
rcu_line 1 RIG_HOST=https://forgejo.example
|
|
check "candidates: Forgejo host emits exactly one candidate" 0 "1" \
|
|
env RIG_HOST=https://forgejo.example bash -c 'set -euo pipefail
|
|
. "$1"; ref_candidate_urls acme/widgets 1.2.3 | grep -c .' _ "$RL"
|
|
check "candidates: trailing slash on RIG_HOST is stripped" 0 \
|
|
"https://forgejo.example/acme/widgets/archive/1.2.3.tar.gz" \
|
|
rcu_line 1 RIG_HOST=https://forgejo.example/
|
|
|
|
# install_script_url — the curl|bash hint must match the forge (#111).
|
|
isu() {
|
|
# shellcheck disable=SC2016
|
|
env "$@" bash -c 'set -euo pipefail
|
|
REPO=heavy-duty/rig; . "$1"; install_script_url' _ "$RL"
|
|
}
|
|
check "install_script_url: GitHub default uses raw.githubusercontent.com" 0 \
|
|
"https://raw.githubusercontent.com/heavy-duty/rig/main/install.sh" isu
|
|
check "install_script_url: Forgejo uses /raw/branch/main/" 0 \
|
|
"https://forgejo.example/heavy-duty/rig/raw/branch/main/install.sh" \
|
|
isu RIG_HOST=https://forgejo.example
|
|
|
|
# resolve_latest_tag follows RIG_HOST too — the probe URL must name the forge.
|
|
rlt_log="$WORK/rlt-log"
|
|
: > "$rlt_log"
|
|
check "resolve: RIG_HOST is the releases/latest origin" 0 "0.2.0" \
|
|
rlt CURL_STUB_REDIRECT=https://forgejo.example/heavy-duty/rig/releases/tag/0.2.0 \
|
|
RIG_HOST=https://forgejo.example CURL_STUB_LOG="$rlt_log"
|
|
check "resolve: the probe hit the Forgejo host" 0 \
|
|
"https://forgejo.example/heavy-duty/rig/releases/latest" \
|
|
cat "$rlt_log"
|
|
|
|
# --- the three channels, driven through the REAL installer -------------------
|
|
# Full install.sh runs against throwaway roots with the stub curl on PATH: the
|
|
# channel selection, the tag-first fallback, and the loud no-releases refusal
|
|
# are all DRIVEN, not grepped (the test/cli.sh install-drill idiom).
|
|
TBDIR="$WORK/tb"; mkdir -p "$TBDIR/rig-7.7.7-relflow/bin"
|
|
cp "$ROOT/bin/rig" "$TBDIR/rig-7.7.7-relflow/bin/rig"
|
|
chmod +x "$TBDIR/rig-7.7.7-relflow/bin/rig"
|
|
echo "7.7.7-relflow" > "$TBDIR/rig-7.7.7-relflow/VERSION"
|
|
tar -C "$TBDIR" -czf "$WORK/release.tgz" rig-7.7.7-relflow
|
|
|
|
rinst() { # rinst <home> <bin> [VAR=val ...] — a real install.sh run, stubbed net
|
|
local h="$1" b="$2"; shift 2
|
|
env -u RIG_REF PATH="$STUB:$PATH" HOME="$FAKEHOME" \
|
|
RIG_ROLE_MARKER="$WORK/no-marker" RIG_HOME="$h" RIG_BIN="$b" \
|
|
CURL_STUB_TARBALL="$WORK/release.tgz" "$@" bash "$ROOT/install.sh"
|
|
}
|
|
|
|
# Channel 1 — RIG_REF unset, a release exists: resolve the tag, download
|
|
# refs/tags/<tag>, and the installed tree records exactly that ref.
|
|
H1="$WORK/h1"; B1="$WORK/b1"
|
|
check "channel latest: resolves and installs the release tag" 0 "done" \
|
|
rinst "$H1" "$B1" \
|
|
CURL_STUB_REDIRECT=https://github.com/heavy-duty/rig/releases/tag/7.7.7-relflow \
|
|
CURL_STUB_OK=refs/tags/7.7.7-relflow
|
|
check "channel latest: the tree landed under the tag's version" 0 "" \
|
|
test -x "$H1/versions/7.7.7-relflow/bin/rig"
|
|
check "channel latest: INSTALLED_FROM names the resolved tag" 0 \
|
|
"heavy-duty/rig@7.7.7-relflow" cat "$H1/versions/7.7.7-relflow/INSTALLED_FROM"
|
|
|
|
# Channel 1, transitional — RIG_REF unset, NO release exists (rig today):
|
|
# fail LOUDLY, name RIG_REF=main as the way out, install nothing. The stub
|
|
# would happily serve refs/heads/main here — a silent fallback would pass the
|
|
# download and FAIL this check by succeeding.
|
|
H2="$WORK/h2"; B2="$WORK/b2"
|
|
check "channel latest: no releases yet — dies, never hangs, never falls back" \
|
|
1 "RIG_REF=main" rinst "$H2" "$B2" \
|
|
CURL_STUB_REDIRECT=https://github.com/heavy-duty/rig/releases \
|
|
CURL_STUB_OK=refs/heads/main
|
|
check "channel latest: the refusal says what is missing" 1 "no release" \
|
|
rinst "$H2" "$B2" CURL_STUB_REDIRECT=https://github.com/heavy-duty/rig/releases
|
|
check "channel latest: the refusal installed NOTHING" 1 "" test -e "$H2"
|
|
|
|
# Channel 2 — RIG_REF=<tag>: refs/tags wins, and the latest-release probe is
|
|
# never consulted (a pin resolves nothing).
|
|
H3="$WORK/h3"; B3="$WORK/b3"; LOG3="$WORK/log3"
|
|
check "channel pinned: RIG_REF=<tag> installs from refs/tags" 0 "refs/tags/7.7.7-relflow" \
|
|
rinst "$H3" "$B3" RIG_REF=7.7.7-relflow \
|
|
CURL_STUB_OK=refs/tags/7.7.7-relflow CURL_STUB_LOG="$LOG3"
|
|
check "channel pinned: no releases/latest probe for an explicit ref" 1 "" \
|
|
grep -q "releases/latest" "$LOG3"
|
|
check "channel pinned: exactly one download (the tag hit first)" 0 "1" \
|
|
grep -c . "$LOG3"
|
|
|
|
# Channel 3 — RIG_REF=<branch>: the tag candidate misses, refs/heads lands.
|
|
H4="$WORK/h4"; B4="$WORK/b4"; LOG4="$WORK/log4"
|
|
check "channel dev: a branch ref falls back to refs/heads" 0 "done" \
|
|
rinst "$H4" "$B4" RIG_REF=feature-x \
|
|
CURL_STUB_OK=refs/heads/feature-x CURL_STUB_LOG="$LOG4"
|
|
check "channel dev: the tag URL was still tried FIRST" 0 "refs/tags/feature-x" \
|
|
sed -n 1p "$LOG4"
|
|
check "channel dev: ...then the branch URL" 0 "refs/heads/feature-x" \
|
|
sed -n 2p "$LOG4"
|
|
|
|
# Neither a tag nor a branch: both candidates miss, and the die says so.
|
|
H5="$WORK/h5"; B5="$WORK/b5"
|
|
check "channel: a ref that is neither tag nor branch dies naming the tries" \
|
|
1 "no candidate URL worked" rinst "$H5" "$B5" RIG_REF=no-such-ref
|
|
|
|
# Channel 4 — RIG_HOST=Forgejo: one archive URL, same redirect grammar for
|
|
# latest (#111). The stub only succeeds when the bare /archive/<ref> form is
|
|
# requested — a regression that still emitted refs/tags would fail here.
|
|
H9="$WORK/h9"; B9="$WORK/b9"; LOG9="$WORK/log9"
|
|
check "channel forgejo latest: resolves and installs via bare archive URL" 0 "done" \
|
|
rinst "$H9" "$B9" RIG_HOST=https://forgejo.example \
|
|
CURL_STUB_REDIRECT=https://forgejo.example/heavy-duty/rig/releases/tag/7.7.7-relflow \
|
|
CURL_STUB_OK='/archive/7.7.7-relflow.tar.gz' CURL_STUB_LOG="$LOG9"
|
|
check "channel forgejo latest: download URL is the bare archive form" 0 \
|
|
"https://forgejo.example/heavy-duty/rig/archive/7.7.7-relflow.tar.gz" \
|
|
cat "$LOG9"
|
|
check "channel forgejo latest: the tree landed" 0 "" \
|
|
test -x "$H9/versions/7.7.7-relflow/bin/rig"
|
|
H10="$WORK/h10"; B10="$WORK/b10"
|
|
check "channel forgejo pinned: RIG_REF uses the bare archive URL" 0 "done" \
|
|
rinst "$H10" "$B10" RIG_HOST=https://forgejo.example RIG_REF=main \
|
|
CURL_STUB_OK='/archive/main.tar.gz'
|
|
check "channel forgejo pinned: the tree landed" 0 "" \
|
|
test -x "$H10/versions/7.7.7-relflow/bin/rig"
|
|
# Refusal hint on a non-GitHub host must not send the operator to
|
|
# raw.githubusercontent.com (that 404s from a Forgejo-only tree).
|
|
H11="$WORK/h11"; B11="$WORK/b11"
|
|
check "channel forgejo latest: no-release hint uses the Forgejo raw URL" \
|
|
1 "https://forgejo.example/heavy-duty/rig/raw/branch/main/install.sh" \
|
|
rinst "$H11" "$B11" RIG_HOST=https://forgejo.example \
|
|
CURL_STUB_REDIRECT=https://forgejo.example/heavy-duty/rig/releases
|
|
|
|
# --- the local channel: RIG_INSTALL_SOURCE (#106) ----------------------------
|
|
# A supported input, not test scaffolding — CI's `install:` job and test/cli.sh
|
|
# both install THIS checkout through it. What release.sh owes is the channel's
|
|
# contract: a directory installs, a tarball installs, neither touches the
|
|
# network, and a bad path refuses BY NAME — never a silent fallback to
|
|
# downloading a release, which would leave a green CI job testing the wrong
|
|
# tree. The stub curl's log is the network witness: any download, even an
|
|
# attempted one, would land a URL in it.
|
|
H6="$WORK/h6"; B6="$WORK/b6"; LOG6="$WORK/log6"
|
|
check "channel local: a directory installs" 0 "done" \
|
|
rinst "$H6" "$B6" RIG_INSTALL_SOURCE="$TBDIR/rig-7.7.7-relflow" CURL_STUB_LOG="$LOG6"
|
|
check "channel local: the tree landed under its VERSION" 0 "" \
|
|
test -x "$H6/versions/7.7.7-relflow/bin/rig"
|
|
check "channel local: INSTALLED_FROM records local:<path>" 0 \
|
|
"local:$TBDIR/rig-7.7.7-relflow" cat "$H6/versions/7.7.7-relflow/INSTALLED_FROM"
|
|
check "channel local: curl was never consulted" 1 "" test -s "$LOG6"
|
|
H7="$WORK/h7"; B7="$WORK/b7"; LOG7="$WORK/log7"
|
|
check "channel local: a tarball installs too" 0 "done" \
|
|
rinst "$H7" "$B7" RIG_INSTALL_SOURCE="$WORK/release.tgz" CURL_STUB_LOG="$LOG7"
|
|
check "channel local: the tarball's tree landed" 0 "" \
|
|
test -x "$H7/versions/7.7.7-relflow/bin/rig"
|
|
check "channel local: ...also without a download" 1 "" test -s "$LOG7"
|
|
H8="$WORK/h8"; B8="$WORK/b8"; LOG8="$WORK/log8"
|
|
check "channel local: a missing path refuses BY NAME" 1 "$WORK/no-such-source" \
|
|
rinst "$H8" "$B8" RIG_INSTALL_SOURCE="$WORK/no-such-source" CURL_STUB_LOG="$LOG8"
|
|
check "channel local: the refusal installed NOTHING" 1 "" test -e "$H8"
|
|
check "channel local: ...and downloaded nothing (no silent fallback)" 1 "" \
|
|
test -s "$LOG8"
|
|
|
|
rm -rf "$WORK"
|
|
|
|
echo "---"
|
|
echo "$PASS passed, $FAIL failed"
|
|
[ "$FAIL" -eq 0 ]
|