release(v1.7.6-alpha): robust apply_update + manifest-override env var
apply_update frontend swap Transient EROFS on .198 (filesystem hiccup — root FS mounts with errors=remount-ro so a fleeting glitch can bounce /opt to RO for a moment) caught the pre-cleanup `rm -rf web-ui.new web-ui.bak` mid- stride and aborted the apply. Rewrote the swap to use a timestamped staging dir (web-ui.new.<ms>) and a timestamped old-copy path so nothing needs to be rm'd before the extract. After the new tree is mv'd into place, the previous rollback copy is rotated aside with a .<ms> suffix (best-effort) and this apply's old copy becomes the new web-ui.bak. If the final mv fails, the staged old is restored so nginx keeps serving. handle_update_check manifest override handle_update_check takes the git path whenever ~/archy/.git exists. On the dev box (.116) that meant the Pull & Rebuild button was always the only option even though the manifest-path OTA was already wired via ARCHIPELAGO_UPDATE_URL. Now: if that env var is set, we skip the git detection entirely and use the manifest path. The regular fleet (no env var, no repo) hits the manifest branch naturally; beta dev nodes (repo + no env var) still get Pull & Rebuild; dev nodes with the env var explicitly set can finally test the manifest OTA end-to-end. Artefacts: archipelago 356e78cc…91a6dd 40372288 archipelago-frontend-1.7.6-alpha.tar.gz 4fb79664…0172e9 76984615 (reused) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
12f48a21c1
commit
9c6251c784
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "archipelago"
|
||||
version = "1.7.5-alpha"
|
||||
version = "1.7.6-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"archipelago-container",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "archipelago"
|
||||
version = "1.7.5-alpha"
|
||||
version = "1.7.6-alpha"
|
||||
edition = "2021"
|
||||
description = "Archipelago Bitcoin Node OS - Native backend"
|
||||
authors = ["Archipelago Team"]
|
||||
|
||||
@ -6,12 +6,18 @@ impl RpcHandler {
|
||||
/// Check for available system updates.
|
||||
/// Tries git-based check first (if repo exists), falls back to manifest-based.
|
||||
pub(super) async fn handle_update_check(&self) -> Result<serde_json::Value> {
|
||||
// Try git-based check first (preferred for beta nodes)
|
||||
// Manifest override: when ARCHIPELAGO_UPDATE_URL is explicitly set,
|
||||
// the operator wants OTA via manifest — typically a dev box where
|
||||
// ~/archy/.git exists but isn't the intended update surface.
|
||||
// Without this short-circuit the dev box always advertises "Pull
|
||||
// & Rebuild" and can never exercise the manifest OTA path.
|
||||
let manifest_override = std::env::var("ARCHIPELAGO_UPDATE_URL").is_ok();
|
||||
|
||||
let repo_dir = std::path::PathBuf::from(
|
||||
std::env::var("HOME").unwrap_or_else(|_| "/home/archipelago".to_string()),
|
||||
)
|
||||
.join("archy");
|
||||
if repo_dir.join(".git").exists() {
|
||||
if !manifest_override && repo_dir.join(".git").exists() {
|
||||
if let Ok(git_status) = self.git_check_update(&repo_dir).await {
|
||||
return Ok(git_status);
|
||||
}
|
||||
|
||||
@ -305,45 +305,47 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
|
||||
info!(name = %name, "Backend binary applied");
|
||||
}
|
||||
_ if name.contains("frontend") && name.ends_with(".tar.gz") => {
|
||||
// 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";
|
||||
// Tarball contents are the *inside* of web-ui/ (root entries
|
||||
// `./test-aiui.html`, `./assets/`, ...). Extract into a
|
||||
// uniquely-named staging dir, then mv into place. No `rm
|
||||
// -rf` pre-cleanup — that's what hit transient EROFS on
|
||||
// .198 and aborted the apply mid-flight.
|
||||
let ts = chrono::Utc::now().timestamp_millis();
|
||||
let staging_new = format!("/opt/archipelago/web-ui.new.{}", ts);
|
||||
let staging_old = format!("/opt/archipelago/web-ui.old.{}", ts);
|
||||
let web_ui = "/opt/archipelago/web-ui";
|
||||
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])
|
||||
.args(["mkdir", "-p", &staging_new])
|
||||
.status()
|
||||
.await
|
||||
.context("Failed to create frontend staging dir")?;
|
||||
if !mk.success() {
|
||||
anyhow::bail!("mkdir {} failed", new_dir);
|
||||
anyhow::bail!("mkdir {} failed", staging_new);
|
||||
}
|
||||
// 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", new_dir])
|
||||
let extract = tokio::process::Command::new("sudo")
|
||||
.args(["tar", "-xzf", &src.to_string_lossy(), "-C", &staging_new])
|
||||
.status()
|
||||
.await
|
||||
.with_context(|| format!("Failed to extract {}", name))?;
|
||||
if !status.success() {
|
||||
if !extract.success() {
|
||||
// Best-effort cleanup of the partial extraction.
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["rm", "-rf", &staging_new])
|
||||
.status()
|
||||
.await;
|
||||
anyhow::bail!("tar extraction failed for {}", name);
|
||||
}
|
||||
// Ownership: match what first-boot + the ISO expect.
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["chown", "-R", "archipelago:archipelago", new_dir])
|
||||
.args(["chown", "-R", "archipelago:archipelago", &staging_new])
|
||||
.status()
|
||||
.await;
|
||||
// Atomic-ish swap: move old aside, new into place.
|
||||
let web_ui = "/opt/archipelago/web-ui";
|
||||
|
||||
// Swap: mv current web-ui aside, then mv new into place.
|
||||
if Path::new(web_ui).exists() {
|
||||
let mv_old = tokio::process::Command::new("sudo")
|
||||
.args(["mv", web_ui, backup_path])
|
||||
.args(["mv", web_ui, &staging_old])
|
||||
.status()
|
||||
.await
|
||||
.context("Failed to rotate old web-ui")?;
|
||||
@ -352,13 +354,41 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
|
||||
}
|
||||
}
|
||||
let mv_new = tokio::process::Command::new("sudo")
|
||||
.args(["mv", new_dir, web_ui])
|
||||
.args(["mv", &staging_new, web_ui])
|
||||
.status()
|
||||
.await
|
||||
.context("Failed to swap new web-ui into place")?;
|
||||
if !mv_new.success() {
|
||||
// Roll back the rename so nginx keeps serving.
|
||||
if Path::new(&staging_old).exists() {
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["mv", &staging_old, web_ui])
|
||||
.status()
|
||||
.await;
|
||||
}
|
||||
anyhow::bail!("failed to move new web-ui into place");
|
||||
}
|
||||
|
||||
// Rotate previous rollback aside (best-effort) and install
|
||||
// this apply's old copy as the new rollback.
|
||||
if Path::new(&staging_old).exists() {
|
||||
if Path::new(backup_path).exists() {
|
||||
// Tag the previous backup with its own ts so it
|
||||
// doesn't collide; best-effort cleanup.
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args([
|
||||
"mv",
|
||||
backup_path,
|
||||
&format!("{}.{}", backup_path, ts),
|
||||
])
|
||||
.status()
|
||||
.await;
|
||||
}
|
||||
let _ = tokio::process::Command::new("sudo")
|
||||
.args(["mv", &staging_old, backup_path])
|
||||
.status()
|
||||
.await;
|
||||
}
|
||||
info!(name = %name, "Frontend archive extracted to /opt/archipelago/web-ui");
|
||||
}
|
||||
_ => {
|
||||
|
||||
@ -1,23 +1,24 @@
|
||||
{
|
||||
"version": "1.7.5-alpha",
|
||||
"version": "1.7.6-alpha",
|
||||
"release_date": "2026-04-20",
|
||||
"changelog": [
|
||||
"Over-the-air update test — no feature changes, just a fresh version number so your node can walk through the whole update flow end-to-end: check, download, install, auto-restart. Safe to apply; nothing to do afterwards."
|
||||
"Install Update is now more robust. Each install gets its own uniquely-named staging folder and then moves files into place — the previous version had a small cleanup step that could hit a transient filesystem hiccup and bail out halfway. You'll also still see a rollback folder after a successful install.",
|
||||
"Dev-box OTA: nodes that build archipelago from source can now opt into the standard Download → Install flow instead of Pull & Rebuild, by setting ARCHIPELAGO_UPDATE_URL in the service environment. Useful when the dev machine has a checked-out repo but you want to test the regular update path."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.4-alpha",
|
||||
"new_version": "1.7.5-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.5-alpha/archipelago",
|
||||
"sha256": "7422a695c1c51e668657a817a96b2cba3a84607f505108ed29a35a17e0a1a2a6",
|
||||
"size_bytes": 40362432
|
||||
"current_version": "1.7.5-alpha",
|
||||
"new_version": "1.7.6-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.6-alpha/archipelago",
|
||||
"sha256": "356e78cc40234a07a38f07c3cb8776f5e4856158256bbd6572f9d6a0f891a6dd",
|
||||
"size_bytes": 40372288
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.5-alpha.tar.gz",
|
||||
"current_version": "1.7.4-alpha",
|
||||
"new_version": "1.7.5-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.5-alpha/archipelago-frontend-1.7.5-alpha.tar.gz",
|
||||
"name": "archipelago-frontend-1.7.6-alpha.tar.gz",
|
||||
"current_version": "1.7.5-alpha",
|
||||
"new_version": "1.7.6-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.6-alpha/archipelago-frontend-1.7.6-alpha.tar.gz",
|
||||
"sha256": "4fb796643cc9dc8469078ca3392f7cc5541071f6849979922b3259e5f20172e9",
|
||||
"size_bytes": 76984615
|
||||
}
|
||||
|
||||
BIN
releases/v1.7.6-alpha/archipelago
Executable file
BIN
releases/v1.7.6-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.6-alpha/archipelago-frontend-1.7.6-alpha.tar.gz
Normal file
BIN
releases/v1.7.6-alpha/archipelago-frontend-1.7.6-alpha.tar.gz
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user