gitee.com/h79/goutils@v1.22.10/common/svc/winsvc.go (about) 1 //go:build windows 2 3 package svc 4 5 import ( 6 "flag" 7 "fmt" 8 "gitee.com/h79/goutils/common/logger" 9 "gitee.com/h79/goutils/common/system" 10 "golang.org/x/sys/windows" 11 "golang.org/x/sys/windows/svc" 12 "golang.org/x/sys/windows/svc/debug" 13 "golang.org/x/sys/windows/svc/mgr" 14 "os" 15 "path/filepath" 16 "time" 17 ) 18 19 const ( 20 kServiceName = "serviceName" 21 kServiceDesc = "serviceDesc" 22 kServiceInstall = "serviceInstall" 23 kServiceRemove = "serviceRemove" 24 kServiceStart = "serviceStart" 25 kServiceReStart = "serviceReStart" 26 kServiceStop = "serviceStop" 27 ) 28 29 var ( 30 appPath string 31 serviceName string 32 serviceDesc string 33 34 flagServiceInstall = flag.Bool(kServiceInstall, false, "Install service") 35 flagServiceRemove = flag.Bool(kServiceRemove, false, "Remove service") 36 flagServiceStart = flag.Bool(kServiceStart, false, "Start service") 37 flagServiceReStart = flag.Bool(kServiceReStart, false, "ReStart service") 38 flagServiceStop = flag.Bool(kServiceStop, false, "Stop service") 39 ) 40 41 func init() { 42 43 flag.StringVar(&serviceName, kServiceName, "", "Set service name") 44 flag.StringVar(&serviceDesc, kServiceDesc, "", "Set service description") 45 46 // change to current dir 47 var err error 48 if appPath, err = getExePath(); err != nil { 49 logger.Fatal("err= %v", err) 50 } 51 if err = os.Chdir(filepath.Dir(appPath)); err != nil { 52 logger.Fatal("err= %v", err) 53 } 54 } 55 56 func DoService(conf Config, startService func(isWindowsService bool), stopService func()) { 57 if len(serviceName) <= 0 { 58 serviceName = conf.ServiceName 59 } 60 if len(serviceDesc) <= 0 { 61 serviceDesc = conf.ServiceDesc 62 } 63 // install service 64 if *flagServiceInstall { 65 if len(serviceName) <= 0 || len(serviceDesc) <= 0 { 66 logger.Error("failed to install service: serviceName OR serviceDesc is empty") 67 os.Exit(-2) 68 } 69 args := os.Args[1:] 70 args = system.StripArgs(args, "-"+kServiceName) 71 args = system.StripArgs(args, "-"+kServiceDesc) 72 args = system.StripArgs(args, "-"+kServiceInstall) 73 if err := Install(appPath, serviceName, serviceDesc, args...); err != nil { 74 logger.Error("failed to install service: %v, err: %v", serviceName, err) 75 } 76 logger.Info("done") 77 os.Exit(0) 78 } 79 80 // uninstall service 81 if *flagServiceRemove { 82 if len(serviceName) <= 0 { 83 logger.Error("failed to uninstall service: serviceName is empty") 84 os.Exit(-2) 85 } 86 if err := Remove(serviceName); err != nil { 87 logger.Error("failed to uninstall service: %v, err: %v", serviceName, err) 88 } else { 89 logger.Info("done") 90 } 91 os.Exit(0) 92 } 93 94 // start service 95 if *flagServiceStart { 96 if len(serviceName) <= 0 { 97 logger.Error("failed to start service: serviceName is empty") 98 os.Exit(-2) 99 } 100 if err := Start(serviceName); err != nil { 101 logger.Error("failed to start service: %v, err: %v", serviceName, err) 102 } else { 103 logger.Info("done") 104 } 105 os.Exit(0) 106 } 107 108 // restart service 109 if *flagServiceReStart { 110 if len(serviceName) <= 0 { 111 logger.Error("failed to restart service: serviceName is empty") 112 os.Exit(-2) 113 } 114 if err := ReStart(serviceName); err != nil { 115 logger.Error("failed to restart service: %v, err: %v", serviceName, err) 116 } else { 117 logger.Info("done") 118 } 119 os.Exit(0) 120 } 121 122 // stop service 123 if *flagServiceStop { 124 if len(serviceName) <= 0 { 125 logger.Error("failed to stop service: serviceName is empty") 126 os.Exit(-2) 127 } 128 if err := Stop(serviceName); err != nil { 129 logger.Error("failed to stop service: %v, err: %v", serviceName, err) 130 } else { 131 logger.Info("done") 132 } 133 os.Exit(0) 134 } 135 136 if !IsWindowsService() { 137 startService(false) 138 return 139 } 140 141 if len(serviceName) <= 0 { 142 logger.Error("failed to Run service: serviceName is empty") 143 os.Exit(-2) 144 } 145 // run as service 146 if err := RunAs(serviceName, startService, stopService, false); err != nil { 147 logger.Error("Run failure, err= %v", err) 148 } 149 } 150 151 func getExePath() (string, error) { 152 prog := os.Args[0] 153 p, err := filepath.Abs(prog) 154 if err != nil { 155 return "", err 156 } 157 fi, err := os.Stat(p) 158 if err == nil { 159 if !fi.Mode().IsDir() { 160 return p, nil 161 } 162 err = fmt.Errorf("GetAppPath: %s is directory", p) 163 } 164 if filepath.Ext(p) == "" { 165 p += ".exe" 166 fi, err = os.Stat(p) 167 if err == nil { 168 if !fi.Mode().IsDir() { 169 return p, nil 170 } 171 err = fmt.Errorf("GetAppPath: %s is directory", p) 172 } 173 } 174 return "", err 175 } 176 177 func IsWindowsService() bool { 178 ok, err := svc.IsWindowsService() 179 if err != nil { 180 fmt.Println("IsWindowsService: err = ", err) 181 } 182 return ok 183 } 184 185 func Install(appPath, name, desc string, params ...string) error { 186 m, err := mgr.Connect() 187 if err != nil { 188 return err 189 } 190 defer m.Disconnect() 191 s, err := m.OpenService(name) 192 if err == nil { 193 s.Close() 194 return fmt.Errorf("InstallService: service %s already exists", name) 195 } 196 s, err = m.CreateService(name, appPath, 197 mgr.Config{ 198 DisplayName: desc, 199 Description: desc, 200 StartType: windows.SERVICE_AUTO_START, 201 }, 202 params..., 203 ) 204 if err != nil { 205 return err 206 } 207 defer s.Close() 208 //_ = eventlog.InstallAsEventCreate(name, eventlog.Error|eventlog.Warning|eventlog.Info) 209 return nil 210 } 211 212 func Remove(name string) error { 213 m, err := mgr.Connect() 214 if err != nil { 215 return err 216 } 217 defer m.Disconnect() 218 s, err := m.OpenService(name) 219 if err != nil { 220 return fmt.Errorf("RemoveService: service %s is not installed", name) 221 } 222 defer s.Close() 223 err = s.Delete() 224 if err != nil { 225 return err 226 } 227 //_ = eventlog.Remove(name) 228 return nil 229 } 230 231 func Start(name string) error { 232 m, err := mgr.Connect() 233 if err != nil { 234 return err 235 } 236 defer m.Disconnect() 237 s, err := m.OpenService(name) 238 if err != nil { 239 return fmt.Errorf("StartService: could not access service: %v", err) 240 } 241 defer s.Close() 242 err = s.Start("p1", "p2", "p3") 243 if err != nil { 244 return fmt.Errorf("StartService: could not start service: %v", err) 245 } 246 return nil 247 } 248 249 func ReStart(name string) error { 250 if err := Control(name, svc.Stop, svc.Stopped); err != nil { 251 return err 252 } 253 return Start(name) 254 } 255 256 func Stop(name string) error { 257 if err := Control(name, svc.Stop, svc.Stopped); err != nil { 258 return err 259 } 260 return nil 261 } 262 263 func Query(name string) (status string, err error) { 264 m, err := mgr.Connect() 265 if err != nil { 266 return 267 } 268 defer m.Disconnect() 269 s, err := m.OpenService(name) 270 if err != nil { 271 err = fmt.Errorf("Query: could not access service: %v", err) 272 return 273 } 274 defer s.Close() 275 276 statusCode, err := s.Query() 277 if err != nil { 278 return 279 } 280 switch statusCode.State { 281 case svc.Stopped: 282 return "Stopped", nil 283 case svc.StartPending: 284 return "StartPending", nil 285 case svc.StopPending: 286 return "StopPending", nil 287 case svc.Running: 288 return "Running", nil 289 case svc.ContinuePending: 290 return "ContinuePending", nil 291 case svc.PausePending: 292 return "PausePending", nil 293 case svc.Paused: 294 return "Paused", nil 295 } 296 panic("unreached") 297 } 298 299 func RunAs(name string, start func(isWindowsService bool), stop func(), isDebug bool) (err error) { 300 301 run := svc.Run 302 if isDebug { 303 run = debug.Run 304 } 305 306 fmt.Println("RunAs: starting service: ", name) 307 if err = run(name, &winService{Start: start, Stop: stop}); err != nil { 308 fmt.Println(name, "service failed: ", err) 309 return 310 } 311 fmt.Println("RunAs:", name, "service stopped") 312 return 313 } 314 315 func Control(name string, c svc.Cmd, to svc.State) error { 316 m, err := mgr.Connect() 317 if err != nil { 318 return err 319 } 320 defer m.Disconnect() 321 s, err := m.OpenService(name) 322 if err != nil { 323 return fmt.Errorf("Control: could not access service: %v", err) 324 } 325 defer s.Close() 326 status, err := s.Control(c) 327 if err != nil { 328 return fmt.Errorf("Control: could not send control=%d: %v", c, err) 329 } 330 timeout := time.Now().Add(10 * time.Second) 331 for status.State != to { 332 if timeout.Before(time.Now()) { 333 return fmt.Errorf("Control: timeout waiting for service to go to state=%d", to) 334 } 335 time.Sleep(300 * time.Millisecond) 336 status, err = s.Query() 337 if err != nil { 338 return fmt.Errorf("Control: could not retrieve service status: %v", err) 339 } 340 } 341 return nil 342 } 343 344 type winService struct { 345 Start func(isWindowsService bool) 346 Stop func() 347 } 348 349 func (p *winService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { 350 logger.Info("Execute: begin") 351 const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue 352 changes <- svc.Status{State: svc.StartPending} 353 changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 354 355 var exit = make(chan struct{}) 356 system.ChildRunning(func() { 357 p.Start(true) 358 close(exit) 359 }) 360 var stop = false 361 loop: 362 for { 363 select { 364 case <-exit: 365 stop = true 366 break 367 case c := <-r: 368 switch c.Cmd { 369 case svc.Interrogate: 370 changes <- c.CurrentStatus 371 // testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4 372 time.Sleep(100 * time.Millisecond) 373 changes <- c.CurrentStatus 374 case svc.Stop, svc.Shutdown: 375 break loop 376 case svc.Pause: 377 changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} 378 case svc.Continue: 379 changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 380 default: 381 logger.Error("Execute:: unexpected control request #%d", c) 382 } 383 } 384 if stop { 385 break 386 } 387 } 388 changes <- svc.Status{State: svc.StopPending} 389 p.Stop() 390 391 logger.Info("Execute: end") 392 return 393 }