release(v1.7.2-alpha): fix Install Update + identity avatar backfill + label
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:
co-authored by
Claude Opus 4.7
parent
0ea9ad9adb
commit
66b4e2b313
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user