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
|
This software is provided 'as-is', without any express or implied
|
||||||
warranty. In no event will the authors be held liable for any damages
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"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() {
|
func main() {
|
||||||
var name = "GoServiceTest"
|
svcConfig := &service.Config{
|
||||||
var displayName = "Go Service Test"
|
Name: "GoServiceTest",
|
||||||
var desc = "This is a test Go service. It is designed to run well."
|
DisplayName: "Go Service Test",
|
||||||
|
Description: "This is a test Go service.",
|
||||||
var s, err = service.NewService(name, displayName, desc)
|
}
|
||||||
log = s
|
|
||||||
|
|
||||||
|
prg := &program{}
|
||||||
|
s, err := service.New(prg, svcConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("%s unable to start: %s", displayName, err)
|
panic(err)
|
||||||
return
|
}
|
||||||
|
logger, err = s.SystemLogger()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(os.Args) > 1 {
|
if len(os.Args) > 1 {
|
||||||
var err error
|
err := service.Control(s, os.Args[1])
|
||||||
verb := os.Args[1]
|
if err != nil {
|
||||||
switch verb {
|
log.Fatal(err)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = s.Run(func() error {
|
err = s.Run()
|
||||||
// start
|
|
||||||
go doWork()
|
|
||||||
return nil
|
|
||||||
}, func() error {
|
|
||||||
// stop
|
|
||||||
stopWork()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
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.
|
// Currently supports Windows, Linux/(systemd | Upstart | SysV), and OSX/Launchd.
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import "bitbucket.org/kardianos/osext"
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// Creates a new service. name is the internal name
|
// Config provides the setup for a Service. The Name field is required.
|
||||||
// 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.
|
|
||||||
type Config struct {
|
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.
|
UserName string // Run as username.
|
||||||
Arguments []string // Run with arguments.
|
Arguments []string // Run with arguments.
|
||||||
@@ -32,6 +25,18 @@ type Config struct {
|
|||||||
KV KeyValue
|
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{}
|
type KeyValue map[string]interface{}
|
||||||
|
|
||||||
// Bool returns the value of the given name, assuming the value is a boolean.
|
// 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
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Alpha API. Do not yet use.
|
// System represents the system and system's service being used.
|
||||||
func NewServiceConfig(c *Config) (Service, error) {
|
type System interface {
|
||||||
return newService(c)
|
// String returns a description of the OS and service platform.
|
||||||
}
|
|
||||||
|
|
||||||
// Represents a generic way to interact with the system's service.
|
|
||||||
type Service interface {
|
|
||||||
Installer
|
|
||||||
Controller
|
|
||||||
Runner
|
|
||||||
Logger
|
|
||||||
String() string
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Generic way to stop and start a service.
|
// LocalSystem get's the local system information.
|
||||||
type Runner interface {
|
func LocalSystem() System {
|
||||||
// Call quickly after initial entry point. Does not return until
|
return system
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple install and remove commands.
|
// Interface represents the service interface for a program. Start runs before
|
||||||
type Installer interface {
|
// the hosting process is granted control and Stop runs when control is returned.
|
||||||
// Installs this service on the system. May return an
|
//
|
||||||
// error if this service is already installed.
|
// 1. OS service manager executes user program.
|
||||||
Install() error
|
// 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
|
// Stop provides a place to clean up program execution before it is terminated.
|
||||||
// error if this service is not already installed.
|
// It should not take more then a few seconds to execute.
|
||||||
Remove() error
|
// Stop should not call os.Exit directly in the function.
|
||||||
|
Stop(s Service) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// A service that implements ServiceController is able to
|
// Service represents a service that can be run or controlled.
|
||||||
// start and stop itself.
|
type Service interface {
|
||||||
type Controller interface {
|
// Run should be called shortly after the program entry point.
|
||||||
// Starts this service on the system.
|
// 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
|
Start() error
|
||||||
|
|
||||||
// Stops this service on the system.
|
// Stop signals to the OS service manager the given service should stop.
|
||||||
Stop() error
|
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 {
|
type Logger interface {
|
||||||
// Basic log functions in the context of the service.
|
Error(v ...interface{}) error
|
||||||
Error(format string, a ...interface{}) error
|
Warning(v ...interface{}) error
|
||||||
Warning(format string, a ...interface{}) error
|
Info(v ...interface{}) error
|
||||||
Info(format string, a ...interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Depreciated. Use osext.Executable instead.
|
Errorf(format string, a ...interface{}) error
|
||||||
// Returns the full path of the running executable
|
Warningf(format string, a ...interface{}) error
|
||||||
// as reported by the system. Includes the executable
|
Infof(format string, a ...interface{}) error
|
||||||
// image name.
|
|
||||||
func GetExePath() (exePath string, err error) {
|
|
||||||
return osext.Executable()
|
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-23
@@ -2,40 +2,57 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/syslog"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"os/user"
|
"os/user"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
"bitbucket.org/kardianos/osext"
|
"bitbucket.org/kardianos/osext"
|
||||||
)
|
)
|
||||||
|
|
||||||
const maxPathSize = 32 * 1024
|
const maxPathSize = 32 * 1024
|
||||||
|
|
||||||
func newService(c *Config) (s *darwinLaunchdService, err error) {
|
const version = "Darwin Launchd"
|
||||||
s = &darwinLaunchdService{
|
|
||||||
|
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,
|
Config: c,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger, err = syslog.New(syslog.LOG_INFO, c.Name)
|
var err error
|
||||||
if err != nil {
|
s.interactive, err = isInteractive()
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
return s, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type darwinLaunchdService struct {
|
type darwinLaunchdService struct {
|
||||||
|
i Interface
|
||||||
*Config
|
*Config
|
||||||
logger *syslog.Writer
|
|
||||||
|
interactive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const version = "Darwin Launchd"
|
|
||||||
|
|
||||||
func (s *darwinLaunchdService) String() string {
|
func (s *darwinLaunchdService) String() string {
|
||||||
return version
|
if len(s.DisplayName) > 0 {
|
||||||
|
return s.DisplayName
|
||||||
|
}
|
||||||
|
return s.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
|
func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
|
||||||
@@ -49,6 +66,9 @@ func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
|
|||||||
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
|
return "/Library/LaunchDaemons/" + s.Name + ".plist", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *darwinLaunchdService) Interactive() bool {
|
||||||
|
return s.interactive
|
||||||
|
}
|
||||||
func (s *darwinLaunchdService) Install() error {
|
func (s *darwinLaunchdService) Install() error {
|
||||||
confPath, err := s.getServiceFilePath()
|
confPath, err := s.getServiceFilePath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -120,11 +140,19 @@ func (s *darwinLaunchdService) Stop() error {
|
|||||||
cmd := exec.Command("launchctl", "unload", confPath)
|
cmd := exec.Command("launchctl", "unload", confPath)
|
||||||
return cmd.Run()
|
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
|
var err error
|
||||||
|
|
||||||
err = onStart()
|
err = s.i.Start(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -135,18 +163,17 @@ func (s *darwinLaunchdService) Run(onStart, onStop func() error) error {
|
|||||||
|
|
||||||
<-sigChan
|
<-sigChan
|
||||||
|
|
||||||
return onStop()
|
return s.i.Stop(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *darwinLaunchdService) Error(format string, a ...interface{}) error {
|
func (s *darwinLaunchdService) Logger() (Logger, error) {
|
||||||
return s.logger.Err(fmt.Sprintf(format, a...))
|
if s.interactive {
|
||||||
|
return ConsoleLogger, nil
|
||||||
|
}
|
||||||
|
return s.SystemLogger()
|
||||||
}
|
}
|
||||||
func (s *darwinLaunchdService) Warning(format string, a ...interface{}) error {
|
func (s *darwinLaunchdService) SystemLogger() (Logger, error) {
|
||||||
return s.logger.Warning(fmt.Sprintf(format, a...))
|
return newSysLogger(s.Name)
|
||||||
}
|
|
||||||
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...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
|
var launchdConfig = `<?xml version='1.0' encoding='UTF-8'?>
|
||||||
|
|||||||
+79
-56
@@ -2,11 +2,11 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/syslog"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
"time"
|
||||||
|
|
||||||
"bitbucket.org/kardianos/osext"
|
"bitbucket.org/kardianos/osext"
|
||||||
)
|
)
|
||||||
@@ -27,23 +27,6 @@ func getFlavor() initFlavor {
|
|||||||
return flavor
|
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 {
|
func isUpstart() bool {
|
||||||
if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
|
if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
|
||||||
return true
|
return true
|
||||||
@@ -59,13 +42,38 @@ func isSystemd() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type linuxService struct {
|
type linuxService struct {
|
||||||
flavor initFlavor
|
i Interface
|
||||||
name, displayName, description string
|
*Config
|
||||||
logger *syslog.Writer
|
|
||||||
|
interactive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ls *linuxService) String() string {
|
var flavor = getFlavor()
|
||||||
return fmt.Sprintf("Linux %s", ls.flavor.String())
|
|
||||||
|
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
|
type initFlavor uint8
|
||||||
@@ -109,8 +117,17 @@ func (f initFlavor) GetTemplate() *template.Template {
|
|||||||
return template.Must(template.New(f.String() + "Script").Parse(templ))
|
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 {
|
func (s *linuxService) Install() error {
|
||||||
confPath := s.flavor.ConfigPath(s.name)
|
confPath := flavor.ConfigPath(s.Name)
|
||||||
_, err := os.Stat(confPath)
|
_, err := os.Stat(confPath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return fmt.Errorf("Init already exists: %s", confPath)
|
return fmt.Errorf("Init already exists: %s", confPath)
|
||||||
@@ -132,33 +149,33 @@ func (s *linuxService) Install() error {
|
|||||||
Description string
|
Description string
|
||||||
Path string
|
Path string
|
||||||
}{
|
}{
|
||||||
s.displayName,
|
s.DisplayName,
|
||||||
s.description,
|
s.Description,
|
||||||
path,
|
path,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.flavor.GetTemplate().Execute(f, to)
|
err = flavor.GetTemplate().Execute(f, to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.flavor == initSystemV {
|
if flavor == initSystemV {
|
||||||
if err = os.Chmod(confPath, 0755); err != nil {
|
if err = os.Chmod(confPath, 0755); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, i := range [...]string{"2", "3", "4", "5"} {
|
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
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, i := range [...]string{"0", "1", "6"} {
|
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
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.flavor == initSystemd {
|
if flavor == initSystemd {
|
||||||
return exec.Command("systemctl", "daemon-reload").Run()
|
return exec.Command("systemctl", "daemon-reload").Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,23 +183,30 @@ func (s *linuxService) Install() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *linuxService) Remove() error {
|
func (s *linuxService) Remove() error {
|
||||||
if s.flavor == initSystemd {
|
if flavor == initSystemd {
|
||||||
exec.Command("systemctl", "disable", s.name+".service").Run()
|
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 err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linuxService) Run(onStart, onStop func() error) (err error) {
|
func (s *linuxService) Logger() (Logger, error) {
|
||||||
err = onStart()
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer func() {
|
|
||||||
err = onStop()
|
|
||||||
}()
|
|
||||||
|
|
||||||
sigChan := make(chan os.Signal, 3)
|
sigChan := make(chan os.Signal, 3)
|
||||||
|
|
||||||
@@ -190,39 +214,38 @@ func (s *linuxService) Run(onStart, onStop func() error) (err error) {
|
|||||||
|
|
||||||
<-sigChan
|
<-sigChan
|
||||||
|
|
||||||
return nil
|
return s.i.Stop(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linuxService) Start() error {
|
func (s *linuxService) Start() error {
|
||||||
switch s.flavor {
|
switch flavor {
|
||||||
case initSystemd:
|
case initSystemd:
|
||||||
return exec.Command("systemctl", "start", s.name+".service").Run()
|
return exec.Command("systemctl", "start", s.Name+".service").Run()
|
||||||
case initUpstart:
|
case initUpstart:
|
||||||
return exec.Command("initctl", "start", s.name).Run()
|
return exec.Command("initctl", "start", s.Name).Run()
|
||||||
default:
|
default:
|
||||||
return exec.Command("service", s.name, "start").Run()
|
return exec.Command("service", s.Name, "start").Run()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *linuxService) Stop() error {
|
func (s *linuxService) Stop() error {
|
||||||
switch s.flavor {
|
switch flavor {
|
||||||
case initSystemd:
|
case initSystemd:
|
||||||
return exec.Command("systemctl", "stop", s.name+".service").Run()
|
return exec.Command("systemctl", "stop", s.Name+".service").Run()
|
||||||
case initUpstart:
|
case initUpstart:
|
||||||
return exec.Command("initctl", "stop", s.name).Run()
|
return exec.Command("initctl", "stop", s.Name).Run()
|
||||||
default:
|
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 {
|
func (s *linuxService) Restart() error {
|
||||||
return s.logger.Err(fmt.Sprintf(format, a...))
|
err := s.Stop()
|
||||||
}
|
if err != nil {
|
||||||
func (s *linuxService) Warning(format string, a ...interface{}) error {
|
return err
|
||||||
return s.logger.Warning(fmt.Sprintf(format, a...))
|
}
|
||||||
}
|
time.Sleep(50 * time.Millisecond)
|
||||||
func (s *linuxService) Info(format string, a ...interface{}) error {
|
return s.Start()
|
||||||
return s.logger.Info(fmt.Sprintf(format, a...))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemVScript = `#!/bin/sh
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"time"
|
||||||
|
|
||||||
"bitbucket.org/kardianos/osext"
|
"bitbucket.org/kardianos/osext"
|
||||||
"code.google.com/p/winsvc/eventlog"
|
"code.google.com/p/winsvc/eventlog"
|
||||||
@@ -9,32 +12,94 @@ import (
|
|||||||
"code.google.com/p/winsvc/svc"
|
"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"
|
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
|
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
|
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
|
||||||
changes <- svc.Status{State: svc.StartPending}
|
changes <- svc.Status{State: svc.StartPending}
|
||||||
|
|
||||||
if err := ws.onStart(); err != nil {
|
if err := ws.i.Start(ws); err != nil {
|
||||||
ws.Error(err.Error())
|
// TODO: log error.
|
||||||
|
// ws.Error(err.Error())
|
||||||
return true, 1
|
return true, 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,10 +112,10 @@ loop:
|
|||||||
changes <- c.CurrentStatus
|
changes <- c.CurrentStatus
|
||||||
case svc.Stop, svc.Shutdown:
|
case svc.Stop, svc.Shutdown:
|
||||||
changes <- svc.Status{State: svc.StopPending}
|
changes <- svc.Status{State: svc.StopPending}
|
||||||
if err := ws.onStop(); err != nil {
|
if err := ws.i.Stop(ws); err != nil {
|
||||||
ws.Error(err.Error())
|
// TODO: Log error.
|
||||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
// ws.Error(err.Error())
|
||||||
continue loop
|
return true, 2
|
||||||
}
|
}
|
||||||
break loop
|
break loop
|
||||||
default:
|
default:
|
||||||
@@ -58,7 +123,11 @@ loop:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return false, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ws *windowsService) Interactive() bool {
|
||||||
|
return ws.interactive
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *windowsService) Install() error {
|
func (ws *windowsService) Install() error {
|
||||||
@@ -73,21 +142,21 @@ func (ws *windowsService) Install() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer m.Disconnect()
|
defer m.Disconnect()
|
||||||
s, err := m.OpenService(ws.name)
|
s, err := m.OpenService(ws.Name)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
s.Close()
|
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{
|
s, err = m.CreateService(ws.Name, exepath, mgr.Config{
|
||||||
DisplayName: ws.displayName,
|
DisplayName: ws.DisplayName,
|
||||||
Description: ws.description,
|
Description: ws.Description,
|
||||||
StartType: mgr.StartAutomatic,
|
StartType: mgr.StartAutomatic,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer s.Close()
|
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 {
|
if err != nil {
|
||||||
s.Delete()
|
s.Delete()
|
||||||
return fmt.Errorf("InstallAsEventCreate() failed: %s", err)
|
return fmt.Errorf("InstallAsEventCreate() failed: %s", err)
|
||||||
@@ -101,34 +170,38 @@ func (ws *windowsService) Remove() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer m.Disconnect()
|
defer m.Disconnect()
|
||||||
s, err := m.OpenService(ws.name)
|
s, err := m.OpenService(ws.Name)
|
||||||
if err != nil {
|
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()
|
defer s.Close()
|
||||||
err = s.Delete()
|
err = s.Delete()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = eventlog.Remove(ws.name)
|
err = eventlog.Remove(ws.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
|
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *windowsService) Run(onStart, onStop func() error) error {
|
func (ws *windowsService) Run() error {
|
||||||
elog, err := eventlog.Open(ws.name)
|
if !ws.interactive {
|
||||||
|
return svc.Run(ws.Name, ws)
|
||||||
|
}
|
||||||
|
err := ws.i.Start(ws)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer elog.Close()
|
|
||||||
|
|
||||||
ws.logger = elog
|
sigChan := make(chan os.Signal)
|
||||||
|
|
||||||
ws.onStart = onStart
|
signal.Notify(sigChan, os.Interrupt, os.Kill)
|
||||||
ws.onStop = onStop
|
|
||||||
return svc.Run(ws.name, ws)
|
<-sigChan
|
||||||
|
|
||||||
|
return ws.i.Stop(ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *windowsService) Start() error {
|
func (ws *windowsService) Start() error {
|
||||||
@@ -138,7 +211,7 @@ func (ws *windowsService) Start() error {
|
|||||||
}
|
}
|
||||||
defer m.Disconnect()
|
defer m.Disconnect()
|
||||||
|
|
||||||
s, err := m.OpenService(ws.name)
|
s, err := m.OpenService(ws.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -153,7 +226,7 @@ func (ws *windowsService) Stop() error {
|
|||||||
}
|
}
|
||||||
defer m.Disconnect()
|
defer m.Disconnect()
|
||||||
|
|
||||||
s, err := m.OpenService(ws.name)
|
s, err := m.OpenService(ws.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -162,21 +235,24 @@ func (ws *windowsService) Stop() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ws *windowsService) Error(format string, a ...interface{}) error {
|
func (ws *windowsService) Restart() error {
|
||||||
if ws.logger == nil {
|
err := ws.Stop()
|
||||||
return nil
|
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 {
|
func (ws *windowsService) Logger() (Logger, error) {
|
||||||
if ws.logger == nil {
|
if ws.interactive {
|
||||||
return nil
|
return ConsoleLogger, nil
|
||||||
}
|
}
|
||||||
return ws.logger.Warning(2, fmt.Sprintf(format, a...))
|
return ws.SystemLogger()
|
||||||
}
|
}
|
||||||
func (ws *windowsService) Info(format string, a ...interface{}) error {
|
func (ws *windowsService) SystemLogger() (Logger, error) {
|
||||||
if ws.logger == nil {
|
el, err := eventlog.Open(ws.Name)
|
||||||
return nil
|
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