diff --git a/core/Cargo.lock b/core/Cargo.lock index 20f4ba60..67902f0a 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -104,7 +104,7 @@ dependencies = [ [[package]] name = "archipelago" -version = "1.7.117-alpha" +version = "1.7.118-alpha" dependencies = [ "anyhow", "archipelago-container", diff --git a/core/archipelago/src/update.rs b/core/archipelago/src/update.rs index a4c3fa25..f76b90f3 100644 --- a/core/archipelago/src/update.rs +++ b/core/archipelago/src/update.rs @@ -1589,10 +1589,24 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> { if !mk.success() { anyhow::bail!("mkdir {} failed", staging_new); } - let extract = - host_sudo(&["tar", "-xzf", &src.to_string_lossy(), "-C", &staging_new]) - .await - .with_context(|| format!("Failed to extract {}", name))?; + // Idle-priority IO: extracting a 200MB tarball at normal + // priority starved small nodes so badly during the v1.7.118 + // rollout that podman/status calls timed out for minutes and + // the update looked broken. The extraction can take as long + // as it likes — the node staying responsive matters more. + let extract = host_sudo(&[ + "ionice", + "-c3", + "nice", + "-n10", + "tar", + "-xzf", + &src.to_string_lossy(), + "-C", + &staging_new, + ]) + .await + .with_context(|| format!("Failed to extract {}", name))?; if !extract.success() { let _ = host_sudo(&["rm", "-rf", &staging_new]).await; anyhow::bail!("tar extraction failed for {}", name); @@ -1687,9 +1701,12 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> { anyhow::bail!("mkdir {} failed", staging_new); } - let extract = host_sudo(&["tar", "-xzf", &archive, "-C", &staging_new]) - .await - .with_context(|| format!("Failed to extract {}", name))?; + // Idle-priority IO — same reasoning as the frontend tarball. + let extract = host_sudo(&[ + "ionice", "-c3", "nice", "-n10", "tar", "-xzf", &archive, "-C", &staging_new, + ]) + .await + .with_context(|| format!("Failed to extract {}", name))?; if !extract.success() { let _ = host_sudo(&["rm", "-rf", &staging_new]).await; anyhow::bail!("tar extraction failed for {}", name); diff --git a/neode-ui/src/locales/en.json b/neode-ui/src/locales/en.json index 98b2e15a..6165b68c 100644 --- a/neode-ui/src/locales/en.json +++ b/neode-ui/src/locales/en.json @@ -706,6 +706,8 @@ "downloadFailed": "Download failed. Please try again.", "applySuccess": "Update applied. The service will restart momentarily.", "applyFailed": "Failed to apply update. You can try again or rollback.", + "applyInProgress": "An update is already being installed — hang tight, the node will restart when it finishes.", + "downloadInProgress": "An update download is already in progress — progress will appear above.", "rollbackSuccess": "Rolled back to previous version. Service will restart.", "rollbackFailed": "Rollback failed.", "pullAndRebuild": "Pull & Rebuild", diff --git a/neode-ui/src/locales/es.json b/neode-ui/src/locales/es.json index 3c9dd268..e322fe1e 100644 --- a/neode-ui/src/locales/es.json +++ b/neode-ui/src/locales/es.json @@ -693,6 +693,8 @@ "downloadFailed": "La descarga fall\u00f3. Intente de nuevo.", "applySuccess": "Actualizaci\u00f3n aplicada. El servicio se reiniciar\u00e1 en un momento.", "applyFailed": "Error al aplicar la actualizaci\u00f3n. Puede intentar de nuevo o revertir.", + "applyInProgress": "Ya se est\u00e1 instalando una actualizaci\u00f3n \u2014 espere, el nodo se reiniciar\u00e1 al terminar.", + "downloadInProgress": "Ya hay una descarga de actualizaci\u00f3n en curso \u2014 el progreso aparecer\u00e1 arriba.", "rollbackSuccess": "Se revirti\u00f3 a la versi\u00f3n anterior. El servicio se reiniciar\u00e1.", "rollbackFailed": "Error al revertir.", "pullAndRebuild": "Pull y Recompilar", diff --git a/neode-ui/src/views/SystemUpdate.vue b/neode-ui/src/views/SystemUpdate.vue index c4176bc5..f9f9436c 100644 --- a/neode-ui/src/views/SystemUpdate.vue +++ b/neode-ui/src/views/SystemUpdate.vue @@ -978,6 +978,11 @@ async function downloadUpdate() { updateInProgress.value = false await loadStatus() showStatus(t('systemUpdate.upToDateMessage')) + } else if (/already running/i.test(msg)) { + // A download/apply is already in flight (auto-updater or another tab) — + // that's progress, not failure. Re-sync state and say so calmly. + await loadStatus() + showStatus(t('systemUpdate.downloadInProgress')) } else { // A failed download is NOT a staged update — return the UI to the // Download button so the user can retry, instead of stranding them on @@ -1065,6 +1070,20 @@ async function applyUpdate() { setTimeout(() => window.location.reload(), 3000) } } catch (e) { + // "Another update operation is already running" is NOT a failure — an + // apply (or the auto-updater) is already doing the work. Presenting it + // as an error made a successfully-updating node look broken (OptiPlex, + // v1.7.118 rollout). Join the in-flight apply instead: same overlay, + // same wait-for-new-version polling. + if (/already running/i.test(errorMessage(e))) { + applying.value = false + if (target) { + startInstallOverlay(target) + } else { + showStatus(t('systemUpdate.applyInProgress')) + } + return + } showStatus(t('systemUpdate.applyFailed'), true) if (import.meta.env.DEV) console.warn('Apply failed', e) applying.value = false