fix: BOX_MANUAL is pasteable try:/or: lines, not prose shell
Some checks failed
ci / check (pull_request) Has been cancelled
ci / install (pull_request) Has been cancelled
ci / db-integration (pull_request) Has been cancelled
labels / labels (pull_request) Has been cancelled

codex REQUEST_CHANGES on !114 / #125: multi-candidate recovery was
rendered as `curl A | bash; if that 404s: curl B | bash`, which
`bash -n` rejects (exit 2). Operators on RIG_SKIP_BOX_INSTALL / no-curl
/ all-candidates-failed paths were handed non-executable recovery.

- box_manual_cmd / box_manual_text: one pasteable command per candidate
- display as separate `try:` / `or:` lines (newlines, no prose join)
- post-probe rewrite still collapses to the URL that worked
- execution tests: both Forgejo URLs visible; each command passes bash -n
This commit is contained in:
grok-reviewer-andresmgsl 2026-07-29 15:22:49 +00:00
parent ec73c86de7
commit 1c9a245595
2 changed files with 83 additions and 21 deletions

View file

@ -747,25 +747,43 @@ if [ "$HOST" = "yes" ]; then
printf '%s/%s/raw/branch/%s/install.sh\n' "$BOX_HOST" "$BOX_REPO" "$BOX_REF" ;;
esac
}
# 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.
# One pasteable recovery command per candidate URL. Never join candidates
# with English prose or shell metacharacters — "curl A | bash; if that
# 404s: curl B | bash" is not valid shell (`bash -n` exits 2) and is the
# same class of operator-facing failure #111 exists to remove (#125 /
# codex REQUEST_CHANGES on !114). Display uses separate prefixed lines
# (try: / or:); after a live probe succeeds the install loop rewrites
# BOX_MANUAL to the single URL that worked.
box_manual_cmd() { # box_manual_cmd <url> — one pasteable install line
printf 'curl -fsSL %s | BOX_YES=1 BOX_REF=%s bash\n' "$1" "$BOX_REF"
}
box_manual_text() {
local _n=0 _url _cmd
while IFS= read -r _url; do
_cmd="$(box_manual_cmd "$_url")"
_cmd="${_cmd%$'\n'}"
_n=$((_n + 1))
if [ "$_n" -eq 1 ]; then
printf 'try: %s\n' "$_cmd"
else
printf 'or: %s\n' "$_cmd"
fi
done < <(box_install_urls)
}
BOX_INSTALL_URL="$(box_install_urls | head -n1)"
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)
# Newline-separated try:/or: lines — each command after the prefix is
# independently pasteable (never one shell-looking string with prose).
BOX_MANUAL="$(box_manual_text)"
if [ "${RIG_SKIP_BOX_INSTALL:-}" = "1" ]; then
log "RIG_SKIP_BOX_INSTALL=1 — skipping box install; to prepare Incus by hand later: ${BOX_MANUAL}"
log "RIG_SKIP_BOX_INSTALL=1 — skipping box install; to prepare Incus by hand later:"
while IFS= read -r _line; do [ -n "$_line" ] && log " ${_line}"; done <<EOF
$BOX_MANUAL
EOF
elif ! command -v curl >/dev/null 2>&1; then
warn "curl not found — skipping box install; once curl is present, prepare Incus with: ${BOX_MANUAL}"
warn "curl not found — skipping box install; once curl is present, prepare Incus with:"
while IFS= read -r _line; do [ -n "$_line" ] && warn " ${_line}"; done <<EOF
$BOX_MANUAL
EOF
else
log "installing box (${BOX_REPO}@${BOX_REF}) and running its host setup — box owns Incus, not rig"
# BOX_YES=1 in the environment: non-interactive AND keeps setup-host, so box
@ -783,7 +801,7 @@ if [ "$HOST" = "yes" ]; then
if curl -fsSL "$_box_url" -o "$BOX_SCRIPT"; then
BOX_GOT="$_box_url"
BOX_INSTALL_URL="$_box_url"
BOX_MANUAL="curl -fsSL ${BOX_INSTALL_URL} | BOX_YES=1 BOX_REF=${BOX_REF} bash"
BOX_MANUAL="$(box_manual_cmd "$BOX_INSTALL_URL" | tr -d '\n')"
break
fi
done < <(box_install_urls)

View file

@ -244,9 +244,11 @@ check "bootstrap: BOX_REF overrides the released default" 0 "" \
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.
# box_manual_cmd formats BOX_REF via %s so the rendered recovery always
# carries the concrete pin (not a bare unexpanded variable).
# 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"
grep -qF 'BOX_YES=1 BOX_REF=%s 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 script (#111).
@ -301,9 +303,51 @@ check "bootstrap: box_install_urls Forgejo branch second" 0 \
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"
# BOX_MANUAL recovery text: separate try:/or: lines, each command pasteable
# (codex REQUEST_CHANGES / #125 — prose-joined strings fail `bash -n`).
# Extract box_manual_cmd + box_manual_text with the same nested-fn idiom.
BIM="$BIU_DIR/box-manual.sh"
{
awk '/^ box_install_urls\(\) \{/,/^ \}/' "$ROOT/commands/bootstrap.sh"
awk '/^ box_manual_cmd\(\) \{/,/^ \}/' "$ROOT/commands/bootstrap.sh"
awk '/^ box_manual_text\(\) \{/,/^ \}/' "$ROOT/commands/bootstrap.sh"
} | sed 's/^ //' > "$BIM"
check "bootstrap: box_manual helpers extracted" 0 "try:" cat "$BIM"
# shellcheck disable=SC2016
bmanual() { # bmanual HOST REF — render BOX_MANUAL text
env BOX_HOST="$1" BOX_REPO=heavy-duty/box BOX_REF="$2" \
bash -c 'set -euo pipefail; . "$1"; box_manual_text' _ "$BIM"
}
# shellcheck disable=SC2016
bmanual_cmds_ok() { # every pasteable command after try:/or: passes bash -n
env BOX_HOST="$1" BOX_REPO=heavy-duty/box BOX_REF="$2" \
bash -c 'set -euo pipefail
. "$1"
while IFS= read -r line; do
[ -n "$line" ] || continue
cmd="$line"
cmd="${cmd#try: }"
cmd="${cmd#or: }"
cmd="${cmd#or: }"
bash -n <<<"$cmd"
done < <(box_manual_text)' _ "$BIM"
}
check "bootstrap: BOX_MANUAL GitHub is a single try: raw.githubusercontent.com line" 0 \
"try: curl -fsSL https://raw.githubusercontent.com/heavy-duty/box/0.9.0/install.sh | BOX_YES=1 BOX_REF=0.9.0 bash" \
bmanual https://github.com 0.9.0
check "bootstrap: BOX_MANUAL Forgejo lists raw/tag first" 0 \
"try: curl -fsSL https://forgejo.example/heavy-duty/box/raw/tag/main/install.sh | BOX_YES=1 BOX_REF=main bash" \
bmanual https://forgejo.example main
check "bootstrap: BOX_MANUAL Forgejo lists raw/branch as or:" 0 \
"or: curl -fsSL https://forgejo.example/heavy-duty/box/raw/branch/main/install.sh | BOX_YES=1 BOX_REF=main bash" \
bmanual https://forgejo.example main
check "bootstrap: BOX_MANUAL GitHub commands pass bash -n" 0 "" \
bmanual_cmds_ok https://github.com 0.9.0
check "bootstrap: BOX_MANUAL Forgejo commands pass bash -n" 0 "" \
bmanual_cmds_ok https://forgejo.example main
# Regression: the old prose join must not return.
check "bootstrap: BOX_MANUAL does not use prose 'if that 404s'" 1 "" \
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 "" \