package main import ( "flag" "fmt" "os" "time" "bosun.org/_version" "bosun.org/slog" "golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/debug" "golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/mgr" ) var win_service_command = flag.String("winsvc", "", "For Windows Service, can be install, remove, start, stop") func init() { mains = append(mains, win_service_main) } func win_service_main() { const svcName = "scollector" var err error switch *win_service_command { case "install": err = installService(svcName, "Stack Exchange's Metric Collection Agent") case "remove": err = removeService(svcName) case "start": err = startService(svcName) case "stop": err = controlService(svcName, svc.Stop, svc.Stopped) case "": isIntSess, err := svc.IsAnInteractiveSession() if err != nil { slog.Fatalf("failed to determine if we are running in an interactive session: %v", err) } if !isIntSess { go runService(svcName, false) } return default: slog.Fatalf("unknown winsvc command: %v", *win_service_command) } if err != nil { slog.Fatalf("failed to %s %s: %v", *win_service_command, svcName, err) } os.Exit(0) } func installService(name, desc string) error { exepath, err := exePath() if err != nil { return err } m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() s, err := m.OpenService(name) if err == nil { s.Close() return fmt.Errorf("service %s already exists", name) } s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: name, StartType: mgr.StartAutomatic, Description: desc}) if err != nil { return err } defer s.Close() err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { s.Delete() return fmt.Errorf("SetupEventLogSource() failed: %s", err) } return nil } func removeService(name string) error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() s, err := m.OpenService(name) if err != nil { return fmt.Errorf("service %s is not installed", name) } defer s.Close() err = s.Delete() if err != nil { return err } err = eventlog.Remove(name) if err != nil { return fmt.Errorf("RemoveEventLogSource() failed: %s", err) } return nil } func startService(name string) error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() s, err := m.OpenService(name) if err != nil { return fmt.Errorf("could not access service: %v", err) } defer s.Close() err = s.Start() if err != nil { return fmt.Errorf("could not start service: %v", err) } return nil } func controlService(name string, c svc.Cmd, to svc.State) error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() s, err := m.OpenService(name) if err != nil { return fmt.Errorf("could not access service: %v", err) } defer s.Close() status, err := s.Control(c) if err != nil { return fmt.Errorf("could not send control=%d: %v", c, err) } timeout := time.Now().Add(10 * time.Second) for status.State != to { if timeout.Before(time.Now()) { return fmt.Errorf("timeout waiting for service to go to state=%d", to) } time.Sleep(300 * time.Millisecond) status, err = s.Query() if err != nil { return fmt.Errorf("could not retrieve service status: %v", err) } } return nil } type s struct{} func (m *s) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown changes <- svc.Status{State: svc.StartPending} changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} loop: for c := range r { switch c.Cmd { case svc.Interrogate: changes <- c.CurrentStatus case svc.Stop, svc.Shutdown: break loop default: slog.Errorf("unexpected control request #%d", c) } } changes <- svc.Status{State: svc.StopPending} return } func runService(name string, isDebug bool) { errFix := fixEventMessageFile(name) //Temp fix. Remove after a few weeks. if errFix != nil { slog.Errorf("%s fixEventMessageFile failed: %v", name, errFix) return } if isDebug { slog.SetEventLog(debug.New(name), 1) } else { elog, err := eventlog.Open(name) if err != nil { return } slog.SetEventLog(elog, 1) defer elog.Close() } slog.Infof("starting service %s%s", name, version.GetVersionInfo("")) run := svc.Run if isDebug { run = debug.Run } err := run(name, &s{}) if err != nil { slog.Errorf("%s service failed: %v", name, err) return } slog.Infof("%s service stopped", name) os.Exit(0) } // This is a temporary method to fix an issue with the EventMessageFile. // See http://stackoverflow.com/questions/29130586 for details. func fixEventMessageFile(src string) error { const addKeyName = `SYSTEM\CurrentControlSet\Services\EventLog\Application` appkey, err := registry.OpenKey(registry.LOCAL_MACHINE, addKeyName, registry.ALL_ACCESS) if err != nil { return err } defer appkey.Close() sk, alreadyExist, err := registry.CreateKey(appkey, src, registry.ALL_ACCESS) if err != nil { return err } defer sk.Close() if alreadyExist { // Update REG_SZ key with expanded value for %systemroot% variable. err = sk.SetStringValue("EventMessageFile", os.ExpandEnv("${systemroot}\\System32\\EventCreate.exe")) if err != nil { return err } } return nil }