//go:build windows package winsvc import ( "fmt" "time" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/mgr" ) const ( ServiceName = "NexusRMMAgent" ServiceDisplayName = "NexusRMM Agent" ServiceDescription = "NexusRMM Remote Monitoring and Management Agent" ) // IsWindowsService gibt true zurück wenn der Prozess als Windows Service läuft. func IsWindowsService() (bool, error) { return svc.IsWindowsService() } type agentSvc struct { runFn func(stop <-chan struct{}) } func (s *agentSvc) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown changes <- svc.Status{State: svc.StartPending} stop := make(chan struct{}) done := make(chan struct{}) go func() { defer close(done) s.runFn(stop) }() changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} loop: for { select { case c := <-r: switch c.Cmd { case svc.Stop, svc.Shutdown: changes <- svc.Status{State: svc.StopPending} close(stop) break loop } case <-done: break loop } } // Auf Agent-Shutdown warten (max 10s) select { case <-done: case <-time.After(10 * time.Second): } return false, 0 } // Run führt den Agent als Windows Service aus. // runFn wird in einer Goroutine gestartet; stop wird geschlossen wenn der Service gestoppt wird. func Run(runFn func(stop <-chan struct{})) error { return svc.Run(ServiceName, &agentSvc{runFn: runFn}) } // Install registriert den Service im Windows Service Manager. // exePath muss der absolute Pfad zur nexus-agent.exe sein. func Install(exePath string) error { m, err := mgr.Connect() if err != nil { return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err) } defer m.Disconnect() // Prüfen ob Service bereits existiert existing, err := m.OpenService(ServiceName) if err == nil { existing.Close() return fmt.Errorf("Service '%s' ist bereits installiert", ServiceName) } s, err := m.CreateService(ServiceName, exePath, mgr.Config{ DisplayName: ServiceDisplayName, Description: ServiceDescription, StartType: mgr.StartAutomatic, ErrorControl: mgr.ServiceRestart, }) if err != nil { return fmt.Errorf("Service erstellen fehlgeschlagen: %w", err) } defer s.Close() return nil } // Uninstall entfernt den Service (stoppt ihn vorher falls nötig). func Uninstall() error { m, err := mgr.Connect() if err != nil { return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err) } defer m.Disconnect() s, err := m.OpenService(ServiceName) if err != nil { return fmt.Errorf("Service '%s' nicht gefunden: %w", ServiceName, err) } defer s.Close() // Service stoppen falls er läuft status, err := s.Query() if err == nil && status.State != svc.Stopped { if _, err := s.Control(svc.Stop); err == nil { // Kurz warten bis er gestoppt ist for i := 0; i < 10; i++ { time.Sleep(500 * time.Millisecond) st, e := s.Query() if e != nil || st.State == svc.Stopped { break } } } } if err := s.Delete(); err != nil { return fmt.Errorf("Service löschen fehlgeschlagen: %w", err) } return nil } // Start startet den installierten Service. func Start() error { m, err := mgr.Connect() if err != nil { return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err) } defer m.Disconnect() s, err := m.OpenService(ServiceName) if err != nil { return fmt.Errorf("Service '%s' nicht gefunden: %w", ServiceName, err) } defer s.Close() return s.Start() } // Stop stoppt den laufenden Service. func Stop() error { m, err := mgr.Connect() if err != nil { return fmt.Errorf("Service Manager Verbindung fehlgeschlagen: %w", err) } defer m.Disconnect() s, err := m.OpenService(ServiceName) if err != nil { return fmt.Errorf("Service '%s' nicht gefunden: %w", ServiceName, err) } defer s.Close() _, err = s.Control(svc.Stop) return err }