release(v1.7.2-alpha): fix Install Update + identity avatar backfill + label
Some checks failed
Build Archipelago ISO (dev) / build-iso (push) Has been cancelled

Three user-visible fixes shipped together.

1. update.apply permission-denied
   apply_update() was doing fs::copy into /usr/local/bin/archipelago and
   tar xzf into /opt/archipelago as the archipelago user — both root-owned.
   The backup step succeeded (it wrote to data_dir) but the swap failed
   with a silent permission denied, wrapped as "Failed to apply archipelago".
   Now uses `sudo install -m 0755` for the binary and `sudo tar -xzf` for
   the frontend, plus a post-apply `sudo systemctl --no-block restart
   archipelago` scheduled 2s after the RPC reply so the UI sees success.

2. Apply → Install label
   en/es locale strings: applyUpdate / applyTitle / applyNow changed from
   "Apply" to "Install". Matches the user's mental model and distinguishes
   the user-facing verb from the internal apply_update() function.

3. Identity avatar backfill
   Identities created before df83163f had profile=None on disk and so
   rendered as initials. load_record() now synthesizes an IdentityProfile
   with a default picture (identicon for regular identities, the hex node
   SVG for derivation_index=0) when profile is missing. The synthetic
   profile lives only in the returned record; the file stays untouched so
   a later explicit Save persists whatever the user actually chose.

Artefacts:
  archipelago                                        70e5444e…67c589  40381960
  archipelago-frontend-1.7.2-alpha.tar.gz            806b027b…358a824 76983699

Changelog rewritten layman-style per saved feedback.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-20 11:25:10 -04:00
parent ef58888aa8
commit 0d5128a121
9 changed files with 89 additions and 36 deletions

2
core/Cargo.lock generated
View File

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

View File

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

@@ -739,6 +739,20 @@ impl IdentityManager {
.and_then(|pk| pk.to_bech32().ok()) .and_then(|pk| pk.to_bech32().ok())
}); });
// Backfill a default avatar for identities created before the
// default-avatar feature shipped. The synthetic profile lives only
// in the returned record — we don't rewrite the file on disk,
// since a later explicit save will persist whatever the user
// actually chose. Master identities (seed index 0) get the hex
// node SVG; all other pre-existing identities get the identicon.
let profile = file.profile.or_else(|| {
let is_master = file.derivation_index == Some(0);
Some(IdentityProfile {
picture: Some(crate::avatar::default_picture(&file.pubkey_hex, is_master)),
..Default::default()
})
});
Ok(IdentityRecord { Ok(IdentityRecord {
id: file.id, id: file.id,
name: file.name, name: file.name,
@@ -749,7 +763,7 @@ impl IdentityManager {
created_at: file.created_at, created_at: file.created_at,
nostr_pubkey: file.nostr_pubkey_hex, nostr_pubkey: file.nostr_pubkey_hex,
nostr_npub, nostr_npub,
profile: file.profile, profile,
}) })
} }

View File

@@ -277,25 +277,42 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
match name.as_str() { match name.as_str() {
"archipelago" => { "archipelago" => {
let dest = Path::new("/usr/local/bin/archipelago"); // /usr/local/bin is root-owned; archipelago user can't
fs::copy(&src, dest) // fs::copy into it directly. Use sudo install which handles
// the copy, mode, and ownership atomically.
let status = tokio::process::Command::new("sudo")
.args([
"install",
"-m",
"0755",
"-o",
"root",
"-g",
"root",
&src.to_string_lossy(),
"/usr/local/bin/archipelago",
])
.status()
.await .await
.with_context(|| format!("Failed to apply {}", name))?; .with_context(|| format!("Failed to spawn install for {}", name))?;
#[cfg(unix)] if !status.success() {
{ anyhow::bail!(
use std::os::unix::fs::PermissionsExt; "sudo install failed for {} (exit {:?})",
std::fs::set_permissions(dest, std::fs::Permissions::from_mode(0o755)) name,
.context("Failed to set binary permissions")?; status.code()
);
} }
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"); let web_ui_dir = Path::new("/opt/archipelago/web-ui");
// Back up current frontend // 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"); let frontend_backup = backup_dir.join("web-ui-backup.tar.gz");
if web_ui_dir.exists() { if web_ui_dir.exists() {
let status = tokio::process::Command::new("tar") let status = tokio::process::Command::new("sudo")
.args([ .args([
"tar",
"-czf", "-czf",
&frontend_backup.to_string_lossy(), &frontend_backup.to_string_lossy(),
"-C", "-C",
@@ -309,15 +326,21 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
info!("Frontend backed up"); info!("Frontend backed up");
} }
} }
// Extract new frontend // Extract new frontend into /opt/archipelago (root-owned dir).
let status = tokio::process::Command::new("tar") let status = tokio::process::Command::new("sudo")
.args(["-xzf", &src.to_string_lossy(), "-C", "/opt/archipelago"]) .args(["tar", "-xzf", &src.to_string_lossy(), "-C", "/opt/archipelago"])
.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
// what first-boot + the ISO layout expect.
let _ = tokio::process::Command::new("sudo")
.args(["chown", "-R", "archipelago:archipelago", "/opt/archipelago/web-ui"])
.status()
.await;
info!(name = %name, "Frontend archive extracted to /opt/archipelago/web-ui"); info!(name = %name, "Frontend archive extracted to /opt/archipelago/web-ui");
} }
_ => { _ => {
@@ -339,7 +362,20 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
// Clean staging // Clean staging
let _ = fs::remove_dir_all(&staging_dir).await; let _ = fs::remove_dir_all(&staging_dir).await;
info!("Update applied. Restart service to take effect."); info!("Update applied — scheduling service restart in 2s so the RPC reply lands first");
// Restart asynchronously so the JSON-RPC response actually reaches the
// UI before systemd kills us. --no-block makes sure systemctl doesn't
// try to wait for the current service (us) to exit cleanly before
// starting the new process — it would deadlock otherwise.
tokio::spawn(async {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
let _ = tokio::process::Command::new("sudo")
.args(["systemctl", "--no-block", "restart", "archipelago"])
.status()
.await;
});
Ok(()) Ok(())
} }

View File

@@ -663,18 +663,18 @@
"autoApply": "Auto-Apply", "autoApply": "Auto-Apply",
"autoApplyDesc": "Check daily and automatically install updates at 3 AM. Service restarts as needed.", "autoApplyDesc": "Check daily and automatically install updates at 3 AM. Service restarts as needed.",
"downloadUpdate": "Download Update", "downloadUpdate": "Download Update",
"applyUpdate": "Apply Update", "applyUpdate": "Install Update",
"checkForUpdates": "Check for Updates", "checkForUpdates": "Check for Updates",
"checking": "Checking...", "checking": "Checking...",
"rollback": "Rollback to Previous", "rollback": "Rollback to Previous",
"backToSettings": "Back to Settings", "backToSettings": "Back to Settings",
"percentComplete": "{percent}% complete", "percentComplete": "{percent}% complete",
"applyWarning": "Installing components and restarting services. Do not power off.", "applyWarning": "Installing components and restarting services. Do not power off.",
"applyTitle": "Apply Update?", "applyTitle": "Install Update?",
"applyMessage": "The backend service will restart. This may take a moment.", "applyMessage": "The backend service will restart. This may take a moment.",
"rollbackTitle": "Rollback Version?", "rollbackTitle": "Rollback Version?",
"rollbackMessage": "This will restore the previous version. The backend service will restart.", "rollbackMessage": "This will restore the previous version. The backend service will restart.",
"applyNow": "Apply Now", "applyNow": "Install Now",
"rollbackButton": "Rollback", "rollbackButton": "Rollback",
"upToDateMessage": "Your system is up to date. No updates available. Your system is running the latest version.", "upToDateMessage": "Your system is up to date. No updates available. Your system is running the latest version.",
"checkFailed": "Failed to check for updates. Check your internet connection.", "checkFailed": "Failed to check for updates. Check your internet connection.",

View File

@@ -662,18 +662,18 @@
"autoApply": "Aplicaci\u00f3n autom\u00e1tica", "autoApply": "Aplicaci\u00f3n autom\u00e1tica",
"autoApplyDesc": "Buscar diariamente y aplicar actualizaciones autom\u00e1ticamente a las 3 AM. Los servicios se reinician seg\u00fan sea necesario.", "autoApplyDesc": "Buscar diariamente y aplicar actualizaciones autom\u00e1ticamente a las 3 AM. Los servicios se reinician seg\u00fan sea necesario.",
"downloadUpdate": "Descargar actualizaci\u00f3n", "downloadUpdate": "Descargar actualizaci\u00f3n",
"applyUpdate": "Aplicar actualizaci\u00f3n", "applyUpdate": "Instalar actualizaci\u00f3n",
"checkForUpdates": "Buscar actualizaciones", "checkForUpdates": "Buscar actualizaciones",
"checking": "Verificando...", "checking": "Verificando...",
"rollback": "Revertir a la versi\u00f3n anterior", "rollback": "Revertir a la versi\u00f3n anterior",
"backToSettings": "Volver a configuraci\u00f3n", "backToSettings": "Volver a configuraci\u00f3n",
"percentComplete": "{percent}% completado", "percentComplete": "{percent}% completado",
"applyWarning": "Instalando componentes y reiniciando servicios. No apague el equipo.", "applyWarning": "Instalando componentes y reiniciando servicios. No apague el equipo.",
"applyTitle": "\u00bfAplicar actualizaci\u00f3n?", "applyTitle": "\u00bfInstalar actualizaci\u00f3n?",
"applyMessage": "El servicio del backend se reiniciar\u00e1. Esto puede tomar un momento.", "applyMessage": "El servicio del backend se reiniciar\u00e1. Esto puede tomar un momento.",
"rollbackTitle": "\u00bfRevertir versi\u00f3n?", "rollbackTitle": "\u00bfRevertir versi\u00f3n?",
"rollbackMessage": "Esto restaurar\u00e1 la versi\u00f3n anterior. El servicio del backend se reiniciar\u00e1.", "rollbackMessage": "Esto restaurar\u00e1 la versi\u00f3n anterior. El servicio del backend se reiniciar\u00e1.",
"applyNow": "Aplicar ahora", "applyNow": "Instalar ahora",
"rollbackButton": "Revertir", "rollbackButton": "Revertir",
"upToDateMessage": "Su sistema est\u00e1 actualizado. No hay actualizaciones disponibles. Su sistema est\u00e1 ejecutando la \u00faltima versi\u00f3n.", "upToDateMessage": "Su sistema est\u00e1 actualizado. No hay actualizaciones disponibles. Su sistema est\u00e1 ejecutando la \u00faltima versi\u00f3n.",
"checkFailed": "Error al buscar actualizaciones. Verifique su conexi\u00f3n a internet.", "checkFailed": "Error al buscar actualizaciones. Verifique su conexi\u00f3n a internet.",

View File

@@ -1,25 +1,28 @@
{ {
"version": "1.7.1-alpha", "version": "1.7.2-alpha",
"release_date": "2026-04-20", "release_date": "2026-04-20",
"changelog": [ "changelog": [
"Over-the-air update test — same features as 1.7.0, just a fresh version number so your node can try the new download-and-apply flow end-to-end. Safe to apply; nothing to do afterwards." "Install Update now actually installs. Before, the button would back up your current version then fail with 'Failed to apply update' because the installer couldn't write into system folders.",
"The button's also been renamed to 'Install Update' (previously 'Apply Update') and the node restarts itself a moment after you click it — no more manual restart step.",
"Your existing identities now show the generated avatar instead of just their initials — same look as freshly created ones.",
"Everything from 1.7.0-alpha and 1.7.1-alpha carries over (default avatars on creation, one-click Save publishes to Nostr relays, public blob URLs for profile pictures, 30-minute download window, VPN peer restore on reboot, reconciler-only-repairs, filebrowser fix)."
], ],
"components": [ "components": [
{ {
"name": "archipelago", "name": "archipelago",
"current_version": "1.7.0-alpha", "current_version": "1.7.1-alpha",
"new_version": "1.7.1-alpha", "new_version": "1.7.2-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.1-alpha/archipelago", "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.2-alpha/archipelago",
"sha256": "7f7981bdf33af6fdb0022338c62e0a102b17c1da95f87f630b07fc2b6056eef0", "sha256": "70e5444efede580fbf29f0f4131e065aaaead3b3d108ed6948abcdac9667c589",
"size_bytes": 40391760 "size_bytes": 40381960
}, },
{ {
"name": "archipelago-frontend-1.7.1-alpha.tar.gz", "name": "archipelago-frontend-1.7.2-alpha.tar.gz",
"current_version": "1.7.0-alpha", "current_version": "1.7.1-alpha",
"new_version": "1.7.1-alpha", "new_version": "1.7.2-alpha",
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.1-alpha/archipelago-frontend-1.7.1-alpha.tar.gz", "download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.2-alpha/archipelago-frontend-1.7.2-alpha.tar.gz",
"sha256": "dc3b63afedc45a663a023702ea23b6ac499d5b2731078a9b5a2feb57ae9a8370", "sha256": "806b027b43dbfbcb60b6c08da226e7e07db1a306848a9028d5a3cd676358a824",
"size_bytes": 76984288 "size_bytes": 76983699
} }
] ]
} }

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

Binary file not shown.