diff --git a/core/Cargo.lock b/core/Cargo.lock index 0871ecd7..95dc1a41 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "archipelago" -version = "1.7.1-alpha" +version = "1.7.2-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/Cargo.toml b/core/archipelago/Cargo.toml index d298a551..4aeda1a1 100644 --- a/core/archipelago/Cargo.toml +++ b/core/archipelago/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "archipelago" -version = "1.7.1-alpha" +version = "1.7.2-alpha" edition = "2021" description = "Archipelago Bitcoin Node OS - Native backend" authors = ["Archipelago Team"] diff --git a/core/archipelago/src/identity_manager.rs b/core/archipelago/src/identity_manager.rs index 5a3827f0..c343d1da 100644 --- a/core/archipelago/src/identity_manager.rs +++ b/core/archipelago/src/identity_manager.rs @@ -739,6 +739,20 @@ impl IdentityManager { .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 { id: file.id, name: file.name, @@ -749,7 +763,7 @@ impl IdentityManager { created_at: file.created_at, nostr_pubkey: file.nostr_pubkey_hex, nostr_npub, - profile: file.profile, + profile, }) } diff --git a/core/archipelago/src/update.rs b/core/archipelago/src/update.rs index fe301691..00150f07 100644 --- a/core/archipelago/src/update.rs +++ b/core/archipelago/src/update.rs @@ -277,25 +277,42 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> { match name.as_str() { "archipelago" => { - let dest = Path::new("/usr/local/bin/archipelago"); - fs::copy(&src, dest) + // /usr/local/bin is root-owned; archipelago user can't + // 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 - .with_context(|| format!("Failed to apply {}", name))?; - #[cfg(unix)] - { - use std::os::unix::fs::PermissionsExt; - std::fs::set_permissions(dest, std::fs::Permissions::from_mode(0o755)) - .context("Failed to set binary permissions")?; + .with_context(|| format!("Failed to spawn install for {}", name))?; + if !status.success() { + anyhow::bail!( + "sudo install failed for {} (exit {:?})", + name, + status.code() + ); } 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 + // 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("tar") + let status = tokio::process::Command::new("sudo") .args([ + "tar", "-czf", &frontend_backup.to_string_lossy(), "-C", @@ -309,15 +326,21 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> { info!("Frontend backed up"); } } - // Extract new frontend - let status = tokio::process::Command::new("tar") - .args(["-xzf", &src.to_string_lossy(), "-C", "/opt/archipelago"]) + // Extract new frontend into /opt/archipelago (root-owned dir). + let status = tokio::process::Command::new("sudo") + .args(["tar", "-xzf", &src.to_string_lossy(), "-C", "/opt/archipelago"]) .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. + 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"); } _ => { @@ -339,7 +362,20 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> { // Clean staging 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(()) } diff --git a/neode-ui/src/locales/en.json b/neode-ui/src/locales/en.json index be726c6b..75e9ad6f 100644 --- a/neode-ui/src/locales/en.json +++ b/neode-ui/src/locales/en.json @@ -663,18 +663,18 @@ "autoApply": "Auto-Apply", "autoApplyDesc": "Check daily and automatically install updates at 3 AM. Service restarts as needed.", "downloadUpdate": "Download Update", - "applyUpdate": "Apply Update", + "applyUpdate": "Install Update", "checkForUpdates": "Check for Updates", "checking": "Checking...", "rollback": "Rollback to Previous", "backToSettings": "Back to Settings", "percentComplete": "{percent}% complete", "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.", "rollbackTitle": "Rollback Version?", "rollbackMessage": "This will restore the previous version. The backend service will restart.", - "applyNow": "Apply Now", + "applyNow": "Install Now", "rollbackButton": "Rollback", "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.", diff --git a/neode-ui/src/locales/es.json b/neode-ui/src/locales/es.json index 79fbca32..4ab451e5 100644 --- a/neode-ui/src/locales/es.json +++ b/neode-ui/src/locales/es.json @@ -662,18 +662,18 @@ "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.", "downloadUpdate": "Descargar actualizaci\u00f3n", - "applyUpdate": "Aplicar actualizaci\u00f3n", + "applyUpdate": "Instalar actualizaci\u00f3n", "checkForUpdates": "Buscar actualizaciones", "checking": "Verificando...", "rollback": "Revertir a la versi\u00f3n anterior", "backToSettings": "Volver a configuraci\u00f3n", "percentComplete": "{percent}% completado", "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.", "rollbackTitle": "\u00bfRevertir versi\u00f3n?", "rollbackMessage": "Esto restaurar\u00e1 la versi\u00f3n anterior. El servicio del backend se reiniciar\u00e1.", - "applyNow": "Aplicar ahora", + "applyNow": "Instalar ahora", "rollbackButton": "Revertir", "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.",