service: add Config.Executable field to allow service to control other binaries.

This commit is contained in:
Daniel Theophanes
2015-03-14 08:49:17 -07:00
parent 17f5541e81
commit 49d681e0cb
6 changed files with 344 additions and 339 deletions
+334 -321
View File
@@ -1,321 +1,334 @@
// Copyright 2015 Daniel Theophanes. // Copyright 2015 Daniel Theophanes.
// Use of this source code is governed by a zlib-style // Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.package service // license that can be found in the LICENSE file.package service
// Package service provides a simple way to create a system service. // Package service provides a simple way to create a system service.
// Currently supports Windows, Linux/(systemd | Upstart | SysV), and OSX/Launchd. // Currently supports Windows, Linux/(systemd | Upstart | SysV), and OSX/Launchd.
// //
// Windows controls services by setting up callbacks that is non-trivial. This // Windows controls services by setting up callbacks that is non-trivial. This
// is very different then other systems. This package provides the same API // is very different then other systems. This package provides the same API
// despite the substantial differences. // despite the substantial differences.
// It also can be used to detect how a program is called, from an interactive // It also can be used to detect how a program is called, from an interactive
// terminal or from a service manager. // terminal or from a service manager.
// //
// Examples in the example/ folder. // Examples in the example/ folder.
// //
// package main // package main
// //
// import ( // import (
// "log" // "log"
// //
// "github.com/kardianos/service" // "github.com/kardianos/service"
// ) // )
// //
// var logger service.Logger // var logger service.Logger
// //
// type program struct{} // type program struct{}
// //
// func (p *program) Start(s service.Service) error { // func (p *program) Start(s service.Service) error {
// // Start should not block. Do the actual work async. // // Start should not block. Do the actual work async.
// go p.run() // go p.run()
// return nil // return nil
// } // }
// func (p *program) run() { // func (p *program) run() {
// // Do work here // // Do work here
// } // }
// func (p *program) Stop(s service.Service) error { // func (p *program) Stop(s service.Service) error {
// // Stop should not block. Return with a few seconds. // // Stop should not block. Return with a few seconds.
// return nil // return nil
// } // }
// //
// func main() { // func main() {
// svcConfig := &service.Config{ // svcConfig := &service.Config{
// Name: "GoServiceTest", // Name: "GoServiceTest",
// DisplayName: "Go Service Test", // DisplayName: "Go Service Test",
// Description: "This is a test Go service.", // Description: "This is a test Go service.",
// } // }
// //
// prg := &program{} // prg := &program{}
// s, err := service.New(prg, svcConfig) // s, err := service.New(prg, svcConfig)
// if err != nil { // if err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }
// logger, err = s.Logger(nil) // logger, err = s.Logger(nil)
// if err != nil { // if err != nil {
// log.Fatal(err) // log.Fatal(err)
// } // }
// err = s.Run() // err = s.Run()
// if err != nil { // if err != nil {
// logger.Error(err) // logger.Error(err)
// } // }
// } // }
package service // import "github.com/kardianos/service" package service // import "github.com/kardianos/service"
import ( import (
"errors" "errors"
"fmt" "fmt"
) "path/filepath"
// Config provides the setup for a Service. The Name field is required. "github.com/kardianos/osext"
type Config struct { )
Name string // Required name of the service. No spaces suggested.
DisplayName string // Display name, spaces allowed. // Config provides the setup for a Service. The Name field is required.
Description string // Long description of service. type Config struct {
Dependencies []string // Array of service dependencies. Name string // Required name of the service. No spaces suggested.
DisplayName string // Display name, spaces allowed.
UserName string // Run as username. Description string // Long description of service.
Arguments []string // Run with arguments. Dependencies []string // Array of service dependencies.
WorkingDirectory string // Service working directory. UserName string // Run as username.
ChRoot string Arguments []string // Run with arguments.
UserService bool // Install as a current user service.
// Optional field to specify the executable for service.
// System specific options. // If empty the current executable is used.
Option KeyValue Executable string
} WorkingDirectory string // Service working directory.
ChRoot string
var ( UserService bool // Install as a current user service.
system System
systemRegistry []System // System specific options.
) Option KeyValue
}
var (
// ErrNameFieldRequired is returned when Conifg.Name is empty. func (c *Config) execPath() (string, error) {
ErrNameFieldRequired = errors.New("Config.Name field is required.") if len(c.Executable) != 0 {
// ErrNoServiceSystemDetected is returned when no system was detected. return filepath.Abs(c.Executable)
ErrNoServiceSystemDetected = errors.New("No service system detected.") }
) return osext.Executable()
}
// New creates a new service based on a service interface and configuration.
func New(i Interface, c *Config) (Service, error) { var (
if len(c.Name) == 0 { system System
return nil, ErrNameFieldRequired systemRegistry []System
} )
if system == nil {
return nil, ErrNoServiceSystemDetected var (
} // ErrNameFieldRequired is returned when Conifg.Name is empty.
return system.New(i, c) ErrNameFieldRequired = errors.New("Config.Name field is required.")
} // ErrNoServiceSystemDetected is returned when no system was detected.
ErrNoServiceSystemDetected = errors.New("No service system detected.")
// KeyValue provides a list of platform specific options. See platform docs for )
// more details.
type KeyValue map[string]interface{} // New creates a new service based on a service interface and configuration.
func New(i Interface, c *Config) (Service, error) {
// Bool returns the value of the given name, assuming the value is a boolean. if len(c.Name) == 0 {
// If the value isn't found or is not of the type, the defaultValue is returned. return nil, ErrNameFieldRequired
func (kv KeyValue) bool(name string, defaultValue bool) bool { }
if v, found := kv[name]; found { if system == nil {
if castValue, is := v.(bool); is { return nil, ErrNoServiceSystemDetected
return castValue }
} return system.New(i, c)
} }
return defaultValue
} // KeyValue provides a list of platform specific options. See platform docs for
// more details.
// Int returns the value of the given name, assuming the value is an int. type KeyValue map[string]interface{}
// 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 { // Bool returns the value of the given name, assuming the value is a boolean.
if v, found := kv[name]; found { // If the value isn't found or is not of the type, the defaultValue is returned.
if castValue, is := v.(int); is { func (kv KeyValue) bool(name string, defaultValue bool) bool {
return castValue if v, found := kv[name]; found {
} if castValue, is := v.(bool); is {
} return castValue
return defaultValue }
} }
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 { // Int returns the value of the given name, assuming the value is an int.
if v, found := kv[name]; found { // If the value isn't found or is not of the type, the defaultValue is returned.
if castValue, is := v.(string); is { func (kv KeyValue) int(name string, defaultValue int) int {
return castValue if v, found := kv[name]; found {
} if castValue, is := v.(int); is {
} return castValue
return defaultValue }
} }
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 { // Int returns the value of the given name, assuming the value is a string.
if v, found := kv[name]; found { // If the value isn't found or is not of the type, the defaultValue is returned.
if castValue, is := v.(float64); is { func (kv KeyValue) string(name string, defaultValue string) string {
return castValue if v, found := kv[name]; found {
} if castValue, is := v.(string); is {
} return castValue
return defaultValue }
} }
return defaultValue
// Platform returns a description of the system service. }
func Platform() string {
if system == nil { // Int returns the value of the given name, assuming the value is a float64.
return "" // 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 {
return system.String() if v, found := kv[name]; found {
} if castValue, is := v.(float64); is {
return castValue
// Interactive returns false if running under the OS service manager }
// and true otherwise. }
func Interactive() bool { return defaultValue
if system == nil { }
return true
} // Platform returns a description of the system service.
return system.Interactive() func Platform() string {
} if system == nil {
return ""
func newSystem() System { }
for _, choice := range systemRegistry { return system.String()
if choice.Detect() == false { }
continue
} // Interactive returns false if running under the OS service manager
return choice // and true otherwise.
} func Interactive() bool {
return nil if system == nil {
} return true
}
// ChooseSystem chooses a system from the given system services. return system.Interactive()
// SystemServices are considered in the order they are suggested. }
// Calling this may change what Interactive and Platform return.
func ChooseSystem(a ...System) { func newSystem() System {
systemRegistry = a for _, choice := range systemRegistry {
system = newSystem() if choice.Detect() == false {
} continue
}
// ChosenSystem returns the system that service will use. return choice
func ChosenSystem() System { }
return system return nil
} }
// AvailableSystems returns the list of system services considered // ChooseSystem chooses a system from the given system services.
// when choosing the system service. // SystemServices are considered in the order they are suggested.
func AvailableSystems() []System { // Calling this may change what Interactive and Platform return.
return systemRegistry func ChooseSystem(a ...System) {
} systemRegistry = a
system = newSystem()
// System represents the service manager that is available. }
type System interface {
// String returns a description of the system. // ChosenSystem returns the system that service will use.
String() string func ChosenSystem() System {
return system
// Detect returns true if the system is available to use. }
Detect() bool
// AvailableSystems returns the list of system services considered
// Interactive returns false if running under the system service manager // when choosing the system service.
// and true otherwise. func AvailableSystems() []System {
Interactive() bool return systemRegistry
}
// New creates a new service for this system.
New(i Interface, c *Config) (Service, error) // System represents the service manager that is available.
} type System interface {
// String returns a description of the system.
// Interface represents the service interface for a program. Start runs before String() string
// the hosting process is granted control and Stop runs when control is returned.
// // Detect returns true if the system is available to use.
// 1. OS service manager executes user program. Detect() bool
// 2. User program sees it is executed from a service manager (IsInteractive is false).
// 3. User program calls Service.Run() which blocks. // Interactive returns false if running under the system service manager
// 4. Interface.Start() is called and quickly returns. // and true otherwise.
// 5. User program runs. Interactive() bool
// 6. OS service manager signals the user program to stop.
// 7. Interface.Stop() is called and quickly returns. // New creates a new service for this system.
// - For a successful exit, os.Exit should not be called in Interface.Stop(). New(i Interface, c *Config) (Service, error)
// 8. Service.Run returns. }
// 9. User program should quickly exit.
type Interface interface { // Interface represents the service interface for a program. Start runs before
// Start provides a place to initiate the service. The service doesn't not // the hosting process is granted control and Stop runs when control is returned.
// signal a completed start until after this function returns, so the //
// Start function must not take more then a few seconds at most. // 1. OS service manager executes user program.
Start(s Service) error // 2. User program sees it is executed from a service manager (IsInteractive is false).
// 3. User program calls Service.Run() which blocks.
// Stop provides a place to clean up program execution before it is terminated. // 4. Interface.Start() is called and quickly returns.
// It should not take more then a few seconds to execute. // 5. User program runs.
// Stop should not call os.Exit directly in the function. // 6. OS service manager signals the user program to stop.
Stop(s Service) error // 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.
// TODO: Add Configure to Service interface. // 9. User program should quickly exit.
type Interface interface {
// Service represents a service that can be run or controlled. // Start provides a place to initiate the service. The service doesn't not
type Service interface { // signal a completed start until after this function returns, so the
// Run should be called shortly after the program entry point. // Start function must not take more then a few seconds at most.
// After Interface.Stop has finished running, Run will stop blocking. Start(s Service) error
// After Run stops blocking, the program must exit shortly after.
Run() 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.
// Start signals to the OS service manager the given service should start. // Stop should not call os.Exit directly in the function.
Start() error Stop(s Service) error
}
// Stop signals to the OS service manager the given service should stop.
Stop() error // TODO: Add Configure to Service interface.
// Restart signals to the OS service manager the given service should stop then start. // Service represents a service that can be run or controlled.
Restart() error type Service interface {
// Run should be called shortly after the program entry point.
// Install setups up the given service in the OS service manager. This may require // After Interface.Stop has finished running, Run will stop blocking.
// greater rights. Will return an error if it is already installed. // After Run stops blocking, the program must exit shortly after.
Install() error Run() error
// Uninstall removes the given service from the OS service manager. This may require // Start signals to the OS service manager the given service should start.
// greater rights. Will return an error if the service is not present. Start() error
Uninstall() error
// Stop signals to the OS service manager the given service should stop.
// Opens and returns a system logger. If the user program is running Stop() error
// 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 // Restart signals to the OS service manager the given service should stop then start.
// returned from Logger's functions. Restart() error
Logger(errs chan<- error) (Logger, error)
// Install setups up the given service in the OS service manager. This may require
// SystemLogger opens and returns a system logger. If errs is non-nil errors // greater rights. Will return an error if it is already installed.
// will be sent on errs as well as returned from Logger's functions. Install() error
SystemLogger(errs chan<- error) (Logger, error)
// Uninstall removes the given service from the OS service manager. This may require
// String displays the name of the service. The display name if present, // greater rights. Will return an error if the service is not present.
// otherwise the name. Uninstall() error
String() string
} // Opens and returns a system logger. If the user program is running
// interactively rather then as a service, the returned logger will write to
// ControlAction list valid string texts to use in Control. // os.Stderr. If errs is non-nil errors will be sent on errs as well as
var ControlAction = [5]string{"start", "stop", "restart", "install", "uninstall"} // returned from Logger's functions.
Logger(errs chan<- error) (Logger, error)
// Control issues control functions to the service from a given action string.
func Control(s Service, action string) error { // SystemLogger opens and returns a system logger. If errs is non-nil errors
var err error // will be sent on errs as well as returned from Logger's functions.
switch action { SystemLogger(errs chan<- error) (Logger, error)
case ControlAction[0]:
err = s.Start() // String displays the name of the service. The display name if present,
case ControlAction[1]: // otherwise the name.
err = s.Stop() String() string
case ControlAction[2]: }
err = s.Restart()
case ControlAction[3]: // ControlAction list valid string texts to use in Control.
err = s.Install() var ControlAction = [5]string{"start", "stop", "restart", "install", "uninstall"}
case ControlAction[4]:
err = s.Uninstall() // Control issues control functions to the service from a given action string.
default: func Control(s Service, action string) error {
err = fmt.Errorf("Unknown action %s", action) var err error
} switch action {
if err != nil { case ControlAction[0]:
return fmt.Errorf("Failed to %s %v: %v", action, s, err) err = s.Start()
} case ControlAction[1]:
return nil err = s.Stop()
} case ControlAction[2]:
err = s.Restart()
// Logger writes to the system log. case ControlAction[3]:
type Logger interface { err = s.Install()
Error(v ...interface{}) error case ControlAction[4]:
Warning(v ...interface{}) error err = s.Uninstall()
Info(v ...interface{}) error default:
err = fmt.Errorf("Unknown action %s", action)
Errorf(format string, a ...interface{}) error }
Warningf(format string, a ...interface{}) error if err != nil {
Infof(format string, a ...interface{}) error 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
}
+1 -3
View File
@@ -12,8 +12,6 @@ import (
"syscall" "syscall"
"text/template" "text/template"
"time" "time"
"github.com/kardianos/osext"
) )
const maxPathSize = 32 * 1024 const maxPathSize = 32 * 1024
@@ -98,7 +96,7 @@ func (s *darwinLaunchdService) Install() error {
} }
defer f.Close() defer f.Close()
path, err := osext.Executable() path, err := s.execPath()
if err != nil { if err != nil {
return err return err
} }
+1 -3
View File
@@ -12,8 +12,6 @@ import (
"syscall" "syscall"
"text/template" "text/template"
"time" "time"
"github.com/kardianos/osext"
) )
func isSystemd() bool { func isSystemd() bool {
@@ -75,7 +73,7 @@ func (s *systemd) Install() error {
} }
defer f.Close() defer f.Close()
path, err := osext.Executable() path, err := s.execPath()
if err != nil { if err != nil {
return err return err
} }
+1 -3
View File
@@ -12,8 +12,6 @@ import (
"syscall" "syscall"
"text/template" "text/template"
"time" "time"
"github.com/kardianos/osext"
) )
type sysv struct { type sysv struct {
@@ -67,7 +65,7 @@ func (s *sysv) Install() error {
} }
defer f.Close() defer f.Close()
path, err := osext.Executable() path, err := s.execPath()
if err != nil { if err != nil {
return err return err
} }
+5 -7
View File
@@ -8,12 +8,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"os" "os"
"os/exec"
"os/signal" "os/signal"
"syscall"
"text/template" "text/template"
"time" "time"
"github.com/kardianos/osext"
) )
func isUpstart() bool { func isUpstart() bool {
@@ -77,7 +75,7 @@ func (s *upstart) Install() error {
} }
defer f.Close() defer f.Close()
path, err := osext.Executable() path, err := s.execPath()
if err != nil { if err != nil {
return err return err
} }
@@ -122,7 +120,7 @@ func (s *upstart) Run() (err error) {
sigChan := make(chan os.Signal, 3) sigChan := make(chan os.Signal, 3)
signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) signal.Notify(sigChan, os.Interrupt, os.Kill)
<-sigChan <-sigChan
@@ -130,11 +128,11 @@ func (s *upstart) Run() (err error) {
} }
func (s *upstart) Start() 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 { func (s *upstart) Stop() error {
return run("initctl", "stop", s.Name) return exec.Command("initctl", "stop", s.Name).Run()
} }
func (s *upstart) Restart() error { func (s *upstart) Restart() error {
+2 -2
View File
@@ -16,7 +16,6 @@ import (
"code.google.com/p/winsvc/eventlog" "code.google.com/p/winsvc/eventlog"
"code.google.com/p/winsvc/mgr" "code.google.com/p/winsvc/mgr"
"code.google.com/p/winsvc/svc" "code.google.com/p/winsvc/svc"
"github.com/kardianos/osext"
) )
const version = "Windows Service" const version = "Windows Service"
@@ -165,10 +164,11 @@ loop:
} }
func (ws *windowsService) Install() error { func (ws *windowsService) Install() error {
exepath, err := osext.Executable() exepath, err := ws.execPath()
if err != nil { if err != nil {
return err return err
} }
binPath := &bytes.Buffer{} binPath := &bytes.Buffer{}
// Quote exe path in case it contains a string. // Quote exe path in case it contains a string.
binPath.WriteRune('"') binPath.WriteRune('"')