//go:build windows // service_windows.go — Service Control Manager integration for the // agent on Windows (P2-16). Implements the svc.Handler interface so // `restic-manager-agent run` works under both interactive and SCM // contexts. install/uninstall live in install_windows.go. // // UNTESTED on Windows in this repo's CI (the runners are Linux). // The shape mirrors the canonical example in // golang.org/x/sys/windows/svc/example. Treat any deviation from // that example as suspicious. package service import ( "context" "errors" "log/slog" "golang.org/x/sys/windows/svc" ) // ServiceName is the SCM identifier for the agent service. const ServiceName = "restic-manager-agent" // AgentRun is the function the service handler calls to start the // agent's main loop. Pass cmd/agent's run-loop entry point at the // call site so this package stays free of cross-cmd imports. type AgentRun func(ctx context.Context) error // Run delegates to the SCM dispatcher when running under Windows // service control, otherwise runs the agent loop in the foreground // (for `restic-manager-agent run` from a console, e.g. while // debugging on a developer's box). func Run(agentRun AgentRun) error { isService, err := svc.IsWindowsService() if err != nil { return err } if !isService { ctx, cancel := context.WithCancel(context.Background()) defer cancel() return agentRun(ctx) } return svc.Run(ServiceName, &handler{run: agentRun}) } // handler implements svc.Handler. Execute is called once when the // service is started. We spawn the agent loop in a goroutine and // listen for SCM Stop / Shutdown notifications, cancelling the // context to wind down cleanly. type handler struct { run AgentRun } func (h *handler) Execute(_ []string, req <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) { const accepted = svc.AcceptStop | svc.AcceptShutdown status <- svc.Status{State: svc.StartPending} ctx, cancel := context.WithCancel(context.Background()) defer cancel() doneCh := make(chan error, 1) go func() { doneCh <- h.run(ctx) }() status <- svc.Status{State: svc.Running, Accepts: accepted} for { select { case c := <-req: switch c.Cmd { case svc.Interrogate: status <- c.CurrentStatus case svc.Stop, svc.Shutdown: slog.Info("svc: stop requested") cancel() status <- svc.Status{State: svc.StopPending} if err := <-doneCh; err != nil && !errors.Is(err, context.Canceled) { slog.Warn("svc: agent loop exited with error", "err", err) return false, 1 } return false, 0 } case err := <-doneCh: // Agent loop exited on its own — uncommon (only via signal // or fatal error). Surface as an SCM stop. if err != nil && !errors.Is(err, context.Canceled) { slog.Warn("svc: agent loop exited unexpectedly", "err", err) return false, 1 } return false, 0 } } }