123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371 |
- // 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), 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/penggy/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/penggy/service"
- import (
- "errors"
- "fmt"
- "os"
- "path/filepath"
- )
- const (
- optionKeepAlive = "KeepAlive"
- optionKeepAliveDefault = true
- optionRunAtLoad = "RunAtLoad"
- optionRunAtLoadDefault = false
- optionUserService = "UserService"
- optionUserServiceDefault = false
- optionSessionCreate = "SessionCreate"
- optionSessionCreateDefault = false
- optionRunWait = "RunWait"
- optionReloadSignal = "ReloadSignal"
- optionPIDFile = "PIDFile"
- )
- // 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 implemented on Linux or OS X.
- Dependencies []string
- // The following fields are not supported on Windows.
- WorkingDirectory string // Initial working directory.
- ChRoot string
- // System specific options.
- // * OS X
- // - KeepAlive bool (true)
- // - RunAtLoad bool (false)
- // - UserService bool (false) - Install as a current user service.
- // - SessionCreate bool (false) - Create a full user session.
- // * POSIX
- // - RunWait func() (wait for SIGNAL) - Do not install signal but wait for this function to return.
- // - ReloadSignal string () [USR1, ...] - Signal to send on reaload.
- // - PIDFile string () [/run/prog.pid] - Location of the PID file.
- Option KeyValue
- }
- var (
- system System
- systemRegistry []System
- )
- var (
- // ErrNameFieldRequired is returned when Conifg.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.")
- )
- // 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 platform specific options. See platform docs for
- // more details.
- 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 float64.
- // 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 not
- // 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
- }
- // 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
- }
- // 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
- }
- func (c *Config) execPath() (string, error) {
- if len(c.Executable) != 0 {
- return filepath.Abs(c.Executable)
- }
- return os.Executable()
- }
|