//go:build windows package updater import ( "context" "fmt" "log/slog" "os" "os/exec" "path/filepath" "syscall" "time" ) // helperScript is rendered with fmt.Sprintf, args order: // // %[1]s — running binary path (source for the .old copy) // %[2]s — .old path // %[3]s — staged .new path // %[4]s — running binary path (rename target) const helperScript = `@echo off timeout /t 3 /nobreak >nul copy /Y "%[1]s" "%[2]s" sc stop restic-manager-agent :wait sc query restic-manager-agent | find "STOPPED" >nul if errorlevel 1 (timeout /t 1 /nobreak >nul & goto wait) move /Y "%[3]s" "%[4]s" sc start restic-manager-agent del "%%~f0" ` // Update on Windows can't overwrite the running .exe in-process // (exclusive file lock), so we stage the new binary, write a small // detached helper script that waits, stops the service, swaps the // binary, and starts the service, then exit cleanly. SCM treats // clean exits after sc stop as intentional and does not auto-restart; // the helper's final sc start handles that. func Update(ctx context.Context, serverURL string) error { binPath, err := resolveOwnBinary() if err != nil { return err } stage, err := fetch(ctx, serverURL, binPath) if err != nil { return err } helperPath := filepath.Join(filepath.Dir(binPath), "agent-update.cmd") body := fmt.Sprintf(helperScript, binPath, binPath+".old", stage, binPath) if err := os.WriteFile(helperPath, []byte(body), 0o755); err != nil { return err } cmd := exec.Command("cmd.exe", "/c", helperPath) cmd.SysProcAttr = &syscall.SysProcAttr{ HideWindow: true, CreationFlags: 0x00000008 | 0x08000000, // DETACHED_PROCESS | CREATE_NO_WINDOW } if err := cmd.Start(); err != nil { return err } slog.Info("agent self-update: helper spawned, exiting cleanly", "binary", binPath, "helper", helperPath) time.Sleep(200 * time.Millisecond) os.Exit(0) return nil // unreachable } // swap is unused on Windows — the helper script does the swap. // Defined to satisfy the build (UpdateForTest references it). func swap(_, _ string) error { return fmt.Errorf("updater.swap not implemented on Windows; use the helper script via Update") }