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
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:
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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"]
|
||||||
|
|||||||
@@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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.",
|
||||||
|
|||||||
@@ -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
BIN
releases/v1.7.2-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.2-alpha/archipelago-frontend-1.7.2-alpha.tar.gz
Normal file
BIN
releases/v1.7.2-alpha/archipelago-frontend-1.7.2-alpha.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user