mirror of
https://github.com/garethgeorge/backrest.git
synced 2026-05-04 03:50:30 +00:00
fix: modernize windows installer with Inno Setup (#867)
Build Snapshot Release / build (push) Has been cancelled
Release Please / release-please (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Build Snapshot Release / Windows installers (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled
Build Snapshot Release / build (push) Has been cancelled
Release Please / release-please (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Build Snapshot Release / Windows installers (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled
This commit is contained in:
@@ -55,13 +55,36 @@ jobs:
|
||||
dist/*.tar.gz
|
||||
dist/*.zip
|
||||
|
||||
- name: Generate Installers
|
||||
run: |
|
||||
mkdir -p dist-installers
|
||||
./scripts/generate-installers.sh ./dist-installers
|
||||
installer:
|
||||
name: Windows installers
|
||||
needs: [ build ]
|
||||
runs-on: windows-2022
|
||||
|
||||
- name: Upload Installers
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: backrest-snapshot-installers
|
||||
path: dist-installers/*.exe
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download build artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: backrest-snapshot-builds
|
||||
|
||||
- name: Unzip artifacts and compile Inno Setup installers
|
||||
shell: powershell
|
||||
run: |
|
||||
mkdir windows_installers
|
||||
foreach ($arch in "x86_64", "arm64") {
|
||||
$src = "backrest_Windows_$arch"
|
||||
Expand-Archive ".\${src}.zip"
|
||||
cp build\windows\* $src
|
||||
& "c:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DArch=$arch ${src}\installer.iss
|
||||
cp "$src\Output\*" windows_installers
|
||||
}
|
||||
|
||||
- name: Upload installers
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: backrest-snapshot-installers
|
||||
path: windows_installers\*.exe
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
!define BUILD_DIR "."
|
||||
!define OUT_DIR "."
|
||||
!define APP_NAME "Backrest"
|
||||
!define COMP_NAME "garethgeorge"
|
||||
!define WEB_SITE "https://github.com/garethgeorge/backrest"
|
||||
!define COPYRIGHT "garethgeorge 2024"
|
||||
!define DESCRIPTION "${APP_NAME} installer"
|
||||
!define LICENSE_TXT "${BUILD_DIR}\LICENSE"
|
||||
!define MAIN_APP_EXE "backrest-windows-tray.exe"
|
||||
!define INSTALL_TYPE "SetShellVarContext current"
|
||||
!define REG_ROOT "HKCU"
|
||||
!define REG_UNINSTALL_PATH "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}"
|
||||
# Extract version from the changelog.
|
||||
!searchparse /file "${BUILD_DIR}\CHANGELOG.md" `## [` VERSION `]`
|
||||
# User variables.
|
||||
Var UIPort
|
||||
Var WelcomeTitle
|
||||
Var WelcomeText
|
||||
Var WelcomePortNote
|
||||
Var OldVersion
|
||||
Var Cmd
|
||||
Var InstallMode
|
||||
Var InstallModeLower
|
||||
|
||||
######################################################################
|
||||
# Installer file properties
|
||||
# NSIS requires X.X.X.X format in VIProductVersion.
|
||||
VIProductVersion "${VERSION}.0"
|
||||
VIAddVersionKey "ProductName" "${APP_NAME}"
|
||||
VIAddVersionKey "CompanyName" "${COMP_NAME}"
|
||||
VIAddVersionKey "LegalCopyright" "${COPYRIGHT}"
|
||||
VIAddVersionKey "FileDescription" "${DESCRIPTION}"
|
||||
VIAddVersionKey "FileVersion" "${VERSION}"
|
||||
VIAddVersionKey "ProductVersion" "${VERSION}"
|
||||
|
||||
######################################################################
|
||||
# Installer settings
|
||||
Unicode True
|
||||
RequestExecutionLevel user
|
||||
SetCompressor LZMA
|
||||
Name "${APP_NAME}"
|
||||
Caption "$(^Name) ${VERSION} Setup"
|
||||
!ifdef ARCH
|
||||
OutFile "${OUT_DIR}\Backrest-${ARCH}-setup.exe"
|
||||
!else
|
||||
OutFile "${OUT_DIR}\Backrest-setup.exe"
|
||||
!endif
|
||||
XPStyle on
|
||||
# Default installation directory.
|
||||
InstallDir "$LOCALAPPDATA\Programs\Backrest"
|
||||
# If existing installation is detected, use that directory instead.
|
||||
InstallDirRegKey "${REG_ROOT}" "${REG_UNINSTALL_PATH}" "UninstallString"
|
||||
ManifestDPIAware true
|
||||
ShowInstDetails show
|
||||
ShowUninstDetails show
|
||||
# Include NSIS headers used by this script.
|
||||
!include "MUI2.nsh"
|
||||
!include "LogicLib.nsh"
|
||||
!include "Memento.nsh"
|
||||
!include "WordFunc.nsh"
|
||||
# Defines for the Memento macro.
|
||||
!define MEMENTO_REGISTRY_ROOT "${REG_ROOT}"
|
||||
!define MEMENTO_REGISTRY_KEY "${REG_UNINSTALL_PATH}"
|
||||
|
||||
######################################################################
|
||||
# GUI pages
|
||||
# Prompt to confirm exiting the installer.
|
||||
!define MUI_ABORTWARNING
|
||||
!define MUI_UNABORTWARNING
|
||||
|
||||
!define MUI_WELCOMEPAGE_TITLE "$WelcomeTitle"
|
||||
!define MUI_TEXT_WELCOME_INFO_TEXT "$WelcomeText"
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE onPreWelcome
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_LEAVE onLeaveWelcome
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
|
||||
!insertmacro MUI_PAGE_LICENSE "${LICENSE_TXT}"
|
||||
|
||||
!define MUI_COMPONENTSPAGE_NODESC
|
||||
!define MUI_COMPONENTSPAGE_TEXT_COMPLIST "Select components to install:$\r$\n$\r$\nSelections will be remembered for future upgrades"
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE onPreComponents
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_PRE onPreDirectory
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MAIN_APP_EXE}"
|
||||
!define MUI_FINISHPAGE_RUN_TEXT "&Start ${APP_NAME} (runs in the system tray)"
|
||||
# Use the built-in readme option to open the app URL.
|
||||
!define MUI_FINISHPAGE_SHOWREADME http://localhost:$UIPort/
|
||||
!define MUI_FINISHPAGE_SHOWREADME_TEXT "&Open Backrest user interface"
|
||||
!define MUI_PAGE_CUSTOMFUNCTION_SHOW onShowFinish
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
||||
# Uninstall pages.
|
||||
!define MUI_UNFINISHPAGE_NOAUTOCLOSE
|
||||
!insertmacro MUI_UNPAGE_CONFIRM
|
||||
!insertmacro MUI_UNPAGE_INSTFILES
|
||||
!insertmacro MUI_UNPAGE_FINISH
|
||||
|
||||
!insertmacro MUI_LANGUAGE "English"
|
||||
|
||||
######################################################################
|
||||
# Functions
|
||||
# Have to define the function this way to allow re-using it in the uninstall section.
|
||||
!macro KillProcess UN
|
||||
Function ${UN}KillProcess
|
||||
ReadEnvStr $Cmd COMSPEC
|
||||
DetailPrint "Stopping Backrest if it is running..."
|
||||
# Gracefully attempt to stop Backrest processes for the current user.
|
||||
# Do it 5 times, then kill forcefully.
|
||||
nsExec::ExecToLog '$Cmd /C echo off & (for /L %i in (1,1,5) do tasklist /FI "USERNAME eq %USERNAME%" | findstr /I /V "setup" | findstr "backrest" && taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest-windows-tray.exe" || exit) & taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest-windows-tray.exe" /F & taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest.exe" /F '
|
||||
FunctionEnd
|
||||
!macroend
|
||||
!insertmacro KillProcess ""
|
||||
!insertmacro KillProcess "un."
|
||||
|
||||
Function .onInit
|
||||
# $R0, $R1 etc are registers; used here as local variables.
|
||||
# Read some environment variables.
|
||||
ReadEnvStr $Cmd COMSPEC
|
||||
ReadEnvStr $R1 BACKREST_PORT
|
||||
${If} "$R1" == ""
|
||||
# Use the default port and welcome text if the var is empty.
|
||||
StrCpy $UIPort "9898"
|
||||
StrCpy $WelcomePortNote ""
|
||||
${Else}
|
||||
# Extract port number.
|
||||
${WordFind} "$R1" ":" "+2" $UIPort
|
||||
StrCpy $WelcomePortNote "$\r$\n$\r$\nNOTE: detected BACKREST_PORT environment variable. Will use port $UIPort for shortcuts."
|
||||
${EndIf}
|
||||
|
||||
# Read the previous Backrest version, if any.
|
||||
ReadRegStr $OldVersion ${REG_ROOT} "${REG_UNINSTALL_PATH}" "DisplayVersion"
|
||||
${If} "$OldVersion" == "00.00.00.00"
|
||||
# Old pre-1.6.2 installer installed into C:\Program Files; override the default path when upgrading.
|
||||
StrCpy $INSTDIR "$LOCALAPPDATA\Programs\Backrest"
|
||||
${EndIf}
|
||||
|
||||
${If} "$OldVersion" != ""
|
||||
# Detected existing installation.
|
||||
${MementoSectionRestore}
|
||||
${VersionCompare} "$OldVersion" "${VERSION}" $R3
|
||||
${Select} $R3
|
||||
${Case} "0"
|
||||
StrCpy $InstallMode "Reinstall"
|
||||
${Case} "1"
|
||||
StrCpy $InstallMode "Downgrade"
|
||||
${CaseElse}
|
||||
StrCpy $InstallMode "Upgrade"
|
||||
${EndSelect}
|
||||
StrCpy $WelcomeTitle "Welcome to ${APP_NAME} $InstallMode"
|
||||
# Convert to lowercase for Welcome text.
|
||||
${StrFilter} "$InstallMode" "-" "" "" $InstallModeLower
|
||||
StrCpy $WelcomeText "Setup will guide you through the $InstallModeLower of ${APP_NAME} from version $OldVersion to ${VERSION}.$\r$\n$\r$\nInstallation directory is $INSTDIR $WelcomePortNote$\r$\n$\r$\nClick Next to continue."
|
||||
${Else}
|
||||
# New installation.
|
||||
# Check if port is already in use and go into the abort mode.
|
||||
nsExec::ExecToStack '$Cmd /C netstat.exe -na | findstr LISTENING | findstr ":$UIPort " '
|
||||
Pop $R4
|
||||
${If} "$R4" == "0"
|
||||
StrCpy "$InstallMode" "Abort"
|
||||
StrCpy $WelcomeTitle "Error"
|
||||
StrCpy $WelcomeText "*** WARNING ***$\r$\nBackrest binds to port $UIPort for web UI. This port is currently in use by another Backrest instance or another application.$\r$\n$\r$\nPerform the following:$\r$\nClick Start - type $\"environment$\", Enter to open System Properties.$\r$\nClick Environment Variables. Click New in the top section. Enter BACKREST_PORT as the name and 127.0.0.1:port as the value, where $\"port$\" is a number between 1024 and 65535 (avoid known ports; try 9900), then OK 3 times.$\r$\nExit and re-run this installer to have it pick up the new value.$\r$\nSee installation documentation for more details.$\r$\n$\r$\nClick Exit to exit."
|
||||
${Else}
|
||||
StrCpy $WelcomeTitle "Welcome to ${APP_NAME} Setup"
|
||||
StrCpy $WelcomeText "Setup will guide you through the installation of ${APP_NAME}.$WelcomePortNote$\r$\n$\r$\nClick Next to continue."
|
||||
${EndIf}
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function onPreWelcome
|
||||
${If} "$InstallMode" == "Abort"
|
||||
# Change text on the button.
|
||||
GetDlgItem $R5 $HWNDPARENT 1
|
||||
${NSD_SetText} $R5 "&Exit"
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function onLeaveWelcome
|
||||
${If} "$InstallMode" == "Abort"
|
||||
Quit
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function onPreComponents
|
||||
${If} "$InstallMode" != ""
|
||||
GetDlgItem $R6 $HWNDPARENT 1
|
||||
${NSD_SetText} $R6 "$(^InstallBtn)"
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function onPreDirectory
|
||||
# Skip directory page.
|
||||
${If} "$InstallMode" != ""
|
||||
Abort
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function onShowFinish
|
||||
# Run custom functions when the checkboxes are clicked.
|
||||
${NSD_OnClick} $mui.FinishPage.Run onChkRun
|
||||
FunctionEnd
|
||||
|
||||
Function onChkRun
|
||||
Pop $R7
|
||||
${NSD_GetState} $mui.FinishPage.Run $7
|
||||
${If} $7 == ${BST_UNCHECKED}
|
||||
${NSD_Uncheck} $mui.FinishPage.ShowReadme
|
||||
EnableWindow $mui.FinishPage.ShowReadme 0
|
||||
${Else}
|
||||
EnableWindow $mui.FinishPage.ShowReadme 1
|
||||
${EndIf}
|
||||
FunctionEnd
|
||||
|
||||
Function .onInstSuccess
|
||||
${MementoSectionSave}
|
||||
FunctionEnd
|
||||
|
||||
######################################################################
|
||||
# Sections
|
||||
Section "Application files"
|
||||
SectionIn RO
|
||||
${INSTALL_TYPE}
|
||||
Call KillProcess
|
||||
# Clean up remnants from the old installer (except for items in "Program Files" which would require elevation).
|
||||
${If} "$OldVersion" == "00.00.00.00"
|
||||
Delete "$DESKTOP\${APP_NAME} Console.lnk"
|
||||
Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME} Website.lnk"
|
||||
Delete "$SMPROGRAMS\${APP_NAME}\Uninstall ${APP_NAME}.lnk"
|
||||
DeleteRegKey ${REG_ROOT} "Software\Microsoft\Windows\CurrentVersion\App Paths\${MAIN_APP_EXE}"
|
||||
${EndIf}
|
||||
|
||||
# Allow reinstall and downgrade by overwriting the files.
|
||||
SetOverwrite on
|
||||
SetOutPath "$INSTDIR"
|
||||
File "${BUILD_DIR}\backrest.exe"
|
||||
File "${BUILD_DIR}\backrest-windows-tray.exe"
|
||||
File "${BUILD_DIR}\LICENSE"
|
||||
File "${BUILD_DIR}\icon.ico"
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
# Start Menu shortcuts.
|
||||
CreateDirectory "$SMPROGRAMS\${APP_NAME}"
|
||||
CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" "" "$INSTDIR\icon.ico" 0
|
||||
CreateShortCut "$SMPROGRAMS\${APP_NAME}\${APP_NAME} UI.lnk" "http://localhost:$UIPort/" "" "$INSTDIR\icon.ico" 0
|
||||
WriteIniStr "$SMPROGRAMS\${APP_NAME}\${APP_NAME} website.url" "InternetShortcut" "URL" "${WEB_SITE}"
|
||||
|
||||
# Registry entries.
|
||||
WriteRegStr ${REG_ROOT} "${REG_UNINSTALL_PATH}" "DisplayName" "${APP_NAME}"
|
||||
WriteRegStr ${REG_ROOT} "${REG_UNINSTALL_PATH}" "UninstallString" "$INSTDIR\uninstall.exe"
|
||||
WriteRegStr ${REG_ROOT} "${REG_UNINSTALL_PATH}" "DisplayIcon" "$INSTDIR\icon.ico"
|
||||
WriteRegStr ${REG_ROOT} "${REG_UNINSTALL_PATH}" "DisplayVersion" "${VERSION}"
|
||||
WriteRegStr ${REG_ROOT} "${REG_UNINSTALL_PATH}" "Publisher" "${COMP_NAME}"
|
||||
WriteRegStr ${REG_ROOT} "${REG_UNINSTALL_PATH}" "URLInfoAbout" "${WEB_SITE}"
|
||||
WriteRegStr ${REG_ROOT} "${REG_UNINSTALL_PATH}" "InstallLocation" "$INSTDIR"
|
||||
SectionEnd
|
||||
|
||||
${MementoSection} "Run application at startup (recommended)" sect_startup
|
||||
CreateDirectory $SMSTARTUP
|
||||
CreateShortcut "$SMSTARTUP\${APP_NAME}.lnk" "$INSTDIR\${MAIN_APP_EXE}" "" "$INSTDIR\icon.ico" 0
|
||||
${MementoSectionEnd}
|
||||
|
||||
${MementoSection} "Desktop shortcut" sect_desktop
|
||||
CreateShortCut "$DESKTOP\${APP_NAME} UI.lnk" "http://localhost:$UIPort/" "" "$INSTDIR\icon.ico" 0
|
||||
${MementoSectionEnd}
|
||||
${MementoSectionDone}
|
||||
|
||||
# If a previous installation created the shortcuts, remove them when user deselects
|
||||
# upon upgrade/reinstall to honour the new choice.
|
||||
Section "-Remove deselected shortcuts"
|
||||
${IfNot} ${SectionIsSelected} ${sect_startup}
|
||||
Delete "$SMSTARTUP\${APP_NAME}.lnk"
|
||||
${EndIf}
|
||||
${IfNot} ${SectionIsSelected} ${sect_desktop}
|
||||
Delete "$DESKTOP\${APP_NAME} UI.lnk"
|
||||
${EndIf}
|
||||
SectionEnd
|
||||
|
||||
Section "Uninstall"
|
||||
${INSTALL_TYPE}
|
||||
Call un.KillProcess
|
||||
Delete "$INSTDIR\LICENSE"
|
||||
Delete "$INSTDIR\icon.ico"
|
||||
Delete "$INSTDIR\install.lock"
|
||||
Delete "$INSTDIR\restic*.exe"
|
||||
Delete "$INSTDIR\backrest.exe"
|
||||
Delete "$INSTDIR\backrest-windows-tray.exe"
|
||||
Delete "$INSTDIR\uninstall.exe"
|
||||
RmDir "$INSTDIR"
|
||||
# Delete Start Menu shortcuts.
|
||||
Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME}.lnk"
|
||||
Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME} UI.lnk"
|
||||
Delete "$SMPROGRAMS\${APP_NAME}\${APP_NAME} website.url"
|
||||
RmDir "$SMPROGRAMS\${APP_NAME}"
|
||||
# Startup and desktop shortcuts.
|
||||
Delete "$SMSTARTUP\${APP_NAME}.lnk"
|
||||
Delete "$DESKTOP\${APP_NAME} UI.lnk"
|
||||
# Registry key.
|
||||
DeleteRegKey ${REG_ROOT} "${REG_UNINSTALL_PATH}"
|
||||
SectionEnd
|
||||
@@ -0,0 +1,352 @@
|
||||
#define B "Backrest"
|
||||
#define TrayExe "backrest-windows-tray.exe"
|
||||
#define Website "https://github.com/garethgeorge/backrest/"
|
||||
; The following is needed to extract the version from the change log.
|
||||
; If the application executable had the version info, then could use built-in GetVersion* functions.
|
||||
#define fHandle FileOpen("CHANGELOG.md")
|
||||
#expr FileRead(fHandle)
|
||||
#expr FileRead(fHandle)
|
||||
#define Line FileRead(fHandle)
|
||||
#expr FileClose(fHandle)
|
||||
#define VStart Pos("[", Line) + 1
|
||||
#define VEnd Pos("]", Line)
|
||||
#define VLen (VEnd - VStart)
|
||||
#define BackrestVersion Copy(Line, VStart, VLen)
|
||||
|
||||
[Setup]
|
||||
AppName={#B}
|
||||
AppVersion={#BackrestVersion}
|
||||
AppVerName={#B} {#BackrestVersion}
|
||||
AppPublisher=garethgeorge
|
||||
AppPublisherURL={#Website}
|
||||
VersionInfoVersion={#BackrestVersion}
|
||||
DefaultDirName={autopf}\{#B}
|
||||
DefaultGroupName={#B}
|
||||
DisableProgramGroupPage=yes
|
||||
AlwaysShowDirOnReadyPage=yes
|
||||
UninstallDisplayIcon={app}\icon.ico
|
||||
#ifndef Arch
|
||||
#define Arch "x86_64"
|
||||
#endif
|
||||
OutputBaseFilename={#B}Setup-{#Arch}
|
||||
PrivilegesRequired=lowest
|
||||
PrivilegesRequiredOverridesAllowed=dialog
|
||||
UsePreviousPrivileges=no
|
||||
SetupMutex={#B}Setup
|
||||
; Disable built-in RestartManager functionality, see comments under [Files].
|
||||
CloseApplications=no
|
||||
RestartApplications=no
|
||||
#if SameText(Arch, "arm64")
|
||||
#define ArchAllowed "arm64"
|
||||
#else
|
||||
#define ArchAllowed "x64os"
|
||||
#endif
|
||||
ArchitecturesAllowed={#ArchAllowed}
|
||||
ArchitecturesInstallIn64BitMode={#ArchAllowed}
|
||||
SetupLogging=yes
|
||||
UninstallLogging=yes
|
||||
|
||||
[Tasks]
|
||||
Name: "adminstartcurrent"; Description: "Run {#B} as the current user. Automatically start when the current user logs in. Inherits user access to network resources. Configuration is stored in the current user profile."; GroupDescription: "Execution context"; Check: IsAdminInstallMode; Flags: exclusive
|
||||
Name: "adminstartsystem"; Description: "Run {#B} as the system user. Automatically start before any user logs in. Configuration is stored in the system user profile."; GroupDescription: "Execution context"; Check: IsAdminInstallMode; Flags: exclusive unchecked
|
||||
Name: "autostart"; Description: "Automatically start {#B} at log on"; Check: IsUserInstallMode
|
||||
Name: "desktopicon"; Description: "Create a desktop icon"; Flags: unchecked
|
||||
Name: "addtopath"; Description: "Add {#B} directory to PATH ({code:GetEnvTarget})"; Flags: unchecked
|
||||
#define PortDesc "Select a network port for the web interface"
|
||||
Name: "port9898"; Description: "Default (9898)"; GroupDescription: "{#PortDesc}"; Flags: exclusive; Check: IsUserInstallMode
|
||||
Name: "port9899"; Description: "9899"; GroupDescription: "{#PortDesc}"; Flags: exclusive unchecked; Check: IsUserInstallMode
|
||||
Name: "port9900"; Description: "9900"; GroupDescription: "{#PortDesc}"; Flags: exclusive unchecked; Check: IsUserInstallMode
|
||||
Name: "port9901"; Description: "9901"; GroupDescription: "{#PortDesc}"; Flags: exclusive unchecked; Check: IsUserInstallMode
|
||||
Name: "port9902"; Description: "9902"; GroupDescription: "{#PortDesc}"; Flags: exclusive unchecked; Check: IsUserInstallMode
|
||||
|
||||
[Files]
|
||||
; Need to stop Backrest not only when uninstalling but also before upgrades or reinstalls
|
||||
; This is only an issue when Backrest runs as SYSTEM or non-current user. Inno can natively close applications in other cases,
|
||||
; but doing it the same way in all cases for consistency.
|
||||
Source: "LICENSE"; DestDir: "{app}"; Flags: ignoreversion; BeforeInstall: StopBackrest
|
||||
Source: "icon.ico"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "{#TrayExe}"; DestDir: "{app}"; Flags: ignoreversion
|
||||
Source: "backrest.exe"; DestDir: "{app}"; Flags: ignoreversion
|
||||
|
||||
[Icons]
|
||||
; For user install mode only.
|
||||
Name: "{autostartup}\{#B} systray"; Filename: "{app}\{#TrayExe}"; Parameters: "{code:GetPortParam}"; IconFilename: "{app}\icon.ico"; Check: IsUserInstallMode
|
||||
Name: "{group}\{#B} systray"; Filename: "{app}\{#TrayExe}"; Parameters: "{code:GetPortParam}"; IconFilename: "{app}\icon.ico"; Check: IsUserInstallMode
|
||||
; For both modes.
|
||||
Name: "{group}\{#B}{code:GetIconSuffix}"; Filename: "http://localhost:{code:GetPort}/"; IconFilename: "{app}\icon.ico"
|
||||
Name: "{group}\{#B} website"; Filename: "{#Website}"
|
||||
Name: "{autodesktop}\{#B}{code:GetIconSuffix}"; Filename: "http://localhost:{code:GetPort}/"; IconFilename: "{app}\icon.ico"; Tasks: desktopicon
|
||||
|
||||
[Messages]
|
||||
PrivilegesRequiredOverrideText2=%1 can be installed to run with standard or administrative privileges.%n%nIf you need to use Windows VSS feature with "--use-fs-snapshot" restic option, select the administrative option.%nProtecting Backrest web UI with a password is highly recommended, especially with the administrative install.
|
||||
PrivilegesRequiredOverrideAllUsers=Install &system-wide with administrative privileges
|
||||
|
||||
[Run]
|
||||
; Use Task Scheduler to run Backrest elevated. The 30s delay is needed to avoid an issue with tray icon being broken.
|
||||
; The double-quotes escape double-quotes inside the parameter. The backslash escapes double-quotes inside the -Command block.
|
||||
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""$t = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME ; $t.Delay = 'PT30S'; Register-ScheduledTask -Force -TaskName '{#B}' -RunLevel Highest -Trigger $t -Action $(New-ScheduledTaskAction -Execute \""{app}\{#TrayExe}\"" -Argument '--bind-address 127.0.0.1:9897' -WorkingDirectory '{app}') -Settings $(New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -ExecutionTimeLimit 0); Start-ScheduledTask -TaskName '{#B}'"" "; Flags: runascurrentuser logoutput runhidden; Tasks: adminstartcurrent; Check: IsAdminInstallMode
|
||||
; System user task. No need for systray here, and running it without returning control is the only way to stop it gracefully later.
|
||||
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""Register-ScheduledTask -Force -TaskName '{#B}' -RunLevel Highest -User System -Trigger $(New-ScheduledTaskTrigger -AtStartup) -Action $(New-ScheduledTaskAction -Execute \""{app}\backrest.exe\"" -Argument '--bind-address 127.0.0.1:9897' -WorkingDirectory '{app}') -Settings $(New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -ExecutionTimeLimit 0); Start-ScheduledTask -TaskName '{#B}'"" "; Flags: runascurrentuser logoutput runhidden; Tasks: adminstartsystem; Check: IsAdminInstallMode
|
||||
; PATH
|
||||
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""$newp = '{app}'; $a = [Environment]::GetEnvironmentVariable('PATH', '{code:GetEnvTarget}') -split ';' ; if ($a -notcontains $newp) {{ echo 'Adding to PATH'; $a += $newp; $path = $a -join ';' ; [Environment]::SetEnvironmentVariable('PATH', $path, '{code:GetEnvTarget}') }"" "; Flags: logoutput runhidden; Tasks: addtopath
|
||||
; Remove from PATH for existing installation when unchecked.
|
||||
; Reuse the same command in multiple places. Two quotes to escape in preprocessor, two more for the Parameters directive.
|
||||
#define PathDelCmd "-ExecutionPolicy Bypass -Command """"$newp = '{app}'; $a = [Environment]::GetEnvironmentVariable('PATH', '{code:GetEnvTarget}') -split ';' ; if ($a -contains $newp) {{ echo 'Removing from PATH'; $path = ($a | Where-Object {{ $_ -ne $newp }) -join ';' ; [Environment]::SetEnvironmentVariable('PATH', $path, '{code:GetEnvTarget}') }"""" "
|
||||
Filename: "powershell.exe"; Parameters: "{#PathDelCmd}"; Flags: logoutput runhidden; Tasks: not addtopath; Check: IsExistingInstallation
|
||||
|
||||
Filename: "{app}\{#TrayExe}"; Parameters: "{code:GetPortParam}"; Description: "Start {#B} (runs in the system tray)"; Flags: postinstall waituntilidle; Check: IsUserInstallMode
|
||||
Filename: "http://localhost:{code:GetPort}/"; Description: "Open {#B} user interface"; Flags: postinstall shellexec
|
||||
|
||||
[UninstallRun]
|
||||
Filename: "powershell.exe"; Parameters: "{#PathDelCmd}"; Flags: logoutput runhidden; RunOnceId: "RemoveFromPath"
|
||||
|
||||
[UninstallDelete]
|
||||
Type: files; Name: "{app}\restic*.exe"
|
||||
Type: files; Name: "{app}\install.lock"
|
||||
; Built-in deletion runs before this section and fails to remove the directory due to the files above.
|
||||
Type: dirifempty; Name: "{app}"
|
||||
|
||||
[Code]
|
||||
var
|
||||
UserInstallationExists, AdminInstallationExists: Boolean;
|
||||
PreviousAdminUser, PreviousAdminTasks, PreviousVersionUser, PreviousVersionAdmin: String;
|
||||
AppName, AppDirAdmin, AppDirUser, RegKey, Cmd: String;
|
||||
|
||||
procedure AssignGlobals();
|
||||
begin
|
||||
AppName := ExpandConstant('{#SetupSetting("AppName")}');
|
||||
Cmd := ExpandConstant('{cmd}');
|
||||
RegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + AppName + '_is1';
|
||||
if RegQueryStringValue(HKEY_CURRENT_USER, RegKey, 'Inno Setup: App Path', AppDirUser)
|
||||
then UserInstallationExists := True else UserInstallationExists := False;
|
||||
if RegQueryStringValue(HKEY_LOCAL_MACHINE, RegKey, 'Inno Setup: App Path', AppDirAdmin)
|
||||
then AdminInstallationExists := True else AdminInstallationExists := False;
|
||||
RegQueryStringValue(HKEY_CURRENT_USER, RegKey, 'DisplayVersion', PreviousVersionUser)
|
||||
RegQueryStringValue(HKEY_LOCAL_MACHINE, RegKey, 'DisplayVersion', PreviousVersionAdmin)
|
||||
RegQueryStringValue(HKEY_LOCAL_MACHINE, RegKey, 'Inno Setup: Selected Tasks', PreviousAdminTasks);
|
||||
RegQueryStringValue(HKEY_LOCAL_MACHINE, RegKey, 'Inno Setup: User', PreviousAdminUser);
|
||||
end;
|
||||
|
||||
function IsUserInstallMode(): Boolean;
|
||||
begin
|
||||
if IsAdminInstallMode then Result := False else Result := True;
|
||||
end;
|
||||
|
||||
function GetPort(Param: String): String;
|
||||
var
|
||||
S: String;
|
||||
A: array of String;
|
||||
i: Integer;
|
||||
begin
|
||||
if IsAdminInstallMode then Result := '9897'
|
||||
else
|
||||
begin
|
||||
A := StringSplit(WizardSelectedTasks(False), [','], stAll);
|
||||
for i := 0 to GetArrayLength(A) - 1 do
|
||||
begin
|
||||
S := A[i];
|
||||
if Pos('port', S) > 0 then StringChangeEx(S, 'port', '', True);
|
||||
end;
|
||||
Result := S;
|
||||
end;
|
||||
end;
|
||||
|
||||
function GetPortParam(Param: String): String;
|
||||
var
|
||||
S: String;
|
||||
begin
|
||||
S := GetPort('');
|
||||
// Don't add any shortcut parameters for default port selection.
|
||||
if S = '9898' then Result := '' else Result := '--bind-address 127.0.0.1:' + S;
|
||||
end;
|
||||
|
||||
function GetIconSuffix(Param: String): String;
|
||||
begin
|
||||
if IsAdminInstallMode and UserInstallationExists then Result := ' (system-wide)'
|
||||
else if IsUserInstallMode and AdminInstallationExists then Result := ' (current user)'
|
||||
else Result := '';
|
||||
end;
|
||||
|
||||
function GetEnvTarget(Param: String): String;
|
||||
begin
|
||||
if IsAdminInstallMode then Result := 'Machine' else Result := 'User';
|
||||
end;
|
||||
|
||||
function IsExistingInstallation(): Boolean;
|
||||
begin
|
||||
if UserInstallationExists or AdminInstallationExists then Result := True else Result := False;
|
||||
end;
|
||||
|
||||
procedure StopBackrest();
|
||||
var
|
||||
ResultCode: Integer;
|
||||
begin
|
||||
// Attempt to terminate Backrest gracefully for the current user, wait for a second (since taskkill returns immediately),
|
||||
// then check and kill forcefully if it's still running.
|
||||
if IsUserInstallMode then
|
||||
ExecAndLogOutput(Cmd, '/C "taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest-windows-tray.exe" & ping -n 2 127.0.0.1 >nul & tasklist /FI "USERNAME eq %USERNAME%" | findstr /I /V "setup" | findstr "backrest" && (echo Forcing & taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest-windows-tray.exe" /F & taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest.exe" /F)" ',
|
||||
'', SW_HIDE, ewWaitUntilTerminated, ResultCode, nil)
|
||||
// For admin installs stop through the task scheduler. For the SYSTEM or non-current user this is the only way to stop gracefully.
|
||||
// Ending the scheduled task makes a gracefull attempt, then force kills.
|
||||
else if IsAdminInstallMode then
|
||||
begin
|
||||
ExecAndLogOutput(Cmd, '/C schtasks /End /TN ' + AppName + ' || (taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest-windows-tray.exe" /F & taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest.exe" /F)',
|
||||
'', SW_HIDE, ewWaitUntilTerminated, ResultCode, nil);
|
||||
// Remove the task when uninstalling.
|
||||
if IsUninstaller then
|
||||
ExecAndLogOutput(Cmd, '/C schtasks /Delete /TN ' + AppName + ' /F', '', SW_HIDE, ewWaitUntilTerminated, ResultCode, nil);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure ShowUpgradeMsg(AppDir: String; InstalledVersion: String);
|
||||
var
|
||||
AppVersion, Msg: String;
|
||||
InstalledVersionFull, NewVersionFull: Int64;
|
||||
CompResult: Integer;
|
||||
begin
|
||||
AppVersion := ExpandConstant('{#SetupSetting("AppVersion")}');
|
||||
// Convert both old and new versions to the correct type. It also automatically adds '.0' if '0.0.0' format is used.
|
||||
if StrToVersion(InstalledVersion, InstalledVersionFull) and StrToVersion(AppVersion, NewVersionFull) then
|
||||
begin
|
||||
CompResult := ComparePackedVersion(InstalledVersionFull, NewVersionFull);
|
||||
if CompResult < 0 then Msg := 'upgrade'
|
||||
else if CompResult = 0 then Msg := 'reinstall'
|
||||
else if CompResult > 0 then Msg := 'downgrade'
|
||||
else Msg := 'upgrade/reinstall/downgrade';
|
||||
end;
|
||||
MsgBox('Detected existing installation of Backrest ' + InstalledVersion +
|
||||
' in ' + Chr(13) + Chr(10) + AppDir + Chr(13) + Chr(10) + Chr(13) + Chr(10) +
|
||||
'Setup will ' + Msg + ' to version ' + AppVersion + '.', mbInformation, MB_OK);
|
||||
end;
|
||||
|
||||
function NextButtonClick(CurPageID: Integer): Boolean;
|
||||
var
|
||||
ResultPortCheck: Integer;
|
||||
begin
|
||||
Result := True
|
||||
if CurPageID = wpSelectTasks then
|
||||
begin
|
||||
// Prevent installing an admin instance when a user instance exists under the same user.
|
||||
// It wouldn't work anyway due to the same configuration path.
|
||||
if IsAdminInstallMode and WizardIsTaskSelected('adminstartcurrent') and UserInstallationExists then
|
||||
begin
|
||||
MsgBox('Detected an existing non-administrative installation under the same user ' + GetUserNameString +
|
||||
'. Cannot proceed with this selection.', mbError, MB_OK)
|
||||
Result := False;
|
||||
end
|
||||
else if IsUserInstallMode and not UserInstallationExists then
|
||||
begin
|
||||
if ExecAndLogOutput(Cmd, '/C netstat -na | findstr LISTENING | findstr /C:":' + GetPort('') + ' "', '', SW_HIDE,
|
||||
ewWaitUntilTerminated, ResultPortCheck, nil) and (ResultPortCheck = 0) then
|
||||
begin
|
||||
MsgBox('Selected port is in use, choose another one and try again.', mbError, MB_OK)
|
||||
Result := False;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure RunListClickCheck(Sender: TObject);
|
||||
begin
|
||||
if not WizardForm.RunList.Checked[0] then
|
||||
begin
|
||||
WizardForm.RunList.Checked[1] := False;
|
||||
WizardForm.RunList.ItemEnabled[1] := False;
|
||||
end
|
||||
else WizardForm.RunList.ItemEnabled[1] := True;
|
||||
end;
|
||||
|
||||
procedure CurPageChanged(CurPageID: Integer);
|
||||
var
|
||||
i: Integer;
|
||||
begin
|
||||
if CurPageID = wpSelectTasks then
|
||||
// Prevent the user from switching installation type back and forth in admin mode.
|
||||
// It would cause issues with terminating processes and also with Backrest data being in different user profiles.
|
||||
if IsAdminInstallMode and AdminInstallationExists then
|
||||
for i := 1 to 2 do WizardForm.TasksList.ItemEnabled[i] := False
|
||||
end;
|
||||
|
||||
procedure InitializeWizard();
|
||||
begin
|
||||
WizardForm.ReadyMemo.ScrollBars := ssVertical;
|
||||
WizardForm.ReadyMemo.WordWrap := True;
|
||||
// Uncheck and disable the "open" checkbox when the "start" is unchecked on the Finish page.
|
||||
if IsUserInstallMode then WizardForm.RunList.OnClickCheck := @RunListClickCheck;
|
||||
end;
|
||||
|
||||
function InitializeSetup(): Boolean;
|
||||
var
|
||||
OldUninstaller: String;
|
||||
I: Integer;
|
||||
begin
|
||||
Result := True;
|
||||
AssignGlobals;
|
||||
// Check for presence of the old Nullsoft installation.
|
||||
if RegQueryStringValue(HKEY_CURRENT_USER, 'Software\Microsoft\Windows\CurrentVersion\Uninstall\Backrest',
|
||||
'UninstallString', OldUninstaller) then
|
||||
begin
|
||||
MsgBox('Detected an existing installation done by the old installer that needs to be uninstalled before proceeding. Your configuration will not be impacted.' + Chr(13) + Chr(10) + Chr(13) + Chr(10) +
|
||||
'Re-run this setup after uninstallation is complete.', mbInformation, MB_OK);
|
||||
ExecAsOriginalUser(OldUninstaller, '', '', SW_SHOWNORMAL, ewNoWait, I);
|
||||
Abort;
|
||||
end;
|
||||
// Upgrade/reinstall scenarios.
|
||||
if IsUserInstallMode and UserInstallationExists then ShowUpgradeMsg(AppDirUser, PreviousVersionUser)
|
||||
else if IsUserInstallMode and AdminInstallationExists then
|
||||
begin
|
||||
if GetUserNameString = PreviousAdminUser then
|
||||
begin
|
||||
// Prevent installing a user instance when an admin instance already exists under the same user.
|
||||
MsgBox('Detected an existing administrative installation under the same user ' + PreviousAdminUser +
|
||||
'. Cannot proceed. Uninstall it and try again. Setup will exit now.', mbError, MB_OK);
|
||||
Result := False;
|
||||
end
|
||||
else begin
|
||||
// But allow and notify about an existing admin instance under a different user, if present.
|
||||
MsgBox('Detected an existing administrative installation of Backrest ' + PreviousVersionAdmin + ' under user ' +
|
||||
PreviousAdminUser + '.' + Chr(13) + Chr(10) + Chr(13) + Chr(10) +
|
||||
'Setup will install a non-administrative instance for the current user.', mbInformation, MB_OK);
|
||||
end;
|
||||
end
|
||||
// Notify the user about the existing installation of the same type, if present.
|
||||
else if IsAdminInstallMode and AdminInstallationExists then
|
||||
begin
|
||||
ShowUpgradeMsg(AppDirAdmin, PreviousVersionAdmin);
|
||||
// Warn if attempting to upgrade/reinstall under a different user.
|
||||
if (Pos('adminstartcurrent', PreviousAdminTasks) > 0) and (GetUserNameString <> PreviousAdminUser) then
|
||||
MsgBox('Warning! Previous installation of this type was done by user ' + PreviousAdminUser +
|
||||
'. If you proceed, Backrest will be reinstalled to run under the current user. Configuration will be lost.', mbError, MB_OK);
|
||||
end;
|
||||
end;
|
||||
|
||||
function InitializeUninstall(): Boolean;
|
||||
begin
|
||||
Result := True;
|
||||
AssignGlobals;
|
||||
if AdminInstallationExists and UserInstallationExists then
|
||||
MsgBox('Detected two existing installations of Backrest. Ensure you are running the correct uninstaller. Windows Settings - Apps panel shows only one entry at a time.' + Chr(13) + Chr(10) +
|
||||
'Use Control Panel - Programs and Features instead to identify the correct uninstaller. Or run unins000.exe directly from the appropriate Backrest directory.', mbInformation, MB_OK);
|
||||
end;
|
||||
|
||||
procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);
|
||||
var
|
||||
BackrestConfig: String;
|
||||
begin
|
||||
// Under the system user installation a special profile is used.
|
||||
if IsAdminInstallMode and AdminInstallationExists and (Pos('adminstartsystem', PreviousAdminTasks) > 0) then
|
||||
BackrestConfig := 'C:\Windows\system32\config\systemprofile\AppData\Roaming\backrest'
|
||||
else
|
||||
BackrestConfig := GetEnv('APPDATA') + '\backrest';
|
||||
case CurUninstallStep of
|
||||
usUninstall:
|
||||
StopBackrest;
|
||||
usDone:
|
||||
begin
|
||||
if MsgBox('Do you want to delete Backrest configuration in this location?' + Chr(13) + Chr(10) + BackrestConfig,
|
||||
mbConfirmation, MB_YESNO or MB_DEFBUTTON2) = IDYES then
|
||||
if DelTree(BackrestConfig, True, True, True) then MsgBox('Done!', mbInformation, MB_OK)
|
||||
else MsgBox('Failed to remove the path', mbInformation, MB_OK);
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
Reference in New Issue
Block a user