fix(release): stop the Umbrel/Start9 step vanishing over a lapsed ghcr login
Hit live mid-release on 2026-07-31: the seeder store step appeared to have been
dropped from the script. It had not. `_seeder_store_configured` required an
EXISTING `podman login ghcr.io`, which had lapsed, so the step disqualified
itself and printed one line that named neither Umbrel nor Start9:
- Seeder store artifacts (not configured — need podman+start-sdk+skopeo+deno+yq and a ghcr login)
All five tools were present. The login was the only unmet requirement, and
nothing said so.
Two fixes:
- The gate now accepts GHCR_TOKEN as satisfying the login requirement, because
build-umbrel-image.sh logs in with it. Requiring a login that already existed
made GHCR_TOKEN useless here: the gate skipped the whole step before the build
script that would have consumed the token ever ran, so setting it in
scripts/.env could not have brought the prompt back.
- The messages name both stores and report WHICH prerequisite failed rather
than listing all six. Anyone scanning the output for "Umbrel" or "Start9"
now finds it, instead of concluding the step is gone.
Tonight's run would have printed:
- Seeder store packages for Umbrel + Start9 (not configured):
missing: a ghcr login — run: podman login ghcr.io -u peerloomllc (or set GHCR_TOKEN in scripts/.env)
Since the preflight now trusts a SET token, a stale or under-scoped one reaches
the image build instead of being caught earlier, so ensure_registry_login
reports a rejected token explicitly. It previously ran `login && echo; return 0`,
where the `;` made the failure path fall through to a bare success return.
Not done, and the TODO note that suggested it was wrong: letting the .s9pk build
when the ghcr login fails. The s9pk pins the pushed image's manifest-list digest,
so it genuinely cannot proceed without the push. The all-or-nothing gate is
correct.
Verified by exercising the gate across five states: not logged in with no token
(names the login, unconfigured), token set but not logged in (configured - the
fix), tools absent (names only the absent ones), nothing on PATH at all (podman
missing, and the login check correctly does not also fire), and
--skip-seeder-store (unconfigured, with no misleading "missing" noise). Both
preflight branches were rendered to check the wording. 370 unit tests pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VzbDH8kk3vYuQ1CzNNz39h
This commit is contained in:
co-authored by
Claude Opus 5
parent
13fcce3d2b
commit
0d8ab49c1c
+37
-13
@@ -934,17 +934,35 @@ _seeder_configured && PUBLISH_SEEDER=true
|
||||
# website clone. These publish the Umbrel/Start9 packages; the mobile app release
|
||||
# doesn't need them.
|
||||
PUBLISH_SEEDER_STORE=false
|
||||
GHCR_USER="${GHCR_USER:-peerloomllc}"
|
||||
|
||||
# Echo one line per UNMET prerequisite, nothing when all are met. Reporting
|
||||
# which one failed matters: this step disqualifying itself over a single lapsed
|
||||
# credential, behind a message listing all six requirements, reads as "the step
|
||||
# was removed from the script" (it did, on 2026-07-31, mid-release).
|
||||
_seeder_store_missing() {
|
||||
local missing=() c
|
||||
for c in podman start-sdk skopeo deno yq; do
|
||||
command -v "$c" >/dev/null 2>&1 || missing+=("$c (not on PATH)")
|
||||
done
|
||||
# The image push needs write:packages on ghcr. An existing podman login
|
||||
# satisfies that — and so does GHCR_TOKEN, because build-umbrel-image.sh logs
|
||||
# in with it. Requiring a login that already exists made GHCR_TOKEN useless
|
||||
# here: the gate skipped the whole step before the build script that would
|
||||
# have consumed the token ever ran.
|
||||
if command -v podman >/dev/null 2>&1; then
|
||||
if [ -z "${GHCR_TOKEN:-}" ] \
|
||||
&& ! podman login --get-login "${SEEDER_IMAGE%%/*}" >/dev/null 2>&1 \
|
||||
&& ! podman login --get-login ghcr.io >/dev/null 2>&1; then
|
||||
missing+=("a ghcr login — run: podman login ${SEEDER_IMAGE%%/*} -u ${GHCR_USER} (or set GHCR_TOKEN in scripts/.env)")
|
||||
fi
|
||||
fi
|
||||
printf '%s\n' "${missing[@]}"
|
||||
}
|
||||
|
||||
_seeder_store_configured() {
|
||||
$SKIP_SEEDER_STORE && return 1
|
||||
command -v podman >/dev/null 2>&1 || return 1
|
||||
command -v start-sdk >/dev/null 2>&1 || return 1
|
||||
command -v skopeo >/dev/null 2>&1 || return 1
|
||||
command -v deno >/dev/null 2>&1 || return 1
|
||||
command -v yq >/dev/null 2>&1 || return 1
|
||||
# Must already be logged in to ghcr (the image push needs write:packages).
|
||||
podman login --get-login "${SEEDER_IMAGE%%/*}" >/dev/null 2>&1 || \
|
||||
podman login --get-login ghcr.io >/dev/null 2>&1 || return 1
|
||||
return 0
|
||||
[ -z "$(_seeder_store_missing)" ]
|
||||
}
|
||||
_seeder_store_configured && PUBLISH_SEEDER_STORE=true
|
||||
|
||||
@@ -1068,16 +1086,22 @@ if ! $ZAPSTORE_ONLY && ! $CHECK_VERSIONS_ONLY; then
|
||||
{ [ -n "${WEBSITE_DIR:-}" ] && [ -d "${WEBSITE_DIR:-}/.git" ]; } \
|
||||
|| _store_reg_note=" (Start9 registry publish skipped — set WEBSITE_DIR to a website clone)"
|
||||
while true; do
|
||||
read -rp " Publish seeder store artifacts (ghcr image + Start9 .s9pk + registry)?${_store_reg_note} [y/N] " _r
|
||||
read -rp " Publish seeder store packages for Umbrel + Start9 (ghcr image + .s9pk + registry)?${_store_reg_note} [y/N] " _r
|
||||
case "${_r:-n}" in
|
||||
[Yy]) PUBLISH_SEEDER_STORE=true; echo " ✓ Seeder store artifacts"; break ;;
|
||||
[Nn]) PUBLISH_SEEDER_STORE=false; echo " ✗ Seeder store artifacts (skipped)"; break ;;
|
||||
[Yy]) PUBLISH_SEEDER_STORE=true; echo " ✓ Seeder store packages (Umbrel + Start9)"; break ;;
|
||||
[Nn]) PUBLISH_SEEDER_STORE=false; echo " ✗ Seeder store packages (Umbrel + Start9) (skipped)"; break ;;
|
||||
*) echo " Please enter y or n." ;;
|
||||
esac
|
||||
done
|
||||
else
|
||||
PUBLISH_SEEDER_STORE=false
|
||||
echo " - Seeder store artifacts (not configured — need podman+start-sdk+skopeo+deno+yq and a ghcr login)"
|
||||
# Name the stores AND the specific unmet requirement. Anyone scanning this
|
||||
# output for "Umbrel" or "Start9" must find it here, or they will conclude
|
||||
# the step is gone rather than gated.
|
||||
echo " - Seeder store packages for Umbrel + Start9 (not configured):"
|
||||
while IFS= read -r _m; do
|
||||
[ -n "$_m" ] && echo " missing: $_m"
|
||||
done < <(_seeder_store_missing)
|
||||
fi
|
||||
# The .s9pk uploads to the GitHub Release (the registry redirects to it).
|
||||
if $PUBLISH_SEEDER_STORE && ! $PUBLISH_GITHUB; then
|
||||
|
||||
@@ -53,8 +53,17 @@ ensure_registry_login () {
|
||||
echo "==> $host: using existing login ($(podman login --get-login "$host" 2>/dev/null))"; return 0
|
||||
fi
|
||||
if [ -n "${GHCR_TOKEN:-}" ]; then
|
||||
printf '%s' "$GHCR_TOKEN" | "$ENGINE" login "$host" -u "$GHCR_USER" --password-stdin >/dev/null \
|
||||
&& echo "==> $host: logged in as $GHCR_USER (GHCR_TOKEN)"; return 0
|
||||
# Report a rejected token here rather than letting it surface as an opaque
|
||||
# 403 at push time, minutes into a multi-arch build. The release preflight
|
||||
# now treats a SET GHCR_TOKEN as satisfying the login requirement, so a
|
||||
# stale or under-scoped token reaches this point instead of being caught
|
||||
# by the old "must already be logged in" gate.
|
||||
if printf '%s' "$GHCR_TOKEN" | "$ENGINE" login "$host" -u "$GHCR_USER" --password-stdin >/dev/null 2>&1; then
|
||||
echo "==> $host: logged in as $GHCR_USER (GHCR_TOKEN)"; return 0
|
||||
fi
|
||||
echo "build-umbrel-image: GHCR_TOKEN is set but logging in to $host as $GHCR_USER failed." >&2
|
||||
echo " The token must be a classic PAT with write:packages, and not expired." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ "$ENGINE" = docker ]; then echo "==> $host: assuming an existing docker login (set GHCR_TOKEN to auto-login)"; return 0; fi
|
||||
echo "build-umbrel-image: not logged in to $host and GHCR_TOKEN is unset." >&2
|
||||
|
||||
Reference in New Issue
Block a user