//go:build windows // install_windows.go — thin wrappers around the Service Control // Manager via golang.org/x/sys/windows/svc/mgr. Used by the agent's // `install` / `uninstall` / `start` / `stop` subcommands. // // UNTESTED in CI. Mirrors the canonical example shape; if you need // to extend this, prefer copying from x/sys/windows/svc/example // over inventing new patterns. package service import ( "fmt" "os" "path/filepath" "golang.org/x/sys/windows/svc/mgr" ) // Install registers the service with the SCM, pointing it at the // currently-running binary. The service starts on every boot and // runs as LocalSystem (default). func Install() error { exe, err := os.Executable() if err != nil { return fmt.Errorf("install: locate executable: %w", err) } exe, err = filepath.Abs(exe) if err != nil { return fmt.Errorf("install: absolutise path: %w", err) } m, err := mgr.Connect() if err != nil { return fmt.Errorf("install: connect SCM: %w", err) } defer m.Disconnect() if existing, err := m.OpenService(ServiceName); err == nil { _ = existing.Close() return fmt.Errorf("service %q already installed; uninstall first", ServiceName) } s, err := m.CreateService(ServiceName, exe, mgr.Config{ StartType: mgr.StartAutomatic, DisplayName: "Restic-manager agent", Description: "Backs up this host on the schedule the central restic-manager dictates.", }, "run") if err != nil { return fmt.Errorf("install: create service: %w", err) } defer s.Close() return nil } // Uninstall removes the service from the SCM. Caller is expected to // stop the service first; this returns the SCM's error if it's // still running. func Uninstall() error { m, err := mgr.Connect() if err != nil { return fmt.Errorf("uninstall: connect SCM: %w", err) } defer m.Disconnect() s, err := m.OpenService(ServiceName) if err != nil { return fmt.Errorf("uninstall: open service: %w", err) } defer s.Close() if err := s.Delete(); err != nil { return fmt.Errorf("uninstall: delete service: %w", err) } return nil } // Start asks the SCM to start the installed service. No-op if it's // already running (the SCM returns an error which we surface). func Start() error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() s, err := m.OpenService(ServiceName) if err != nil { return err } defer s.Close() return s.Start() } // Stop sends a stop control to the service. func Stop() error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() s, err := m.OpenService(ServiceName) if err != nil { return err } defer s.Close() _, err = s.Control(0x00000001) // SERVICE_CONTROL_STOP return err }