forked from heavy-duty/rig
feat: install channel is forge-agnostic (RIG_HOST / BOX_HOST)
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
This commit is contained in:
parent
90cbfe0057
commit
24abfbdb79
7 changed files with 163 additions and 26 deletions
10
bin/rig
10
bin/rig
|
|
@ -152,10 +152,12 @@ commands:
|
|||
|
||||
install/upgrade:
|
||||
curl -fsSL https://raw.githubusercontent.com/heavy-duty/rig/main/install.sh | bash
|
||||
Installs the latest RELEASE (RIG_REF=<tag> pins one, RIG_REF=main
|
||||
tracks the development tree). Re-run any time: an installed version
|
||||
converges (no-op), a new one installs side by side at
|
||||
<root>/versions/<v> and becomes the default.
|
||||
Installs the latest RELEASE from GitHub (the default RIG_HOST).
|
||||
RIG_REF=<tag> pins one, RIG_REF=main tracks the development tree.
|
||||
From a Forgejo host: set RIG_HOST to that origin and fetch install.sh
|
||||
from <host>/heavy-duty/rig/raw/branch/main/install.sh (#111).
|
||||
Re-run any time: an installed version converges (no-op), a new one
|
||||
installs side by side at <root>/versions/<v> and becomes the default.
|
||||
EOF
|
||||
}
|
||||
|
||||
|
|
|
|||
3
changelog.d/111.md
Normal file
3
changelog.d/111.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
### Changed
|
||||
|
||||
- `install.sh` and bootstrap's box fetch take `RIG_HOST` / `BOX_HOST` so a Forgejo origin can serve the install channel (#111)
|
||||
|
|
@ -723,7 +723,26 @@ if [ "$HOST" = "yes" ]; then
|
|||
BOX_RELEASE=0.9.0
|
||||
BOX_REPO="${BOX_REPO:-heavy-duty/box}"
|
||||
BOX_REF="${BOX_REF:-$BOX_RELEASE}"
|
||||
BOX_INSTALL_URL="https://raw.githubusercontent.com/${BOX_REPO}/${BOX_REF}/install.sh"
|
||||
# BOX_HOST: which forge serves box (#111). Parallel to RIG_HOST /
|
||||
# RIG_TEMPLATES_HOST — default GitHub keeps every existing bootstrap
|
||||
# byte-unchanged; set BOX_HOST=https://forgejo.heavyduty.builders when
|
||||
# box is installed from this instance. Raw-file grammar differs:
|
||||
# GitHub raw.githubusercontent.com/<repo>/<ref>/install.sh
|
||||
# Forgejo <host>/<repo>/raw/{tag|branch}/<ref>/install.sh
|
||||
# Version-shaped refs (BOX_RELEASE pins) use raw/tag/; anything else
|
||||
# (BOX_REF=main) uses raw/branch/.
|
||||
BOX_HOST="${BOX_HOST:-https://github.com}"
|
||||
BOX_HOST="${BOX_HOST%/}"
|
||||
case "$BOX_HOST" in
|
||||
https://github.com|http://github.com|*//github.com)
|
||||
BOX_INSTALL_URL="https://raw.githubusercontent.com/${BOX_REPO}/${BOX_REF}/install.sh" ;;
|
||||
*)
|
||||
case "$BOX_REF" in
|
||||
[0-9]*|v[0-9]*) BOX_RAW_KIND=tag ;;
|
||||
*) BOX_RAW_KIND=branch ;;
|
||||
esac
|
||||
BOX_INSTALL_URL="${BOX_HOST}/${BOX_REPO}/raw/${BOX_RAW_KIND}/${BOX_REF}/install.sh" ;;
|
||||
esac
|
||||
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}"
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ log "writing ${UNIT_DIR}/coolify-dump.service"
|
|||
cat > "$UNIT_DIR/coolify-dump.service" <<UNIT
|
||||
[Unit]
|
||||
Description=Age-encrypted dump of the Coolify control-plane database
|
||||
Documentation=https://github.com/heavy-duty/rig
|
||||
Documentation=https://forgejo.heavyduty.builders/heavy-duty/rig
|
||||
Requires=docker.service
|
||||
After=docker.service
|
||||
|
||||
|
|
@ -231,7 +231,7 @@ log "writing ${UNIT_DIR}/coolify-dump.timer (${SCHEDULE})"
|
|||
cat > "$UNIT_DIR/coolify-dump.timer" <<UNIT
|
||||
[Unit]
|
||||
Description=Nightly Coolify control-plane dump
|
||||
Documentation=https://github.com/heavy-duty/rig
|
||||
Documentation=https://forgejo.heavyduty.builders/heavy-duty/rig
|
||||
|
||||
[Timer]
|
||||
OnCalendar=${SCHEDULE}
|
||||
|
|
|
|||
69
install.sh
69
install.sh
|
|
@ -37,6 +37,11 @@ set -euo pipefail
|
|||
|
||||
REPO="${RIG_REPO:-heavy-duty/rig}"
|
||||
REF="${RIG_REF:-}" # empty = the latest release, resolved below
|
||||
# The forge this REPO lives on is RIG_HOST (#111), default https://github.com.
|
||||
# Parallel to RIG_TEMPLATES_HOST — not the same variable, because the registry
|
||||
# and rig itself may live on different forges. Default stays GitHub so every
|
||||
# existing curl|bash one-liner is byte-unchanged; set
|
||||
# RIG_HOST=https://forgejo.heavyduty.builders to install from this instance.
|
||||
|
||||
# cloud-init's runcmd runs with NO $HOME in the environment, and under set -u
|
||||
# the expansions just below turned that into a death instead of an install —
|
||||
|
|
@ -101,11 +106,18 @@ warn_bootstrapped() { # $1 = what is about to happen
|
|||
# (curl's %{redirect_url} is that header, parsed): no API, no token, no
|
||||
# rate-limit pain. A repo with no releases redirects to /releases — not to
|
||||
# /releases/tag/<tag> — so this returns 1 there instead of inventing a ref,
|
||||
# and the CALLER owns the loud story. test/release.sh extracts this function
|
||||
# (awk, the valid_version idiom) and drives it against a stubbed curl.
|
||||
# and the CALLER owns the loud story. Host comes from RIG_HOST (default
|
||||
# GitHub); both GitHub and Forgejo serve the same /releases/latest →
|
||||
# /releases/tag/<tag> redirect grammar, measured 2026-07-29 (#111).
|
||||
# test/release.sh extracts this function (awk, the valid_version idiom) and
|
||||
# drives it against a stubbed curl.
|
||||
resolve_latest_tag() {
|
||||
local loc
|
||||
loc="$(curl -fsSI -o /dev/null -w '%{redirect_url}' "https://github.com/$1/releases/latest")" || return 1
|
||||
# Default is inlined (not $RIG_HOST_DEFAULT) so test/release.sh's awk
|
||||
# extract of this function stays self-contained — same discipline as
|
||||
# valid_version.
|
||||
local host="${RIG_HOST:-https://github.com}" loc
|
||||
host="${host%/}"
|
||||
loc="$(curl -fsSI -o /dev/null -w '%{redirect_url}' "$host/$1/releases/latest")" || return 1
|
||||
case "$loc" in
|
||||
*/releases/tag/?*) printf '%s\n' "${loc##*/releases/tag/}" ;;
|
||||
*) return 1 ;;
|
||||
|
|
@ -113,12 +125,39 @@ resolve_latest_tag() {
|
|||
}
|
||||
|
||||
# ref_candidate_urls <owner/repo> <ref> — the download candidates for an
|
||||
# explicit RIG_REF, in order: refs/tags first, so a tag always outranks a
|
||||
# branch that happens to share its name (the pin must win), refs/heads as
|
||||
# the fallback that keeps RIG_REF=main the dev channel.
|
||||
# explicit RIG_REF, in order. Host comes from RIG_HOST; the two forges are
|
||||
# not URL-compatible (#111 / #109):
|
||||
# GitHub refs/tags first so a tag always outranks a same-named branch
|
||||
# (the pin must win), then refs/heads for RIG_REF=main.
|
||||
# Forgejo one form: /archive/<ref>.tar.gz resolves tags, branches and
|
||||
# SHAs alike (same grammar as templates_archive_urls).
|
||||
ref_candidate_urls() {
|
||||
printf 'https://github.com/%s/archive/refs/tags/%s.tar.gz\n' "$1" "$2"
|
||||
printf 'https://github.com/%s/archive/refs/heads/%s.tar.gz\n' "$1" "$2"
|
||||
local host="${RIG_HOST:-https://github.com}"
|
||||
host="${host%/}"
|
||||
case "$host" in
|
||||
https://github.com|http://github.com|*//github.com)
|
||||
printf '%s/%s/archive/refs/tags/%s.tar.gz\n' "$host" "$1" "$2"
|
||||
printf '%s/%s/archive/refs/heads/%s.tar.gz\n' "$host" "$1" "$2" ;;
|
||||
*)
|
||||
printf '%s/%s/archive/%s.tar.gz\n' "$host" "$1" "$2" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# install_script_url — the curl|bash entrypoint URL for this REPO on RIG_HOST.
|
||||
# GitHub serves raw files at raw.githubusercontent.com; Forgejo at
|
||||
# /raw/branch/<ref>/<path>. The refusal hint and bin/rig usage() both print
|
||||
# this, so a Forgejo install never tells the operator to hit a 404 (#111).
|
||||
# REPO is the installer's global (RIG_REPO); tests that extract this function
|
||||
# must set it.
|
||||
install_script_url() {
|
||||
local host="${RIG_HOST:-https://github.com}"
|
||||
host="${host%/}"
|
||||
case "$host" in
|
||||
https://github.com|http://github.com|*//github.com)
|
||||
printf 'https://raw.githubusercontent.com/%s/main/install.sh\n' "${REPO:-heavy-duty/rig}" ;;
|
||||
*)
|
||||
printf '%s/%s/raw/branch/main/install.sh\n' "$host" "${REPO:-heavy-duty/rig}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# The registry's candidate URLs, forge-aware — a byte-identical copy of
|
||||
|
|
@ -223,12 +262,14 @@ else
|
|||
if [ -z "$REF" ]; then
|
||||
log "resolving the latest release of $REPO"
|
||||
if ! REF="$(resolve_latest_tag "$REPO")"; then
|
||||
warn "could not resolve the latest release of $REPO — either no release exists yet, or GitHub was unreachable."
|
||||
warn "(rig has no release until 0.1.0 is cut — rig#32. Until then, install the development tree explicitly.)"
|
||||
die "set RIG_REF: e.g. curl -fsSL https://raw.githubusercontent.com/$REPO/main/install.sh | RIG_REF=main bash"
|
||||
warn "could not resolve the latest release of $REPO — either no release exists yet, or ${RIG_HOST:-https://github.com} was unreachable."
|
||||
warn "(install the development tree explicitly with RIG_REF=main when no release exists yet.)"
|
||||
die "set RIG_REF: e.g. curl -fsSL $(install_script_url) | RIG_REF=main bash"
|
||||
fi
|
||||
log "latest release: $REF"
|
||||
urls=("https://github.com/$REPO/archive/refs/tags/$REF.tar.gz")
|
||||
# Same candidate grammar as an explicit pin: on GitHub the tag form wins
|
||||
# first; on Forgejo the single bare archive URL is the whole list (#111).
|
||||
mapfile -t urls < <(ref_candidate_urls "$REPO" "$REF")
|
||||
else
|
||||
mapfile -t urls < <(ref_candidate_urls "$REPO" "$REF")
|
||||
fi
|
||||
|
|
@ -244,7 +285,7 @@ else
|
|||
fi
|
||||
done
|
||||
[ -n "$got" ] \
|
||||
|| die "failed to download $REPO@$REF — not a tag and not a branch (tried refs/tags then refs/heads)"
|
||||
|| die "failed to download $REPO@$REF — no candidate URL worked (host ${RIG_HOST:-https://github.com}; tried tags then heads on GitHub, or the single archive URL on other forges)"
|
||||
|
||||
log "extracting archive"
|
||||
tar -xzf "$TMPDIR/rig.tar.gz" -C "$TMPDIR" \
|
||||
|
|
|
|||
|
|
@ -249,6 +249,15 @@ 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"
|
||||
# BOX_HOST selects the forge that serves box's installer (#111). Default
|
||||
# GitHub keeps the raw.githubusercontent.com grammar; a non-GitHub host
|
||||
# uses Forgejo's /raw/{tag|branch}/<ref>/ form.
|
||||
check "bootstrap: BOX_HOST defaults to GitHub" 0 "" \
|
||||
grep -qF 'BOX_HOST="${BOX_HOST:-https://github.com}"' "$ROOT/commands/bootstrap.sh"
|
||||
check "bootstrap: GitHub box install uses raw.githubusercontent.com" 0 "" \
|
||||
grep -qF 'raw.githubusercontent.com/${BOX_REPO}/${BOX_REF}/install.sh' "$ROOT/commands/bootstrap.sh"
|
||||
check "bootstrap: non-GitHub box install uses /raw/{tag|branch}/" 0 "" \
|
||||
grep -qF '/raw/${BOX_RAW_KIND}/${BOX_REF}/install.sh' "$ROOT/commands/bootstrap.sh"
|
||||
# Opt-out for rehearsals / offline / hand-managed hosts.
|
||||
check "bootstrap: box install honors RIG_SKIP_BOX_INSTALL opt-out" 0 "" \
|
||||
grep -q "RIG_SKIP_BOX_INSTALL" "$ROOT/commands/bootstrap.sh"
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ FAKEHOME="$WORK/home"; mkdir -p "$FAKEHOME"
|
|||
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"
|
||||
|
|
@ -93,14 +94,49 @@ check "resolve: a tagless releases/tag/ redirect fails" 1 "" \
|
|||
check "resolve: a failing curl fails (network down is not a channel)" 1 "" \
|
||||
rlt CURL_STUB_FAIL=1
|
||||
|
||||
rcu_line() { # rcu_line <n> — the nth candidate URL for an explicit ref
|
||||
bash -c 'set -euo pipefail
|
||||
. "$1"; ref_candidate_urls acme/widgets 1.2.3 | sed -n "${2}p"' _ "$RL" "$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
|
||||
|
|
@ -167,8 +203,35 @@ check "channel dev: ...then the branch URL" 0 "refs/heads/feature-x" \
|
|||
|
||||
# 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 both tries" \
|
||||
1 "not a tag and not a branch" rinst "$H5" "$B5" RIG_REF=no-such-ref
|
||||
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
|
||||
|
|
|
|||
Loading…
Reference in a new issue