fix: merge windows tray functionality into backrest.exe for single binary install on windows (#903)
Some checks failed
Release Please / release-please (push) Has been cancelled
Release Preview / call-reusable-release (push) Has been cancelled
Test / test-nix (push) Has been cancelled
Test / test-win (push) Has been cancelled
Update Restic / update-restic-version (push) Has been cancelled

This commit is contained in:
Gareth
2025-09-28 20:57:15 -07:00
committed by GitHub
parent 25d2771fd7
commit 3ccb883b2f
6 changed files with 30 additions and 70 deletions

View File

@@ -25,7 +25,6 @@ builds:
- CGO_ENABLED=0
goos:
- darwin
- windows
- freebsd
goarch:
- amd64
@@ -44,18 +43,17 @@ builds:
- arm
ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}
- id: backrestmon
main: ./cmd/backrestmon
binary: backrest-windows-tray
- id: windows
main: ./cmd/backrest
env:
- CGO_ENABLED=1
- GO111MODULE=on
ldflags: -H=windowsgui
goos:
- windows
goarch:
- amd64
- arm64
ldflags:
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -H windowsgui
archives:
- format: tar.gz

View File

@@ -1,5 +1,4 @@
#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.
@@ -65,13 +64,12 @@ Name: "port9902"; Description: "9902"; GroupDescription: "{#PortDesc}"; Flags: e
; 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
Name: "{autostartup}\{#B} systray"; Filename: "{app}\backrest.exe"; Parameters: "--windows-tray {code:GetPortParam}"; IconFilename: "{app}\icon.ico"; Check: IsUserInstallMode
Name: "{group}\{#B} systray"; Filename: "{app}\backrest.exe"; Parameters: "--windows-tray {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}"
@@ -84,7 +82,7 @@ PrivilegesRequiredOverrideAllUsers=Install &system-wide with administrative priv
[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'; $s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries; $s.ExecutionTimeLimit = 'PT0S'; 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 $s ; Start-ScheduledTask -TaskName '{#B}'"" "; Flags: runascurrentuser logoutput runhidden; Tasks: adminstartcurrent; Check: IsAdminInstallMode
Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""$t = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME ; $t.Delay = 'PT30S'; $s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries; $s.ExecutionTimeLimit = 'PT0S'; Register-ScheduledTask -Force -TaskName '{#B}' -RunLevel Highest -Trigger $t -Action $(New-ScheduledTaskAction -Execute \""{app}\backrest.exe\"" -Argument '--windows-tray --bind-address 127.0.0.1:9897' -WorkingDirectory '{app}') -Settings $s ; 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 ""$s = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries; $s.ExecutionTimeLimit = 'PT0S'; 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 $s ; Start-ScheduledTask -TaskName '{#B}'"" "; Flags: runascurrentuser logoutput runhidden; Tasks: adminstartsystem; Check: IsAdminInstallMode
; PATH
@@ -94,7 +92,7 @@ Filename: "powershell.exe"; Parameters: "-ExecutionPolicy Bypass -Command ""$new
#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: "{app}\backrest.exe"; Parameters: "--windows-tray {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]
@@ -184,13 +182,13 @@ 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)" ',
ExecAndLogOutput(Cmd, '/C "taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest.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.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)',
ExecAndLogOutput(Cmd, '/C schtasks /End /TN ' + AppName + ' || (taskkill /FI "USERNAME eq %USERNAME%" /IM "backrest.exe" /F)',
'', SW_HIDE, ewWaitUntilTerminated, ResultCode, nil);
// Remove the task when uninstalling.
if IsUninstaller then

View File

@@ -44,8 +44,7 @@ var (
commit = "unknown"
)
func main() {
flag.Parse()
func runApp() {
installLoggers()
resticPath, err := resticinstaller.FindOrInstallResticBinary()

View File

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,10 @@
//go:build !windows
package main
import "flag"
func main() {
flag.Parse()
runApp()
}

View File

@@ -4,15 +4,11 @@
package main
import (
"context"
"flag"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"runtime"
"syscall"
"github.com/garethgeorge/backrest/internal/env"
"github.com/getlantern/systray"
@@ -24,26 +20,19 @@ import (
//go:embed icon.ico
var icon []byte
var windowsTray = flag.Bool("windows-tray", false, "run the windows tray application")
func main() {
flag.Parse()
backrest, err := findBackrest()
if err != nil {
reportError(err)
return
if *windowsTray {
startTray()
} else {
runApp()
}
}
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, backrest, os.Args[1:]...)
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
cmd.Env = os.Environ()
cmd.Env = append(cmd.Env, "ENV=production")
if err := cmd.Start(); err != nil {
reportError(err)
cancel()
return
}
func startTray() {
go runApp()
systray.Run(func() {
systray.SetTitle("Backrest Tray")
@@ -85,44 +74,10 @@ func main() {
mQuit.ClickedCh = make(chan struct{})
go func() {
<-mQuit.ClickedCh
cancel()
systray.Quit()
}()
}, func() {
cancel()
})
if err := cmd.Wait(); err != nil {
systray.Quit()
if ctx.Err() != context.Canceled {
reportError(fmt.Errorf("backrest process exited unexpectedly with error: %w", err))
}
return
}
}
func findBackrest() (string, error) {
// Backrest binary must be installed in the same directory as the backresttray binary.
ex, err := os.Executable()
if err != nil {
return "", err
}
dir := filepath.Dir(ex)
wantPath := filepath.Join(dir, backrestBinName())
if stat, err := os.Stat(wantPath); err == nil && !stat.IsDir() {
return wantPath, nil
}
return "", fmt.Errorf("backrest binary not found at %s", wantPath)
}
func backrestBinName() string {
if runtime.GOOS == "windows" {
return "backrest.exe"
} else {
return "backrest"
}
}
func openBrowser(url string) error {