From 85417de9524e611e6441af25b8d2fb1dd23ec50f Mon Sep 17 00:00:00 2001 From: Dorian Date: Wed, 22 Apr 2026 13:54:44 -0400 Subject: [PATCH] release(v1.7.40-alpha): fix tarball root perms at source so OTA can't 500 again MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- core/Cargo.lock | 2 +- core/archipelago/Cargo.toml | 2 +- neode-ui/package.json | 2 +- .../src/views/settings/AccountInfoSection.vue | 10 ++++++ scripts/create-release-manifest.sh | 33 ++++++++++++++++++- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 37fa3cf3..1ef9fcda 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "archipelago" -version = "1.7.39-alpha" +version = "1.7.40-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index 4e3ead58..85dea42d 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "archipelago" -version = "1.7.39-alpha" +version = "1.7.40-alpha" edition = "2021" description = "Archipelago Bitcoin Node OS - Native backend" authors = ["Archipelago Team"] diff --git a/neode-ui/package.json b/neode-ui/package.json index 0660c435..483ec639 100644 --- a/neode-ui/package.json +++ b/neode-ui/package.json @@ -1,7 +1,7 @@ { "name": "neode-ui", "private": true, - "version": "1.7.39-alpha", + "version": "1.7.40-alpha", "type": "module", "scripts": { "start": "./start-dev.sh", diff --git a/neode-ui/src/views/settings/AccountInfoSection.vue b/neode-ui/src/views/settings/AccountInfoSection.vue index 82921281..90993946 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -180,6 +180,16 @@ init()
+ +
+
+ v1.7.40-alpha + Apr 22, 2026 +
+
+

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.

+
+
diff --git a/scripts/create-release-manifest.sh b/scripts/create-release-manifest.sh index 5281261d..6327d3e7 100755 --- a/scripts/create-release-manifest.sh +++ b/scripts/create-release-manifest.sh @@ -87,8 +87,39 @@ if [ -z "$FRONTEND_ARCHIVE" ]; then echo " Including AIUI from demo/aiui/" cp -r "$PROJECT_ROOT/demo/aiui" "$STAGING_DIR/aiui" 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..." - 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" fi fi