fix(install): stream per-member progress for orchestrator stack installs

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 <noreply@anthropic.com>
This commit is contained in:
archipelago 2026-07-16 13:02:05 -04:00
parent a687df9bd9
commit 500472944c

View File

@ -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,