diff --git a/commands/bootstrap.sh b/commands/bootstrap.sh index b20617d..c7cce46 100755 --- a/commands/bootstrap.sh +++ b/commands/bootstrap.sh @@ -747,10 +747,21 @@ if [ "$HOST" = "yes" ]; then printf '%s/%s/raw/branch/%s/install.sh\n' "$BOX_HOST" "$BOX_REPO" "$BOX_REF" ;; esac } - # Prefer the first candidate for the operator-facing manual path; the - # install loop below walks the full list when the network is live. + # Operator-facing recovery command. On multi-candidate hosts (Forgejo) + # list every URL — taking only the first would hand BOX_REF=main a + # /raw/tag/main URL that 404s (#111 review: "tells the operator to run a + # command that 404s"). After a live probe succeeds, the install loop + # rewrites BOX_MANUAL to the URL that actually worked. BOX_INSTALL_URL="$(box_install_urls | head -n1)" - BOX_MANUAL="curl -fsSL ${BOX_INSTALL_URL} | BOX_YES=1 BOX_REF=${BOX_REF} bash" + BOX_MANUAL="" + while IFS= read -r _box_manual_url; do + _box_manual_cmd="curl -fsSL ${_box_manual_url} | BOX_YES=1 BOX_REF=${BOX_REF} bash" + if [ -z "$BOX_MANUAL" ]; then + BOX_MANUAL="$_box_manual_cmd" + else + BOX_MANUAL="${BOX_MANUAL}; if that 404s: ${_box_manual_cmd}" + fi + done < <(box_install_urls) 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 diff --git a/test/cli.sh b/test/cli.sh index 4812aac..e77a2aa 100644 --- a/test/cli.sh +++ b/test/cli.sh @@ -265,6 +265,46 @@ check "bootstrap: non-GitHub box install tries /raw/tag/ first" 0 "" \ # shellcheck disable=SC2016 check "bootstrap: non-GitHub box install falls back to /raw/branch/" 0 "" \ grep -qF 'raw/branch/%s/install.sh' "$ROOT/commands/bootstrap.sh" +# Drive box_install_urls for real (codex/claude: grep-only cannot catch +# order or emission bugs). Extract with the release.sh awk idiom; the +# function is nested under `if [ "$HOST" = "yes" ]` so strip two spaces. +BIU_DIR="$(mktemp -d)" +BIU="$BIU_DIR/box-install-urls.sh" +awk '/^ box_install_urls\(\) \{/,/^ \}/' "$ROOT/commands/bootstrap.sh" \ + | sed 's/^ //' > "$BIU" +check "bootstrap: box_install_urls extracted (guards the awk)" 0 "raw" cat "$BIU" +biu_line() { # biu_line HOST REF N — the Nth candidate (1-based) + local host="$1" ref="$2" n="$3" + # shellcheck disable=SC2016 # $1/$2 are the inner bash -c positionals + env BOX_HOST="$host" BOX_REPO=heavy-duty/box BOX_REF="$ref" \ + bash -c 'set -euo pipefail; . "$1"; box_install_urls | sed -n "${2}p"' \ + _ "$BIU" "$n" +} +biu_count() { # biu_count HOST REF — how many candidates + local host="$1" ref="$2" + # shellcheck disable=SC2016 # $1 is the inner bash -c positional + env BOX_HOST="$host" BOX_REPO=heavy-duty/box BOX_REF="$ref" \ + bash -c 'set -euo pipefail; . "$1"; box_install_urls | grep -c .' \ + _ "$BIU" +} +check "bootstrap: box_install_urls GitHub is a single raw.githubusercontent.com URL" 0 \ + "https://raw.githubusercontent.com/heavy-duty/box/0.9.0/install.sh" \ + biu_line https://github.com 0.9.0 1 +check "bootstrap: box_install_urls GitHub emits exactly one candidate" 0 "1" \ + biu_count https://github.com 0.9.0 +check "bootstrap: box_install_urls Forgejo tag-first for a version pin" 0 \ + "https://forgejo.example/heavy-duty/box/raw/tag/0.9.0/install.sh" \ + biu_line https://forgejo.example 0.9.0 1 +check "bootstrap: box_install_urls Forgejo branch second" 0 \ + "https://forgejo.example/heavy-duty/box/raw/branch/0.9.0/install.sh" \ + biu_line https://forgejo.example 0.9.0 2 +check "bootstrap: box_install_urls Forgejo tag-first even for BOX_REF=main" 0 \ + "https://forgejo.example/heavy-duty/box/raw/tag/main/install.sh" \ + biu_line https://forgejo.example main 1 +# BOX_MANUAL must not hand the operator only a 404ing first candidate. +check "bootstrap: BOX_MANUAL names the branch fallback when tag may 404" 0 "" \ + grep -qF 'if that 404s:' "$ROOT/commands/bootstrap.sh" +rm -rf "$BIU_DIR" # 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" diff --git a/test/release.sh b/test/release.sh index b5b7bd0..2bd3f0e 100644 --- a/test/release.sh +++ b/test/release.sh @@ -42,10 +42,14 @@ FAKEHOME="$WORK/home"; mkdir -p "$FAKEHOME" # 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 '/^release_tag_url\(\) \{/,/^\}/' "$ROOT/install.sh" >> "$RL" -awk '/^install_script_url\(\) \{/,/^\}/' "$ROOT/install.sh" >> "$RL" +# Grouped redirect — shellcheck SC2129 flags four individual >> to the same +# file (crossed the threshold when release_tag_url joined the extract set). +{ + awk '/^resolve_latest_tag\(\) \{/,/^\}/' "$ROOT/install.sh" + awk '/^ref_candidate_urls\(\) \{/,/^\}/' "$ROOT/install.sh" + awk '/^release_tag_url\(\) \{/,/^\}/' "$ROOT/install.sh" + 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" @@ -142,6 +146,10 @@ check "resolve: RIG_HOST is the releases/latest origin" 0 "0.2.0" \ check "resolve: the probe hit the Forgejo host" 0 \ "https://forgejo.example/heavy-duty/rig/releases/latest" \ cat "$rlt_log" +# Forgejo's no-release path is a 404 (not GitHub's /releases redirect) — +# curl -f fails and || return 1 fires. Drive that branch under RIG_HOST. +check "resolve: Forgejo no-release is a failing curl (404), not a /releases redirect" 1 "" \ + rlt RIG_HOST=https://forgejo.example CURL_STUB_FAIL=1 # --- the three channels, driven through the REAL installer ------------------- # Full install.sh runs against throwaway roots with the stub curl on PATH: the