release(v1.7.4-alpha): fix Install Update tar extraction + progress overshoot

apply_update was extracting the frontend tarball with
`tar -xzf -C /opt/archipelago`, but the tar contents are the *inside*
of web-ui/ (root entries are ./test-aiui.html, ./assets/, etc.). So
the files landed directly in /opt/archipelago instead of under web-ui/,
and tar bailed on nginx-owned paths mid-extraction. First end-to-end
OTA test (.198) found it: "tar: ./assets/SystemUpdate-…js: Cannot
open: No such file or directory".

Now extracts into web-ui.new, chowns, then atomically swaps: move
existing web-ui → web-ui.bak, then web-ui.new → web-ui. Same pattern
as the manual sideload that's been working.

Frontend: SystemUpdate.vue fake download progress was capped at "<90"
with a Math.random()*15 increment — the last tick could push to
~104.99%. Capped at 95% with a smaller step so it stops at 95 and the
real RPC completion jumps it to 100.

Artefacts:
  archipelago                                      a14ad7e4…2a2be3  40361984
  archipelago-frontend-1.7.4-alpha.tar.gz          4fb79664…0172e9  76984615

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-04-20 12:02:14 -04:00
parent 3a479e5b09
commit 170f8ae787
7 changed files with 64 additions and 46 deletions

2
core/Cargo.lock generated
View File

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

View File

@ -1,6 +1,6 @@
[package] [package]
name = "archipelago" name = "archipelago"
version = "1.7.3-alpha" version = "1.7.4-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

@ -305,42 +305,60 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
info!(name = %name, "Backend binary applied"); info!(name = %name, "Backend binary applied");
} }
_ if name.contains("frontend") && name.ends_with(".tar.gz") => { _ if name.contains("frontend") && name.ends_with(".tar.gz") => {
let web_ui_dir = Path::new("/opt/archipelago/web-ui"); // The tarball contents are the *inside* of web-ui/ — root
// Back up current frontend. /opt/archipelago is root-owned; // entries are `./test-aiui.html`, `./assets/`, etc. Extract
// the backup goes under our data_dir where we can write. // into a sibling staging dir, then swap atomically so
let frontend_backup = backup_dir.join("web-ui-backup.tar.gz"); // nginx never sees a half-written tree.
if web_ui_dir.exists() { let new_dir = "/opt/archipelago/web-ui.new";
let status = tokio::process::Command::new("sudo") let backup_path = "/opt/archipelago/web-ui.bak";
.args([ // Wipe any previous attempt's staging / backup dirs.
"tar", let _ = tokio::process::Command::new("sudo")
"-czf", .args(["rm", "-rf", new_dir, backup_path])
&frontend_backup.to_string_lossy(), .status()
"-C", .await;
"/opt/archipelago", let mk = tokio::process::Command::new("sudo")
"web-ui", .args(["mkdir", "-p", new_dir])
]) .status()
.status() .await
.await .context("Failed to create frontend staging dir")?;
.context("Failed to backup frontend")?; if !mk.success() {
if status.success() { anyhow::bail!("mkdir {} failed", new_dir);
info!("Frontend backed up");
}
} }
// 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") 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() .status()
.await .await
.with_context(|| format!("Failed to extract {}", name))?; .with_context(|| format!("Failed to extract {}", name))?;
if !status.success() { if !status.success() {
anyhow::bail!("tar extraction failed for {}", name); anyhow::bail!("tar extraction failed for {}", name);
} }
// nginx serves this tree; keep ownership consistent with // Ownership: match what first-boot + the ISO expect.
// what first-boot + the ISO layout expect.
let _ = tokio::process::Command::new("sudo") let _ = tokio::process::Command::new("sudo")
.args(["chown", "-R", "archipelago:archipelago", "/opt/archipelago/web-ui"]) .args(["chown", "-R", "archipelago:archipelago", new_dir])
.status() .status()
.await; .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"); info!(name = %name, "Frontend archive extracted to /opt/archipelago/web-ui");
} }
_ => { _ => {

View File

@ -345,10 +345,12 @@ async function downloadUpdate() {
downloadPercent.value = 0 downloadPercent.value = 0
statusMessage.value = '' 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(() => { const progressInterval = setInterval(() => {
if (downloadPercent.value < 90) { if (downloadPercent.value < 95) {
downloadPercent.value += Math.random() * 15 downloadPercent.value = Math.min(95, downloadPercent.value + Math.random() * 3)
} }
}, 500) }, 500)

View File

@ -1,28 +1,26 @@
{ {
"version": "1.7.3-alpha", "version": "1.7.4-alpha",
"release_date": "2026-04-20", "release_date": "2026-04-20",
"changelog": [ "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.", "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.",
"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.", "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."
"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."
], ],
"components": [ "components": [
{ {
"name": "archipelago", "name": "archipelago",
"current_version": "1.7.2-alpha", "current_version": "1.7.3-alpha",
"new_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.3-alpha/archipelago", "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.4-alpha/archipelago",
"sha256": "99184b95f8d3041b7714ec9e58a194e466fa470c117992a4715c40980022dc1b", "sha256": "a14ad7e4dbcb8f74377d44a4bd5e600b285481df3b30c08f8bea2cd17e2a2be3",
"size_bytes": 40350664 "size_bytes": 40361984
}, },
{ {
"name": "archipelago-frontend-1.7.3-alpha.tar.gz", "name": "archipelago-frontend-1.7.4-alpha.tar.gz",
"current_version": "1.7.2-alpha", "current_version": "1.7.3-alpha",
"new_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.3-alpha/archipelago-frontend-1.7.3-alpha.tar.gz", "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": "7b933cf458754faba18224d12b4793d7e152fc8296c3ee0441240fdc2374a8bc", "sha256": "4fb796643cc9dc8469078ca3392f7cc5541071f6849979922b3259e5f20172e9",
"size_bytes": 76987031 "size_bytes": 76984615
} }
] ]
} }

BIN
releases/v1.7.4-alpha/archipelago Executable file

Binary file not shown.