fix(bootstrap): retry transient Fill failures when resolving build jars

A single HTTP 502 from fill.papermc.io aborted the entire bootstrap. Build
resolution used a one-shot curl, so one gateway blip from an upstream that
flaps was indistinguishable from a permanent failure, and the run died before
Docker, k3s or any game server was provisioned.

Pass --retry 5 --retry-delay 2 to the build-resolution fetches. 502/503/504
are already in curl's built-in transient set, so the tool had solved this; the
flags were simply never passed. papermc_latest_jar is shared by the Paper and
the Velocity resolve, so hardening it once covers both callers. The
LOOHP/Limbo CI metadata fetch feeds the same step and gets the same treatment.

Deliberately no --retry-connrefused. It only adds ECONNREFUSED to a set that
already covers this incident, and it needs curl 7.52.0 while the yum (el7)
path the script supports ships 7.29.0, where an unrecognised long option is a
parse error rather than a warning:

  # centos:7, curl 7.29.0
  $ curl -fsSL --retry 5 --retry-delay 2 --retry-connrefused https://example.com
  curl: option --retry-connrefused: is unknown

Under set -Eeuo pipefail that exits 2 and trips the || die, so both hardened
fetches would hard-fail on a host where they used to work, each naming a cause
that is not the real one. A comment above papermc_latest_jar records this so
the flag does not come back.

The failure message was actively misleading. "no Paper build for Minecraft
26.2 (the login gate speaks only that protocol)" reads as "that Minecraft
version is unsupported", sending the reader after a version-pinning problem
that does not exist: Paper 26.2 build 60 resolved fine minutes later. Say what
is actually known instead, that the build likely exists and Fill is flapping.

Verified against a local always-502 server: curl now issues 6 requests
(1 initial + 5 retries) over 10.1s before giving up, where it previously
issued 1 and died. Verified on centos:7 that this flag set is accepted, and
against the live Fill v3 API that the resolve still returns a jar URL.

Known and deliberately unchanged: no fetch sets --max-time, so an upstream
that accepts a connection and never answers still blocks forever. --retry does
not cover that, as it fires only once a request completes with a failure. The
remaining single-shot downloads (cloudflared, the Docker GPG key and repo
list, k3s, the Temurin JRE, the Velocity jar, the Go toolchain) keep their
existing no-retry shape rather than widen this diff on a script that is about
to provision a live host.
This commit is contained in:
flyemoji committed 2026-07-19 19:33:48 +09:00
1 parent 87279a1366
commit c96b36a41f
1 file changed
+9 -3
+9 -3
View File
@@ -782,7 +782,8 @@ resolve_game_jars() {
log "resolving the newest LOOHP/Limbo CI build" log "resolving the newest LOOHP/Limbo CI build"
# Fetch first, filter second: `curl | grep | head` dies of SIGPIPE under `set -o pipefail` # Fetch first, filter second: `curl | grep | head` dies of SIGPIPE under `set -o pipefail`
# the moment head closes the pipe early. Same shape everywhere below. # the moment head closes the pipe early. Same shape everywhere below.
meta="$(curl -fsSL "${ci}/api/json")" || die "could not read the LOOHP/Limbo CI build metadata" meta="$(curl -fsSL --retry 5 --retry-delay 2 "${ci}/api/json")" \
|| die "could not read the LOOHP/Limbo CI build metadata"
file="$(printf '%s' "$meta" | grep -o 'Limbo-[0-9A-Za-z._-]*\.jar' || true)" file="$(printf '%s' "$meta" | grep -o 'Limbo-[0-9A-Za-z._-]*\.jar' || true)"
file="${file%%$'\n'*}" file="${file%%$'\n'*}"
[ -n "$file" ] || die "no Limbo jar in the LOOHP/Limbo CI artifact list" [ -n "$file" ] || die "no Limbo jar in the LOOHP/Limbo CI artifact list"
@@ -799,14 +800,19 @@ resolve_game_jars() {
# is never coming back; Fill wants a descriptive User-Agent. # is never coming back; Fill wants a descriptive User-Agent.
log "resolving the newest Paper ${MC_VERSION} build" log "resolving the newest Paper ${MC_VERSION} build"
PAPER_JAR_URL="$(papermc_latest_jar paper "$MC_VERSION")" \ PAPER_JAR_URL="$(papermc_latest_jar paper "$MC_VERSION")" \
|| die "no Paper build for Minecraft ${MC_VERSION} (the login gate speaks only that protocol)" || die "could not resolve a Paper build for Minecraft ${MC_VERSION} (the login gate pins this protocol; the build likely exists — Fill upstream is down or flapping)"
ok "Limbo ${LIMBO_VERSION} + Paper, both on Minecraft ${MC_VERSION}" ok "Limbo ${LIMBO_VERSION} + Paper, both on Minecraft ${MC_VERSION}"
} }
# papermc_latest_jar prints the download URL of the newest build of <project> <version>. # papermc_latest_jar prints the download URL of the newest build of <project> <version>.
# --retry rides out Fill's transient gateway errors (502/503/504 are in curl's retry
# set): a single blip must not abort the whole bootstrap claiming the build is missing.
# Plain --retry only, deliberately: --retry-connrefused needs curl 7.52+, which the yum
# (el7) path does not have, and it would only add ECONNREFUSED to an already-covered set.
papermc_latest_jar() { papermc_latest_jar() {
local project="$1" version="$2" json urls url local project="$1" version="$2" json urls url
json="$(curl -fsSL -A "felis-bootstrap (+https://github.com/MliroLirrorsIngenuity/Felis)" \ json="$(curl -fsSL --retry 5 --retry-delay 2 \
-A "felis-bootstrap (+https://github.com/MliroLirrorsIngenuity/Felis)" \
"https://fill.papermc.io/v3/projects/${project}/versions/${version}/builds/latest")" || return 1 "https://fill.papermc.io/v3/projects/${project}/versions/${version}/builds/latest")" || return 1
urls="$(printf '%s' "$json" | grep -o 'https://fill-data\.papermc\.io/[^"]*\.jar' || true)" urls="$(printf '%s' "$json" | grep -o 'https://fill-data\.papermc\.io/[^"]*\.jar' || true)"
url="${urls%%$'\n'*}" url="${urls%%$'\n'*}"