From 500472944c02095ca7e90a718840363649f2b8fb Mon Sep 17 00:00:00 2001 From: archipelago Date: Thu, 16 Jul 2026 13:02:05 -0400 Subject: [PATCH] fix(install): stream per-member progress for orchestrator stack installs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Orchestrator-installed stacks (indeedhub, btcpay, netbird, immich, mempool) sat at "Preparing… 5%" for the entire install — the install_stack_via_orchestrator path never called set_install_phase / set_install_progress after the initial Preparing, so a 7-image, multi-gigabyte indeedhub pull gave the user zero feedback ("just says preparing for ages"). The legacy installers already report phases and the frontend already interpolates X-of-N across the PullingImage band; the new path simply never fed it. Now: PullingImage phase up front, an X-of-N counter plus a per-member "Installing postgres (2 of 7)…" message as each member installs, and the legacy installers' truthful PostInstall→Done settle at the end. Co-Authored-By: Claude Fable 5 --- .../archipelago/src/api/rpc/package/stacks.rs | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/core/archipelago/src/api/rpc/package/stacks.rs b/core/archipelago/src/api/rpc/package/stacks.rs index 7899edf8..3fa5f3ee 100644 --- a/core/archipelago/src/api/rpc/package/stacks.rs +++ b/core/archipelago/src/api/rpc/package/stacks.rs @@ -621,8 +621,33 @@ async fn install_stack_via_orchestrator( )) .await; + // Phase: PullingImage — each member's orchestrator.install covers its + // whole pipeline (pull + create + start + health wait), and on a fresh + // node the pull dominates wall-clock. The X-of-N counter below is what + // the UI interpolates across the PullingImage band (20→70%); without + // these updates an orchestrator-installed stack sat at "Preparing… 5%" + // for the entire multi-gigabyte pull (indeedhub: 7 images). + let total = app_ids.len() as u64; + handler + .set_install_phase(stack_name, InstallPhase::PullingImage) + .await; + let mut installed = 0usize; - for app_id in app_ids { + for (idx, app_id) in app_ids.iter().enumerate() { + handler + .set_install_progress(stack_name, idx as u64, total) + .await; + // Message after progress: set_install_progress resets the label, + // set_install_message preserves the phase + counters just written. + let component = app_id + .strip_prefix(&format!("{stack_name}-")) + .unwrap_or(app_id); + handler + .set_install_message( + stack_name, + &format!("Installing {} ({} of {})…", component, idx + 1, total), + ) + .await; match orchestrator.install(app_id).await { Ok(container_name) => { installed += 1; @@ -672,6 +697,20 @@ async fn install_stack_via_orchestrator( } } + // Truthful end-of-install signal, mirroring the legacy stack installers: + // the real readiness gate is the scanner's next sweep, this just settles + // the bar at 95→100→done instead of leaving it mid-band. + handler + .set_install_progress(stack_name, total, total) + .await; + handler + .set_install_phase(stack_name, InstallPhase::PostInstall) + .await; + handler + .set_install_phase(stack_name, InstallPhase::Done) + .await; + handler.clear_install_progress(stack_name).await; + install_log(&format!("INSTALL ORCH OK: {} stack", stack_name)).await; Ok(Some(serde_json::json!({ "success": true,