From 17a2c7bcb56b584af722df86c3bc7e5d1b05a554 Mon Sep 17 00:00:00 2001 From: Eugene <141402+jdevelop@users.noreply.github.com> Date: Thu, 10 Jun 2021 08:11:16 -0400 Subject: [PATCH] Adds service control options for Windows service. (#258) - StartType string ("automatic") - start service. Constants: ServiceStartDisabled, ServiceStartManual, ServiceStartDisabled - OnFailure string ("noaction" ) - action to perform on service failure. Constants: OnFailureRestart, OnFailureReboot, OnFailureNoAction - OnFailureDelayDuration string ( "1s" ) - delay before restarting the service, time.Duration string. - OnFailureResetPeriod int ( 10 ) - reset period for errors, seconds. Fixes kardianos/service#226 Fixes kardianos/service#207 Fixes kardianos/service#103 Co-authored-by: Eugene Co-authored-by: Daniel Theophanes --- service.go | 11 +++++++--- service_windows.go | 52 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/service.go b/service.go index 6033981..e0ceaf3 100644 --- a/service.go +++ b/service.go @@ -188,9 +188,14 @@ func New(i Interface, c *Config) (Service, error) { // - LimitNOFILE int (-1) - Maximum open files (ulimit -n) // (https://serverfault.com/questions/628610/increasing-nproc-for-processes-launched-by-systemd-on-centos-7) // * Windows -// - DelayedAutoStart bool (false) - After booting, start this service after some delay. -// - Password string () - Password to use when interfacing with the system service manager. -// - Interactive bool (false) - The service can interact with the desktop. (more information https://docs.microsoft.com/en-us/windows/win32/services/interactive-services) +// - DelayedAutoStart bool (false) - After booting, start this service after some delay. +// - Password string () - Password to use when interfacing with the system service manager. +// - Interactive bool (false) - The service can interact with the desktop. (more information https://docs.microsoft.com/en-us/windows/win32/services/interactive-services) +// - DelayedAutoStart bool (false) - after booting start this service after some delay. +// - StartType string ("automatic") - Start service type. (automatic | manual | disabled) +// - OnFailure string ("restart" ) - Action to perform on service failure. (restart | reboot | noaction) +// - OnFailureDelayDuration string ( "1s" ) - Delay before restarting the service, time.Duration string. +// - OnFailureResetPeriod int ( 10 ) - Reset period for errors, seconds. type KeyValue map[string]interface{} // bool returns the value of the given name, assuming the value is a boolean. diff --git a/service_windows.go b/service_windows.go index b6d8314..cbee503 100644 --- a/service_windows.go +++ b/service_windows.go @@ -19,7 +19,21 @@ import ( "golang.org/x/sys/windows/svc/mgr" ) -const version = "windows-service" +const ( + version = "windows-service" + + StartType = "StartType" + ServiceStartManual = "manual" + ServiceStartDisabled = "disabled" + ServiceStartAutomatic = "automatic" + + OnFailure = "OnFailure" + OnFailureRestart = "restart" + OnFailureReboot = "reboot" + OnFailureNoAction = "noaction" + OnFailureDelayDuration = "OnFailureDelayDuration" + OnFailureResetPeriod = "OnFailureResetPeriod" +) type windowsService struct { i Interface @@ -220,6 +234,15 @@ func (ws *windowsService) Install() error { s.Close() return fmt.Errorf("service %s already exists", ws.Name) } + var startType int32 + switch ws.Option.string(StartType, ServiceStartAutomatic) { + case ServiceStartAutomatic: + startType = mgr.StartAutomatic + case ServiceStartManual: + startType = mgr.StartManual + case ServiceStartDisabled: + startType = mgr.StartDisabled + } serviceType := windows.SERVICE_WIN32_OWN_PROCESS if ws.Option.bool("Interactive") { @@ -229,7 +252,7 @@ func (ws *windowsService) Install() error { s, err = m.CreateService(ws.Name, exepath, mgr.Config{ DisplayName: ws.DisplayName, Description: ws.Description, - StartType: mgr.StartAutomatic, + StartType: uint32(startType), ServiceStartName: ws.UserName, Password: ws.Option.string("Password", ""), Dependencies: ws.Dependencies, @@ -239,6 +262,31 @@ func (ws *windowsService) Install() error { if err != nil { return err } + if onFailure := ws.Option.string(OnFailure, ""); onFailure != "" { + var delay = 1 * time.Second + if d, err := time.ParseDuration(ws.Option.string(OnFailureDelayDuration, "1s")); err == nil { + delay = d + } + var actionType int + switch onFailure { + case OnFailureReboot: + actionType = mgr.ComputerReboot + case OnFailureRestart: + actionType = mgr.ServiceRestart + case OnFailureNoAction: + actionType = mgr.NoAction + default: + actionType = mgr.ServiceRestart + } + if err := s.SetRecoveryActions([]mgr.RecoveryAction{ + { + Type: actionType, + Delay: delay, + }, + }, uint32(ws.Option.int(OnFailureResetPeriod, 10))); err != nil { + return err + } + } defer s.Close() err = eventlog.InstallAsEventCreate(ws.Name, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil {