mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-09 22:25:02 +03:00
243 lines
9.2 KiB
NSIS
243 lines
9.2 KiB
NSIS
; electron-builder names a one-click per-user install directory after the package name, which every
|
|
; channel shares, so OpenCode, OpenCode Beta and OpenCode Dev all installed into one "@opencodedesktop"
|
|
; directory and each channel's installer and uninstaller replaced or deleted the others' files.
|
|
; Each channel now installs into a directory named after its app ID, like its data directory.
|
|
; Only the installer moves, and NSIS rejects a variable that the uninstaller build never sets.
|
|
!ifndef BUILD_UNINSTALLER
|
|
Var /GLOBAL sharedInstallDir
|
|
!endif
|
|
|
|
!macro customInit
|
|
StrCpy $sharedInstallDir ""
|
|
${StdUtils.GetParentPath} $R0 $INSTDIR
|
|
${If} $INSTDIR == "$R0\${APP_FILENAME}"
|
|
${If} ${FileExists} "$INSTDIR\${APP_EXECUTABLE_FILENAME}"
|
|
StrCpy $sharedInstallDir $INSTDIR
|
|
; The old uninstaller would delete every channel's files from the shared directory, so the
|
|
; install skips it and removes only this channel's files afterwards. The install writes the
|
|
; uninstall entry again for the new directory.
|
|
DeleteRegValue SHELL_CONTEXT "${UNINSTALL_REGISTRY_KEY}" UninstallString
|
|
!ifdef UNINSTALL_REGISTRY_KEY_2
|
|
DeleteRegValue SHELL_CONTEXT "${UNINSTALL_REGISTRY_KEY_2}" UninstallString
|
|
!endif
|
|
${EndIf}
|
|
StrCpy $INSTDIR "$R0\${APP_ID}"
|
|
${EndIf}
|
|
!macroend
|
|
|
|
!macro customInstall
|
|
; Chromium's sandbox needs read/execute access to the installed runtime files.
|
|
; https://github.com/electron/electron/issues/49143#issuecomment-3618354787
|
|
nsExec::ExecToLog '"$SYSDIR\icacls.exe" "$INSTDIR" /grant "*S-1-15-2-2:(OI)(CI)(RX)"'
|
|
Pop $0
|
|
!insertmacro leaveSharedInstall
|
|
!macroend
|
|
|
|
; Removes this channel's files from the shared directory, and the directory with the files every
|
|
; channel shared once no other channel's uninstaller is left in it. A taskbar pin still starts the
|
|
; old executable, so it is pointed at the new one.
|
|
!macro leaveSharedInstall
|
|
${If} $sharedInstallDir != ""
|
|
Delete "$sharedInstallDir\${APP_EXECUTABLE_FILENAME}"
|
|
Delete "$sharedInstallDir\${UNINSTALL_FILENAME}"
|
|
!insertmacro removeStagedUpdates $sharedInstallDir
|
|
FindFirst $R0 $R1 "$sharedInstallDir\Uninstall *.exe"
|
|
FindClose $R0
|
|
${If} $R1 == ""
|
|
RMDir /r $sharedInstallDir
|
|
${EndIf}
|
|
${If} ${FileExists} "$QUICKLAUNCH\User Pinned\TaskBar\${SHORTCUT_NAME}.lnk"
|
|
CreateShortCut "$QUICKLAUNCH\User Pinned\TaskBar\${SHORTCUT_NAME}.lnk" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" 0
|
|
WinShell::SetLnkAUMI "$QUICKLAUNCH\User Pinned\TaskBar\${SHORTCUT_NAME}.lnk" "${APP_ID}"
|
|
${EndIf}
|
|
${EndIf}
|
|
!macroend
|
|
|
|
!macro customUnInstall
|
|
${IfNot} ${isUpdated}
|
|
!insertmacro removeStagedUpdates $INSTDIR
|
|
${EndIf}
|
|
!macroend
|
|
|
|
; The stock check starts Windows PowerShell up to five times per installer and again in the old
|
|
; uninstaller, about 1.2 s each. This walks the process list in-process instead and keeps the stock
|
|
; behaviour: stop every process whose image lives under $INSTDIR. An update gives the quitting app
|
|
; 10 s to exit on its own; a manual install asks first, then stops it at once.
|
|
!macro customCheckAppRunning
|
|
System::Call "kernel32::GetCurrentProcessId() i .R9"
|
|
; $R8 is the tick count after which processes still running are terminated.
|
|
System::Call "kernel32::GetTickCount() i .R8"
|
|
IntOp $R8 $R8 + 10000
|
|
StrCpy $0 ""
|
|
|
|
${Do}
|
|
System::Call "kernel32::GetTickCount() i .r1"
|
|
IntOp $1 $1 - $R8
|
|
StrCpy $R7 0
|
|
System::Call "kernel32::CreateToolhelp32Snapshot(i 2, i 0) p .R0"
|
|
${If} $R0 <> -1
|
|
; PROCESSENTRY32W for the 32-bit installer: nine 4-byte fields, then WCHAR szExeFile[260].
|
|
System::Alloc 556
|
|
Pop $R1
|
|
System::Call "*$R1(i 556)"
|
|
System::Call "kernel32::Process32FirstW(p R0, p R1) i .R2"
|
|
${DoWhile} $R2 != 0
|
|
System::Call "*$R1(i, i, i .R3)"
|
|
${If} $R3 != $R9
|
|
System::Call "kernel32::OpenProcess(i 0x1000, i 0, i R3) p .R4"
|
|
${If} $R4 != 0
|
|
System::Call "kernel32::QueryFullProcessImageNameW(p R4, i 0, w .R5, *i ${NSIS_MAX_STRLEN}) i .R6"
|
|
System::Call "kernel32::CloseHandle(p R4)"
|
|
${If} $R6 != 0
|
|
; A channel leaving the shared directory stops its own app there, but not the others'.
|
|
StrCpy $2 0
|
|
!ifndef BUILD_UNINSTALLER
|
|
${If} $sharedInstallDir != ""
|
|
${AndIf} $R5 == "$sharedInstallDir\${APP_EXECUTABLE_FILENAME}"
|
|
StrCpy $2 1
|
|
${EndIf}
|
|
!endif
|
|
StrLen $R6 "$INSTDIR\"
|
|
StrCpy $R5 $R5 $R6
|
|
${If} $R5 == "$INSTDIR\"
|
|
StrCpy $2 1
|
|
${EndIf}
|
|
${If} $2 == 1
|
|
IntOp $R7 $R7 + 1
|
|
${If} $1 >= 0
|
|
System::Call "kernel32::OpenProcess(i 1, i 0, i R3) p .R4"
|
|
${If} $R4 != 0
|
|
System::Call "kernel32::TerminateProcess(p R4, i 1)"
|
|
System::Call "kernel32::CloseHandle(p R4)"
|
|
${EndIf}
|
|
${EndIf}
|
|
${EndIf}
|
|
${EndIf}
|
|
${EndIf}
|
|
${EndIf}
|
|
System::Call "kernel32::Process32NextW(p R0, p R1) i .R2"
|
|
${Loop}
|
|
System::Free $R1
|
|
System::Call "kernel32::CloseHandle(p R0)"
|
|
${EndIf}
|
|
|
|
${If} $R7 == 0
|
|
${ExitDo}
|
|
${EndIf}
|
|
|
|
${If} $0 == ""
|
|
StrCpy $0 "running"
|
|
${IfNot} ${isUpdated}
|
|
${If} ${Cmd} `MessageBox MB_OKCANCEL|MB_ICONEXCLAMATION "$(appRunning)" /SD IDOK IDCANCEL`
|
|
Quit
|
|
${EndIf}
|
|
System::Call "kernel32::GetTickCount() i .R8"
|
|
${EndIf}
|
|
DetailPrint "$(appClosing)"
|
|
${ElseIf} $1 >= 2000
|
|
${If} ${Cmd} `MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "$(appCannotBeClosed)" /SD IDCANCEL IDCANCEL`
|
|
Quit
|
|
${EndIf}
|
|
System::Call "kernel32::GetTickCount() i .R8"
|
|
${EndIf}
|
|
|
|
Sleep 100
|
|
${Loop}
|
|
!macroend
|
|
|
|
; Staged updates. Once an update has downloaded, the running app starts its installer with --stage.
|
|
; That unpacks the app next to the install directory, as "<install dir>.update-<payload hash>",
|
|
; while the user keeps working. After the restart, the install renames that directory into place
|
|
; instead of decompressing 700 MB. Anything unexpected falls back to the normal extraction.
|
|
!macro removeStagedUpdates DIR
|
|
RMDir /r "${DIR}.staging"
|
|
${StdUtils.GetParentPath} $R8 "${DIR}"
|
|
FindFirst $R9 $R7 "${DIR}.update-*"
|
|
${DoWhile} $R7 != ""
|
|
RMDir /r "$R8\$R7"
|
|
FindNext $R9 $R7
|
|
${Loop}
|
|
FindClose $R9
|
|
!macroend
|
|
|
|
!ifndef BUILD_UNINSTALLER
|
|
!ifdef APP_64 | APP_ARM64
|
|
!ifdef APP_64 & APP_ARM64
|
|
!error "Staged updates expect one architecture per installer"
|
|
!endif
|
|
!ifdef APP_ARM64
|
|
!define STAGED_PACKAGE "${APP_ARM64}"
|
|
!define STAGED_PACKAGE_HASH "${APP_ARM64_HASH}"
|
|
!define STAGED_PACKAGE_SIZE "${APP_ARM64_UNPACKED_SIZE}"
|
|
!else
|
|
!define STAGED_PACKAGE "${APP_64}"
|
|
!define STAGED_PACKAGE_HASH "${APP_64_HASH}"
|
|
!define STAGED_PACKAGE_SIZE "${APP_64_UNPACKED_SIZE}"
|
|
!endif
|
|
|
|
; Runs first in .onInit, before the single-instance check, and exits 0 only when it staged.
|
|
!macro preInit
|
|
${StdUtils.TestParameter} $R0 "stage"
|
|
${If} $R0 == "true"
|
|
SetErrorLevel 2
|
|
ReadRegStr $R1 HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation
|
|
; Leave room for the staged copy and as much again, in MB.
|
|
${DriveSpace} "$R1" "/D=F /S=M" $R2
|
|
IntOp $R3 ${STAGED_PACKAGE_SIZE} / 512
|
|
${If} $R1 != ""
|
|
${AndIf} ${FileExists} "$R1\${APP_EXECUTABLE_FILENAME}"
|
|
${AndIf} $R2 > $R3
|
|
!insertmacro removeStagedUpdates $R1
|
|
; Stage beside the directory the install will use, which customInit moves out of the shared one.
|
|
${StdUtils.GetParentPath} $R5 $R1
|
|
${If} $R1 == "$R5\${APP_FILENAME}"
|
|
StrCpy $R1 "$R5\${APP_ID}"
|
|
!insertmacro removeStagedUpdates $R1
|
|
${EndIf}
|
|
InitPluginsDir
|
|
; Stored like the template's copy, so the installer keeps one copy of the payload.
|
|
!ifdef COMPRESS
|
|
SetCompress off
|
|
!endif
|
|
File /oname=$PLUGINSDIR\staged.7z "${STAGED_PACKAGE}"
|
|
!ifdef COMPRESS
|
|
SetCompress "${COMPRESS}"
|
|
!endif
|
|
ClearErrors
|
|
SetOutPath "$R1.staging"
|
|
${IfNot} ${Errors}
|
|
Nsis7z::Extract "$PLUGINSDIR\staged.7z"
|
|
SetOutPath $PLUGINSDIR
|
|
StrCpy $R4 "${STAGED_PACKAGE_HASH}" 16
|
|
ClearErrors
|
|
Rename "$R1.staging" "$R1.update-$R4"
|
|
${IfNot} ${Errors}
|
|
SetErrorLevel 0
|
|
${EndIf}
|
|
${EndIf}
|
|
${EndIf}
|
|
Quit
|
|
${EndIf}
|
|
!macroend
|
|
|
|
; Called by the patched app-builder-lib template in place of extraction. Sets $R0 to "installed"
|
|
; when it moved the staged app into the empty install directory.
|
|
!macro customInstallStagedApp
|
|
StrCpy $R0 ""
|
|
StrCpy $R1 "${STAGED_PACKAGE_HASH}" 16
|
|
${If} ${FileExists} "$INSTDIR.update-$R1\${APP_EXECUTABLE_FILENAME}"
|
|
SetOutPath $PLUGINSDIR
|
|
; Removes the directory only while it is empty, so the rename never mixes two versions.
|
|
RMDir $INSTDIR
|
|
ClearErrors
|
|
Rename "$INSTDIR.update-$R1" $INSTDIR
|
|
${IfNot} ${Errors}
|
|
StrCpy $R0 "installed"
|
|
${EndIf}
|
|
SetOutPath $INSTDIR
|
|
${EndIf}
|
|
!insertmacro removeStagedUpdates $INSTDIR
|
|
!macroend
|
|
!endif
|
|
!endif
|