From f9aee4de5d67efb8e8f43b8f9ca30c4b7adcaa4c Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Tue, 31 Mar 2026 23:08:31 +1300 Subject: [PATCH] (fix): clear stale install data before starting new installation --- app/views/install/installer/js/installer.js | 15 ++++++--- .../install/installer/js/modules/progress.js | 32 ++++++++++++++----- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/app/views/install/installer/js/installer.js b/app/views/install/installer/js/installer.js index 4433d67b99..07ec7bb1ef 100644 --- a/app/views/install/installer/js/installer.js +++ b/app/views/install/installer/js/installer.js @@ -399,11 +399,18 @@ } } } - if (action === 'next' && String(target) === '5' && typeof validateInstallRequest === 'function') { - const isValid = await validateInstallRequest(); - if (!isValid) { - return; + if (action === 'next' && String(target) === '5') { + if (typeof validateInstallRequest === 'function') { + const isValid = await validateInstallRequest(); + if (!isValid) { + return; + } } + // Clear stale install data from previous runs so initStep5 + // starts a fresh install instead of trying to resume. + const { clearInstallLock, clearInstallId } = window.InstallerStepsState || {}; + clearInstallLock?.(); + clearInstallId?.(); } if (isInstallLocked() && Number(target) !== 5) { requestStep(5, true); diff --git a/app/views/install/installer/js/modules/progress.js b/app/views/install/installer/js/modules/progress.js index 868f820c95..7c36fd7951 100644 --- a/app/views/install/installer/js/modules/progress.js +++ b/app/views/install/installer/js/modules/progress.js @@ -722,7 +722,8 @@ }); startSyncedSpinnerRotation(list); - notifyInstallComplete(activeInstall?.installId, sessionDetails).finally(() => { + const completeId = activeInstall?.installId || getStoredInstallId?.(); + notifyInstallComplete(completeId, sessionDetails).finally(() => { setTimeout(() => redirectToApp(protocol), TIMINGS?.redirectDelay ?? 0); }); }; @@ -912,21 +913,28 @@ }; const isSnapshotTerminal = (snapshot) => { - if (!snapshot?.steps) return true; + if (!snapshot?.steps) return 'empty'; const stepEntries = Object.values(snapshot.steps); - if (stepEntries.length === 0) return true; + if (stepEntries.length === 0) return 'empty'; const hasError = stepEntries.some((s) => s.status === STATUS.ERROR); - if (hasError) return true; + if (hasError) return 'error'; const allCompleted = INSTALLATION_STEPS.every((step) => { const detail = snapshot.steps[step.id]; return detail && detail.status === STATUS.COMPLETED; }); - return allCompleted; + if (allCompleted) return 'completed'; + return false; }; const resumeInstall = async (installId) => { const snapshot = await fetchInstallStatus(installId); - if (!snapshot || isSnapshotTerminal(snapshot)) return false; + const terminal = isSnapshotTerminal(snapshot); + if (!snapshot || terminal) { + if (terminal === 'completed') { + return 'completed'; + } + return false; + } activeInstall = { installId, controller: new AbortController(), @@ -1086,8 +1094,16 @@ const lock = getInstallLock?.(); const existingInstallId = lock?.installId || getStoredInstallId?.(); if (existingInstallId) { - resumeInstall(existingInstallId).then((resumed) => { - if (!resumed) { + resumeInstall(existingInstallId).then((result) => { + if (result === 'completed') { + // Install already finished — redirect to console + // instead of bouncing back to step 1. + stopSyncedSpinnerRotation(); + setUnloadGuard(false); + clearInstallLock?.(); + clearInstallId?.(); + startSslCheck(null); + } else if (!result) { recoverToLastStep(); } });