diff --git a/service.go b/service.go index 9fa87f6..3dc99ae 100644 --- a/service.go +++ b/service.go @@ -1,321 +1,334 @@ -// 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 - -// 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/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" -) - -// 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. - Dependencies []string // Array of service dependencies. - - UserName string // Run as username. - Arguments []string // Run with arguments. - - WorkingDirectory string // Service working directory. - ChRoot string - UserService bool // Install as a current user service. - - // System specific options. - 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 -} - -// Int 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 -} - -// Int 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 -} - -// 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 -} +// 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 + +// 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/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" + "path/filepath" + + "github.com/kardianos/osext" +) + +// 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. + Dependencies []string // Array of service dependencies. + + 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 + WorkingDirectory string // Service working directory. + ChRoot string + UserService bool // Install as a current user service. + + // System specific options. + Option KeyValue +} + +func (c *Config) execPath() (string, error) { + if len(c.Executable) != 0 { + return filepath.Abs(c.Executable) + } + return osext.Executable() +} + +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 +} + +// Int 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 +} + +// Int 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 +} + +// 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 +} diff --git a/service_darwin.go b/service_darwin.go index 18b1eb3..da8ae9b 100644 --- a/service_darwin.go +++ b/service_darwin.go @@ -12,8 +12,6 @@ import ( "syscall" "text/template" "time" - - "github.com/kardianos/osext" ) const maxPathSize = 32 * 1024 @@ -98,7 +96,7 @@ func (s *darwinLaunchdService) Install() error { } defer f.Close() - path, err := osext.Executable() + path, err := s.execPath() if err != nil { return err } diff --git a/service_systemd_linux.go b/service_systemd_linux.go index 7616730..95fb8e0 100644 --- a/service_systemd_linux.go +++ b/service_systemd_linux.go @@ -12,8 +12,6 @@ import ( "syscall" "text/template" "time" - - "github.com/kardianos/osext" ) func isSystemd() bool { @@ -75,7 +73,7 @@ func (s *systemd) Install() error { } defer f.Close() - path, err := osext.Executable() + path, err := s.execPath() if err != nil { return err } diff --git a/service_sysv_linux.go b/service_sysv_linux.go index b08244e..f0ea205 100644 --- a/service_sysv_linux.go +++ b/service_sysv_linux.go @@ -12,8 +12,6 @@ import ( "syscall" "text/template" "time" - - "github.com/kardianos/osext" ) type sysv struct { @@ -67,7 +65,7 @@ func (s *sysv) Install() error { } defer f.Close() - path, err := osext.Executable() + path, err := s.execPath() if err != nil { return err } diff --git a/service_upstart_linux.go b/service_upstart_linux.go index 0e922e5..73611ed 100644 --- a/service_upstart_linux.go +++ b/service_upstart_linux.go @@ -8,12 +8,10 @@ import ( "errors" "fmt" "os" + "os/exec" "os/signal" - "syscall" "text/template" "time" - - "github.com/kardianos/osext" ) func isUpstart() bool { @@ -77,7 +75,7 @@ func (s *upstart) Install() error { } defer f.Close() - path, err := osext.Executable() + path, err := s.execPath() if err != nil { return err } @@ -122,7 +120,7 @@ func (s *upstart) Run() (err error) { sigChan := make(chan os.Signal, 3) - signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) + signal.Notify(sigChan, os.Interrupt, os.Kill) <-sigChan @@ -130,11 +128,11 @@ func (s *upstart) Run() (err error) { } func (s *upstart) Start() error { - return run("initctl", "start", s.Name) + return exec.Command("initctl", "start", s.Name).Run() } func (s *upstart) Stop() error { - return run("initctl", "stop", s.Name) + return exec.Command("initctl", "stop", s.Name).Run() } func (s *upstart) Restart() error { diff --git a/service_windows.go b/service_windows.go index 6597c51..4e63b8b 100644 --- a/service_windows.go +++ b/service_windows.go @@ -16,7 +16,6 @@ import ( "code.google.com/p/winsvc/eventlog" "code.google.com/p/winsvc/mgr" "code.google.com/p/winsvc/svc" - "github.com/kardianos/osext" ) const version = "Windows Service" @@ -165,10 +164,11 @@ loop: } func (ws *windowsService) Install() error { - exepath, err := osext.Executable() + exepath, err := ws.execPath() if err != nil { return err } + binPath := &bytes.Buffer{} // Quote exe path in case it contains a string. binPath.WriteRune('"')