|
@@ -0,0 +1,371 @@
|
|
|
+// 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()
|
|
|
+}
|