From b8d084368e20e02ba644783078d54532a0f8b435 Mon Sep 17 00:00:00 2001 From: Dorian Date: Wed, 22 Apr 2026 13:26:54 -0400 Subject: [PATCH] release(v1.7.39-alpha): hotfix web-ui perms after OTA (nginx 500) + startup self-heal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v1.7.38 shipped with an OTA bug: the tar-extracted staging dir inherited 700 perms and nginx (www-data) returned 500/403 on every request after the swap. .116 hit this on rollout; had to chmod by hand to recover. - update.rs: after extraction, explicitly chmod 755 dirs + 644 files on the new staging dir before the mv into place, so nginx can stat/serve them. - main.rs: self-heal on startup — if /opt/archipelago/web-ui is not world-readable, run `sudo chmod -R u=rwX,go=rX` to repair. This is what rescues nodes upgrading from v1.7.37/v1.7.38, since their extractor (running on the old binary) doesn't have the chmod fix yet — the new binary's first boot fixes the mess before nginx serves a single request. Everything v1.7.38 shipped is still in this release: - auth.rs auto-heals is_onboarding_complete() from setup_complete + password_hash so nodes don't bounce back to /onboarding/intro after browser clear / reboot / update - useOnboarding tri-state: backend-unreachable no longer defaults to intro - login sounds gated by isFirstInstallPhase() — silent after onboarding, typing sounds unaffected - FIPS app / Nostr Relay / Nostr VPN / Routstr / Penpot removed from catalog + frontend + Rust + docker + icons; 15 image versions deleted from tx1138, .168, gitea-local - AIUI baked into release tarball via demo/aiui/ - prebuild hook syncs app-catalog/catalog.json → public/catalog.json Co-Authored-By: Claude Opus 4.7 (1M context) --- core/Cargo.lock | 2 +- core/archipelago/Cargo.toml | 2 +- core/archipelago/src/main.rs | 30 +++++++++++++++++++ core/archipelago/src/update.rs | 15 ++++++++++ neode-ui/package.json | 2 +- .../src/views/settings/AccountInfoSection.vue | 10 +++++++ 6 files changed, 58 insertions(+), 3 deletions(-) diff --git a/core/Cargo.lock b/core/Cargo.lock index 9e0c5da4..37fa3cf3 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "archipelago" -version = "1.7.37-alpha" +version = "1.7.39-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index 88dfa420..4e3ead58 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "archipelago" -version = "1.7.37-alpha" +version = "1.7.39-alpha" edition = "2021" description = "Archipelago Bitcoin Node OS - Native backend" authors = ["Archipelago Team"] diff --git a/core/archipelago/src/main.rs b/core/archipelago/src/main.rs index 2c0a5546..48670dd6 100644 --- a/core/archipelago/src/main.rs +++ b/core/archipelago/src/main.rs @@ -86,6 +86,36 @@ async fn main() -> Result<()> { info!("Starting Archipelago Bitcoin Node OS"); + // Self-heal web-ui permissions. The OTA updater in <=v1.7.38 left + // /opt/archipelago/web-ui as drwx------ (700) after the atomic + // swap — nginx (www-data) then returned 500/403 on every request + // until someone shelled in and chmod'd it. Check on every boot + // and repair if needed so a node auto-recovers after the next + // service restart that follows a broken OTA. + tokio::spawn(async move { + use std::os::unix::fs::PermissionsExt; + let web_ui = std::path::Path::new("/opt/archipelago/web-ui"); + if let Ok(meta) = tokio::fs::metadata(web_ui).await { + let mode = meta.permissions().mode() & 0o777; + if mode & 0o005 != 0o005 { + tracing::warn!( + "web-ui perms {:o} not world-readable — self-healing", + mode + ); + let _ = tokio::process::Command::new("sudo") + .args([ + "-n", + "chmod", + "-R", + "u=rwX,go=rX", + "/opt/archipelago/web-ui", + ]) + .status() + .await; + } + } + }); + // Load configuration let config = Config::load().await?; info!("📁 Data directory: {}", config.data_dir.display()); diff --git a/core/archipelago/src/update.rs b/core/archipelago/src/update.rs index 979016c0..f297f451 100644 --- a/core/archipelago/src/update.rs +++ b/core/archipelago/src/update.rs @@ -914,6 +914,21 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> { ]) .await; + // Set world-readable perms so nginx (runs as www-data) + // can stat + serve the files. Without this, the tar + // extraction inherits the staging-dir's 700 mode and + // nginx returns 403/500 for every request after the + // swap — exactly what bit .116 on the v1.7.38 rollout. + let _ = host_sudo(&["chmod", "755", &staging_new]).await; + let _ = host_sudo(&[ + "find", &staging_new, "-type", "d", "-exec", "chmod", "755", "{}", "+", + ]) + .await; + let _ = host_sudo(&[ + "find", &staging_new, "-type", "f", "-exec", "chmod", "644", "{}", "+", + ]) + .await; + // Preserve paths that are installed outside the Vue build // (baked in by the ISO or sibling installers) and so // aren't in the new tarball. Without this copy, every OTA diff --git a/neode-ui/package.json b/neode-ui/package.json index c411e476..0660c435 100644 --- a/neode-ui/package.json +++ b/neode-ui/package.json @@ -1,7 +1,7 @@ { "name": "neode-ui", "private": true, - "version": "1.7.38-alpha", + "version": "1.7.39-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 be56ae57..82921281 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -180,6 +180,16 @@ init()
+ +
+
+ v1.7.39-alpha + Apr 22, 2026 +
+
+

Hotfix for v1.7.38 — on some nodes the update landed with the web UI directory set to private file permissions, so nginx returned a 500 / "Internal Server Error" on every page. This release fixes the updater to set world-readable permissions on the new frontend, and the node also now self-heals on boot if it ever finds the UI directory in that state again.

+
+