From 6ce743c39da330ef8ed4e245343faf098785d0af Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Fri, 8 May 2026 12:43:59 +0200 Subject: [PATCH] fix(self-update): auto-restart live daemon after upgrade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Old isRunningAsDaemon() only matched "start" in argv — never true for `unarr self-update`, so the daemon kept running the old binary in memory and heartbeat reported the stale version (web gated features wrong). Now: detect live daemon via state file + isDaemonAlive (PID alive + heartbeat fresh), call runDaemonSvcRestart through the system service manager. On failure show clear manual recovery command instead of leaving the daemon dead. No-op when daemon is not running. --- internal/cmd/self_update.go | 57 +++++++++++++++---------------------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/internal/cmd/self_update.go b/internal/cmd/self_update.go index b936eb0..31fb891 100644 --- a/internal/cmd/self_update.go +++ b/internal/cmd/self_update.go @@ -3,14 +3,11 @@ package cmd import ( "context" "fmt" - "os" - "os/exec" - "runtime" "strings" - "syscall" "github.com/fatih/color" "github.com/spf13/cobra" + "github.com/torrentclaw/unarr/internal/agent" "github.com/torrentclaw/unarr/internal/upgrade" ) @@ -23,7 +20,11 @@ func newSelfUpdateCmd() *cobra.Command { Long: `Download and install the latest version of unarr. Checks GitHub for the latest release, verifies the checksum, and -replaces the current binary. A backup is kept at .backup.`, +replaces the current binary. A backup is kept at .backup. + +If the daemon is running, it is automatically restarted so the new +version is loaded into memory (otherwise heartbeat would keep +reporting the old version until a manual restart).`, Example: ` unarr self-update unarr self-update --force`, RunE: func(cmd *cobra.Command, args []string) error { @@ -40,12 +41,12 @@ func runSelfUpdate(force bool) error { bold := color.New(color.Bold) green := color.New(color.FgGreen) yellow := color.New(color.FgYellow) + red := color.New(color.FgRed) fmt.Println() bold.Println(" unarr self-update") fmt.Println() - // Check latest version fmt.Print(" Checking latest version... ") ctx := context.Background() latest, err := upgrade.CheckLatest(ctx) @@ -89,37 +90,25 @@ func runSelfUpdate(force bool) error { if result.BackupPath != "" { fmt.Printf(" Backup: %s\n", result.BackupPath) } - fmt.Println() - // If running as daemon, re-exec to restart with new binary - // For interactive use, just suggest restarting - if isRunningAsDaemon() { - fmt.Println(" Restarting daemon with new version...") - binPath, err := os.Executable() - if err != nil { - return fmt.Errorf("could not determine executable path: %w", err) + // Auto-restart daemon if it is running, otherwise the live process keeps + // serving the old version (heartbeat reports old version → web gates + // features against the wrong version). + if state := agent.ReadState(); state != nil && isDaemonAlive(state) { + fmt.Println() + fmt.Printf(" → Daemon running (PID %d), restarting to load new version...\n", state.PID) + if err := runDaemonSvcRestart(); err != nil { + fmt.Println() + red.Printf(" ✗ Auto-restart failed: %v\n", err) + fmt.Println(" The new binary is on disk but the daemon is still running the old version.") + fmt.Println(" Run manually: unarr daemon restart") + fmt.Println(" (If the daemon runs under a different user/session, restart it there.)") + fmt.Println() + return nil } - execErr := syscall.Exec(binPath, os.Args, os.Environ()) - if execErr != nil && runtime.GOOS == "windows" { - // Windows doesn't support syscall.Exec — start new process - proc := exec.Command(binPath, os.Args[1:]...) - proc.Stdout = os.Stdout - proc.Stderr = os.Stderr - proc.Stdin = os.Stdin - return proc.Start() - } - return execErr + green.Println(" ✓ Daemon restarted") } + fmt.Println() return nil } - -func isRunningAsDaemon() bool { - // Simple heuristic: check if "start" was in the original args - for _, arg := range os.Args { - if arg == "start" { - return true - } - } - return false -}