diff --git a/core/Cargo.lock b/core/Cargo.lock index 84079f9e..691dfbb8 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "archipelago" -version = "1.7.3-alpha" +version = "1.7.4-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index 962e9885..a25bf93f 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "archipelago" -version = "1.7.3-alpha" +version = "1.7.4-alpha" edition = "2021" description = "Archipelago Bitcoin Node OS - Native backend" authors = ["Archipelago Team"] diff --git a/core/archipelago/src/update.rs b/core/archipelago/src/update.rs index 00150f07..c3e3cc2d 100644 --- a/core/archipelago/src/update.rs +++ b/core/archipelago/src/update.rs @@ -305,42 +305,60 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> { info!(name = %name, "Backend binary applied"); } _ if name.contains("frontend") && name.ends_with(".tar.gz") => { - let web_ui_dir = Path::new("/opt/archipelago/web-ui"); - // Back up current frontend. /opt/archipelago is root-owned; - // the backup goes under our data_dir where we can write. - let frontend_backup = backup_dir.join("web-ui-backup.tar.gz"); - if web_ui_dir.exists() { - let status = tokio::process::Command::new("sudo") - .args([ - "tar", - "-czf", - &frontend_backup.to_string_lossy(), - "-C", - "/opt/archipelago", - "web-ui", - ]) - .status() - .await - .context("Failed to backup frontend")?; - if status.success() { - info!("Frontend backed up"); - } + // The tarball contents are the *inside* of web-ui/ — root + // entries are `./test-aiui.html`, `./assets/`, etc. Extract + // into a sibling staging dir, then swap atomically so + // nginx never sees a half-written tree. + let new_dir = "/opt/archipelago/web-ui.new"; + let backup_path = "/opt/archipelago/web-ui.bak"; + // Wipe any previous attempt's staging / backup dirs. + let _ = tokio::process::Command::new("sudo") + .args(["rm", "-rf", new_dir, backup_path]) + .status() + .await; + let mk = tokio::process::Command::new("sudo") + .args(["mkdir", "-p", new_dir]) + .status() + .await + .context("Failed to create frontend staging dir")?; + if !mk.success() { + anyhow::bail!("mkdir {} failed", new_dir); } - // Extract new frontend into /opt/archipelago (root-owned dir). + // Extract INTO the staging dir — tar's ./ entries land at + // the right place (web-ui.new/assets/... etc.). let status = tokio::process::Command::new("sudo") - .args(["tar", "-xzf", &src.to_string_lossy(), "-C", "/opt/archipelago"]) + .args(["tar", "-xzf", &src.to_string_lossy(), "-C", new_dir]) .status() .await .with_context(|| format!("Failed to extract {}", name))?; if !status.success() { anyhow::bail!("tar extraction failed for {}", name); } - // nginx serves this tree; keep ownership consistent with - // what first-boot + the ISO layout expect. + // Ownership: match what first-boot + the ISO expect. let _ = tokio::process::Command::new("sudo") - .args(["chown", "-R", "archipelago:archipelago", "/opt/archipelago/web-ui"]) + .args(["chown", "-R", "archipelago:archipelago", new_dir]) .status() .await; + // Atomic-ish swap: move old aside, new into place. + let web_ui = "/opt/archipelago/web-ui"; + if Path::new(web_ui).exists() { + let mv_old = tokio::process::Command::new("sudo") + .args(["mv", web_ui, backup_path]) + .status() + .await + .context("Failed to rotate old web-ui")?; + if !mv_old.success() { + anyhow::bail!("failed to move old web-ui aside"); + } + } + let mv_new = tokio::process::Command::new("sudo") + .args(["mv", new_dir, web_ui]) + .status() + .await + .context("Failed to swap new web-ui into place")?; + if !mv_new.success() { + anyhow::bail!("failed to move new web-ui into place"); + } info!(name = %name, "Frontend archive extracted to /opt/archipelago/web-ui"); } _ => { diff --git a/neode-ui/src/views/SystemUpdate.vue b/neode-ui/src/views/SystemUpdate.vue index 5e98feef..f91bdd75 100644 --- a/neode-ui/src/views/SystemUpdate.vue +++ b/neode-ui/src/views/SystemUpdate.vue @@ -345,10 +345,12 @@ async function downloadUpdate() { downloadPercent.value = 0 statusMessage.value = '' - // Simulate incremental progress while waiting for the RPC + // Simulate incremental progress while waiting for the RPC. Capped at + // 95% so the bar never shows >100% before the real completion jumps it + // to 100 — previously the random increment could overshoot. const progressInterval = setInterval(() => { - if (downloadPercent.value < 90) { - downloadPercent.value += Math.random() * 15 + if (downloadPercent.value < 95) { + downloadPercent.value = Math.min(95, downloadPercent.value + Math.random() * 3) } }, 500) diff --git a/releases/manifest.json b/releases/manifest.json index 5f1097c0..c2819495 100644 --- a/releases/manifest.json +++ b/releases/manifest.json @@ -1,28 +1,26 @@ { - "version": "1.7.3-alpha", + "version": "1.7.4-alpha", "release_date": "2026-04-20", "changelog": [ - "The version number in the sidebar now always matches the actual running version — no more lying to you about being on an older release after an update.", - "FIPS Mesh card on the server page: cleaner layout on desktop (no more awkward gaps), and a one-click Reconnect button when the public anchor is unreachable — it restarts the FIPS daemon so it can re-bootstrap from the anchor.", - "Profile pictures now show correctly in the identity list and editor. Before, uploaded images silently failed to render because the URL was only reachable over Tor; the UI now rewrites them to a local path while keeping the external URL for other Nostr clients.", - "Identity rows now show your Display Name first (from your Nostr profile) with the internal identity name beside it in parentheses, so you see the name other people will see — not just the one you picked when creating it." + "Install Update actually installs now. Before, the final step extracted the new UI into the wrong folder and bailed with 'Failed to apply update' — your node ended up backing up cleanly but never swapping in the new files. Fixed.", + "Download progress no longer overshoots 100%. You'll see the bar climb smoothly to 95% and then jump to 100% when the download actually finishes." ], "components": [ { "name": "archipelago", - "current_version": "1.7.2-alpha", - "new_version": "1.7.3-alpha", - "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.3-alpha/archipelago", - "sha256": "99184b95f8d3041b7714ec9e58a194e466fa470c117992a4715c40980022dc1b", - "size_bytes": 40350664 + "current_version": "1.7.3-alpha", + "new_version": "1.7.4-alpha", + "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.4-alpha/archipelago", + "sha256": "a14ad7e4dbcb8f74377d44a4bd5e600b285481df3b30c08f8bea2cd17e2a2be3", + "size_bytes": 40361984 }, { - "name": "archipelago-frontend-1.7.3-alpha.tar.gz", - "current_version": "1.7.2-alpha", - "new_version": "1.7.3-alpha", - "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.3-alpha/archipelago-frontend-1.7.3-alpha.tar.gz", - "sha256": "7b933cf458754faba18224d12b4793d7e152fc8296c3ee0441240fdc2374a8bc", - "size_bytes": 76987031 + "name": "archipelago-frontend-1.7.4-alpha.tar.gz", + "current_version": "1.7.3-alpha", + "new_version": "1.7.4-alpha", + "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.4-alpha/archipelago-frontend-1.7.4-alpha.tar.gz", + "sha256": "4fb796643cc9dc8469078ca3392f7cc5541071f6849979922b3259e5f20172e9", + "size_bytes": 76984615 } ] } diff --git a/releases/v1.7.4-alpha/archipelago b/releases/v1.7.4-alpha/archipelago new file mode 100755 index 00000000..4d234ecd Binary files /dev/null and b/releases/v1.7.4-alpha/archipelago differ diff --git a/releases/v1.7.4-alpha/archipelago-frontend-1.7.4-alpha.tar.gz b/releases/v1.7.4-alpha/archipelago-frontend-1.7.4-alpha.tar.gz new file mode 100644 index 00000000..2465942a Binary files /dev/null and b/releases/v1.7.4-alpha/archipelago-frontend-1.7.4-alpha.tar.gz differ