From ae12ff25174a0cbf2ded5c4e66000bdb06db88f7 Mon Sep 17 00:00:00 2001 From: Dorian Date: Mon, 20 Jul 2026 06:08:04 +0100 Subject: [PATCH] fix(iso): fail builds on missing VPN binaries, skip units cleanly, invalidate stale rootfs cache v1.7.104 shipped ISOs whose units crash-looped (nostr-relay 3s loop, archipelago-diag 203/EXEC) because build-time extraction failures were mere warnings and the cached rootfs never tracked its recipe: - hash the rootfs-defining region of the build script and rebuild when it changes (stale cache shipped ISOs without wpasupplicant/iw/rfkill) - refuse to build without nvpn / nostr-rs-relay unless ALLOW_MISSING_VPN_BINARIES=1 - ConditionPathExists on nostr-relay/nostr-vpn/diag units so a stripped image skips them instead of crash-looping - post-install test: every enabled archipelago*/nostr* unit must have an existing ExecStart payload Co-Authored-By: Claude Fable 5 --- .../_archived/build-auto-installer-iso.sh | 36 ++++++++++++++++++- image-recipe/configs/nostr-relay.service | 3 ++ image-recipe/configs/nostr-vpn.service | 3 ++ scripts/run-post-install-tests.sh | 17 +++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/image-recipe/_archived/build-auto-installer-iso.sh b/image-recipe/_archived/build-auto-installer-iso.sh index bdcd18ed..cac6683a 100755 --- a/image-recipe/_archived/build-auto-installer-iso.sh +++ b/image-recipe/_archived/build-auto-installer-iso.sh @@ -254,8 +254,17 @@ container_pull() { echo "📦 Step 1: Building root filesystem..." ROOTFS_TAR="$WORK_DIR/archipelago-rootfs.tar" +ROOTFS_STAMP="$WORK_DIR/archipelago-rootfs.recipe.sha256" -if [ ! -f "$ROOTFS_TAR" ] || [ "$1" == "--rebuild" ]; then +# The cached rootfs must be invalidated when its recipe changes: a stale +# archipelago-rootfs.tar on the build machine shipped ISOs with NO +# wpasupplicant/iw/rfkill (WiFi dead on laptops) long after those packages +# were added to the Dockerfile below — the cache condition never looked at +# the recipe. Hash the rootfs-defining region of this script; any edit to it +# forces a rebuild. `--rebuild` still forces one unconditionally. +RECIPE_HASH=$(sed -n '/^# STEP 1: Build complete root filesystem/,/^# STEP 2: Build minimal installer/p' "$0" | sha256sum | cut -d' ' -f1) + +if [ ! -f "$ROOTFS_TAR" ] || [ "${1:-}" == "--rebuild" ] || [ "$(cat "$ROOTFS_STAMP" 2>/dev/null)" != "$RECIPE_HASH" ]; then echo " Using Docker to create Debian root filesystem..." # Create a Dockerfile for building the rootfs @@ -694,6 +703,7 @@ SYSTEMDSERVICE $CONTAINER_CMD export archipelago-rootfs-tmp > "$ROOTFS_TAR" $CONTAINER_CMD rm archipelago-rootfs-tmp + echo "$RECIPE_HASH" > "$ROOTFS_STAMP" echo "✅ Root filesystem created: $(du -h "$ROOTFS_TAR" | cut -f1)" else echo "✅ Using cached root filesystem: $(du -h "$ROOTFS_TAR" | cut -f1)" @@ -1218,6 +1228,23 @@ else echo " ⚠ nostr-rs-relay image not available — relay binary will be missing" fi +# A missing nvpn/nostr-rs-relay used to be a warning, and the resulting ISO +# shipped units that crash-looped (or silently lacked VPN signaling) on every +# install. Refuse to produce that ISO unless explicitly overridden. +MISSING_VPN_BINARIES="" +[ -f "$ARCH_DIR/bin/nvpn" ] || MISSING_VPN_BINARIES="$MISSING_VPN_BINARIES nvpn" +[ -f "$ARCH_DIR/bin/nostr-rs-relay" ] || MISSING_VPN_BINARIES="$MISSING_VPN_BINARIES nostr-rs-relay" +if [ -n "$MISSING_VPN_BINARIES" ]; then + if [ "${ALLOW_MISSING_VPN_BINARIES:-0}" = "1" ]; then + echo " ⚠ Building WITHOUT:$MISSING_VPN_BINARIES (ALLOW_MISSING_VPN_BINARIES=1)" + else + echo " ❌ Required binaries not extracted:$MISSING_VPN_BINARIES" + echo " The registry (146.59.87.168:3000) must be reachable and hold the images," + echo " or set ALLOW_MISSING_VPN_BINARIES=1 to ship without VPN signaling." + exit 1 + fi +fi + # Copy WireGuard helper script if [ -f "$WORK_DIR/archipelago-wg" ]; then cp "$WORK_DIR/archipelago-wg" "$ARCH_DIR/bin/archipelago-wg" @@ -3345,6 +3372,11 @@ echo "" echo "=== Done ===" DIAGSCRIPT chmod +x /mnt/target/opt/archipelago/scripts/first-boot-diag.sh +# v1.7.104 shipped installs where this script was missing while its unit was +# enabled (203/EXEC forever). Verify the write actually landed, loudly. +if [ ! -x /mnt/target/opt/archipelago/scripts/first-boot-diag.sh ]; then + echo "ERROR: first-boot-diag.sh was not written to the target" >&2 +fi # Systemd oneshot service for first-boot diagnostics cat > /mnt/target/etc/systemd/system/archipelago-diag.service <<'DIAGSVC' @@ -3352,6 +3384,8 @@ cat > /mnt/target/etc/systemd/system/archipelago-diag.service <<'DIAGSVC' Description=Archipelago First Boot Diagnostics After=multi-user.target archipelago.service nginx.service ConditionPathExists=!/var/log/archipelago-first-boot-diag.log +# Skip cleanly (instead of failing 203/EXEC) if the script is missing. +ConditionPathExists=/opt/archipelago/scripts/first-boot-diag.sh [Service] Type=oneshot diff --git a/image-recipe/configs/nostr-relay.service b/image-recipe/configs/nostr-relay.service index 8d1c133a..27366ca0 100644 --- a/image-recipe/configs/nostr-relay.service +++ b/image-recipe/configs/nostr-relay.service @@ -3,6 +3,9 @@ Description=Archipelago Private Nostr Relay After=network-online.target Wants=network-online.target Before=nostr-vpn.service +# An ISO built without the relay binary (registry unreachable at build time) +# must not crash-loop every 3s forever — skip cleanly instead. +ConditionPathExists=/usr/local/bin/nostr-rs-relay [Service] Type=simple diff --git a/image-recipe/configs/nostr-vpn.service b/image-recipe/configs/nostr-vpn.service index 19437a57..995601a4 100644 --- a/image-recipe/configs/nostr-vpn.service +++ b/image-recipe/configs/nostr-vpn.service @@ -4,6 +4,9 @@ After=network-online.target tor.service archipelago.service Wants=network-online.target StartLimitIntervalSec=300 StartLimitBurst=10 +# An ISO built without the nvpn binary (registry unreachable at build time) +# must not restart-loop — skip cleanly instead. +ConditionPathExists=/usr/local/bin/nvpn [Service] Type=simple diff --git a/scripts/run-post-install-tests.sh b/scripts/run-post-install-tests.sh index 47ac80cd..f6a1069f 100755 --- a/scripts/run-post-install-tests.sh +++ b/scripts/run-post-install-tests.sh @@ -103,6 +103,23 @@ for f in /usr/local/bin/archipelago \ fi done +# 1.1b — Every enabled archipelago/nostr unit must have an existing ExecStart +# payload. v1.7.104 shipped nostr-relay.service without its binary (3s +# crash-loop forever) and archipelago-diag.service without its script +# (203/EXEC) — catch that class of ISO defect on first boot, by generic rule. +for unit in /etc/systemd/system/archipelago*.service /etc/systemd/system/nostr*.service; do + [ -f "$unit" ] || continue + systemctl is-enabled "$(basename "$unit")" >/dev/null 2>&1 || continue + exec_bin=$(grep -m1 '^ExecStart=' "$unit" | sed 's/^ExecStart=[-+@!:]*//' | awk '{print $1}') + case "$exec_bin" in + /*) if [ -e "$exec_bin" ]; then + pass "Unit payload exists: $(basename "$unit") → $exec_bin" + else + fail "Unit payload missing" "$(basename "$unit") → $exec_bin" + fi ;; + esac +done + # 1.2 — Critical services active for svc in archipelago nginx; do if systemctl is-active "$svc" >/dev/null 2>&1; then