mig/service/service_windows.go

215 строки
4.4 KiB
Go

package service /* import "mig.ninja/mig/service" */
import (
"fmt"
"time"
"github.com/kardianos/osext"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
func newService(c *Config) (*windowsService, error) {
return &windowsService{
name: c.Name,
displayName: c.DisplayName,
description: c.Description,
}, nil
}
type windowsService struct {
name, displayName, description string
onStart, onStop func() error
logger *eventlog.Log
}
const version = "Windows Service"
var interactive = false
func init() {
var err error
interactive, err = svc.IsAnInteractiveSession()
if err != nil {
panic(err)
}
}
func IsInteractive() bool {
return interactive
}
func (ws *windowsService) String() string {
return version
}
func (ws *windowsService) 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}
if err := ws.onStart(); err != nil {
ws.Error(err.Error())
return true, 1
}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop:
for {
c := <-r
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
if err := ws.onStop(); err != nil {
ws.Error(err.Error())
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
continue loop
}
break loop
default:
continue loop
}
}
return
}
func (ws *windowsService) Install() error {
exepath, err := osext.Executable()
if err != nil {
return err
}
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", ws.name)
}
s, err = m.CreateService(ws.name, exepath, mgr.Config{
DisplayName: ws.displayName,
Description: ws.description,
StartType: mgr.StartAutomatic,
})
if err != nil {
return err
}
defer s.Close()
err = eventlog.InstallAsEventCreate(ws.name, eventlog.Error|eventlog.Warning|eventlog.Info)
if err != nil {
s.Delete()
return fmt.Errorf("InstallAsEventCreate() failed: %s", err)
}
return nil
}
func (ws *windowsService) Remove() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
if err != nil {
// Try to remove the eventlog source as well here, just to ensure we are not
// in a situation where the eventlog object exists but the service does not
eventlog.Remove(ws.name)
return fmt.Errorf("service %s is not installed", ws.name)
}
err = s.Delete()
if err != nil {
return err
}
s.Close()
cutoff := time.Now().Add(time.Second * 30)
for {
s2, err := m.OpenService(ws.name)
if err != nil {
break
}
s2.Close()
time.Sleep(time.Millisecond * 250)
if time.Now().After(cutoff) {
break
}
}
err = eventlog.Remove(ws.name)
if err != nil {
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
}
return nil
}
func (ws *windowsService) Run(onStart, onStop func() error) error {
elog, err := eventlog.Open(ws.name)
if err != nil {
return err
}
defer elog.Close()
ws.logger = elog
ws.onStart = onStart
ws.onStop = onStop
return svc.Run(ws.name, ws)
}
func (ws *windowsService) Start() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
if err != nil {
return err
}
defer s.Close()
return s.Start()
}
func (ws *windowsService) Stop() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.name)
if err != nil {
return err
}
defer s.Close()
_, err = s.Control(svc.Stop)
return err
}
func (s *windowsService) IntervalMode(interval int) error {
return fmt.Errorf("interval mode service only supported on darwin")
}
func (ws *windowsService) Error(format string, a ...interface{}) error {
if ws.logger == nil {
return nil
}
return ws.logger.Error(3, fmt.Sprintf(format, a...))
}
func (ws *windowsService) Warning(format string, a ...interface{}) error {
if ws.logger == nil {
return nil
}
return ws.logger.Warning(2, fmt.Sprintf(format, a...))
}
func (ws *windowsService) Info(format string, a ...interface{}) error {
if ws.logger == nil {
return nil
}
return ws.logger.Info(1, fmt.Sprintf(format, a...))
}