Refactor Windows installer for side-by-side updates and improved directory handling
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
All checks were successful
farmcontrol/farmcontrol-ui/pipeline/head This commit looks good
- Updated the NSIS installer to support side-by-side updates, allowing the running application to remain active while a new version is staged in a separate directory. - Modified the installation directory handling to ensure the correct paths are used for shortcuts and registry entries. - Enhanced the update process to include a retry mechanism for folder swaps, improving reliability during updates. - Adjusted comments and documentation for clarity on the update process and its implications.
This commit is contained in:
parent
8d689617f5
commit
d2d64ef45d
@ -60,12 +60,14 @@ Function .onInit
|
||||
!insertmacro progressStatus "Starting Farm Control installation..."
|
||||
!insertmacro progressPercent 5
|
||||
|
||||
; In-app updates keep the running process alive for progress UI.
|
||||
; In-app updates install into Farm Control.new while the running process
|
||||
; keeps using Farm Control; the folders are swapped after exit (/RESTARTFC).
|
||||
; Interactive / silent fresh installs still remove the previous version first.
|
||||
${If} $IsInAppUpdate == "1"
|
||||
!insertmacro prepareInPlaceUpdate
|
||||
!insertmacro prepareSideBySideUpdate
|
||||
!insertmacro progressPercent 15
|
||||
${Else}
|
||||
StrCpy $FinalInstDir $INSTDIR
|
||||
!insertmacro uninstallPreviousFarmControl
|
||||
!insertmacro progressPercent 20
|
||||
${EndIf}
|
||||
@ -97,7 +99,7 @@ Section "Farm Control" SecMain
|
||||
!insertmacro progressStatus "Writing uninstall information..."
|
||||
!insertmacro progressPercent 90
|
||||
|
||||
WriteRegStr HKCU "Software\Tom Butcher\Farm Control" "InstallDir" $INSTDIR
|
||||
WriteRegStr HKCU "Software\Tom Butcher\Farm Control" "InstallDir" $FinalInstDir
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
|
||||
"DisplayName" "Farm Control"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
|
||||
@ -105,7 +107,7 @@ Section "Farm Control" SecMain
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
|
||||
"Publisher" "Tom Butcher"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
|
||||
"UninstallString" "$INSTDIR\Uninstall.exe"
|
||||
"UninstallString" "$FinalInstDir\Uninstall.exe"
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
|
||||
"NoModify" 1
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\Farm Control" \
|
||||
|
||||
@ -5,11 +5,13 @@
|
||||
Var ProgressLogFile
|
||||
Var IsInAppUpdate
|
||||
Var RestartAfterInstall
|
||||
Var FinalInstDir
|
||||
|
||||
!macro initProgressLog
|
||||
StrCpy $ProgressLogFile ""
|
||||
StrCpy $IsInAppUpdate "0"
|
||||
StrCpy $RestartAfterInstall "0"
|
||||
StrCpy $FinalInstDir ""
|
||||
|
||||
${GetParameters} $R9
|
||||
|
||||
@ -104,36 +106,48 @@ Var RestartAfterInstall
|
||||
ExecWait 'taskkill /F /IM launcher.exe /T' $R0
|
||||
!macroend
|
||||
|
||||
; Windows allows renaming open executables; move locked binaries aside so File can replace them.
|
||||
!macro moveAsideIfPresent filePath
|
||||
${If} ${FileExists} "${filePath}"
|
||||
Delete "${filePath}.old"
|
||||
ClearErrors
|
||||
Rename "${filePath}" "${filePath}.old"
|
||||
ClearErrors
|
||||
; Side-by-side update: install into Farm Control.new while the running app
|
||||
; keeps using Farm Control, then swap folders after the process exits.
|
||||
!macro prepareSideBySideUpdate
|
||||
!insertmacro progressPhase "Preparing update"
|
||||
!insertmacro progressStatus "Preparing staging folder for update..."
|
||||
|
||||
StrCpy $FinalInstDir $INSTDIR
|
||||
StrCpy $INSTDIR "$FinalInstDir.new"
|
||||
|
||||
${If} ${FileExists} "$INSTDIR"
|
||||
DetailPrint "Removing leftover staging folder..."
|
||||
RMDir /r "$INSTDIR"
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
; Prefer renaming the whole bin dir (works even with open files inside).
|
||||
!macro prepareInPlaceUpdate
|
||||
!insertmacro progressPhase "Preparing update"
|
||||
!insertmacro progressStatus "Preparing files for update..."
|
||||
!macro swapUpdateStaging
|
||||
!insertmacro progressPhase "Applying update"
|
||||
!insertmacro progressStatus "Replacing Farm Control with the new version..."
|
||||
DetailPrint "Replacing Farm Control with staged update..."
|
||||
|
||||
${If} ${FileExists} "$INSTDIR\bin.old"
|
||||
RMDir /r "$INSTDIR\bin.old"
|
||||
StrCpy $R7 0
|
||||
swap_delete_retry:
|
||||
RMDir /r "$FinalInstDir"
|
||||
${If} ${FileExists} "$FinalInstDir"
|
||||
IntOp $R7 $R7 + 1
|
||||
${If} $R7 < 20
|
||||
Sleep 500
|
||||
Goto swap_delete_retry
|
||||
${EndIf}
|
||||
!insertmacro progressFailure "Could not remove the previous Farm Control installation."
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
${If} ${FileExists} "$INSTDIR\bin"
|
||||
ClearErrors
|
||||
Rename "$INSTDIR\bin" "$INSTDIR\bin.old"
|
||||
Rename "$INSTDIR" "$FinalInstDir"
|
||||
${If} ${Errors}
|
||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\FarmControl.exe"
|
||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\launcher.exe"
|
||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\bun.exe"
|
||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\bspatch.exe"
|
||||
!insertmacro moveAsideIfPresent "$INSTDIR\bin\zig-zstd.exe"
|
||||
${EndIf}
|
||||
!insertmacro progressFailure "Could not activate the new Farm Control installation."
|
||||
Abort
|
||||
${EndIf}
|
||||
|
||||
StrCpy $INSTDIR $FinalInstDir
|
||||
!insertmacro progressLog "installer:STATUS:Update files activated"
|
||||
!macroend
|
||||
|
||||
!macro uninstallPreviousFarmControl
|
||||
@ -180,15 +194,15 @@ Var RestartAfterInstall
|
||||
|
||||
!macro createDesktopShortcut
|
||||
SetShellVarContext current
|
||||
SetOutPath "$INSTDIR\bin"
|
||||
CreateShortCut "$DESKTOP\Farm Control.lnk" "$INSTDIR\bin\FarmControl.exe" "" "$INSTDIR\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||
SetOutPath "$FinalInstDir\bin"
|
||||
CreateShortCut "$DESKTOP\Farm Control.lnk" "$FinalInstDir\bin\FarmControl.exe" "" "$FinalInstDir\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||
!macroend
|
||||
|
||||
!macro createStartMenuShortcut
|
||||
SetShellVarContext current
|
||||
CreateDirectory "$SMPROGRAMS\Farm Control"
|
||||
SetOutPath "$INSTDIR\bin"
|
||||
CreateShortCut "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$INSTDIR\bin\FarmControl.exe" "" "$INSTDIR\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||
SetOutPath "$FinalInstDir\bin"
|
||||
CreateShortCut "$SMPROGRAMS\Farm Control\Farm Control.lnk" "$FinalInstDir\bin\FarmControl.exe" "" "$FinalInstDir\bin\FarmControl.exe" 0 SW_SHOWNORMAL "" "Farm Control"
|
||||
!macroend
|
||||
|
||||
!macro removeDesktopShortcut
|
||||
@ -202,16 +216,6 @@ Var RestartAfterInstall
|
||||
RMDir "$SMPROGRAMS\Farm Control"
|
||||
!macroend
|
||||
|
||||
!macro cleanupUpdateBackups
|
||||
; Best-effort; may fail while the previous process still holds handles.
|
||||
RMDir /r "$INSTDIR\bin.old"
|
||||
Delete "$INSTDIR\bin\FarmControl.exe.old"
|
||||
Delete "$INSTDIR\bin\launcher.exe.old"
|
||||
Delete "$INSTDIR\bin\bun.exe.old"
|
||||
Delete "$INSTDIR\bin\bspatch.exe.old"
|
||||
Delete "$INSTDIR\bin\zig-zstd.exe.old"
|
||||
!macroend
|
||||
|
||||
!macro customInstall
|
||||
!insertmacro progressPhase "Configuring Farm Control"
|
||||
!insertmacro progressStatus "Registering farmcontrol URI handler..."
|
||||
@ -219,20 +223,15 @@ Var RestartAfterInstall
|
||||
DeleteRegKey HKCU "Software\Classes\farmcontrol"
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol" "" "URL:farmcontrol"
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol" "URL Protocol" ""
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol\DefaultIcon" "" "$INSTDIR\bin\FarmControl.exe"
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol\DefaultIcon" "" "$FinalInstDir\bin\FarmControl.exe"
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol\shell" "" ""
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open" "" ""
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open\command" "" '"$INSTDIR\bin\bun.exe" "$INSTDIR\bin\deeplink.js" "%1"'
|
||||
WriteRegStr HKCU "Software\Classes\farmcontrol\shell\Open\command" "" '"$FinalInstDir\bin\bun.exe" "$FinalInstDir\bin\deeplink.js" "%1"'
|
||||
|
||||
!insertmacro progressStatus "Creating shortcuts..."
|
||||
DetailPrint "Creating shortcuts"
|
||||
!insertmacro createDesktopShortcut
|
||||
!insertmacro createStartMenuShortcut
|
||||
|
||||
; Don't remove bin.old during an in-app update — the running process still uses it.
|
||||
${If} $IsInAppUpdate != "1"
|
||||
!insertmacro cleanupUpdateBackups
|
||||
${EndIf}
|
||||
!macroend
|
||||
|
||||
!macro customUnInstall
|
||||
@ -241,7 +240,8 @@ Var RestartAfterInstall
|
||||
DeleteRegKey HKCU "Software\Classes\farmcontrol"
|
||||
!macroend
|
||||
|
||||
; Wait for the running app to exit, then relaunch. Only used when /RESTARTFC is passed.
|
||||
; Wait for the running app to exit, swap staged update into place, then relaunch.
|
||||
; Only used when /RESTARTFC is passed.
|
||||
Function restartFarmControlAfterUpdate
|
||||
${If} $RestartAfterInstall != "1"
|
||||
Return
|
||||
@ -262,7 +262,11 @@ Function restartFarmControlAfterUpdate
|
||||
Goto restart_wait_loop
|
||||
${EndIf}
|
||||
|
||||
${If} $IsInAppUpdate == "1"
|
||||
!insertmacro swapUpdateStaging
|
||||
${EndIf}
|
||||
|
||||
!insertmacro progressStatus "Starting Farm Control..."
|
||||
SetOutPath "$INSTDIR\bin"
|
||||
Exec "$INSTDIR\bin\FarmControl.exe"
|
||||
SetOutPath "$FinalInstDir\bin"
|
||||
Exec "$FinalInstDir\bin\FarmControl.exe"
|
||||
FunctionEnd
|
||||
|
||||
@ -81,8 +81,7 @@ export const resolveAppLaunchPath = () => {
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Prefer the installed launcher so post-update restart targets the new binary
|
||||
// (in-app updates may leave the running process under bin.old).
|
||||
// Prefer the installed launcher so restarts target the registered install dir.
|
||||
const installDir = readWindowsInstallDirFromRegistry()
|
||||
if (installDir) {
|
||||
const launcherFromRegistry = resolveExistingLauncher(
|
||||
|
||||
@ -220,8 +220,8 @@ export const launchWindowsInstaller = async (
|
||||
|
||||
// Silent NSIS install in a detached child process (not a batch file).
|
||||
// /S = silent (https://nsis.sourceforge.io/Reference/SilentInstall)
|
||||
// /UPDATE = in-app update (skip killing this process; overwrite in place)
|
||||
// /RESTARTFC = installer waits for this process to exit and relaunches the app
|
||||
// /UPDATE = in-app update (stage into Farm Control.new; swap after exit)
|
||||
// /RESTARTFC = installer waits for this process to exit, swaps folders, relaunches
|
||||
// /LOG= + FARMCONTROL_INSTALL_LOG = progress log (installer:% / PHASE / STATUS)
|
||||
const installerArgs = [
|
||||
'/S',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user