service: move to v2 API (beta).
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012 Daniel Theophanes
|
||||
Copyright (c) 2015 Daniel Theophanes
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
// A simple and consistent method to extract a configuration from a file.
|
||||
// This doesn't contain any method to actually decode the file. The actual
|
||||
// decoding is done in an external function.
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/kardianos/osext"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
)
|
||||
|
||||
const DefaultPostfix = "_config.json"
|
||||
|
||||
// Simple JSON based configuration decoder.
|
||||
func DecodeJsonConfig(r io.Reader, v interface{}) error {
|
||||
d := json.NewDecoder(r)
|
||||
return d.Decode(v)
|
||||
}
|
||||
|
||||
// Simple JSON based configuration encoder.
|
||||
func EncodeJsonConfig(w io.Writer, v interface{}) error {
|
||||
bb, err := json.MarshalIndent(v, "", "\t")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, tot := 0, 0
|
||||
for {
|
||||
n, err = w.Write(bb[tot:])
|
||||
tot += n
|
||||
if len(bb) == tot {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Return a configuration file path. If baseName is empty,
|
||||
// then the current executable name without the extension
|
||||
// is used. If postfix is empty then DefaultPostfix is used.
|
||||
func GetConfigFilePath(baseName, postfix string) (string, error) {
|
||||
if len(postfix) == 0 {
|
||||
postfix = DefaultPostfix
|
||||
}
|
||||
path, err := osext.Executable()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
path, exeName := filepath.Split(path)
|
||||
if len(baseName) == 0 {
|
||||
exeName = exeName[:len(exeName)-len(filepath.Ext(exeName))]
|
||||
} else {
|
||||
exeName = baseName
|
||||
}
|
||||
configPath := filepath.Join(path, exeName+postfix)
|
||||
return configPath, nil
|
||||
}
|
||||
|
||||
type DecodeConfig func(r io.Reader, v interface{}) error
|
||||
type EncodeConfig func(w io.Writer, v interface{}) error
|
||||
|
||||
type WatchConfig struct {
|
||||
// Notified here if the file changes.
|
||||
C chan *WatchConfig
|
||||
|
||||
filepath string
|
||||
watch *fsnotify.Watcher
|
||||
decode DecodeConfig
|
||||
|
||||
close chan struct{}
|
||||
}
|
||||
|
||||
// Create a new configuration watcher. Adds a notification if the configuration file changes
|
||||
// so it may be reloaded. If defaultConfig is not nil and encode is not nil, the
|
||||
// configuration file path is checked if a file exists or not. If it doesn't exist
|
||||
// the default configuration is written to the file.
|
||||
func NewWatchConfig(filepath string, decode DecodeConfig, defaultConfig interface{}, encode EncodeConfig) (*WatchConfig, error) {
|
||||
if defaultConfig != nil && encode != nil {
|
||||
f, err := os.Open(filepath)
|
||||
if f != nil {
|
||||
f.Close()
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
f, err = os.Create(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = encode(f, defaultConfig)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
watch, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = watch.Add(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
wc := &WatchConfig{
|
||||
C: make(chan *WatchConfig),
|
||||
|
||||
filepath: filepath,
|
||||
watch: watch,
|
||||
decode: decode,
|
||||
|
||||
close: make(chan struct{}, 0),
|
||||
}
|
||||
go wc.run()
|
||||
|
||||
return wc, nil
|
||||
}
|
||||
|
||||
func (wc *WatchConfig) run() {
|
||||
// Work around watch events being sent more then once.
|
||||
ticker := time.NewTicker(time.Second)
|
||||
trigger := false
|
||||
waitOnce := false
|
||||
for {
|
||||
select {
|
||||
case <-wc.close:
|
||||
close(wc.C)
|
||||
return
|
||||
case <-wc.watch.Errors:
|
||||
// Nothing right now.
|
||||
case <-wc.watch.Events:
|
||||
trigger = true
|
||||
case <-ticker.C:
|
||||
// Think of this as a PLC state machine.
|
||||
if trigger && waitOnce {
|
||||
wc.C <- wc
|
||||
trigger = false
|
||||
waitOnce = false
|
||||
}
|
||||
if trigger && !waitOnce {
|
||||
waitOnce = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send a notification as if the configuration file has changed.
|
||||
func (wc *WatchConfig) TriggerC() {
|
||||
wc.C <- wc
|
||||
}
|
||||
|
||||
// Load the configuration from the file into the provided value.
|
||||
func (wc *WatchConfig) Load(v interface{}) error {
|
||||
f, err := os.Open(wc.filepath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
return wc.decode(f, v)
|
||||
}
|
||||
|
||||
// Stop the watch and any loops that are running.
|
||||
func (wc *WatchConfig) Close() error {
|
||||
wc.close <- struct{}{}
|
||||
return wc.watch.Close()
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConsoleLogger logs to the std err.
|
||||
var ConsoleLogger = consoleLogger{}
|
||||
|
||||
type consoleLogger struct {
|
||||
info, warn, err *log.Logger
|
||||
}
|
||||
|
||||
func init() {
|
||||
ConsoleLogger.info = log.New(os.Stderr, "I: ", log.Ltime)
|
||||
ConsoleLogger.warn = log.New(os.Stderr, "W: ", log.Ltime)
|
||||
ConsoleLogger.err = log.New(os.Stderr, "E: ", log.Ltime)
|
||||
}
|
||||
|
||||
func (c consoleLogger) Error(v ...interface{}) error {
|
||||
c.err.Print(v...)
|
||||
return nil
|
||||
}
|
||||
func (c consoleLogger) Warning(v ...interface{}) error {
|
||||
c.warn.Print(v...)
|
||||
return nil
|
||||
}
|
||||
func (c consoleLogger) Info(v ...interface{}) error {
|
||||
c.info.Print(v...)
|
||||
return nil
|
||||
}
|
||||
func (c consoleLogger) Errorf(format string, a ...interface{}) error {
|
||||
c.err.Printf(format, a...)
|
||||
return nil
|
||||
}
|
||||
func (c consoleLogger) Warningf(format string, a ...interface{}) error {
|
||||
c.warn.Printf(format, a...)
|
||||
return nil
|
||||
}
|
||||
func (c consoleLogger) Infof(format string, a ...interface{}) error {
|
||||
c.info.Printf(format, a...)
|
||||
return nil
|
||||
}
|
||||
Executable
BIN
Binary file not shown.
+60
-74
@@ -1,95 +1,81 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/kardianos/service"
|
||||
"bitbucket.org/kardianos/service2beta"
|
||||
)
|
||||
|
||||
var log service.Logger
|
||||
var logger service.Logger
|
||||
|
||||
type program struct {
|
||||
exit chan struct{}
|
||||
}
|
||||
|
||||
func (p *program) Start(s service.Service) error {
|
||||
if s.Interactive() {
|
||||
logger.Info("Running in terminal.")
|
||||
} else {
|
||||
logger.Info("Running under service manager.")
|
||||
}
|
||||
p.exit = make(chan struct{})
|
||||
go p.run()
|
||||
return nil
|
||||
}
|
||||
func (p *program) run() error {
|
||||
logger.Infof("I'm running %v.", service.LocalSystem())
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case tm := <-ticker.C:
|
||||
err := logger.Infof("Still running at %v...", tm)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
case <-p.exit:
|
||||
ticker.Stop()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (p *program) Stop(s service.Service) error {
|
||||
err := logger.Info("I'm Stopping!")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
close(p.exit)
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
var name = "GoServiceTest"
|
||||
var displayName = "Go Service Test"
|
||||
var desc = "This is a test Go service. It is designed to run well."
|
||||
|
||||
var s, err = service.NewService(name, displayName, desc)
|
||||
log = s
|
||||
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 {
|
||||
fmt.Printf("%s unable to start: %s", displayName, err)
|
||||
return
|
||||
panic(err)
|
||||
}
|
||||
logger, err = s.SystemLogger()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
var err error
|
||||
verb := os.Args[1]
|
||||
switch verb {
|
||||
case "install":
|
||||
err = s.Install()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to install: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" installed.\n", displayName)
|
||||
case "remove":
|
||||
err = s.Remove()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to remove: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" removed.\n", displayName)
|
||||
case "run":
|
||||
doWork()
|
||||
case "start":
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" started.\n", displayName)
|
||||
case "stop":
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to stop: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" stopped.\n", displayName)
|
||||
err := service.Control(s, os.Args[1])
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
err = s.Run(func() error {
|
||||
// start
|
||||
go doWork()
|
||||
return nil
|
||||
}, func() error {
|
||||
// stop
|
||||
stopWork()
|
||||
return nil
|
||||
})
|
||||
err = s.Run()
|
||||
if err != nil {
|
||||
s.Error(err.Error())
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
var exit = make(chan struct{})
|
||||
|
||||
func doWork() {
|
||||
log.Info("I'm Running!")
|
||||
ticker := time.NewTicker(time.Minute)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
log.Info("Still running...")
|
||||
case <-exit:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
func stopWork() {
|
||||
log.Info("I'm Stopping!")
|
||||
exit <- struct{}{}
|
||||
}
|
||||
|
||||
+117
-61
@@ -2,23 +2,16 @@
|
||||
// Currently supports Windows, Linux/(systemd | Upstart | SysV), and OSX/Launchd.
|
||||
package service
|
||||
|
||||
import "bitbucket.org/kardianos/osext"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Creates a new service. name is the internal name
|
||||
// and should not contain spaces. Display name is the pretty print
|
||||
// name. The description is an arbitrary string used to describe the
|
||||
// service.
|
||||
func NewService(name, displayName, description string) (Service, error) {
|
||||
return newService(&Config{
|
||||
Name: name,
|
||||
DisplayName: displayName,
|
||||
Description: description,
|
||||
})
|
||||
}
|
||||
|
||||
// Alpha API. Do not yet use.
|
||||
// Config provides the setup for a Service. The Name field is required.
|
||||
type Config struct {
|
||||
Name, DisplayName, Description string
|
||||
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.
|
||||
@@ -32,6 +25,18 @@ type Config struct {
|
||||
KV KeyValue
|
||||
}
|
||||
|
||||
var errNameFieldRequired = errors.New("Config.Name field is required.")
|
||||
|
||||
// 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
|
||||
}
|
||||
return newService(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.
|
||||
@@ -78,65 +83,116 @@ func (kv KeyValue) float64(name string, defaultValue float64) float64 {
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// Alpha API. Do not yet use.
|
||||
func NewServiceConfig(c *Config) (Service, error) {
|
||||
return newService(c)
|
||||
}
|
||||
|
||||
// Represents a generic way to interact with the system's service.
|
||||
type Service interface {
|
||||
Installer
|
||||
Controller
|
||||
Runner
|
||||
Logger
|
||||
// System represents the system and system's service being used.
|
||||
type System interface {
|
||||
// String returns a description of the OS and service platform.
|
||||
String() string
|
||||
}
|
||||
|
||||
// A Generic way to stop and start a service.
|
||||
type Runner interface {
|
||||
// Call quickly after initial entry point. Does not return until
|
||||
// service is ready to stop. onStart is called when the service is
|
||||
// starting, returning an error will fail to start the service.
|
||||
// If an error is returned from onStop, the service will still stop.
|
||||
// An error passed from onStart or onStop will be returned as
|
||||
// an error from Run.
|
||||
// Both callbacks should return quickly and not block.
|
||||
Run(onStart, onStop func() error) error
|
||||
// LocalSystem get's the local system information.
|
||||
func LocalSystem() System {
|
||||
return system
|
||||
}
|
||||
|
||||
// Simple install and remove commands.
|
||||
type Installer interface {
|
||||
// Installs this service on the system. May return an
|
||||
// error if this service is already installed.
|
||||
Install() 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
|
||||
|
||||
// Removes this service from the system. May return an
|
||||
// error if this service is not already installed.
|
||||
Remove() 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
|
||||
}
|
||||
|
||||
// A service that implements ServiceController is able to
|
||||
// start and stop itself.
|
||||
type Controller interface {
|
||||
// Starts this service on the system.
|
||||
// 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
|
||||
|
||||
// Stops this service on the system.
|
||||
// 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
|
||||
|
||||
// Remove removes the given service from the OS service manager. This may require
|
||||
// greater rights. Will return an error if the service is not present.
|
||||
Remove() error
|
||||
|
||||
// Interactive returns false if running under the OS service manager
|
||||
// and true otherwise.
|
||||
Interactive() bool
|
||||
|
||||
// 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.
|
||||
Logger() (Logger, error)
|
||||
|
||||
// SystemLogger opens and returns a system logger.
|
||||
SystemLogger() (Logger, error)
|
||||
|
||||
// String displays the name of the service. The display name if present,
|
||||
// otherwise the name.
|
||||
String() string
|
||||
}
|
||||
|
||||
// A service that implements ServiceLogger can perform simple system logging.
|
||||
// ControlAction list valid string texts to use in Control.
|
||||
var ControlAction = [5]string{"start", "stop", "restart", "install", "remove"}
|
||||
|
||||
// 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.Remove()
|
||||
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 {
|
||||
// Basic log functions in the context of the service.
|
||||
Error(format string, a ...interface{}) error
|
||||
Warning(format string, a ...interface{}) error
|
||||
Info(format string, a ...interface{}) error
|
||||
}
|
||||
Error(v ...interface{}) error
|
||||
Warning(v ...interface{}) error
|
||||
Info(v ...interface{}) error
|
||||
|
||||
// Depreciated. Use osext.Executable instead.
|
||||
// Returns the full path of the running executable
|
||||
// as reported by the system. Includes the executable
|
||||
// image name.
|
||||
func GetExePath() (exePath string, err error) {
|
||||
return osext.Executable()
|
||||
Errorf(format string, a ...interface{}) error
|
||||
Warningf(format string, a ...interface{}) error
|
||||
Infof(format string, a ...interface{}) error
|
||||
}
|
||||
|
||||
+50
-23
@@ -2,40 +2,57 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"os/user"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/kardianos/osext"
|
||||
)
|
||||
|
||||
const maxPathSize = 32 * 1024
|
||||
|
||||
func newService(c *Config) (s *darwinLaunchdService, err error) {
|
||||
s = &darwinLaunchdService{
|
||||
const version = "Darwin Launchd"
|
||||
|
||||
type darwinSystem struct{}
|
||||
|
||||
func (ls darwinSystem) String() string {
|
||||
return version
|
||||
}
|
||||
|
||||
var system = darwinSystem{}
|
||||
|
||||
func isInteractive() (bool, error) {
|
||||
// TODO: The PPID of Launchd is 1. The PPid of a service process should match launchd's PID.
|
||||
return os.Getppid() != 1, nil
|
||||
}
|
||||
|
||||
func newService(i Interface, c *Config) (*darwinLaunchdService, error) {
|
||||
s := &darwinLaunchdService{
|
||||
i: i,
|
||||
Config: c,
|
||||
}
|
||||
|
||||
s.logger, err = syslog.New(syslog.LOG_INFO, c.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
s.interactive, err = isInteractive()
|
||||
|
||||
return s, nil
|
||||
return s, err
|
||||
}
|
||||
|
||||
type darwinLaunchdService struct {
|
||||
i Interface
|
||||
*Config
|
||||
logger *syslog.Writer
|
||||
|
||||
interactive bool
|
||||
}
|
||||
|
||||
const version = "Darwin Launchd"
|
||||
|
||||
func (s *darwinLaunchdService) String() string {
|
||||
return version
|
||||
if len(s.DisplayName) > 0 {
|
||||
return s.DisplayName
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
|
||||
@@ -49,6 +66,9 @@ func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
|
||||
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
|
||||
}
|
||||
|
||||
func (s *darwinLaunchdService) Interactive() bool {
|
||||
return s.interactive
|
||||
}
|
||||
func (s *darwinLaunchdService) Install() error {
|
||||
confPath, err := s.getServiceFilePath()
|
||||
if err != nil {
|
||||
@@ -120,11 +140,19 @@ func (s *darwinLaunchdService) Stop() error {
|
||||
cmd := exec.Command("launchctl", "unload", confPath)
|
||||
return cmd.Run()
|
||||
}
|
||||
func (s *darwinLaunchdService) Restart() error {
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
|
||||
func (s *darwinLaunchdService) Run() error {
|
||||
var err error
|
||||
|
||||
err = onStart()
|
||||
err = s.i.Start(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -135,18 +163,17 @@ func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
|
||||
|
||||
<-sigChan
|
||||
|
||||
return onStop()
|
||||
return s.i.Stop(s)
|
||||
}
|
||||
|
||||
func (s *darwinLaunchdService) Error(format string, a ...interface{}) error {
|
||||
return s.logger.Err(fmt.Sprintf(format, a...))
|
||||
func (s *darwinLaunchdService) Logger() (Logger, error) {
|
||||
if s.interactive {
|
||||
return ConsoleLogger, nil
|
||||
}
|
||||
return s.SystemLogger()
|
||||
}
|
||||
func (s *darwinLaunchdService) Warning(format string, a ...interface{}) error {
|
||||
return s.logger.Warning(fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (s *darwinLaunchdService) Info(format string, a ...interface{}) error {
|
||||
// On Darwin syslog.log defaults to loggint >= Notice (see /etc/asl.conf).
|
||||
return s.logger.Notice(fmt.Sprintf(format, a...))
|
||||
func (s *darwinLaunchdService) SystemLogger() (Logger, error) {
|
||||
return newSysLogger(s.Name)
|
||||
}
|
||||
|
||||
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
|
||||
|
||||
+79
-56
@@ -2,11 +2,11 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/kardianos/osext"
|
||||
)
|
||||
@@ -27,23 +27,6 @@ func getFlavor() initFlavor {
|
||||
return flavor
|
||||
}
|
||||
|
||||
func newService(c *Config) (Service, error) {
|
||||
s := &linuxService{
|
||||
flavor: getFlavor(),
|
||||
name: c.Name,
|
||||
displayName: c.DisplayName,
|
||||
description: c.Description,
|
||||
}
|
||||
|
||||
var err error
|
||||
s.logger, err = syslog.New(syslog.LOG_INFO, s.name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func isUpstart() bool {
|
||||
if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
|
||||
return true
|
||||
@@ -59,13 +42,38 @@ func isSystemd() bool {
|
||||
}
|
||||
|
||||
type linuxService struct {
|
||||
flavor initFlavor
|
||||
name, displayName, description string
|
||||
logger *syslog.Writer
|
||||
i Interface
|
||||
*Config
|
||||
|
||||
interactive bool
|
||||
}
|
||||
|
||||
func (ls *linuxService) String() string {
|
||||
return fmt.Sprintf("Linux %s", ls.flavor.String())
|
||||
var flavor = getFlavor()
|
||||
|
||||
type linuxSystem struct{}
|
||||
|
||||
func (ls linuxSystem) String() string {
|
||||
return fmt.Sprintf("Linux %s", flavor.String())
|
||||
}
|
||||
|
||||
var system = linuxSystem{}
|
||||
|
||||
func newService(i Interface, c *Config) (Service, error) {
|
||||
s := &linuxService{
|
||||
i: i,
|
||||
Config: c,
|
||||
}
|
||||
var err error
|
||||
s.interactive, err = isInteractive()
|
||||
|
||||
return s, err
|
||||
}
|
||||
|
||||
func (s *linuxService) String() string {
|
||||
if len(s.DisplayName) > 0 {
|
||||
return s.DisplayName
|
||||
}
|
||||
return s.Name
|
||||
}
|
||||
|
||||
type initFlavor uint8
|
||||
@@ -109,8 +117,17 @@ func (f initFlavor) GetTemplate() *template.Template {
|
||||
return template.Must(template.New(f.String() + "Script").Parse(templ))
|
||||
}
|
||||
|
||||
func isInteractive() (bool, error) {
|
||||
// TODO: Is this true for user services?
|
||||
return os.Getppid() != 1, nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Interactive() bool {
|
||||
return s.interactive
|
||||
}
|
||||
|
||||
func (s *linuxService) Install() error {
|
||||
confPath := s.flavor.ConfigPath(s.name)
|
||||
confPath := flavor.ConfigPath(s.Name)
|
||||
_, err := os.Stat(confPath)
|
||||
if err == nil {
|
||||
return fmt.Errorf("Init already exists: %s", confPath)
|
||||
@@ -132,33 +149,33 @@ func (s *linuxService) Install() error {
|
||||
Description string
|
||||
Path string
|
||||
}{
|
||||
s.displayName,
|
||||
s.description,
|
||||
s.DisplayName,
|
||||
s.Description,
|
||||
path,
|
||||
}
|
||||
|
||||
err = s.flavor.GetTemplate().Execute(f, to)
|
||||
err = flavor.GetTemplate().Execute(f, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if s.flavor == initSystemV {
|
||||
if flavor == initSystemV {
|
||||
if err = os.Chmod(confPath, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, i := range [...]string{"2", "3", "4", "5"} {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.name); err != nil {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/S50"+s.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
for _, i := range [...]string{"0", "1", "6"} {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.name); err != nil {
|
||||
if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.Name); err != nil {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if s.flavor == initSystemd {
|
||||
if flavor == initSystemd {
|
||||
return exec.Command("systemctl", "daemon-reload").Run()
|
||||
}
|
||||
|
||||
@@ -166,23 +183,30 @@ func (s *linuxService) Install() error {
|
||||
}
|
||||
|
||||
func (s *linuxService) Remove() error {
|
||||
if s.flavor == initSystemd {
|
||||
exec.Command("systemctl", "disable", s.name+".service").Run()
|
||||
if flavor == initSystemd {
|
||||
exec.Command("systemctl", "disable", s.Name+".service").Run()
|
||||
}
|
||||
if err := os.Remove(s.flavor.ConfigPath(s.name)); err != nil {
|
||||
if err := os.Remove(flavor.ConfigPath(s.Name)); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *linuxService) Run(onStart, onStop func() error) (err error) {
|
||||
err = onStart()
|
||||
func (s *linuxService) Logger() (Logger, error) {
|
||||
if s.interactive {
|
||||
return ConsoleLogger, nil
|
||||
}
|
||||
return s.SystemLogger()
|
||||
}
|
||||
func (s *linuxService) SystemLogger() (Logger, error) {
|
||||
return newSysLogger(s.Name)
|
||||
}
|
||||
|
||||
func (s *linuxService) Run() (err error) {
|
||||
err = s.i.Start(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
err = onStop()
|
||||
}()
|
||||
|
||||
sigChan := make(chan os.Signal, 3)
|
||||
|
||||
@@ -190,39 +214,38 @@ func (s *linuxService) Run(onStart, onStop func() error) (err error) {
|
||||
|
||||
<-sigChan
|
||||
|
||||
return nil
|
||||
return s.i.Stop(s)
|
||||
}
|
||||
|
||||
func (s *linuxService) Start() error {
|
||||
switch s.flavor {
|
||||
switch flavor {
|
||||
case initSystemd:
|
||||
return exec.Command("systemctl", "start", s.name+".service").Run()
|
||||
return exec.Command("systemctl", "start", s.Name+".service").Run()
|
||||
case initUpstart:
|
||||
return exec.Command("initctl", "start", s.name).Run()
|
||||
return exec.Command("initctl", "start", s.Name).Run()
|
||||
default:
|
||||
return exec.Command("service", s.name, "start").Run()
|
||||
return exec.Command("service", s.Name, "start").Run()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *linuxService) Stop() error {
|
||||
switch s.flavor {
|
||||
switch flavor {
|
||||
case initSystemd:
|
||||
return exec.Command("systemctl", "stop", s.name+".service").Run()
|
||||
return exec.Command("systemctl", "stop", s.Name+".service").Run()
|
||||
case initUpstart:
|
||||
return exec.Command("initctl", "stop", s.name).Run()
|
||||
return exec.Command("initctl", "stop", s.Name).Run()
|
||||
default:
|
||||
return exec.Command("service", s.name, "stop").Run()
|
||||
return exec.Command("service", s.Name, "stop").Run()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *linuxService) Error(format string, a ...interface{}) error {
|
||||
return s.logger.Err(fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (s *linuxService) Warning(format string, a ...interface{}) error {
|
||||
return s.logger.Warning(fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (s *linuxService) Info(format string, a ...interface{}) error {
|
||||
return s.logger.Info(fmt.Sprintf(format, a...))
|
||||
func (s *linuxService) Restart() error {
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return s.Start()
|
||||
}
|
||||
|
||||
const systemVScript = `#!/bin/sh
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
|
||||
// +build linux darwin
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
)
|
||||
|
||||
func newSysLogger(name string) (Logger, error) {
|
||||
w, err := syslog.New(syslog.LOG_INFO, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sysLogger{w}, nil
|
||||
}
|
||||
|
||||
type sysLogger struct {
|
||||
*syslog.Writer
|
||||
}
|
||||
|
||||
func (s sysLogger) Error(v ...interface{}) error {
|
||||
return s.Writer.Err(fmt.Sprint(v...))
|
||||
}
|
||||
func (s sysLogger) Warning(v ...interface{}) error {
|
||||
return s.Writer.Warning(fmt.Sprint(v...))
|
||||
}
|
||||
func (s sysLogger) Info(v ...interface{}) error {
|
||||
return s.Writer.Info(fmt.Sprint(v...))
|
||||
}
|
||||
func (s sysLogger) Errorf(format string, a ...interface{}) error {
|
||||
return s.Writer.Err(fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (s sysLogger) Warningf(format string, a ...interface{}) error {
|
||||
return s.Writer.Warning(fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (s sysLogger) Infof(format string, a ...interface{}) error {
|
||||
return s.Writer.Info(fmt.Sprintf(format, a...))
|
||||
}
|
||||
+129
-53
@@ -2,6 +2,9 @@ package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"bitbucket.org/kardianos/osext"
|
||||
"code.google.com/p/winsvc/eventlog"
|
||||
@@ -9,32 +12,94 @@ import (
|
||||
"code.google.com/p/winsvc/svc"
|
||||
)
|
||||
|
||||
func newService(c *Config) (*windowsService, error) {
|
||||
return &windowsService{
|
||||
name: c.Name,
|
||||
displayName: c.DisplayName,
|
||||
description: c.Description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type windowsService struct {
|
||||
name, displayName, description string
|
||||
onStart, onStop func() error
|
||||
logger *eventlog.Log
|
||||
}
|
||||
|
||||
const version = "Windows Service"
|
||||
|
||||
func (ws *windowsService) String() string {
|
||||
type windowsService struct {
|
||||
i Interface
|
||||
*Config
|
||||
|
||||
interactive bool
|
||||
}
|
||||
|
||||
// WindowsLogger allows using windows specific logging methods.
|
||||
type WindowsLogger struct {
|
||||
ev *eventlog.Log
|
||||
}
|
||||
|
||||
type windowsSystem struct{}
|
||||
|
||||
func (windowsSystem) String() string {
|
||||
return version
|
||||
}
|
||||
|
||||
func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
||||
var system = windowsSystem{}
|
||||
|
||||
func (l WindowsLogger) Error(v ...interface{}) error {
|
||||
return l.ev.Error(3, fmt.Sprint(v...))
|
||||
}
|
||||
func (l WindowsLogger) Warning(v ...interface{}) error {
|
||||
return l.ev.Warning(2, fmt.Sprint(v...))
|
||||
}
|
||||
func (l WindowsLogger) Info(v ...interface{}) error {
|
||||
return l.ev.Info(1, fmt.Sprint(v...))
|
||||
}
|
||||
func (l WindowsLogger) Errorf(format string, a ...interface{}) error {
|
||||
return l.ev.Error(3, fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (l WindowsLogger) Warningf(format string, a ...interface{}) error {
|
||||
return l.ev.Warning(2, fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (l WindowsLogger) Infof(format string, a ...interface{}) error {
|
||||
return l.ev.Info(1, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func (l WindowsLogger) NError(eventId uint32, v ...interface{}) error {
|
||||
return l.ev.Error(eventId, fmt.Sprint(v...))
|
||||
}
|
||||
func (l WindowsLogger) NWarning(eventId uint32, v ...interface{}) error {
|
||||
return l.ev.Warning(eventId, fmt.Sprint(v...))
|
||||
}
|
||||
func (l WindowsLogger) NInfo(eventId uint32, v ...interface{}) error {
|
||||
return l.ev.Info(eventId, fmt.Sprint(v...))
|
||||
}
|
||||
func (l WindowsLogger) NErrorf(eventId uint32, format string, a ...interface{}) error {
|
||||
return l.ev.Error(eventId, fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (l WindowsLogger) NWarningf(eventId uint32, format string, a ...interface{}) error {
|
||||
return l.ev.Warning(eventId, fmt.Sprintf(format, a...))
|
||||
}
|
||||
func (l WindowsLogger) NInfof(eventId uint32, format string, a ...interface{}) error {
|
||||
return l.ev.Info(eventId, fmt.Sprintf(format, a...))
|
||||
}
|
||||
|
||||
func isInteractive() (bool, error) {
|
||||
return svc.IsAnInteractiveSession()
|
||||
}
|
||||
|
||||
func newService(i Interface, c *Config) (*windowsService, error) {
|
||||
ws := &windowsService{
|
||||
i: i,
|
||||
Config: c,
|
||||
}
|
||||
var err error
|
||||
ws.interactive, err = isInteractive()
|
||||
return ws, err
|
||||
}
|
||||
|
||||
func (ws *windowsService) String() string {
|
||||
if len(ws.DisplayName) > 0 {
|
||||
return ws.DisplayName
|
||||
}
|
||||
return ws.Name
|
||||
}
|
||||
|
||||
func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
|
||||
if err := ws.onStart(); err != nil {
|
||||
ws.Error(err.Error())
|
||||
if err := ws.i.Start(ws); err != nil {
|
||||
// TODO: log error.
|
||||
// ws.Error(err.Error())
|
||||
return true, 1
|
||||
}
|
||||
|
||||
@@ -47,10 +112,10 @@ loop:
|
||||
changes <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
if err := ws.onStop(); err != nil {
|
||||
ws.Error(err.Error())
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
continue loop
|
||||
if err := ws.i.Stop(ws); err != nil {
|
||||
// TODO: Log error.
|
||||
// ws.Error(err.Error())
|
||||
return true, 2
|
||||
}
|
||||
break loop
|
||||
default:
|
||||
@@ -58,7 +123,11 @@ loop:
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
return false, 0
|
||||
}
|
||||
|
||||
func (ws *windowsService) Interactive() bool {
|
||||
return ws.interactive
|
||||
}
|
||||
|
||||
func (ws *windowsService) Install() error {
|
||||
@@ -73,21 +142,21 @@ func (ws *windowsService) Install() error {
|
||||
return err
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(ws.name)
|
||||
s, err := m.OpenService(ws.Name)
|
||||
if err == nil {
|
||||
s.Close()
|
||||
return fmt.Errorf("service %s already exists", ws.name)
|
||||
return fmt.Errorf("service %s already exists", ws.Name)
|
||||
}
|
||||
s, err = m.CreateService(ws.name, exepath, mgr.Config{
|
||||
DisplayName: ws.displayName,
|
||||
Description: ws.description,
|
||||
s, err = m.CreateService(ws.Name, exepath, mgr.Config{
|
||||
DisplayName: ws.DisplayName,
|
||||
Description: ws.Description,
|
||||
StartType: mgr.StartAutomatic,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer s.Close()
|
||||
err = eventlog.InstallAsEventCreate(ws.name, eventlog.Error|eventlog.Warning|eventlog.Info)
|
||||
err = eventlog.InstallAsEventCreate(ws.Name, eventlog.Error|eventlog.Warning|eventlog.Info)
|
||||
if err != nil {
|
||||
s.Delete()
|
||||
return fmt.Errorf("InstallAsEventCreate() failed: %s", err)
|
||||
@@ -101,34 +170,38 @@ func (ws *windowsService) Remove() error {
|
||||
return err
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(ws.name)
|
||||
s, err := m.OpenService(ws.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("service %s is not installed", ws.name)
|
||||
return fmt.Errorf("service %s is not installed", ws.Name)
|
||||
}
|
||||
defer s.Close()
|
||||
err = s.Delete()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = eventlog.Remove(ws.name)
|
||||
err = eventlog.Remove(ws.Name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ws *windowsService) Run(onStart, onStop func() error) error {
|
||||
elog, err := eventlog.Open(ws.name)
|
||||
func (ws *windowsService) Run() error {
|
||||
if !ws.interactive {
|
||||
return svc.Run(ws.Name, ws)
|
||||
}
|
||||
err := ws.i.Start(ws)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer elog.Close()
|
||||
|
||||
ws.logger = elog
|
||||
sigChan := make(chan os.Signal)
|
||||
|
||||
ws.onStart = onStart
|
||||
ws.onStop = onStop
|
||||
return svc.Run(ws.name, ws)
|
||||
signal.Notify(sigChan, os.Interrupt, os.Kill)
|
||||
|
||||
<-sigChan
|
||||
|
||||
return ws.i.Stop(ws)
|
||||
}
|
||||
|
||||
func (ws *windowsService) Start() error {
|
||||
@@ -138,7 +211,7 @@ func (ws *windowsService) Start() error {
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
s, err := m.OpenService(ws.name)
|
||||
s, err := m.OpenService(ws.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -153,7 +226,7 @@ func (ws *windowsService) Stop() error {
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
||||
s, err := m.OpenService(ws.name)
|
||||
s, err := m.OpenService(ws.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -162,21 +235,24 @@ func (ws *windowsService) Stop() error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (ws *windowsService) Error(format string, a ...interface{}) error {
|
||||
if ws.logger == nil {
|
||||
return nil
|
||||
func (ws *windowsService) Restart() error {
|
||||
err := ws.Stop()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ws.logger.Error(3, fmt.Sprintf(format, a...))
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
return ws.Start()
|
||||
}
|
||||
func (ws *windowsService) Warning(format string, a ...interface{}) error {
|
||||
if ws.logger == nil {
|
||||
return nil
|
||||
func (ws *windowsService) Logger() (Logger, error) {
|
||||
if ws.interactive {
|
||||
return ConsoleLogger, nil
|
||||
}
|
||||
return ws.logger.Warning(2, fmt.Sprintf(format, a...))
|
||||
return ws.SystemLogger()
|
||||
}
|
||||
func (ws *windowsService) Info(format string, a ...interface{}) error {
|
||||
if ws.logger == nil {
|
||||
return nil
|
||||
func (ws *windowsService) SystemLogger() (Logger, error) {
|
||||
el, err := eventlog.Open(ws.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ws.logger.Info(1, fmt.Sprintf(format, a...))
|
||||
return WindowsLogger{el}, nil
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
package stdservice
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ConsoleLogger struct{}
|
||||
|
||||
func (ConsoleLogger) Error(format string, a ...interface{}) error {
|
||||
fmt.Printf("E: "+format+"\n", a...)
|
||||
return nil
|
||||
}
|
||||
func (ConsoleLogger) Warning(format string, a ...interface{}) error {
|
||||
fmt.Printf("W: "+format+"\n", a...)
|
||||
return nil
|
||||
}
|
||||
func (ConsoleLogger) Info(format string, a ...interface{}) error {
|
||||
fmt.Printf("I: "+format+"\n", a...)
|
||||
return nil
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
/*
|
||||
Many services that run on different platforms cannot rely
|
||||
on flags to be passed for configuration. Some platforms
|
||||
require explicit install commands. This package handles the common
|
||||
boilerplate code. The following command may be passed to the
|
||||
executable as the first argument:
|
||||
install | remove | run | start | stop
|
||||
|
||||
These commands will do the following actions:
|
||||
install - Install the running executable as a service on the system.
|
||||
remove - Remove the running executable as a service on the system.
|
||||
run - Run the service as a command line application, output log to prompt.
|
||||
start - Starts the service via system controls.
|
||||
stop - Stops the service via system controls.
|
||||
*/
|
||||
package stdservice
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"bitbucket.org/kardianos/service"
|
||||
)
|
||||
|
||||
// Standard service configuration. Start MUST block.
|
||||
// Stop MUST NOT block for more then a second or two.
|
||||
type Config struct {
|
||||
// Used to register the service with the operating system.
|
||||
Name, DisplayName, LongDescription string
|
||||
|
||||
// Called when the service starts or stops.
|
||||
// Stop may be nil.
|
||||
Start, Stop func(c *Config)
|
||||
|
||||
// Called after logging may be setup but before the service is started.
|
||||
// Init is optional and may be nil.
|
||||
// If Init returns an error, that error is logged to the logger
|
||||
// and the service start is aborted.
|
||||
// Init should not block.
|
||||
Init func(c *Config) error
|
||||
|
||||
s service.Service
|
||||
l service.Logger
|
||||
}
|
||||
|
||||
// Get service after Run() has been called.
|
||||
func (c *Config) Service() service.Service {
|
||||
return c.s
|
||||
}
|
||||
|
||||
// Get logger after Run() has been called.
|
||||
func (c *Config) Logger() service.Logger {
|
||||
return c.l
|
||||
}
|
||||
|
||||
// Fill in configuration, then call Run() to setup basic handling.
|
||||
// Blocks until program completes. Is intended to handle the standard
|
||||
// simple cases for running a service.
|
||||
func (c *Config) Run() {
|
||||
run(c)
|
||||
}
|
||||
|
||||
// Depreciated. Same as *Config.Run().
|
||||
func Run(c *Config) {
|
||||
run(c)
|
||||
}
|
||||
|
||||
func run(c *Config) {
|
||||
var s, err = service.NewService(c.Name, c.DisplayName, c.LongDescription)
|
||||
c.s = s
|
||||
c.l = s
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("%s unable to start: %s", c.DisplayName, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(os.Args) > 1 {
|
||||
var err error
|
||||
verb := os.Args[1]
|
||||
switch verb {
|
||||
case "install":
|
||||
err = s.Install()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to install: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" installed.\n", c.DisplayName)
|
||||
case "remove":
|
||||
err = s.Remove()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to remove: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" removed.\n", c.DisplayName)
|
||||
case "run":
|
||||
c.l = ConsoleLogger{}
|
||||
defer func() {
|
||||
if c.Stop != nil {
|
||||
c.Stop(c)
|
||||
}
|
||||
}()
|
||||
if c.Init != nil {
|
||||
err := c.Init(c)
|
||||
if err != nil {
|
||||
c.l.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Start(c)
|
||||
case "start":
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" started.\n", c.DisplayName)
|
||||
case "stop":
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to stop: %s\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Service \"%s\" stopped.\n", c.DisplayName)
|
||||
default:
|
||||
fmt.Printf("Options for \"%s\": (install | remove | run | start | stop)\n", os.Args[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if c.Init != nil {
|
||||
err := c.Init(c)
|
||||
if err != nil {
|
||||
c.l.Error(err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = s.Run(func() error {
|
||||
// start
|
||||
go c.Start(c)
|
||||
return nil
|
||||
}, func() error {
|
||||
// stop
|
||||
if c.Stop != nil {
|
||||
c.Stop(c)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
c.l.Error(err.Error())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user