//go:build windows package winsvc import ( "fmt" "os" "path/filepath" "time" "github.com/fleetforge/forge-agent/internal/config" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/mgr" ) func InstallService(serverURL, enrollmentToken, label string) error { exePath, err := os.Executable() if err != nil { return fmt.Errorf("failed to get executable path: %w", err) } exePath, err = filepath.EvalSymlinks(exePath) if err != nil { return fmt.Errorf("failed to resolve executable path: %w", err) } cfg := &config.Config{ ServerURL: serverURL, EnrollmentToken: enrollmentToken, Label: label, HeartbeatSec: 30, } if err := config.Save(cfg); err != nil { return fmt.Errorf("failed to save config: %w", err) } fmt.Printf("Config saved to: %s\n", config.ConfigPath()) scm, err := mgr.Connect() if err != nil { return fmt.Errorf("failed to connect to Service Control Manager: %w", err) } defer scm.Disconnect() existing, err := scm.OpenService(ServiceName) if err == nil { existing.Close() return fmt.Errorf("service already exists: %s", ServiceName) } s, err := scm.CreateService(ServiceName, exePath, mgr.Config{ DisplayName: ServiceDisplayName, Description: ServiceDescription, StartType: mgr.StartAutomatic, ErrorControl: mgr.ErrorNormal, }, "run", "--service") if err != nil { return fmt.Errorf("failed to create service: %w", err) } defer s.Close() if err := eventlog.InstallAsEventCreate(ServiceName, eventlog.Error|eventlog.Warning|eventlog.Info); err != nil { s.Delete() return fmt.Errorf("failed to install event log source: %w", err) } fmt.Printf("Service '%s' installed successfully.\n", ServiceDisplayName) fmt.Println("The service will start automatically on system boot.") if err := s.Start(); err != nil { fmt.Printf("Warning: Could not start service: %v\n", err) } else { waitUntilRunning(s) fmt.Println("Service started.") } return nil } func UninstallService() error { scm, err := mgr.Connect() if err != nil { return fmt.Errorf("failed to connect to Service Control Manager: %w", err) } defer scm.Disconnect() s, err := scm.OpenService(ServiceName) if err != nil { return fmt.Errorf("service does not exist: %s", ServiceName) } defer s.Close() st, err := s.Control(svc.Stop) if err == nil { waitUntilStopped(s, st) } if err := eventlog.Remove(ServiceName); err != nil { fmt.Printf("Warning: Could not remove event log source: %v\n", err) } if err := s.Delete(); err != nil { return fmt.Errorf("failed to delete service: %w", err) } fmt.Printf("Service '%s' removed successfully.\n", ServiceDisplayName) if err := config.Delete(); err != nil && !os.IsNotExist(err) { fmt.Printf("Warning: Could not remove config file: %v\n", err) } else { fmt.Println("Config file removed.") } return nil } func waitUntilStopped(s *mgr.Service, initialStatus svc.Status) { state := initialStatus.State for i := 0; i < 30; i++ { if state == svc.Stopped { return } time.Sleep(time.Second) st, err := s.Query() if err != nil { return } state = st.State } } func waitUntilRunning(s *mgr.Service) { for i := 0; i < 30; i++ { st, err := s.Query() if err != nil { return } if st.State == svc.Running { return } time.Sleep(time.Second) } }