bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/cmd/scollector/service_windows.go (about) 1 package main 2 3 import ( 4 "flag" 5 "fmt" 6 "os" 7 "time" 8 9 "bosun.org/_version" 10 "bosun.org/slog" 11 "golang.org/x/sys/windows/svc" 12 "golang.org/x/sys/windows/svc/debug" 13 "golang.org/x/sys/windows/svc/eventlog" 14 "golang.org/x/sys/windows/svc/mgr" 15 ) 16 17 var win_service_command = flag.String("winsvc", "", "For Windows Service, can be install, remove, start, stop") 18 19 var serviceRunning = false 20 21 func init() { 22 mains = append(mains, win_service_main) 23 } 24 25 func win_service_main() { 26 const svcName = "scollector" 27 var err error 28 switch *win_service_command { 29 case "install": 30 err = installService(svcName, "Stack Exchange's Metric Collection Agent") 31 case "remove": 32 err = removeService(svcName) 33 case "start": 34 err = startService(svcName) 35 case "stop": 36 err = controlService(svcName, svc.Stop, svc.Stopped) 37 case "": 38 isIntSess, err := svc.IsAnInteractiveSession() 39 if err != nil { 40 slog.Fatalf("failed to determine if we are running in an interactive session: %v", err) 41 } 42 if !isIntSess { 43 go runService(svcName, false) 44 for { 45 //Need to wait for service go routine to finish initializing. Otherwise the collector goroutines could 46 //use all the CPU and cause Windows Service API to bail with a service unresponsive on startup error. 47 //If service doesn't start within 30 seconds then the Windows Service API will kill the process. 48 time.Sleep(time.Millisecond * 200) 49 if serviceRunning { 50 break 51 } 52 } 53 } 54 return 55 default: 56 slog.Fatalf("unknown winsvc command: %v", *win_service_command) 57 } 58 if err != nil { 59 slog.Fatalf("failed to %s %s: %v", *win_service_command, svcName, err) 60 } 61 os.Exit(0) 62 } 63 64 func installService(name, desc string) error { 65 exepath, err := exePath() 66 if err != nil { 67 return err 68 } 69 m, err := mgr.Connect() 70 if err != nil { 71 return err 72 } 73 defer m.Disconnect() 74 s, err := m.OpenService(name) 75 if err == nil { 76 s.Close() 77 return fmt.Errorf("service %s already exists", name) 78 } 79 s, err = m.CreateService(name, exepath, mgr.Config{DisplayName: name, 80 StartType: mgr.StartAutomatic, 81 Description: desc}) 82 if err != nil { 83 return err 84 } 85 defer s.Close() 86 err = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info) 87 if err != nil { 88 s.Delete() 89 return fmt.Errorf("SetupEventLogSource() failed: %s", err) 90 } 91 return nil 92 } 93 94 func removeService(name string) error { 95 m, err := mgr.Connect() 96 if err != nil { 97 return err 98 } 99 defer m.Disconnect() 100 s, err := m.OpenService(name) 101 if err != nil { 102 return fmt.Errorf("service %s is not installed", name) 103 } 104 defer s.Close() 105 err = s.Delete() 106 if err != nil { 107 return err 108 } 109 err = eventlog.Remove(name) 110 if err != nil { 111 return fmt.Errorf("RemoveEventLogSource() failed: %s", err) 112 } 113 return nil 114 } 115 116 func startService(name string) error { 117 m, err := mgr.Connect() 118 if err != nil { 119 return err 120 } 121 defer m.Disconnect() 122 s, err := m.OpenService(name) 123 if err != nil { 124 return fmt.Errorf("could not access service: %v", err) 125 } 126 defer s.Close() 127 err = s.Start() 128 if err != nil { 129 return fmt.Errorf("could not start service: %v", err) 130 } 131 return nil 132 } 133 134 func controlService(name string, c svc.Cmd, to svc.State) error { 135 m, err := mgr.Connect() 136 if err != nil { 137 return err 138 } 139 defer m.Disconnect() 140 s, err := m.OpenService(name) 141 if err != nil { 142 return fmt.Errorf("could not access service: %v", err) 143 } 144 defer s.Close() 145 status, err := s.Control(c) 146 if err != nil { 147 return fmt.Errorf("could not send control=%d: %v", c, err) 148 } 149 timeout := time.Now().Add(35 * time.Second) 150 for status.State != to { 151 if timeout.Before(time.Now()) { 152 return fmt.Errorf("timeout waiting for service to go to state=%d", to) 153 } 154 time.Sleep(300 * time.Millisecond) 155 status, err = s.Query() 156 if err != nil { 157 return fmt.Errorf("could not retrieve service status: %v", err) 158 } 159 } 160 return nil 161 } 162 163 type s struct{} 164 165 func (m *s) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { 166 const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown 167 changes <- svc.Status{State: svc.StartPending} 168 changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 169 serviceRunning = true 170 loop: 171 for c := range r { 172 switch c.Cmd { 173 case svc.Interrogate: 174 changes <- c.CurrentStatus 175 case svc.Stop, svc.Shutdown: 176 break loop 177 default: 178 slog.Errorf("unexpected control request #%d", c) 179 } 180 } 181 changes <- svc.Status{State: svc.StopPending} 182 return 183 } 184 185 func runService(name string, isDebug bool) { 186 if isDebug { 187 slog.SetEventLog(debug.New(name), 1) 188 } else { 189 elog, err := eventlog.Open(name) 190 if err != nil { 191 return 192 } 193 slog.SetEventLog(elog, 1) 194 defer elog.Close() 195 } 196 slog.Infof("starting service %s%s", name, version.GetVersionInfo("")) 197 run := svc.Run 198 if isDebug { 199 run = debug.Run 200 } 201 err := run(name, &s{}) 202 if err != nil { 203 slog.Errorf("%s service failed: %v", name, err) 204 return 205 } 206 slog.Infof("%s service stopped", name) 207 os.Exit(0) 208 }