release(v1.7.40-alpha): fix tarball root perms at source so OTA can't 500 again
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Has been cancelled

v1.7.38 and v1.7.39 both shipped with `./` inside the frontend tarball marked
drwx------ (700). Tar extraction preserves archive perms, so every node that
pulled the OTA landed with /opt/archipelago/web-ui at 700, nginx (www-data)
returned 500 "permission denied" on every page, and the browser showed
"Internal Server Error nginx". .116 hit this on both v1.7.38 and v1.7.39
rollouts. The v1.7.39 runtime self-heal in main.rs was the wrong layer —
systemd's ReadOnlyPaths namespace made /opt/archipelago read-only from inside
the archipelago service, so chmod from there returned EROFS.

Root cause: create-release-manifest.sh used mktemp -d (700 default umask) for
staging, then tar preserved that 700 in the archive's root entry.

Fix the archive itself:
- chmod 755 staging dir + `find -type d -exec chmod 755` + `-type f chmod 644`
  before tar, so the on-disk entries are correct.
- tar --owner=0 --group=0 --mode='u=rwX,go=rX' to normalize archive perms
  belt-and-braces in case file-mode drift ever reappears.
- Post-tar verify: `tar tvzf | head -1` must show drwxr-xr-x at root, or
  the release script aborts before the manifest is even generated.

Binary unchanged semantically — the main.rs self-heal stays in as a last-
resort belt (can't hurt on nodes whose FS isn't namespace-isolated), and the
update.rs in-extractor chmod stays in so v1.7.40-onwards extractors are
double-safe. The authoritative fix is the archive.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-04-22 13:54:44 -04:00
parent 3218f71703
commit 50744952b7
8 changed files with 56 additions and 15 deletions

2
core/Cargo.lock generated
View File

@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "archipelago" name = "archipelago"
version = "1.7.39-alpha" version = "1.7.40-alpha"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"archipelago-container", "archipelago-container",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "archipelago" name = "archipelago"
version = "1.7.39-alpha" version = "1.7.40-alpha"
edition = "2021" edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend" description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"] authors = ["Archipelago Team"]

View File

@ -1,7 +1,7 @@
{ {
"name": "neode-ui", "name": "neode-ui",
"private": true, "private": true,
"version": "1.7.39-alpha", "version": "1.7.40-alpha",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "./start-dev.sh", "start": "./start-dev.sh",

View File

@ -180,6 +180,16 @@ init()
</button> </button>
</div> </div>
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1"> <div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
<!-- v1.7.40-alpha -->
<div>
<div class="flex items-center gap-2 mb-3">
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.40-alpha</span>
<span class="text-xs text-white/40">Apr 22, 2026</span>
</div>
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
<p>Proper fix for the 500 / Internal Server Error after update. The v1.7.38 and v1.7.39 frontend archives had the wrong permissions baked into the archive itself the tarball's root directory entry was private, so every node that extracted it ended up with a web UI directory nginx couldn't read. v1.7.40 packages the archive with correct world-readable permissions from the start, so no node ever sees the 500 again.</p>
</div>
</div>
<!-- v1.7.39-alpha --> <!-- v1.7.39-alpha -->
<div> <div>
<div class="flex items-center gap-2 mb-3"> <div class="flex items-center gap-2 mb-3">

View File

@ -1,8 +1,8 @@
{ {
"version": "1.7.39-alpha", "version": "1.7.40-alpha",
"release_date": "2026-04-22", "release_date": "2026-04-22",
"changelog": [ "changelog": [
"Hotfix: on some nodes the v1.7.38 update landed the web UI directory with private permissions, so nginx returned 500 / Internal Server Error on every page. v1.7.39 fixes the updater to leave the new frontend world-readable, and the node now also self-heals on boot if it ever finds the UI in that state again.", "Proper fix for the 500 / Internal Server Error after update. The v1.7.38 and v1.7.39 frontend archives had the wrong permissions baked into the archive itself — the tarball's root directory entry was private, so every node that extracted it ended up with a web UI directory nginx couldn't read. v1.7.40 packages the archive with correct world-readable permissions from the start, verified before the release is even cut.",
"Signing in is quiet after the first boot. The intro music, welcome voice, and transition sounds only play during initial onboarding — every login after that is silent. Typing sounds in the search bar and dashboard are unaffected.", "Signing in is quiet after the first boot. The intro music, welcome voice, and transition sounds only play during initial onboarding — every login after that is silent. Typing sounds in the search bar and dashboard are unaffected.",
"Nodes that completed setup no longer get bounced back through the onboarding wizard after clearing browser cache, updating, or rebooting. The node self-heals so already-onboarded nodes always go straight to the login screen.", "Nodes that completed setup no longer get bounced back through the onboarding wizard after clearing browser cache, updating, or rebooting. The node self-heals so already-onboarded nodes always go straight to the login screen.",
"Trimmed the App Store — FIPS, Nostr Relay, Nostr VPN, Routstr, and Penpot are no longer listed and their container images have been removed from all registries. Your node's built-in FIPS transport is untouched." "Trimmed the App Store — FIPS, Nostr Relay, Nostr VPN, Routstr, and Penpot are no longer listed and their container images have been removed from all registries. Your node's built-in FIPS transport is untouched."
@ -11,18 +11,18 @@
{ {
"name": "archipelago", "name": "archipelago",
"current_version": "1.7.37-alpha", "current_version": "1.7.37-alpha",
"new_version": "1.7.39-alpha", "new_version": "1.7.40-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.39-alpha/archipelago", "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.40-alpha/archipelago",
"sha256": "533d5ff9421aba2a1b4acc981746da1692f6a2abb5101da5500f5f920c26fd3a", "sha256": "5c8c0c6e4700f4da3e1cb58167ddea6d93f46d5c7d7f0352f7367b998c672708",
"size_bytes": 41107808 "size_bytes": 41107136
}, },
{ {
"name": "archipelago-frontend-1.7.39-alpha.tar.gz", "name": "archipelago-frontend-1.7.40-alpha.tar.gz",
"current_version": "1.7.37-alpha", "current_version": "1.7.37-alpha",
"new_version": "1.7.39-alpha", "new_version": "1.7.40-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.39-alpha/archipelago-frontend-1.7.39-alpha.tar.gz", "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.40-alpha/archipelago-frontend-1.7.40-alpha.tar.gz",
"sha256": "e5dc5b51a1e72836688b7883b8032245aa252a33500d6eba37839569b5c5b17a", "sha256": "0bb58abd5276c83d42a92b0f09697162a300f0222962ad52c8175fb4c904e3e8",
"size_bytes": 162085565 "size_bytes": 162084678
} }
] ]
} }

Binary file not shown.

View File

@ -87,8 +87,39 @@ if [ -z "$FRONTEND_ARCHIVE" ]; then
echo " Including AIUI from demo/aiui/" echo " Including AIUI from demo/aiui/"
cp -r "$PROJECT_ROOT/demo/aiui" "$STAGING_DIR/aiui" cp -r "$PROJECT_ROOT/demo/aiui" "$STAGING_DIR/aiui"
fi fi
# Force world-readable perms on every entry BEFORE tar, so the
# archive's internal mode bits are 755/644 regardless of what
# the staging dir's umask gave us. Without this, mktemp -d
# creates the staging dir at 700, that 700 gets baked into the
# tarball's root `./` entry, and every node that extracts the
# archive ends up with /opt/archipelago/web-ui at 700 — which
# causes nginx (www-data) to return 500 "permission denied" on
# every page. Bit us on the v1.7.38 + v1.7.39 rollouts.
chmod 755 "$STAGING_DIR"
find "$STAGING_DIR" -type d -exec chmod 755 {} +
find "$STAGING_DIR" -type f -exec chmod 644 {} +
echo "Creating frontend archive $FRONTEND_ARCHIVE..." echo "Creating frontend archive $FRONTEND_ARCHIVE..."
tar -czf "$FRONTEND_ARCHIVE" -C "$STAGING_DIR" . # --mode is a belt-and-braces in case a file's on-disk perms
# drift again; forces 755 dir / 644 file in the archive too.
tar --owner=0 --group=0 \
--mode='u=rwX,go=rX' \
-czf "$FRONTEND_ARCHIVE" \
-C "$STAGING_DIR" .
# Verify the archive root entry is world-readable before we
# declare success — catches regressions in tar-flag handling
# (BSD tar, busybox tar) that might silently drop --mode.
root_mode=$(tar tvzf "$FRONTEND_ARCHIVE" | head -1 | awk '{print $1}')
case "$root_mode" in
drwxr-xr-x|drwxr-x*x*)
echo " Tarball root perms OK: $root_mode"
;;
*)
echo " ERROR: tarball root perms are $root_mode (want drwxr-xr-x) — aborting release"
rm -f "$FRONTEND_ARCHIVE"
rm -rf "$STAGING_DIR"
exit 1
;;
esac
rm -rf "$STAGING_DIR" rm -rf "$STAGING_DIR"
fi fi
fi fi