From d89ba45284b428f8a94ca166ae6c8860f3da031e Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 30 Nov 2025 12:02:43 +0000 Subject: [PATCH] Fix Windows agent self-update restart failure syscall.Exec is not supported on Windows, causing self-update to fail with "failed to restart: not supported by windows". Split restart logic into platform-specific files: - restart_unix.go: Uses syscall.Exec for in-place process replacement - restart_windows.go: Uses os.Exit(0) to let Windows SCM restart service Related to #735 --- internal/agentupdate/restart_unix.go | 22 ++++++++++++++++++++++ internal/agentupdate/restart_windows.go | 22 ++++++++++++++++++++++ internal/agentupdate/update.go | 12 ++---------- 3 files changed, 46 insertions(+), 10 deletions(-) create mode 100644 internal/agentupdate/restart_unix.go create mode 100644 internal/agentupdate/restart_windows.go diff --git a/internal/agentupdate/restart_unix.go b/internal/agentupdate/restart_unix.go new file mode 100644 index 000000000..26bd1aa29 --- /dev/null +++ b/internal/agentupdate/restart_unix.go @@ -0,0 +1,22 @@ +//go:build !windows + +package agentupdate + +import ( + "fmt" + "os" + "syscall" +) + +// restartProcess replaces the current process with a new instance. +// On Unix-like systems, this uses syscall.Exec for an in-place restart. +func restartProcess(execPath string) error { + args := os.Args + env := os.Environ() + + if err := syscall.Exec(execPath, args, env); err != nil { + return fmt.Errorf("failed to restart: %w", err) + } + + return nil +} diff --git a/internal/agentupdate/restart_windows.go b/internal/agentupdate/restart_windows.go new file mode 100644 index 000000000..4542ec418 --- /dev/null +++ b/internal/agentupdate/restart_windows.go @@ -0,0 +1,22 @@ +//go:build windows + +package agentupdate + +import ( + "os" +) + +// restartProcess triggers a restart on Windows. +// For Windows services, we exit cleanly and rely on the Service Control Manager +// to restart the service (services are typically configured with automatic recovery). +// For non-service processes, os.Exit(0) is still appropriate as the process will +// restart with the new binary on next invocation. +func restartProcess(execPath string) error { + // Exit with code 0 to signal clean shutdown. + // Windows Service Control Manager will restart the service if configured + // for automatic recovery (which is the default for our PowerShell installer). + os.Exit(0) + + // This line is never reached, but satisfies the compiler + return nil +} diff --git a/internal/agentupdate/update.go b/internal/agentupdate/update.go index 355b5db38..37d5235a6 100644 --- a/internal/agentupdate/update.go +++ b/internal/agentupdate/update.go @@ -18,7 +18,6 @@ import ( "path/filepath" "runtime" "strings" - "syscall" "time" "github.com/rcourtman/pulse-go-rewrite/internal/utils" @@ -430,15 +429,8 @@ func (u *Updater) performUpdate(ctx context.Context) error { } } - // Restart with same arguments - args := os.Args - env := os.Environ() - - if err := syscall.Exec(execPath, args, env); err != nil { - return fmt.Errorf("failed to restart: %w", err) - } - - return nil + // Restart the process using platform-specific implementation + return restartProcess(execPath) } // determineArch returns the architecture string for download URLs (e.g., "linux-amd64", "darwin-arm64").