// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // Package service provides a simple way to create a system service. // Currently supports Windows, Linux/(systemd | Upstart | SysV | OpenRC), and OSX/Launchd. // // Windows controls services by setting up callbacks that is non-trivial. This // is very different then other systems. This package provides the same API // despite the substantial differences. // It also can be used to detect how a program is called, from an interactive // terminal or from a service manager. // // Examples in the example/ folder. // // package main // // import ( // "log" // // "github.com/kardianos/service" // ) // // var logger service.Logger // // type program struct{} // // func (p *program) Start(s service.Service) error { // // Start should not block. Do the actual work async. // go p.run() // return nil // } // func (p *program) run() { // // Do work here // } // func (p *program) Stop(s service.Service) error { // // Stop should not block. Return with a few seconds. // return nil // } // // func main() { // svcConfig := &service.Config{ // Name: "GoServiceTest", // DisplayName: "Go Service Test", // Description: "This is a test Go service.", // } // // prg := &program{} // s, err := service.New(prg, svcConfig) // if err != nil { // log.Fatal(err) // } // logger, err = s.Logger(nil) // if err != nil { // log.Fatal(err) // } // err = s.Run() // if err != nil { // logger.Error(err) // } // } package service // import "github.com/kardianos/service" import ( "errors" "fmt" ) const ( optionKeepAlive = "KeepAlive" optionKeepAliveDefault = true optionRunAtLoad = "RunAtLoad" optionRunAtLoadDefault = false optionUserService = "UserService" optionUserServiceDefault = false optionSessionCreate = "SessionCreate" optionSessionCreateDefault = false optionLogOutput = "LogOutput" optionLogOutputDefault = false optionPrefix = "Prefix" optionPrefixDefault = "application" optionRunWait = "RunWait" optionReloadSignal = "ReloadSignal" optionPIDFile = "PIDFile" optionLimitNOFILE = "LimitNOFILE" optionLimitNOFILEDefault = -1 // -1 = don't set in configuration optionRestart = "Restart" optionSuccessExitStatus = "SuccessExitStatus" optionSystemdScript = "SystemdScript" optionSysvScript = "SysvScript" optionUpstartScript = "UpstartScript" optionLaunchdConfig = "LaunchdConfig" optionOpenRCScript = "OpenRCScript" optionLogDirectory = "LogDirectory" ) // Status represents service status as an byte value type Status byte // Status of service represented as an byte const ( StatusUnknown Status = iota // Status is unable to be determined due to an error or it was not installed. StatusRunning StatusStopped ) // Config provides the setup for a Service. The Name field is required. type Config struct { Name string // Required name of the service. No spaces suggested. DisplayName string // Display name, spaces allowed. Description string // Long description of service. UserName string // Run as username. Arguments []string // Run with arguments. // Optional field to specify the executable for service. // If empty the current executable is used. Executable string // Array of service dependencies. // Not yet fully implemented on Linux or OS X: // 1. Support linux-systemd dependencies, just put each full line as the // element of the string array, such as // "After=network.target syslog.target" // "Requires=syslog.target" // Note, such lines will be directly appended into the [Unit] of // the generated service config file, will not check their correctness. Dependencies []string // The following fields are not supported on Windows. WorkingDirectory string // Initial working directory. ChRoot string // System specific options. Option KeyValue EnvVars map[string]string } var ( system System systemRegistry []System ) var ( // ErrNameFieldRequired is returned when Config.Name is empty. ErrNameFieldRequired = errors.New("Config.Name field is required.") // ErrNoServiceSystemDetected is returned when no system was detected. ErrNoServiceSystemDetected = errors.New("No service system detected.") // ErrNotInstalled is returned when the service is not installed. ErrNotInstalled = errors.New("the service is not installed") ) // New creates a new service based on a service interface and configuration. func New(i Interface, c *Config) (Service, error) { if len(c.Name) == 0 { return nil, ErrNameFieldRequired } if system == nil { return nil, ErrNoServiceSystemDetected } return system.New(i, c) } // KeyValue provides a list of system specific options. // * OS X // - LaunchdConfig string () - Use custom launchd config. // - KeepAlive bool (true) - Prevent the system from stopping the service automatically. // - RunAtLoad bool (false) - Run the service after its job has been loaded. // - SessionCreate bool (false) - Create a full user session. // // * Solaris // - Prefix string ("application") - Service FMRI prefix. // // * POSIX // - UserService bool (false) - Install as a current user service. // - SystemdScript string () - Use custom systemd script. // - UpstartScript string () - Use custom upstart script. // - SysvScript string () - Use custom sysv script. // - OpenRCScript string () - Use custom OpenRC script. // - RunWait func() (wait for SIGNAL) - Do not install signal but wait for this function to return. // - ReloadSignal string () [USR1, ...] - Signal to send on reload. // - PIDFile string () [/run/prog.pid] - Location of the PID file. // - LogOutput bool (false) - Redirect StdErr & StandardOutPath to files. // - Restart string (always) - How shall service be restarted. // - SuccessExitStatus string () - The list of exit status that shall be considered as successful, // in addition to the default ones. // - LogDirectory string(/var/log) - The path to the log files directory // // * Linux (systemd) // - 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. // - 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. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) bool(name string, defaultValue bool) bool { if v, found := kv[name]; found { if castValue, is := v.(bool); is { return castValue } } return defaultValue } // int returns the value of the given name, assuming the value is an int. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) int(name string, defaultValue int) int { if v, found := kv[name]; found { if castValue, is := v.(int); is { return castValue } } return defaultValue } // string returns the value of the given name, assuming the value is a string. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) string(name string, defaultValue string) string { if v, found := kv[name]; found { if castValue, is := v.(string); is { return castValue } } return defaultValue } // float64 returns the value of the given name, assuming the value is a float64. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) float64(name string, defaultValue float64) float64 { if v, found := kv[name]; found { if castValue, is := v.(float64); is { return castValue } } return defaultValue } // funcSingle returns the value of the given name, assuming the value is a func(). // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) funcSingle(name string, defaultValue func()) func() { if v, found := kv[name]; found { if castValue, is := v.(func()); is { return castValue } } return defaultValue } // Platform returns a description of the system service. func Platform() string { if system == nil { return "" } return system.String() } // Interactive returns false if running under the OS service manager // and true otherwise. func Interactive() bool { if system == nil { return true } return system.Interactive() } func newSystem() System { for _, choice := range systemRegistry { if choice.Detect() == false { continue } return choice } return nil } // ChooseSystem chooses a system from the given system services. // SystemServices are considered in the order they are suggested. // Calling this may change what Interactive and Platform return. func ChooseSystem(a ...System) { systemRegistry = a system = newSystem() } // ChosenSystem returns the system that service will use. func ChosenSystem() System { return system } // AvailableSystems returns the list of system services considered // when choosing the system service. func AvailableSystems() []System { return systemRegistry } // System represents the service manager that is available. type System interface { // String returns a description of the system. String() string // Detect returns true if the system is available to use. Detect() bool // Interactive returns false if running under the system service manager // and true otherwise. Interactive() bool // New creates a new service for this system. New(i Interface, c *Config) (Service, error) } // Interface represents the service interface for a program. Start runs before // the hosting process is granted control and Stop runs when control is returned. // // 1. OS service manager executes user program. // 2. User program sees it is executed from a service manager (IsInteractive is false). // 3. User program calls Service.Run() which blocks. // 4. Interface.Start() is called and quickly returns. // 5. User program runs. // 6. OS service manager signals the user program to stop. // 7. Interface.Stop() is called and quickly returns. // - For a successful exit, os.Exit should not be called in Interface.Stop(). // 8. Service.Run returns. // 9. User program should quickly exit. type Interface interface { // Start provides a place to initiate the service. The service doesn't // signal a completed start until after this function returns, so the // Start function must not take more then a few seconds at most. Start(s Service) error // Stop provides a place to clean up program execution before it is terminated. // It should not take more then a few seconds to execute. // Stop should not call os.Exit directly in the function. Stop(s Service) error } // Shutdowner represents a service interface for a program that differentiates between "stop" and // "shutdown". A shutdown is triggered when the whole box (not just the service) is stopped. type Shutdowner interface { Interface // Shutdown provides a place to clean up program execution when the system is being shutdown. // It is essentially the same as Stop but for the case where machine is being shutdown/restarted // instead of just normally stopping the service. Stop won't be called when Shutdown is. Shutdown(s Service) error } // TODO: Add Configure to Service interface. // Service represents a service that can be run or controlled. type Service interface { // Run should be called shortly after the program entry point. // After Interface.Stop has finished running, Run will stop blocking. // After Run stops blocking, the program must exit shortly after. Run() error // Start signals to the OS service manager the given service should start. Start() error // Stop signals to the OS service manager the given service should stop. Stop() error // Restart signals to the OS service manager the given service should stop then start. Restart() error // Install setups up the given service in the OS service manager. This may require // greater rights. Will return an error if it is already installed. Install() error // Uninstall removes the given service from the OS service manager. This may require // greater rights. Will return an error if the service is not present. Uninstall() error // Opens and returns a system logger. If the user program is running // interactively rather then as a service, the returned logger will write to // os.Stderr. If errs is non-nil errors will be sent on errs as well as // returned from Logger's functions. Logger(errs chan<- error) (Logger, error) // SystemLogger opens and returns a system logger. If errs is non-nil errors // will be sent on errs as well as returned from Logger's functions. SystemLogger(errs chan<- error) (Logger, error) // String displays the name of the service. The display name if present, // otherwise the name. String() string // Platform displays the name of the system that manages the service. // In most cases this will be the same as service.Platform(). Platform() string // Status returns the current service status. Status() (Status, error) } // ControlAction list valid string texts to use in Control. var ControlAction = [5]string{"start", "stop", "restart", "install", "uninstall"} // Control issues control functions to the service from a given action string. func Control(s Service, action string) error { var err error switch action { case ControlAction[0]: err = s.Start() case ControlAction[1]: err = s.Stop() case ControlAction[2]: err = s.Restart() case ControlAction[3]: err = s.Install() case ControlAction[4]: err = s.Uninstall() default: err = fmt.Errorf("Unknown action %s", action) } if err != nil { return fmt.Errorf("Failed to %s %v: %v", action, s, err) } return nil } // Logger writes to the system log. type Logger interface { Error(v ...interface{}) error Warning(v ...interface{}) error Info(v ...interface{}) error Errorf(format string, a ...interface{}) error Warningf(format string, a ...interface{}) error Infof(format string, a ...interface{}) error }