pax_global_header00006660000000000000000000000064143211131560014507gustar00rootroot0000000000000052 comment=f4a4df2b5eb5bdd0169d5ec60e48bbb4d0a6e029 service-1.2.2/000077500000000000000000000000001432111315600131515ustar00rootroot00000000000000service-1.2.2/.gitignore000066400000000000000000000000101432111315600151300ustar00rootroot00000000000000.vscode/service-1.2.2/.travis.yml000066400000000000000000000005551432111315600152670ustar00rootroot00000000000000arch: - ppc64le - amd64 language: go go_import_path: github.com/kardianos/service sudo: required go: - 1.12.x - 1.14.x - master before_install: - go get github.com/mattn/goveralls - go get golang.org/x/tools/cmd/cover script: - chmod +x linux-test-su.sh - sudo ./linux-test-su.sh $GOPATH `which go` - $GOPATH/bin/goveralls -service=travis-ci service-1.2.2/LICENSE000066400000000000000000000015461432111315600141640ustar00rootroot00000000000000Copyright (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 arising from the use of this software. Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely, subject to the following restrictions: 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. service-1.2.2/README.md000066400000000000000000000013611432111315600144310ustar00rootroot00000000000000# service [![GoDoc](https://godoc.org/github.com/kardianos/service?status.svg)](https://godoc.org/github.com/kardianos/service) service will install / un-install, start / stop, and run a program as a service (daemon). Currently supports Windows XP+, Linux/(systemd | Upstart | SysV), and OSX/Launchd. Windows controls services by setting up callbacks that is non-trivial. This is very different then other systems. This package provides the same API despite the substantial differences. It also can be used to detect how a program is called, from an interactive terminal or from a service manager. ## BUGS * Dependencies field is not implemented for Linux systems and Launchd. * OS X when running as a UserService Interactive will not be accurate. service-1.2.2/appveyor.yml000066400000000000000000000004301432111315600155360ustar00rootroot00000000000000version: "{build}" platform: - x86 - x64 clone_folder: c:\gopath\src\github.com\kardianos\service environment: GOPATH: c:\gopath install: - go version - go env - go get -v -t ./... build_script: - go install -v ./... test_script: - go test -v -tags su ./... service-1.2.2/console.go000066400000000000000000000021421432111315600151410ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service 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 } service-1.2.2/example/000077500000000000000000000000001432111315600146045ustar00rootroot00000000000000service-1.2.2/example/logging/000077500000000000000000000000001432111315600162325ustar00rootroot00000000000000service-1.2.2/example/logging/main.go000066400000000000000000000045261432111315600175140ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // Simple service that only works by printing a log message every few seconds. package main import ( "flag" "log" "time" "github.com/kardianos/service" ) var logger service.Logger // Program structures. // Define Start and Stop methods. type program struct { exit chan struct{} } func (p *program) Start(s service.Service) error { if service.Interactive() { logger.Info("Running in terminal.") } else { logger.Info("Running under service manager.") } p.exit = make(chan struct{}) // Start should not block. Do the actual work async. go p.run() return nil } func (p *program) run() error { logger.Infof("I'm running %v.", service.Platform()) ticker := time.NewTicker(2 * time.Second) for { select { case tm := <-ticker.C: logger.Infof("Still running at %v...", tm) case <-p.exit: ticker.Stop() return nil } } } func (p *program) Stop(s service.Service) error { // Any work in Stop should be quick, usually a few seconds at most. logger.Info("I'm Stopping!") close(p.exit) return nil } // Service setup. // Define service config. // Create the service. // Setup the logger. // Handle service controls (optional). // Run the service. func main() { svcFlag := flag.String("service", "", "Control the system service.") flag.Parse() options := make(service.KeyValue) options["Restart"] = "on-success" options["SuccessExitStatus"] = "1 2 8 SIGKILL" svcConfig := &service.Config{ Name: "GoServiceExampleLogging", DisplayName: "Go Service Example for Logging", Description: "This is an example Go service that outputs log messages.", Dependencies: []string{ "Requires=network.target", "After=network-online.target syslog.target"}, Option: options, } prg := &program{} s, err := service.New(prg, svcConfig) if err != nil { log.Fatal(err) } errs := make(chan error, 5) logger, err = s.Logger(errs) if err != nil { log.Fatal(err) } go func() { for { err := <-errs if err != nil { log.Print(err) } } }() if len(*svcFlag) != 0 { err := service.Control(s, *svcFlag) if err != nil { log.Printf("Valid actions: %q\n", service.ControlAction) log.Fatal(err) } return } err = s.Run() if err != nil { logger.Error(err) } } service-1.2.2/example/runner/000077500000000000000000000000001432111315600161155ustar00rootroot00000000000000service-1.2.2/example/runner/runner.go000066400000000000000000000066511432111315600177650ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // Simple service that only works by printing a log message every few seconds. package main import ( "encoding/json" "flag" "fmt" "log" "os" "os/exec" "path/filepath" "github.com/kardianos/service" ) // Config is the runner app config structure. type Config struct { Name, DisplayName, Description string Dir string Exec string Args []string Env []string Stderr, Stdout string } var logger service.Logger type program struct { exit chan struct{} service service.Service *Config cmd *exec.Cmd } func (p *program) Start(s service.Service) error { // Look for exec. // Verify home directory. fullExec, err := exec.LookPath(p.Exec) if err != nil { return fmt.Errorf("Failed to find executable %q: %v", p.Exec, err) } p.cmd = exec.Command(fullExec, p.Args...) p.cmd.Dir = p.Dir p.cmd.Env = append(os.Environ(), p.Env...) go p.run() return nil } func (p *program) run() { logger.Info("Starting ", p.DisplayName) defer func() { if service.Interactive() { p.Stop(p.service) } else { p.service.Stop() } }() if p.Stderr != "" { f, err := os.OpenFile(p.Stderr, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) if err != nil { logger.Warningf("Failed to open std err %q: %v", p.Stderr, err) return } defer f.Close() p.cmd.Stderr = f } if p.Stdout != "" { f, err := os.OpenFile(p.Stdout, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0777) if err != nil { logger.Warningf("Failed to open std out %q: %v", p.Stdout, err) return } defer f.Close() p.cmd.Stdout = f } err := p.cmd.Run() if err != nil { logger.Warningf("Error running: %v", err) } return } func (p *program) Stop(s service.Service) error { close(p.exit) logger.Info("Stopping ", p.DisplayName) if p.cmd.Process != nil { p.cmd.Process.Kill() } if service.Interactive() { os.Exit(0) } return nil } func getConfigPath() (string, error) { fullexecpath, err := os.Executable() if err != nil { return "", err } dir, execname := filepath.Split(fullexecpath) ext := filepath.Ext(execname) name := execname[:len(execname)-len(ext)] return filepath.Join(dir, name+".json"), nil } func getConfig(path string) (*Config, error) { f, err := os.Open(path) if err != nil { return nil, err } defer f.Close() conf := &Config{} r := json.NewDecoder(f) err = r.Decode(&conf) if err != nil { return nil, err } return conf, nil } func main() { svcFlag := flag.String("service", "", "Control the system service.") flag.Parse() configPath, err := getConfigPath() if err != nil { log.Fatal(err) } config, err := getConfig(configPath) if err != nil { log.Fatal(err) } svcConfig := &service.Config{ Name: config.Name, DisplayName: config.DisplayName, Description: config.Description, } prg := &program{ exit: make(chan struct{}), Config: config, } s, err := service.New(prg, svcConfig) if err != nil { log.Fatal(err) } prg.service = s errs := make(chan error, 5) logger, err = s.Logger(errs) if err != nil { log.Fatal(err) } go func() { for { err := <-errs if err != nil { log.Print(err) } } }() if len(*svcFlag) != 0 { err := service.Control(s, *svcFlag) if err != nil { log.Printf("Valid actions: %q\n", service.ControlAction) log.Fatal(err) } return } err = s.Run() if err != nil { logger.Error(err) } } service-1.2.2/example/runner/runner.json000066400000000000000000000007441432111315600203260ustar00rootroot00000000000000{ "Name": "builder", "DisplayName": "Go Builder", "Description": "Run the Go Builder", "Dir": "C:\\dev\\go\\src", "Exec": "C:\\windows\\system32\\cmd.exe", "Args": ["/C","C:\\dev\\go\\src\\all.bat"], "Env": [ "PATH=C:\\TDM-GCC-64\\bin;C:\\Program Files (x86)\\Git\\cmd", "GOROOT_BOOTSTRAP=C:\\dev\\go_ready", "HOMEDRIVE=C:", "HOMEPATH=\\Documents and Settings\\Administrator" ], "Stderr": "C:\\builder_err.log", "Stdout": "C:\\builder_out.log" }service-1.2.2/example/simple/000077500000000000000000000000001432111315600160755ustar00rootroot00000000000000service-1.2.2/example/simple/main.go000066400000000000000000000017631432111315600173570ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // simple does nothing except block while running the service. package main import ( "log" "github.com/kardianos/service" ) var logger service.Logger type program struct{} func (p *program) Start(s service.Service) error { // Start should not block. Do the actual work async. go p.run() return nil } func (p *program) run() { // Do work here } func (p *program) Stop(s service.Service) error { // Stop should not block. Return with a few seconds. return nil } func main() { svcConfig := &service.Config{ Name: "GoServiceExampleSimple", DisplayName: "Go Service Example", Description: "This is an example Go service.", } prg := &program{} s, err := service.New(prg, svcConfig) if err != nil { log.Fatal(err) } logger, err = s.Logger(nil) if err != nil { log.Fatal(err) } err = s.Run() if err != nil { logger.Error(err) } } service-1.2.2/example/stopPause/000077500000000000000000000000001432111315600165675ustar00rootroot00000000000000service-1.2.2/example/stopPause/main.go000066400000000000000000000022671432111315600200510ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // simple does nothing except block while running the service. package main import ( "log" "os" "time" "github.com/kardianos/service" ) var logger service.Logger type program struct{} func (p *program) Start(s service.Service) error { // Start should not block. Do the actual work async. go p.run() return nil } func (p *program) run() { // Do work here } func (p *program) Stop(s service.Service) error { // Stop should not block. Return with a few seconds. <-time.After(time.Second * 13) return nil } func main() { svcConfig := &service.Config{ Name: "GoServiceExampleStopPause", DisplayName: "Go Service Example: Stop Pause", Description: "This is an example Go service that pauses on stop.", } prg := &program{} s, err := service.New(prg, svcConfig) if err != nil { log.Fatal(err) } if len(os.Args) > 1 { err = service.Control(s, os.Args[1]) if err != nil { log.Fatal(err) } return } logger, err = s.Logger(nil) if err != nil { log.Fatal(err) } err = s.Run() if err != nil { logger.Error(err) } } service-1.2.2/go.mod000066400000000000000000000001521432111315600142550ustar00rootroot00000000000000module github.com/kardianos/service go 1.12 require golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 service-1.2.2/go.sum000066400000000000000000000003171432111315600143050ustar00rootroot00000000000000golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211 h1:9UQO31fZ+0aKQOFldThf7BKPMJTiBfWycGh/u3UoO88= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= service-1.2.2/linux-test-su.sh000077500000000000000000000005461432111315600162560ustar00rootroot00000000000000#!/usr/bin/env bash # This script is used to run the tests under linux as root # # Usage: # linux-test-su.sh goPath goBinPath # # goPath is the standard GOPATH # goBinPath is the location of go # # Typical usage: # sudo ./linux-test-su.sh $GOPATH `which go` export GOPATH=$1 export GOROOT=`dirname $(dirname $2)` $GOROOT/bin/go test -v -tags su ./... service-1.2.2/linux_test/000077500000000000000000000000001432111315600153475ustar00rootroot00000000000000service-1.2.2/linux_test/Makefile000066400000000000000000000022351432111315600170110ustar00rootroot00000000000000 all: sysv systemd upstart openrc clean # compile `go test` binary statically test: @CGO_ENABLED=0 go test -installsuffix netgo -a -c .. clean: -rm service.test sysv: test @echo sysv @cp service.test sysv/ @docker build -q --tag="service.test.sysv" sysv @-docker run service.test.sysv @-docker rm $(shell docker ps -l -q) @-docker rmi -f service.test.sysv @-rm sysv/service.test systemd: test @echo systemd @cp service.test systemd/ @docker build -q --tag="service.test.systemd" systemd @-docker run --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro service.test.systemd @-docker rm $(shell docker ps -l -q) @-docker rmi -f service.test.systemd @-rm systemd/service.test upstart: test @echo upstart @cp service.test upstart/ @docker build -q --tag="service.test.upstart" upstart @-docker run service.test.upstart @-docker rm $(shell docker ps -l -q) @-docker rmi -f service.test.upstart @-rm upstart/service.test openrc: test @echo openrc @cp service.test openrc/ @docker build -q --tag="service.test.openrc" openrc @-docker run service.test.openrc @-docker rm $(shell docker ps -l -q) @-docker rmi -f service.test.openrc @-rm openrc/service.testservice-1.2.2/linux_test/README.md000066400000000000000000000002631432111315600166270ustar00rootroot00000000000000## Docker images to help vet linux configurations. Although the actual init systems won't run in docker, it may still be usefull to attempt to run it in different environments. service-1.2.2/linux_test/openrc/000077500000000000000000000000001432111315600166355ustar00rootroot00000000000000service-1.2.2/linux_test/openrc/Dockerfile000066400000000000000000000001151432111315600206240ustar00rootroot00000000000000FROM alpine:latest ADD service.test /tmp/ CMD /tmp/service.test -test.v=true service-1.2.2/linux_test/systemd/000077500000000000000000000000001432111315600170375ustar00rootroot00000000000000service-1.2.2/linux_test/systemd/Dockerfile000066400000000000000000000001321432111315600210250ustar00rootroot00000000000000FROM minimum2scp/systemd:latest ADD service.test /tmp/ CMD /tmp/service.test -test.v=true service-1.2.2/linux_test/sysv/000077500000000000000000000000001432111315600163535ustar00rootroot00000000000000service-1.2.2/linux_test/sysv/Dockerfile000066400000000000000000000001341432111315600203430ustar00rootroot00000000000000FROM minimum2scp/baseimage:latest ADD service.test /tmp/ CMD /tmp/service.test -test.v=true service-1.2.2/linux_test/upstart/000077500000000000000000000000001432111315600170515ustar00rootroot00000000000000service-1.2.2/linux_test/upstart/Dockerfile000066400000000000000000000001141432111315600210370ustar00rootroot00000000000000FROM ubuntu:14.04 ADD service.test /tmp/ CMD /tmp/service.test -test.v=true service-1.2.2/name_test.go000066400000000000000000000006701432111315600154620ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "runtime" "strings" "testing" ) func TestPlatformName(t *testing.T) { got := Platform() t.Logf("Platform is %v", got) wantPrefix := runtime.GOOS + "-" if !strings.HasPrefix(got, wantPrefix) { t.Errorf("Platform() want: /^%s.*$/, got: %s", wantPrefix, got) } } service-1.2.2/service.go000066400000000000000000000363111432111315600151440ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // Package service provides a simple way to create a system service. // Currently supports Windows, Linux/(systemd | Upstart | SysV | OpenRC), and OSX/Launchd. // // Windows controls services by setting up callbacks that is non-trivial. This // is very different then other systems. This package provides the same API // despite the substantial differences. // It also can be used to detect how a program is called, from an interactive // terminal or from a service manager. // // Examples in the example/ folder. // // package main // // import ( // "log" // // "github.com/kardianos/service" // ) // // var logger service.Logger // // type program struct{} // // func (p *program) Start(s service.Service) error { // // Start should not block. Do the actual work async. // go p.run() // return nil // } // func (p *program) run() { // // Do work here // } // func (p *program) Stop(s service.Service) error { // // Stop should not block. Return with a few seconds. // return nil // } // // func main() { // svcConfig := &service.Config{ // Name: "GoServiceTest", // DisplayName: "Go Service Test", // Description: "This is a test Go service.", // } // // prg := &program{} // s, err := service.New(prg, svcConfig) // if err != nil { // log.Fatal(err) // } // logger, err = s.Logger(nil) // if err != nil { // log.Fatal(err) // } // err = s.Run() // if err != nil { // logger.Error(err) // } // } package service // import "github.com/kardianos/service" import ( "errors" "fmt" ) const ( optionKeepAlive = "KeepAlive" optionKeepAliveDefault = true optionRunAtLoad = "RunAtLoad" optionRunAtLoadDefault = false optionUserService = "UserService" optionUserServiceDefault = false optionSessionCreate = "SessionCreate" optionSessionCreateDefault = false optionLogOutput = "LogOutput" optionLogOutputDefault = false optionPrefix = "Prefix" optionPrefixDefault = "application" optionRunWait = "RunWait" optionReloadSignal = "ReloadSignal" optionPIDFile = "PIDFile" optionLimitNOFILE = "LimitNOFILE" optionLimitNOFILEDefault = -1 // -1 = don't set in configuration optionRestart = "Restart" optionSuccessExitStatus = "SuccessExitStatus" optionSystemdScript = "SystemdScript" optionSysvScript = "SysvScript" optionUpstartScript = "UpstartScript" optionLaunchdConfig = "LaunchdConfig" optionOpenRCScript = "OpenRCScript" optionLogDirectory = "LogDirectory" ) // Status represents service status as an byte value type Status byte // Status of service represented as an byte const ( StatusUnknown Status = iota // Status is unable to be determined due to an error or it was not installed. StatusRunning StatusStopped ) // Config provides the setup for a Service. The Name field is required. type Config struct { Name string // Required name of the service. No spaces suggested. DisplayName string // Display name, spaces allowed. Description string // Long description of service. UserName string // Run as username. Arguments []string // Run with arguments. // Optional field to specify the executable for service. // If empty the current executable is used. Executable string // Array of service dependencies. // Not yet fully implemented on Linux or OS X: // 1. Support linux-systemd dependencies, just put each full line as the // element of the string array, such as // "After=network.target syslog.target" // "Requires=syslog.target" // Note, such lines will be directly appended into the [Unit] of // the generated service config file, will not check their correctness. Dependencies []string // The following fields are not supported on Windows. WorkingDirectory string // Initial working directory. ChRoot string // System specific options. Option KeyValue EnvVars map[string]string } var ( system System systemRegistry []System ) var ( // ErrNameFieldRequired is returned when Config.Name is empty. ErrNameFieldRequired = errors.New("Config.Name field is required.") // ErrNoServiceSystemDetected is returned when no system was detected. ErrNoServiceSystemDetected = errors.New("No service system detected.") // ErrNotInstalled is returned when the service is not installed. ErrNotInstalled = errors.New("the service is not installed") ) // New creates a new service based on a service interface and configuration. func New(i Interface, c *Config) (Service, error) { if len(c.Name) == 0 { return nil, ErrNameFieldRequired } if system == nil { return nil, ErrNoServiceSystemDetected } return system.New(i, c) } // KeyValue provides a list of system specific options. // * OS X // - LaunchdConfig string () - Use custom launchd config. // - KeepAlive bool (true) - Prevent the system from stopping the service automatically. // - RunAtLoad bool (false) - Run the service after its job has been loaded. // - SessionCreate bool (false) - Create a full user session. // // * Solaris // - Prefix string ("application") - Service FMRI prefix. // // * POSIX // - UserService bool (false) - Install as a current user service. // - SystemdScript string () - Use custom systemd script. // - UpstartScript string () - Use custom upstart script. // - SysvScript string () - Use custom sysv script. // - OpenRCScript string () - Use custom OpenRC script. // - RunWait func() (wait for SIGNAL) - Do not install signal but wait for this function to return. // - ReloadSignal string () [USR1, ...] - Signal to send on reload. // - PIDFile string () [/run/prog.pid] - Location of the PID file. // - LogOutput bool (false) - Redirect StdErr & StandardOutPath to files. // - Restart string (always) - How shall service be restarted. // - SuccessExitStatus string () - The list of exit status that shall be considered as successful, // in addition to the default ones. // - LogDirectory string(/var/log) - The path to the log files directory // // * Linux (systemd) // - LimitNOFILE int (-1) - Maximum open files (ulimit -n) // (https://serverfault.com/questions/628610/increasing-nproc-for-processes-launched-by-systemd-on-centos-7) // * Windows // - DelayedAutoStart bool (false) - After booting, start this service after some delay. // - Password string () - Password to use when interfacing with the system service manager. // - Interactive bool (false) - The service can interact with the desktop. (more information https://docs.microsoft.com/en-us/windows/win32/services/interactive-services) // - DelayedAutoStart bool (false) - after booting start this service after some delay. // - StartType string ("automatic") - Start service type. (automatic | manual | disabled) // - OnFailure string ("restart" ) - Action to perform on service failure. (restart | reboot | noaction) // - OnFailureDelayDuration string ( "1s" ) - Delay before restarting the service, time.Duration string. // - OnFailureResetPeriod int ( 10 ) - Reset period for errors, seconds. type KeyValue map[string]interface{} // bool returns the value of the given name, assuming the value is a boolean. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) bool(name string, defaultValue bool) bool { if v, found := kv[name]; found { if castValue, is := v.(bool); is { return castValue } } return defaultValue } // int returns the value of the given name, assuming the value is an int. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) int(name string, defaultValue int) int { if v, found := kv[name]; found { if castValue, is := v.(int); is { return castValue } } return defaultValue } // string returns the value of the given name, assuming the value is a string. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) string(name string, defaultValue string) string { if v, found := kv[name]; found { if castValue, is := v.(string); is { return castValue } } return defaultValue } // float64 returns the value of the given name, assuming the value is a float64. // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) float64(name string, defaultValue float64) float64 { if v, found := kv[name]; found { if castValue, is := v.(float64); is { return castValue } } return defaultValue } // funcSingle returns the value of the given name, assuming the value is a func(). // If the value isn't found or is not of the type, the defaultValue is returned. func (kv KeyValue) funcSingle(name string, defaultValue func()) func() { if v, found := kv[name]; found { if castValue, is := v.(func()); is { return castValue } } return defaultValue } // Platform returns a description of the system service. func Platform() string { if system == nil { return "" } return system.String() } // Interactive returns false if running under the OS service manager // and true otherwise. func Interactive() bool { if system == nil { return true } return system.Interactive() } func newSystem() System { for _, choice := range systemRegistry { if choice.Detect() == false { continue } return choice } return nil } // ChooseSystem chooses a system from the given system services. // SystemServices are considered in the order they are suggested. // Calling this may change what Interactive and Platform return. func ChooseSystem(a ...System) { systemRegistry = a system = newSystem() } // ChosenSystem returns the system that service will use. func ChosenSystem() System { return system } // AvailableSystems returns the list of system services considered // when choosing the system service. func AvailableSystems() []System { return systemRegistry } // System represents the service manager that is available. type System interface { // String returns a description of the system. String() string // Detect returns true if the system is available to use. Detect() bool // Interactive returns false if running under the system service manager // and true otherwise. Interactive() bool // New creates a new service for this system. New(i Interface, c *Config) (Service, error) } // Interface represents the service interface for a program. Start runs before // the hosting process is granted control and Stop runs when control is returned. // // 1. OS service manager executes user program. // 2. User program sees it is executed from a service manager (IsInteractive is false). // 3. User program calls Service.Run() which blocks. // 4. Interface.Start() is called and quickly returns. // 5. User program runs. // 6. OS service manager signals the user program to stop. // 7. Interface.Stop() is called and quickly returns. // - For a successful exit, os.Exit should not be called in Interface.Stop(). // 8. Service.Run returns. // 9. User program should quickly exit. type Interface interface { // Start provides a place to initiate the service. The service doesn't // signal a completed start until after this function returns, so the // Start function must not take more then a few seconds at most. Start(s Service) error // Stop provides a place to clean up program execution before it is terminated. // It should not take more then a few seconds to execute. // Stop should not call os.Exit directly in the function. Stop(s Service) error } // Shutdowner represents a service interface for a program that differentiates between "stop" and // "shutdown". A shutdown is triggered when the whole box (not just the service) is stopped. type Shutdowner interface { Interface // Shutdown provides a place to clean up program execution when the system is being shutdown. // It is essentially the same as Stop but for the case where machine is being shutdown/restarted // instead of just normally stopping the service. Stop won't be called when Shutdown is. Shutdown(s Service) error } // TODO: Add Configure to Service interface. // Service represents a service that can be run or controlled. type Service interface { // Run should be called shortly after the program entry point. // After Interface.Stop has finished running, Run will stop blocking. // After Run stops blocking, the program must exit shortly after. Run() error // Start signals to the OS service manager the given service should start. Start() error // Stop signals to the OS service manager the given service should stop. Stop() error // Restart signals to the OS service manager the given service should stop then start. Restart() error // Install setups up the given service in the OS service manager. This may require // greater rights. Will return an error if it is already installed. Install() error // Uninstall removes the given service from the OS service manager. This may require // greater rights. Will return an error if the service is not present. Uninstall() error // Opens and returns a system logger. If the user program is running // interactively rather then as a service, the returned logger will write to // os.Stderr. If errs is non-nil errors will be sent on errs as well as // returned from Logger's functions. Logger(errs chan<- error) (Logger, error) // SystemLogger opens and returns a system logger. If errs is non-nil errors // will be sent on errs as well as returned from Logger's functions. SystemLogger(errs chan<- error) (Logger, error) // String displays the name of the service. The display name if present, // otherwise the name. String() string // Platform displays the name of the system that manages the service. // In most cases this will be the same as service.Platform(). Platform() string // Status returns the current service status. Status() (Status, error) } // ControlAction list valid string texts to use in Control. var ControlAction = [5]string{"start", "stop", "restart", "install", "uninstall"} // Control issues control functions to the service from a given action string. func Control(s Service, action string) error { var err error switch action { case ControlAction[0]: err = s.Start() case ControlAction[1]: err = s.Stop() case ControlAction[2]: err = s.Restart() case ControlAction[3]: err = s.Install() case ControlAction[4]: err = s.Uninstall() default: err = fmt.Errorf("Unknown action %s", action) } if err != nil { return fmt.Errorf("Failed to %s %v: %v", action, s, err) } return nil } // Logger writes to the system log. type Logger interface { Error(v ...interface{}) error Warning(v ...interface{}) error Info(v ...interface{}) error Errorf(format string, a ...interface{}) error Warningf(format string, a ...interface{}) error Infof(format string, a ...interface{}) error } service-1.2.2/service_aix.go000066400000000000000000000122131432111315600160000ustar00rootroot00000000000000//+build aix // Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "bytes" "fmt" "os" "os/exec" "os/signal" "regexp" "strconv" "strings" "syscall" "text/template" "time" ) const maxPathSize = 32 * 1024 const version = "aix-ssrc" type aixSystem struct{} func (aixSystem) String() string { return version } func (aixSystem) Detect() bool { return true } func (aixSystem) Interactive() bool { return interactive } func (aixSystem) New(i Interface, c *Config) (Service, error) { s := &aixService{ i: i, Config: c, } return s, nil } func getArgsFromPid(pid int) string { cmd := exec.Command("ps", "-o", "args", "-p", strconv.Itoa(pid)) var out bytes.Buffer cmd.Stdout = &out if err := cmd.Run(); err == nil { lines := strings.Split(out.String(), "\n") if len(lines) > 1 { return strings.TrimSpace(lines[1]) } } return "" } func init() { ChooseSystem(aixSystem{}) } var interactive = false func init() { var err error interactive, err = isInteractive() if err != nil { panic(err) } } func isInteractive() (bool, error) { // The parent process of a service process should be srcmstr. return getArgsFromPid(os.Getppid()) != "/usr/sbin/srcmstr", nil } type aixService struct { i Interface *Config } func (s *aixService) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *aixService) Platform() string { return version } func (s *aixService) template() *template.Template { functions := template.FuncMap{ "bool": func(v bool) string { if v { return "true" } return "false" }, } customConfig := s.Option.string(optionSysvScript, "") if customConfig != "" { return template.Must(template.New("").Funcs(functions).Parse(customConfig)) } else { return template.Must(template.New("").Funcs(functions).Parse(svcConfig)) } } func (s *aixService) configPath() (cp string, err error) { cp = "/etc/rc.d/init.d/" + s.Config.Name return } func (s *aixService) Install() error { // install service path, err := s.execPath() if err != nil { return err } err = run("mkssys", "-s", s.Name, "-p", path, "-u", "0", "-R", "-Q", "-S", "-n", "15", "-f", "9", "-d", "-w", "30") if err != nil { return err } // write start script confPath, err := s.configPath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Init already exists: %s", confPath) } f, err := os.Create(confPath) if err != nil { return err } defer f.Close() var to = &struct { *Config Path string }{ s.Config, path, } err = s.template().Execute(f, to) if err != nil { return err } if err = os.Chmod(confPath, 0755); err != nil { return err } rcd := "/etc/rc" if _, err = os.Stat("/etc/rc.d/rc2.d"); err == nil { rcd = "/etc/rc.d/rc" } for _, i := range [...]string{"2", "3"} { if err = os.Symlink(confPath, rcd+i+".d/S50"+s.Name); err != nil { continue } if err = os.Symlink(confPath, rcd+i+".d/K02"+s.Name); err != nil { continue } } return nil } func (s *aixService) Uninstall() error { s.Stop() err := run("rmssys", "-s", s.Name) if err != nil { return err } confPath, err := s.configPath() if err != nil { return err } return os.Remove(confPath) } func (s *aixService) Status() (Status, error) { exitCode, out, err := runWithOutput("lssrc", "-s", s.Name) if exitCode == 0 && err != nil { if !strings.Contains(err.Error(), "failed with stderr") { return StatusUnknown, err } } re := regexp.MustCompile(`\s+` + s.Name + `\s+(\w+\s+)?(\d+\s+)?(\w+)`) matches := re.FindStringSubmatch(out) if len(matches) == 4 { status := string(matches[3]) if status == "inoperative" { return StatusStopped, nil } else if status == "active" { return StatusRunning, nil } else { fmt.Printf("Got unknown service status %s\n", status) return StatusUnknown, err } } confPath, err := s.configPath() if err != nil { return StatusUnknown, err } if _, err = os.Stat(confPath); err == nil { return StatusStopped, nil } return StatusUnknown, ErrNotInstalled } func (s *aixService) Start() error { return run("startsrc", "-s", s.Name) } func (s *aixService) Stop() error { return run("stopsrc", "-s", s.Name) } func (s *aixService) Restart() error { err := s.Stop() if err != nil { return err } time.Sleep(50 * time.Millisecond) return s.Start() } func (s *aixService) Run() error { var err error err = s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *aixService) Logger(errs chan<- error) (Logger, error) { if interactive { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *aixService) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } var svcConfig = `#!/bin/ksh case "$1" in start ) startsrc -s {{.Name}} ;; stop ) stopsrc -s {{.Name}} ;; * ) echo "Usage: $0 (start | stop)" exit 1 esac ` service-1.2.2/service_darwin.go000066400000000000000000000163271432111315600165150ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "errors" "fmt" "os" "os/signal" "os/user" "path/filepath" "regexp" "strings" "syscall" "text/template" "time" ) const maxPathSize = 32 * 1024 const ( version = "darwin-launchd" defaultDarwinLogDirectory = "/var/log" ) type darwinSystem struct{} func (darwinSystem) String() string { return version } func (darwinSystem) Detect() bool { return true } func (darwinSystem) Interactive() bool { return interactive } func (darwinSystem) New(i Interface, c *Config) (Service, error) { s := &darwinLaunchdService{ i: i, Config: c, userService: c.Option.bool(optionUserService, optionUserServiceDefault), } return s, nil } func init() { ChooseSystem(darwinSystem{}) } var interactive = false func init() { var err error interactive, err = isInteractive() if err != nil { panic(err) } } 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 } type darwinLaunchdService struct { i Interface *Config userService bool } func (s *darwinLaunchdService) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *darwinLaunchdService) Platform() string { return version } func (s *darwinLaunchdService) getHomeDir() (string, error) { u, err := user.Current() if err == nil { return u.HomeDir, nil } // alternate methods homeDir := os.Getenv("HOME") // *nix if homeDir == "" { return "", errors.New("User home directory not found.") } return homeDir, nil } func (s *darwinLaunchdService) getServiceFilePath() (string, error) { if s.userService { homeDir, err := s.getHomeDir() if err != nil { return "", err } return homeDir + "/Library/LaunchAgents/" + s.Name + ".plist", nil } return "/Library/LaunchDaemons/" + s.Name + ".plist", nil } func (s *darwinLaunchdService) logDir() (string, error) { if customDir := s.Option.string(optionLogDirectory, ""); customDir != "" { return customDir, nil } if !s.userService { return defaultDarwinLogDirectory, nil } return s.getHomeDir() } func (s *darwinLaunchdService) getLogPaths() (string, string, error) { logDir, err := s.logDir() if err != nil { return "", "", err } return s.getLogPath(logDir, "out"), s.getLogPath(logDir, "err"), nil } func (s *darwinLaunchdService) getLogPath(logDir, logType string) string { return fmt.Sprintf("%s/%s.%s.log", logDir, s.Name, logType) } func (s *darwinLaunchdService) template() *template.Template { functions := template.FuncMap{ "bool": func(v bool) string { if v { return "true" } return "false" }, } customConfig := s.Option.string(optionLaunchdConfig, "") if customConfig != "" { return template.Must(template.New("").Funcs(functions).Parse(customConfig)) } return template.Must(template.New("").Funcs(functions).Parse(launchdConfig)) } func (s *darwinLaunchdService) Install() error { confPath, err := s.getServiceFilePath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Init already exists: %s", confPath) } if s.userService { // Ensure that ~/Library/LaunchAgents exists. err = os.MkdirAll(filepath.Dir(confPath), 0700) if err != nil { return err } } f, err := os.Create(confPath) if err != nil { return err } defer f.Close() path, err := s.execPath() if err != nil { return err } stdOutPath, stdErrPath, _ := s.getLogPaths() var to = &struct { *Config Path string KeepAlive, RunAtLoad bool SessionCreate bool StandardOutPath string StandardErrorPath string }{ Config: s.Config, Path: path, KeepAlive: s.Option.bool(optionKeepAlive, optionKeepAliveDefault), RunAtLoad: s.Option.bool(optionRunAtLoad, optionRunAtLoadDefault), SessionCreate: s.Option.bool(optionSessionCreate, optionSessionCreateDefault), StandardOutPath: stdOutPath, StandardErrorPath: stdErrPath, } return s.template().Execute(f, to) } func (s *darwinLaunchdService) Uninstall() error { s.Stop() confPath, err := s.getServiceFilePath() if err != nil { return err } return os.Remove(confPath) } func (s *darwinLaunchdService) Status() (Status, error) { exitCode, out, err := runWithOutput("launchctl", "list", s.Name) if exitCode == 0 && err != nil { if !strings.Contains(err.Error(), "failed with stderr") { return StatusUnknown, err } } re := regexp.MustCompile(`"PID" = ([0-9]+);`) matches := re.FindStringSubmatch(out) if len(matches) == 2 { return StatusRunning, nil } confPath, err := s.getServiceFilePath() if err != nil { return StatusUnknown, err } if _, err = os.Stat(confPath); err == nil { return StatusStopped, nil } return StatusUnknown, ErrNotInstalled } func (s *darwinLaunchdService) Start() error { confPath, err := s.getServiceFilePath() if err != nil { return err } return run("launchctl", "load", confPath) } func (s *darwinLaunchdService) Stop() error { confPath, err := s.getServiceFilePath() if err != nil { return err } return run("launchctl", "unload", confPath) } 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() error { err := s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *darwinLaunchdService) Logger(errs chan<- error) (Logger, error) { if interactive { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *darwinLaunchdService) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } var launchdConfig = ` Disabled {{- if .EnvVars}} EnvironmentVariables {{- range $k, $v := .EnvVars}} {{html $k}} {{html $v}} {{- end}} {{- end}} KeepAlive <{{bool .KeepAlive}}/> Label {{html .Name}} ProgramArguments {{html .Path}} {{- if .Config.Arguments}} {{- range .Config.Arguments}} {{html .}} {{- end}} {{- end}} {{- if .ChRoot}} RootDirectory {{html .ChRoot}} {{- end}} RunAtLoad <{{bool .RunAtLoad}}/> SessionCreate <{{bool .SessionCreate}}/> {{- if .StandardErrorPath}} StandardErrorPath {{html .StandardErrorPath}} {{- end}} {{- if .StandardOutPath}} StandardOutPath {{html .StandardOutPath}} {{- end}} {{- if .UserName}} UserName {{html .UserName}} {{- end}} {{- if .WorkingDirectory}} WorkingDirectory {{html .WorkingDirectory}} {{- end}} ` service-1.2.2/service_freebsd.go000066400000000000000000000076451432111315600166460ustar00rootroot00000000000000// Copyright 2019 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "fmt" "os" "os/signal" "syscall" "text/template" ) const version = "freebsd" type freebsdSystem struct{} func (freebsdSystem) String() string { return version } func (freebsdSystem) Detect() bool { return true } func (freebsdSystem) Interactive() bool { return interactive } func (freebsdSystem) New(i Interface, c *Config) (Service, error) { s := &freebsdService{ i: i, Config: c, } return s, nil } func init() { ChooseSystem(freebsdSystem{}) } var interactive = false func init() { var err error interactive, err = isInteractive() if err != nil { panic(err) } } func isInteractive() (bool, error) { return os.Getenv("IS_DAEMON") != "1", nil } type freebsdService struct { i Interface *Config } func (s *freebsdService) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *freebsdService) Platform() string { return version } func (s *freebsdService) template() *template.Template { functions := template.FuncMap{ "bool": func(v bool) string { if v { return "true" } return "false" }, } customConfig := s.Option.string(optionSysvScript, "") if customConfig != "" { return template.Must(template.New("").Funcs(functions).Parse(customConfig)) } else { return template.Must(template.New("").Funcs(functions).Parse(rcScript)) } } func (s *freebsdService) configPath() (cp string, err error) { cp = "/usr/local/etc/rc.d/" + s.Config.Name return } func (s *freebsdService) Install() error { path, err := s.execPath() if err != nil { return err } // write start script confPath, err := s.configPath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Init already exists: %s", confPath) } f, err := os.Create(confPath) if err != nil { return err } defer f.Close() var to = &struct { *Config Path string }{ s.Config, path, } err = s.template().Execute(f, to) if err != nil { return err } if err = os.Chmod(confPath, 0755); err != nil { return err } return nil } func (s *freebsdService) Uninstall() error { cp, err := s.configPath() if err != nil { return err } return os.Remove(cp) } func (s *freebsdService) Status() (Status, error) { cp, err := s.configPath() if err != nil { return StatusUnknown, err } if _, err = os.Stat(cp); os.IsNotExist(err) { return StatusStopped, ErrNotInstalled } status, _, err := runCommand("service", false, s.Name, "status") if status == 1 { return StatusStopped, nil } else if err != nil { return StatusUnknown, err } return StatusRunning, nil } func (s *freebsdService) Start() error { return run("service", s.Name, "start") } func (s *freebsdService) Stop() error { return run("service", s.Name, "stop") } func (s *freebsdService) Restart() error { return run("service", s.Name, "restart") } func (s *freebsdService) Run() error { var err error err = s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *freebsdService) Logger(errs chan<- error) (Logger, error) { if interactive { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *freebsdService) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } var rcScript = `#!/bin/sh # PROVIDE: {{.Name}} # REQUIRE: SERVERS # KEYWORD: shutdown . /etc/rc.subr name="{{.Name}}" {{.Name}}_env="IS_DAEMON=1" pidfile="/var/run/${name}.pid" command="/usr/sbin/daemon" daemon_args="-P ${pidfile} -r -t \"${name}: daemon\"{{if .WorkingDirectory}} -c {{.WorkingDirectory}}{{end}}" command_args="${daemon_args} {{.Path}}{{range .Arguments}} {{.}}{{end}}" run_rc_command "$1" ` service-1.2.2/service_go1.8.go000066400000000000000000000003201432111315600160470ustar00rootroot00000000000000//+build go1.8 package service import ( "os" "path/filepath" ) func (c *Config) execPath() (string, error) { if len(c.Executable) != 0 { return filepath.Abs(c.Executable) } return os.Executable() } service-1.2.2/service_linux.go000066400000000000000000000056471432111315600163730ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "bufio" "fmt" "io/ioutil" "os" "strings" ) var cgroupFile = "/proc/1/cgroup" type linuxSystemService struct { name string detect func() bool interactive func() bool new func(i Interface, platform string, c *Config) (Service, error) } func (sc linuxSystemService) String() string { return sc.name } func (sc linuxSystemService) Detect() bool { return sc.detect() } func (sc linuxSystemService) Interactive() bool { return sc.interactive() } func (sc linuxSystemService) New(i Interface, c *Config) (Service, error) { return sc.new(i, sc.String(), c) } func init() { ChooseSystem(linuxSystemService{ name: "linux-systemd", detect: isSystemd, interactive: func() bool { is, _ := isInteractive() return is }, new: newSystemdService, }, linuxSystemService{ name: "linux-upstart", detect: isUpstart, interactive: func() bool { is, _ := isInteractive() return is }, new: newUpstartService, }, linuxSystemService{ name: "linux-openrc", detect: isOpenRC, interactive: func() bool { is, _ := isInteractive() return is }, new: newOpenRCService, }, linuxSystemService{ name: "unix-systemv", detect: func() bool { return true }, interactive: func() bool { is, _ := isInteractive() return is }, new: newSystemVService, }, ) } func binaryName(pid int) (string, error) { statPath := fmt.Sprintf("/proc/%d/stat", pid) dataBytes, err := ioutil.ReadFile(statPath) if err != nil { return "", err } // First, parse out the image name data := string(dataBytes) binStart := strings.IndexRune(data, '(') + 1 binEnd := strings.IndexRune(data[binStart:], ')') return data[binStart : binStart+binEnd], nil } func isInteractive() (bool, error) { inContainer, err := isInContainer(cgroupFile) if err != nil { return false, err } if inContainer { return true, nil } ppid := os.Getppid() if ppid == 1 { return false, nil } binary, _ := binaryName(ppid) return binary != "systemd", nil } // isInContainer checks if the service is being executed in docker or lxc // container. func isInContainer(cgroupPath string) (bool, error) { const maxlines = 5 // maximum lines to scan f, err := os.Open(cgroupPath) if err != nil { return false, err } defer f.Close() scan := bufio.NewScanner(f) lines := 0 for scan.Scan() && !(lines > maxlines) { if strings.Contains(scan.Text(), "docker") || strings.Contains(scan.Text(), "lxc") { return true, nil } lines++ } if err := scan.Err(); err != nil { return false, err } return false, nil } var tf = map[string]interface{}{ "cmd": func(s string) string { return `"` + strings.Replace(s, `"`, `\"`, -1) + `"` }, "cmdEscape": func(s string) string { return strings.Replace(s, " ", `\x20`, -1) }, } service-1.2.2/service_linux_test.go000066400000000000000000000103651432111315600174230ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "errors" "io/ioutil" "os" "testing" ) // createTestCgroupFiles creates mock files for tests func createTestCgroupFiles() (*os.File, *os.File, error) { // docker cgroup setup hDockerGrp, err := ioutil.TempFile("", "*") if err != nil { return nil, nil, errors.New("docker tempfile create failed") } _, err = hDockerGrp.Write([]byte(dockerCgroup)) if err != nil { return nil, nil, errors.New("docker tempfile write failed") } // linux cgroup setup hLinuxGrp, err := ioutil.TempFile("", "*") if err != nil { return nil, nil, errors.New("\"normal\" tempfile create failed") } _, err = hLinuxGrp.Write([]byte(linuxCgroup)) if err != nil { return nil, nil, errors.New("\"normal\" tempfile write failed") } return hDockerGrp, hLinuxGrp, nil } // removeTestFile closes and removes the provided file func removeTestFile(hFile *os.File) { hFile.Close() os.Remove(hFile.Name()) } func Test_isInContainer(t *testing.T) { // setup hDockerGrp, hLinuxGrp, err := createTestCgroupFiles() if err != nil { t.Fatal(err) } defer func() { // tear down removeTestFile(hDockerGrp) removeTestFile(hLinuxGrp) }() // TEST type args struct { cgroupPath string } tests := []struct { name string args args want bool wantErr bool }{ {"docker", args{hDockerGrp.Name()}, true, false}, {"linux", args{hLinuxGrp.Name()}, false, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := isInContainer(tt.args.cgroupPath) if (err != nil) != tt.wantErr { t.Errorf("isInContainer() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("isInContainer() = %v, want %v", got, tt.want) } }) } } func Test_isInteractive(t *testing.T) { // setup hDockerGrp, hLinuxGrp, err := createTestCgroupFiles() if err != nil { t.Fatal(err) } defer func() { // tear down removeTestFile(hDockerGrp) removeTestFile(hLinuxGrp) }() // stack emulation for before() and after() for storing global values strStack := make(chan string, 4) // TEST tests := []struct { name string before func() after func() want bool wantErr bool }{ {"docker", func() { strStack <- cgroupFile cgroupFile = hDockerGrp.Name() }, func() { cgroupFile = <-strStack }, true, false, }, {"linux", func() { strStack <- cgroupFile cgroupFile = hLinuxGrp.Name() }, func() { cgroupFile = <-strStack }, true, false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.before() got, err := isInteractive() tt.after() if (err != nil) != tt.wantErr { t.Errorf("isInteractive() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("isInteractive() = %v, want %v", got, tt.want) } }) } } const ( dockerCgroup = `13:name=systemd:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 12:pids:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 11:hugetlb:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 10:net_prio:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 9:perf_event:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 8:net_cls:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 7:freezer:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 6:devices:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 5:memory:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 4:blkio:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 3:cpuacct:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 2:cpu:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee 1:cpuset:/docker/bc9f0894926991e3064b731c26d86af6df7390c0e6453e6027f9545aba5809ee` linuxCgroup = `11:cpuset:/ 10:pids:/init.scope 9:perf_event:/ 8:memory:/init.scope 7:blkio:/ 6:devices:/init.scope 5:rdma:/ 4:net_cls,net_prio:/ 3:freezer:/ 2:cpu,cpuacct:/ 1:name=systemd:/init.scope 0::/init.scope` ) service-1.2.2/service_nosu_test.go000066400000000000000000000005351432111315600172460ustar00rootroot00000000000000// Copyright 2016 Lawrence Woodman // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // +build !su package service_test import "testing" func TestInstallRunRestartStopRemove(t *testing.T) { t.Skip("skipping test as not running as root/admin (Build tag: su)") } service-1.2.2/service_openrc_linux.go000066400000000000000000000116161432111315600177320ustar00rootroot00000000000000package service import ( "bytes" "errors" "fmt" "os" "os/exec" "os/signal" "regexp" "syscall" "text/template" "time" ) func isOpenRC() bool { if _, err := exec.LookPath("openrc-init"); err == nil { return true } if _, err := os.Stat("/etc/inittab"); err == nil { filerc, err := os.Open("/etc/inittab") if err != nil { return false } defer filerc.Close() buf := new(bytes.Buffer) buf.ReadFrom(filerc) contents := buf.String() re := regexp.MustCompile(`::sysinit:.*openrc.*sysinit`) matches := re.FindStringSubmatch(contents) if len(matches) > 0 { return true } return false } return false } type openrc struct { i Interface platform string *Config } func (s *openrc) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *openrc) Platform() string { return s.platform } func (s *openrc) template() *template.Template { customScript := s.Option.string(optionOpenRCScript, "") if customScript != "" { return template.Must(template.New("").Funcs(tf).Parse(customScript)) } return template.Must(template.New("").Funcs(tf).Parse(openRCScript)) } func newOpenRCService(i Interface, platform string, c *Config) (Service, error) { s := &openrc{ i: i, platform: platform, Config: c, } return s, nil } var errNoUserServiceOpenRC = errors.New("user services are not supported on OpenRC") func (s *openrc) configPath() (cp string, err error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { err = errNoUserServiceOpenRC return } cp = "/etc/init.d/" + s.Config.Name return } func (s *openrc) Install() error { confPath, err := s.configPath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Init already exists: %s", confPath) } f, err := os.Create(confPath) if err != nil { return err } defer f.Close() err = os.Chmod(confPath, 0755) if err != nil { return err } path, err := s.execPath() if err != nil { return err } var to = &struct { *Config Path string LogDirectory string }{ s.Config, path, s.Option.string(optionLogDirectory, defaultLogDirectory), } err = s.template().Execute(f, to) if err != nil { return err } // run rc-update return s.runAction("add") } func (s *openrc) Uninstall() error { confPath, err := s.configPath() if err != nil { return err } if err := os.Remove(confPath); err != nil { return err } return s.runAction("delete") } func (s *openrc) Logger(errs chan<- error) (Logger, error) { if system.Interactive() { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *openrc) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } func (s *openrc) Run() (err error) { err = s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *openrc) Status() (Status, error) { // rc-service uses the errno library for its exit codes: // errno 0 = service started // errno 1 = EPERM 1 Operation not permitted // errno 2 = ENOENT 2 No such file or directory // errno 3 = ESRCH 3 No such process // for more info, see https://man7.org/linux/man-pages/man3/errno.3.html _, out, err := runWithOutput("rc-service", s.Name, "status") if err != nil { if exiterr, ok := err.(*exec.ExitError); ok { // The program has exited with an exit code != 0 exitCode := exiterr.ExitCode() switch { case exitCode == 1: return StatusUnknown, err case exitCode == 2: return StatusUnknown, ErrNotInstalled case exitCode == 3: return StatusStopped, nil default: return StatusUnknown, fmt.Errorf("unknown error: %v - %v", out, err) } } else { return StatusUnknown, err } } return StatusRunning, nil } func (s *openrc) Start() error { return run("rc-service", s.Name, "start") } func (s *openrc) Stop() error { return run("rc-service", s.Name, "stop") } func (s *openrc) Restart() error { err := s.Stop() if err != nil { return err } time.Sleep(50 * time.Millisecond) return s.Start() } func (s *openrc) runAction(action string) error { return s.run(action, s.Name) } func (s *openrc) run(action string, args ...string) error { return run("rc-update", append([]string{action}, args...)...) } const openRCScript = `#!/sbin/openrc-run supervisor=supervise-daemon name="{{.DisplayName}}" description="{{.Description}}" command={{.Path|cmdEscape}} {{- if .Arguments }} command_args="{{range .Arguments}}{{.}} {{end}}" {{- end }} name=$(basename $(readlink -f $command)) supervise_daemon_args="--stdout {{.LogDirectory}}/${name}.log --stderr {{.LogDirectory}}/${name}.err" {{range $k, $v := .EnvVars -}} export {{$k}}={{$v}} {{end -}} {{- if .Dependencies }} depend() { {{- range $i, $dep := .Dependencies}} {{"\t"}}{{$dep}}{{end}} } {{- end}} ` service-1.2.2/service_solaris.go000066400000000000000000000135441432111315600167030ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "bytes" "encoding/xml" "fmt" "os" "os/signal" "regexp" "syscall" "text/template" "time" ) const maxPathSize = 32 * 1024 const version = "solaris-smf" type solarisSystem struct{} func (solarisSystem) String() string { return version } func (solarisSystem) Detect() bool { return true } func (solarisSystem) Interactive() bool { return interactive } func (solarisSystem) New(i Interface, c *Config) (Service, error) { s := &solarisService{ i: i, Config: c, Prefix: c.Option.string(optionPrefix, optionPrefixDefault), } return s, nil } func init() { ChooseSystem(solarisSystem{}) } var interactive = false func init() { var err error interactive, err = isInteractive() if err != nil { panic(err) } } func isInteractive() (bool, error) { // The PPid of a service process be 1 / init. return os.Getppid() != 1, nil } type solarisService struct { i Interface *Config Prefix string } func (s *solarisService) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *solarisService) Platform() string { return version } func (s *solarisService) template() *template.Template { functions := template.FuncMap{ "bool": func(v bool) string { if v { return "true" } return "false" }, } customConfig := s.Option.string(optionSysvScript, "") if customConfig != "" { return template.Must(template.New("").Funcs(functions).Parse(customConfig)) } else { return template.Must(template.New("").Funcs(functions).Parse(manifest)) } } func (s *solarisService) configPath() (string, error) { return "/lib/svc/manifest/" + s.Prefix + "/" + s.Config.Name + ".xml", nil } func (s *solarisService) getFMRI() string { return "svc:/" + s.Prefix + "/" + s.Config.Name + ":default" } func (s *solarisService) Install() error { // write start script confPath, err := s.configPath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Manifest already exists: %s", confPath) } f, err := os.Create(confPath) if err != nil { return err } defer f.Close() path, err := s.execPath() if err != nil { return err } Display := "" escaped := &bytes.Buffer{} if err := xml.EscapeText(escaped, []byte(s.DisplayName)); err == nil { Display = escaped.String() } var to = &struct { *Config Prefix string Display string Path string }{ s.Config, s.Prefix, Display, path, } err = s.template().Execute(f, to) if err != nil { return err } // import service err = run("svcadm", "restart", "manifest-import") if err != nil { return err } return nil } func (s *solarisService) Uninstall() error { s.Stop() confPath, err := s.configPath() if err != nil { return err } err = os.Remove(confPath) if err != nil { return err } // unregister service err = run("svcadm", "restart", "manifest-import") if err != nil { return err } return nil } func (s *solarisService) Status() (Status, error) { fmri := s.getFMRI() exitCode, out, err := runWithOutput("svcs", fmri) if exitCode != 0 { return StatusUnknown, ErrNotInstalled } re := regexp.MustCompile(`(degraded|disabled|legacy_run|maintenance|offline|online)\s+\w+` + fmri) matches := re.FindStringSubmatch(out) if len(matches) == 2 { status := string(matches[1]) if status == "online" { return StatusRunning, nil } else { return StatusStopped, nil } } return StatusUnknown, err } func (s *solarisService) Start() error { return run("/usr/sbin/svcadm", "enable", s.getFMRI()) } func (s *solarisService) Stop() error { return run("/usr/sbin/svcadm", "disable", s.getFMRI()) } func (s *solarisService) Restart() error { err := s.Stop() if err != nil { return err } time.Sleep(50 * time.Millisecond) return s.Start() } func (s *solarisService) Run() error { var err error err = s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *solarisService) Logger(errs chan<- error) (Logger, error) { if interactive { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *solarisService) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } var manifest = ` ` service-1.2.2/service_su_test.go000066400000000000000000000071531432111315600167140ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // This needs to be run as root/admin hence the reason there is a build tag // +build su package service_test import ( "flag" "fmt" "io/ioutil" "log" "os" "path/filepath" "regexp" "testing" "time" "github.com/kardianos/service" ) const runAsServiceArg = "RunThisAsService" func TestMain(m *testing.M) { reportDir := flag.String("su.reportDir", "", "") runAsService := flag.Bool("su.runAsService", false, "") flag.Parse() if !*runAsService { os.Exit(m.Run()) } if len(*reportDir) == 0 { log.Fatal("missing su.reportDir argument") } writeReport(*reportDir, "call") runService() writeReport(*reportDir, "finished") } func TestInstallRunRestartStopRemove(t *testing.T) { p := &program{} reportDir := mustTempDir(t) defer os.RemoveAll(reportDir) s := mustNewRunAsService(t, p, reportDir) _ = s.Uninstall() if err := s.Install(); err != nil { t.Fatal("Install", err) } defer s.Uninstall() if err := s.Start(); err != nil { t.Fatal("Start", err) } defer s.Stop() checkReport(t, reportDir, "Start()", 1, 0) if err := s.Restart(); err != nil { t.Fatal("restart", err) } checkReport(t, reportDir, "Restart()", 2, 1) p.numStopped = 0 if err := s.Stop(); err != nil { t.Fatal("stop", err) } checkReport(t, reportDir, "Stop()", 2, 2) if err := s.Uninstall(); err != nil { t.Fatal("uninstall", err) } } func runService() { p := &program{} sc := &service.Config{ Name: "go_service_test", } s, err := service.New(p, sc) if err != nil { log.Fatal(err) } if err = s.Run(); err != nil { log.Fatal(err) } } func mustTempDir(t *testing.T) string { dir, err := ioutil.TempDir("", "servicetest") if err != nil { t.Fatal(err) } return dir } func writeReport(reportDir string, action string) { b := []byte("go_test_service_report") timeStamp := time.Now().UnixNano() err := ioutil.WriteFile( filepath.Join(reportDir, fmt.Sprintf("%d-%s", timeStamp, action)), b, 0644, ) if err != nil { log.Fatal(err) } } var matchActionRegexp = regexp.MustCompile("^(\\d+-)([a-z]*)$") func getReport( t *testing.T, reportDir string, ) (numCalls int, numFinished int) { numCalls = 0 numFinished = 0 files, err := ioutil.ReadDir(reportDir) if err != nil { t.Fatalf("ReadDir(%s) err: %s", reportDir, err) } for _, file := range files { if matchActionRegexp.MatchString(file.Name()) { action := matchActionRegexp.ReplaceAllString(file.Name(), "$2") switch action { case "call": numCalls++ case "finished": numFinished++ default: t.Fatalf("getReport() found report with incorrect action: %s", action) } } } return } func checkReport( t *testing.T, reportDir string, msgPrefix string, wantNumCalled int, wantNumFinished int, ) { var numCalled int var numFinished int for i := 0; i < 25; i++ { numCalled, numFinished = getReport(t, reportDir) <-time.After(200 * time.Millisecond) if numCalled == wantNumCalled && numFinished == wantNumFinished { return } } if numCalled != wantNumCalled { t.Fatalf("%s - numCalled: %d, want %d", msgPrefix, numCalled, wantNumCalled) } if numFinished != wantNumFinished { t.Fatalf("%s - numFinished: %d, want %d", msgPrefix, numFinished, wantNumFinished) } } func mustNewRunAsService( t *testing.T, p *program, reportDir string, ) service.Service { sc := &service.Config{ Name: "go_service_test", Arguments: []string{"-test.v=true", "-su.runAsService", "-su.reportDir", reportDir}, } s, err := service.New(p, sc) if err != nil { t.Fatal(err) } return s } service-1.2.2/service_systemd_linux.go000066400000000000000000000162621432111315600201360ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "bytes" "errors" "fmt" "os" "os/signal" "path/filepath" "regexp" "strconv" "strings" "syscall" "text/template" ) func isSystemd() bool { if _, err := os.Stat("/run/systemd/system"); err == nil { return true } if _, err := os.Stat("/proc/1/comm"); err == nil { filerc, err := os.Open("/proc/1/comm") if err != nil { return false } defer filerc.Close() buf := new(bytes.Buffer) buf.ReadFrom(filerc) contents := buf.String() if strings.Trim(contents, " \r\n") == "systemd" { return true } } return false } type systemd struct { i Interface platform string *Config } func newSystemdService(i Interface, platform string, c *Config) (Service, error) { s := &systemd{ i: i, platform: platform, Config: c, } return s, nil } func (s *systemd) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *systemd) Platform() string { return s.platform } func (s *systemd) configPath() (cp string, err error) { if !s.isUserService() { cp = "/etc/systemd/system/" + s.unitName() return } homeDir, err := os.UserHomeDir() if err != nil { return } systemdUserDir := filepath.Join(homeDir, ".config/systemd/user") err = os.MkdirAll(systemdUserDir, os.ModePerm) if err != nil { return } cp = filepath.Join(systemdUserDir, s.unitName()) return } func (s *systemd) unitName() string { return s.Config.Name + ".service" } func (s *systemd) getSystemdVersion() int64 { _, out, err := s.runWithOutput("systemctl", "--version") if err != nil { return -1 } re := regexp.MustCompile(`systemd ([0-9]+)`) matches := re.FindStringSubmatch(out) if len(matches) != 2 { return -1 } v, err := strconv.ParseInt(matches[1], 10, 64) if err != nil { return -1 } return v } func (s *systemd) hasOutputFileSupport() bool { defaultValue := true version := s.getSystemdVersion() if version == -1 { return defaultValue } if version < 236 { return false } return defaultValue } func (s *systemd) template() *template.Template { customScript := s.Option.string(optionSystemdScript, "") if customScript != "" { return template.Must(template.New("").Funcs(tf).Parse(customScript)) } return template.Must(template.New("").Funcs(tf).Parse(systemdScript)) } func (s *systemd) isUserService() bool { return s.Option.bool(optionUserService, optionUserServiceDefault) } func (s *systemd) Install() error { confPath, err := s.configPath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Init already exists: %s", confPath) } f, err := os.OpenFile(confPath, os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return err } defer f.Close() path, err := s.execPath() if err != nil { return err } var to = &struct { *Config Path string HasOutputFileSupport bool ReloadSignal string PIDFile string LimitNOFILE int Restart string SuccessExitStatus string LogOutput bool LogDirectory string }{ s.Config, path, s.hasOutputFileSupport(), s.Option.string(optionReloadSignal, ""), s.Option.string(optionPIDFile, ""), s.Option.int(optionLimitNOFILE, optionLimitNOFILEDefault), s.Option.string(optionRestart, "always"), s.Option.string(optionSuccessExitStatus, ""), s.Option.bool(optionLogOutput, optionLogOutputDefault), s.Option.string(optionLogDirectory, defaultLogDirectory), } err = s.template().Execute(f, to) if err != nil { return err } err = s.runAction("enable") if err != nil { return err } return s.run("daemon-reload") } func (s *systemd) Uninstall() error { err := s.runAction("disable") if err != nil { return err } cp, err := s.configPath() if err != nil { return err } if err := os.Remove(cp); err != nil { return err } return nil } func (s *systemd) Logger(errs chan<- error) (Logger, error) { if system.Interactive() { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *systemd) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } func (s *systemd) Run() (err error) { err = s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *systemd) Status() (Status, error) { exitCode, out, err := s.runWithOutput("systemctl", "is-active", s.unitName()) if exitCode == 0 && err != nil { return StatusUnknown, err } switch { case strings.HasPrefix(out, "active"): return StatusRunning, nil case strings.HasPrefix(out, "inactive"): // inactive can also mean its not installed, check unit files exitCode, out, err := s.runWithOutput("systemctl", "list-unit-files", "-t", "service", s.unitName()) if exitCode == 0 && err != nil { return StatusUnknown, err } if strings.Contains(out, s.Name) { // unit file exists, installed but not running return StatusStopped, nil } // no unit file return StatusUnknown, ErrNotInstalled case strings.HasPrefix(out, "activating"): return StatusRunning, nil case strings.HasPrefix(out, "failed"): return StatusUnknown, errors.New("service in failed state") default: return StatusUnknown, ErrNotInstalled } } func (s *systemd) Start() error { return s.runAction("start") } func (s *systemd) Stop() error { return s.runAction("stop") } func (s *systemd) Restart() error { return s.runAction("restart") } func (s *systemd) runWithOutput(command string, arguments ...string) (int, string, error) { if s.isUserService() { arguments = append(arguments, "--user") } return runWithOutput(command, arguments...) } func (s *systemd) run(action string, args ...string) error { if s.isUserService() { return run("systemctl", append([]string{action, "--user"}, args...)...) } return run("systemctl", append([]string{action}, args...)...) } func (s *systemd) runAction(action string) error { return s.run(action, s.unitName()) } const systemdScript = `[Unit] Description={{.Description}} ConditionFileIsExecutable={{.Path|cmdEscape}} {{range $i, $dep := .Dependencies}} {{$dep}} {{end}} [Service] StartLimitInterval=5 StartLimitBurst=10 ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}} {{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}} {{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}} {{if .UserName}}User={{.UserName}}{{end}} {{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}} {{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}} {{if and .LogOutput .HasOutputFileSupport -}} StandardOutput=file:{{.LogDirectory}}/{{.Name}}.out StandardError=file:{{.LogDirectory}}/{{.Name}}.err {{- end}} {{if gt .LimitNOFILE -1 }}LimitNOFILE={{.LimitNOFILE}}{{end}} {{if .Restart}}Restart={{.Restart}}{{end}} {{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}} RestartSec=120 EnvironmentFile=-/etc/sysconfig/{{.Name}} {{range $k, $v := .EnvVars -}} Environment={{$k}}={{$v}} {{end -}} [Install] WantedBy=multi-user.target ` service-1.2.2/service_sysv_linux.go000066400000000000000000000133531432111315600174500ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "errors" "fmt" "os" "os/signal" "strings" "syscall" "text/template" "time" ) type sysv struct { i Interface platform string *Config } func newSystemVService(i Interface, platform string, c *Config) (Service, error) { s := &sysv{ i: i, platform: platform, Config: c, } return s, nil } func (s *sysv) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *sysv) Platform() string { return s.platform } var errNoUserServiceSystemV = errors.New("User services are not supported on SystemV.") func (s *sysv) configPath() (cp string, err error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { err = errNoUserServiceSystemV return } cp = "/etc/init.d/" + s.Config.Name return } func (s *sysv) template() *template.Template { customScript := s.Option.string(optionSysvScript, "") if customScript != "" { return template.Must(template.New("").Funcs(tf).Parse(customScript)) } return template.Must(template.New("").Funcs(tf).Parse(sysvScript)) } func (s *sysv) Install() error { confPath, err := s.configPath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Init already exists: %s", confPath) } f, err := os.Create(confPath) if err != nil { return err } defer f.Close() path, err := s.execPath() if err != nil { return err } var to = &struct { *Config Path string LogDirectory string }{ s.Config, path, s.Option.string(optionLogDirectory, defaultLogDirectory), } err = s.template().Execute(f, to) if err != nil { return err } 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 { continue } } for _, i := range [...]string{"0", "1", "6"} { if err = os.Symlink(confPath, "/etc/rc"+i+".d/K02"+s.Name); err != nil { continue } } return nil } func (s *sysv) Uninstall() error { cp, err := s.configPath() if err != nil { return err } if err := os.Remove(cp); err != nil { return err } return nil } func (s *sysv) Logger(errs chan<- error) (Logger, error) { if system.Interactive() { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *sysv) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } func (s *sysv) Run() (err error) { err = s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *sysv) Status() (Status, error) { _, out, err := runWithOutput("service", s.Name, "status") if err != nil { return StatusUnknown, err } switch { case strings.HasPrefix(out, "Running"): return StatusRunning, nil case strings.HasPrefix(out, "Stopped"): return StatusStopped, nil default: return StatusUnknown, ErrNotInstalled } } func (s *sysv) Start() error { return run("service", s.Name, "start") } func (s *sysv) Stop() error { return run("service", s.Name, "stop") } func (s *sysv) Restart() error { err := s.Stop() if err != nil { return err } time.Sleep(50 * time.Millisecond) return s.Start() } const sysvScript = `#!/bin/sh # For RedHat and cousins: # chkconfig: - 99 01 # description: {{.Description}} # processname: {{.Path}} ### BEGIN INIT INFO # Provides: {{.Path}} # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: {{.DisplayName}} # Description: {{.Description}} ### END INIT INFO cmd="{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}" name=$(basename $(readlink -f $0)) pid_file="/var/run/$name.pid" stdout_log="{{.LogDirectory}}/$name.log" stderr_log="{{.LogDirectory}}/$name.err" {{range $k, $v := .EnvVars -}} export {{$k}}={{$v}} {{end -}} [ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name get_pid() { cat "$pid_file" } is_running() { [ -f "$pid_file" ] && cat /proc/$(get_pid)/stat > /dev/null 2>&1 } case "$1" in start) if is_running; then echo "Already started" else echo "Starting $name" {{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}} $cmd >> "$stdout_log" 2>> "$stderr_log" & echo $! > "$pid_file" if ! is_running; then echo "Unable to start, see $stdout_log and $stderr_log" exit 1 fi fi ;; stop) if is_running; then echo -n "Stopping $name.." kill $(get_pid) for i in $(seq 1 10) do if ! is_running; then break fi echo -n "." sleep 1 done echo if is_running; then echo "Not stopped; may still be shutting down or shutdown may have failed" exit 1 else echo "Stopped" if [ -f "$pid_file" ]; then rm "$pid_file" fi fi else echo "Not running" fi ;; restart) $0 stop if is_running; then echo "Unable to stop, will not attempt to start" exit 1 fi $0 start ;; status) if is_running; then echo "Running" else echo "Stopped" exit 1 fi ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0 ` service-1.2.2/service_test.go000066400000000000000000000031351432111315600162010ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service_test import ( "os" "testing" "time" "github.com/kardianos/service" ) func TestRunInterrupt(t *testing.T) { p := &program{} sc := &service.Config{ Name: "go_service_test", } s, err := service.New(p, sc) if err != nil { t.Fatalf("New err: %s", err) } go func() { <-time.After(1 * time.Second) interruptProcess(t) }() go func() { for i := 0; i < 25 && p.numStopped == 0; i++ { <-time.After(200 * time.Millisecond) } if p.numStopped == 0 { t.Fatal("Run() hasn't been stopped") } }() if err = s.Run(); err != nil { t.Fatalf("Run() err: %s", err) } } const testInstallEnv = "TEST_USER_INSTALL" // Should always run, without asking for any permission func TestUserRunInterrupt(t *testing.T) { if os.Getenv(testInstallEnv) != "1" { t.Skipf("env %q is not set to 1", testInstallEnv) } p := &program{} options := make(service.KeyValue) options["UserService"] = true sc := &service.Config{ Name: "go_user_service_test", Option: options, } s, err := service.New(p, sc) if err != nil { t.Fatalf("New err: %s", err) } err = s.Install() if err != nil { t.Errorf("Install err: %s", err) } err = s.Uninstall() if err != nil { t.Fatalf("Uninstall err: %s", err) } } type program struct { numStopped int } func (p *program) Start(s service.Service) error { go p.run() return nil } func (p *program) run() { // Do work here } func (p *program) Stop(s service.Service) error { p.numStopped++ return nil } service-1.2.2/service_unix.go000066400000000000000000000067611432111315600162150ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. //go:build linux || darwin || solaris || aix || freebsd // +build linux darwin solaris aix freebsd package service import ( "bytes" "fmt" "io" "io/ioutil" "log/syslog" "os/exec" "syscall" ) const defaultLogDirectory = "/var/log" func newSysLogger(name string, errs chan<- error) (Logger, error) { w, err := syslog.New(syslog.LOG_INFO, name) if err != nil { return nil, err } return sysLogger{w, errs}, nil } type sysLogger struct { *syslog.Writer errs chan<- error } func (s sysLogger) send(err error) error { if err != nil && s.errs != nil { s.errs <- err } return err } func (s sysLogger) Error(v ...interface{}) error { return s.send(s.Writer.Err(fmt.Sprint(v...))) } func (s sysLogger) Warning(v ...interface{}) error { return s.send(s.Writer.Warning(fmt.Sprint(v...))) } func (s sysLogger) Info(v ...interface{}) error { return s.send(s.Writer.Info(fmt.Sprint(v...))) } func (s sysLogger) Errorf(format string, a ...interface{}) error { return s.send(s.Writer.Err(fmt.Sprintf(format, a...))) } func (s sysLogger) Warningf(format string, a ...interface{}) error { return s.send(s.Writer.Warning(fmt.Sprintf(format, a...))) } func (s sysLogger) Infof(format string, a ...interface{}) error { return s.send(s.Writer.Info(fmt.Sprintf(format, a...))) } func run(command string, arguments ...string) error { _, _, err := runCommand(command, false, arguments...) return err } func runWithOutput(command string, arguments ...string) (int, string, error) { return runCommand(command, true, arguments...) } func runCommand(command string, readStdout bool, arguments ...string) (int, string, error) { cmd := exec.Command(command, arguments...) var output string var stdout io.ReadCloser var err error if readStdout { // Connect pipe to read Stdout stdout, err = cmd.StdoutPipe() if err != nil { // Failed to connect pipe return 0, "", fmt.Errorf("%q failed to connect stdout pipe: %v", command, err) } } // Connect pipe to read Stderr stderr, err := cmd.StderrPipe() if err != nil { // Failed to connect pipe return 0, "", fmt.Errorf("%q failed to connect stderr pipe: %v", command, err) } // Do not use cmd.Run() if err := cmd.Start(); err != nil { // Problem while copying stdin, stdout, or stderr return 0, "", fmt.Errorf("%q failed: %v", command, err) } // Zero exit status // Darwin: launchctl can fail with a zero exit status, // so check for emtpy stderr if command == "launchctl" { slurp, _ := ioutil.ReadAll(stderr) if len(slurp) > 0 && !bytes.HasSuffix(slurp, []byte("Operation now in progress\n")) { return 0, "", fmt.Errorf("%q failed with stderr: %s", command, slurp) } } if readStdout { out, err := ioutil.ReadAll(stdout) if err != nil { return 0, "", fmt.Errorf("%q failed while attempting to read stdout: %v", command, err) } else if len(out) > 0 { output = string(out) } } if err := cmd.Wait(); err != nil { exitStatus, ok := isExitError(err) if ok { // Command didn't exit with a zero exit status. return exitStatus, output, err } // An error occurred and there is no exit status. return 0, output, fmt.Errorf("%q failed: %v", command, err) } return 0, output, nil } func isExitError(err error) (int, bool) { if exiterr, ok := err.(*exec.ExitError); ok { if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { return status.ExitStatus(), true } } return 0, false } service-1.2.2/service_upstart_linux.go000066400000000000000000000134171432111315600201470ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "errors" "fmt" "os" "os/signal" "regexp" "strings" "syscall" "text/template" ) func isUpstart() bool { if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil { return true } if _, err := os.Stat("/sbin/initctl"); err == nil { if _, out, err := runWithOutput("/sbin/initctl", "--version"); err == nil { if strings.Contains(out, "initctl (upstart") { return true } } } return false } type upstart struct { i Interface platform string *Config } func newUpstartService(i Interface, platform string, c *Config) (Service, error) { s := &upstart{ i: i, platform: platform, Config: c, } return s, nil } func (s *upstart) String() string { if len(s.DisplayName) > 0 { return s.DisplayName } return s.Name } func (s *upstart) Platform() string { return s.platform } // Upstart has some support for user services in graphical sessions. // Due to the mix of actual support for user services over versions, just don't bother. // Upstart will be replaced by systemd in most cases anyway. var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.") func (s *upstart) configPath() (cp string, err error) { if s.Option.bool(optionUserService, optionUserServiceDefault) { err = errNoUserServiceUpstart return } cp = "/etc/init/" + s.Config.Name + ".conf" return } func (s *upstart) hasKillStanza() bool { defaultValue := true version := s.getUpstartVersion() if version == nil { return defaultValue } maxVersion := []int{0, 6, 5} if matches, err := versionAtMost(version, maxVersion); err != nil || matches { return false } return defaultValue } func (s *upstart) hasSetUIDStanza() bool { defaultValue := true version := s.getUpstartVersion() if version == nil { return defaultValue } maxVersion := []int{1, 4, 0} if matches, err := versionAtMost(version, maxVersion); err != nil || matches { return false } return defaultValue } func (s *upstart) getUpstartVersion() []int { _, out, err := runWithOutput("/sbin/initctl", "--version") if err != nil { return nil } re := regexp.MustCompile(`initctl \(upstart (\d+.\d+.\d+)\)`) matches := re.FindStringSubmatch(out) if len(matches) != 2 { return nil } return parseVersion(matches[1]) } func (s *upstart) template() *template.Template { customScript := s.Option.string(optionUpstartScript, "") if customScript != "" { return template.Must(template.New("").Funcs(tf).Parse(customScript)) } else { return template.Must(template.New("").Funcs(tf).Parse(upstartScript)) } } func (s *upstart) Install() error { confPath, err := s.configPath() if err != nil { return err } _, err = os.Stat(confPath) if err == nil { return fmt.Errorf("Init already exists: %s", confPath) } f, err := os.Create(confPath) if err != nil { return err } defer f.Close() path, err := s.execPath() if err != nil { return err } var to = &struct { *Config Path string HasKillStanza bool HasSetUIDStanza bool LogOutput bool LogDirectory string }{ s.Config, path, s.hasKillStanza(), s.hasSetUIDStanza(), s.Option.bool(optionLogOutput, optionLogOutputDefault), s.Option.string(optionLogDirectory, defaultLogDirectory), } return s.template().Execute(f, to) } func (s *upstart) Uninstall() error { cp, err := s.configPath() if err != nil { return err } if err := os.Remove(cp); err != nil { return err } return nil } func (s *upstart) Logger(errs chan<- error) (Logger, error) { if system.Interactive() { return ConsoleLogger, nil } return s.SystemLogger(errs) } func (s *upstart) SystemLogger(errs chan<- error) (Logger, error) { return newSysLogger(s.Name, errs) } func (s *upstart) Run() (err error) { err = s.i.Start(s) if err != nil { return err } s.Option.funcSingle(optionRunWait, func() { var sigChan = make(chan os.Signal, 3) signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt) <-sigChan })() return s.i.Stop(s) } func (s *upstart) Status() (Status, error) { exitCode, out, err := runWithOutput("initctl", "status", s.Name) if exitCode == 0 && err != nil { return StatusUnknown, err } switch { case strings.HasPrefix(out, fmt.Sprintf("%s start/running", s.Name)): return StatusRunning, nil case strings.HasPrefix(out, fmt.Sprintf("%s stop/waiting", s.Name)): return StatusStopped, nil default: return StatusUnknown, ErrNotInstalled } } func (s *upstart) Start() error { return run("initctl", "start", s.Name) } func (s *upstart) Stop() error { return run("initctl", "stop", s.Name) } func (s *upstart) Restart() error { return run("initctl", "restart", s.Name) } // The upstart script should stop with an INT or the Go runtime will terminate // the program before the Stop handler can run. const upstartScript = `# {{.Description}} {{if .DisplayName}}description "{{.DisplayName}}"{{end}} {{if .HasKillStanza}}kill signal INT{{end}} {{if .ChRoot}}chroot {{.ChRoot}}{{end}} {{if .WorkingDirectory}}chdir {{.WorkingDirectory}}{{end}} start on filesystem or runlevel [2345] stop on runlevel [!2345] {{if and .UserName .HasSetUIDStanza}}setuid {{.UserName}}{{end}} respawn respawn limit 10 5 umask 022 console none pre-start script test -x {{.Path}} || { stop; exit 0; } end script # Start script {{if .LogOutput}} stdout_log="{{.LogDirectory}}/{{.Name}}.out" stderr_log="{{.LogDirectory}}/{{.Name}}.err" {{end}} if [ -f "/etc/sysconfig/{{.Name}}" ]; then set -a source /etc/sysconfig/{{.Name}} set +a fi exec {{if and .UserName (not .HasSetUIDStanza)}}sudo -E -u {{.UserName}} {{end}}{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}{{if .LogOutput}} >> $stdout_log 2>> $stderr_log{{end}} end script ` service-1.2.2/service_windows.go000066400000000000000000000312411432111315600167130ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "fmt" "os" "os/signal" "strconv" "strings" "sync" "syscall" "time" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" "golang.org/x/sys/windows/svc" "golang.org/x/sys/windows/svc/eventlog" "golang.org/x/sys/windows/svc/mgr" ) const ( version = "windows-service" StartType = "StartType" ServiceStartManual = "manual" ServiceStartDisabled = "disabled" ServiceStartAutomatic = "automatic" OnFailure = "OnFailure" OnFailureRestart = "restart" OnFailureReboot = "reboot" OnFailureNoAction = "noaction" OnFailureDelayDuration = "OnFailureDelayDuration" OnFailureResetPeriod = "OnFailureResetPeriod" errnoServiceDoesNotExist syscall.Errno = 1060 ) type windowsService struct { i Interface *Config errSync sync.Mutex stopStartErr error } // WindowsLogger allows using windows specific logging methods. type WindowsLogger struct { ev *eventlog.Log errs chan<- error } type windowsSystem struct{} func (windowsSystem) String() string { return version } func (windowsSystem) Detect() bool { return true } func (windowsSystem) Interactive() bool { return interactive } func (windowsSystem) New(i Interface, c *Config) (Service, error) { ws := &windowsService{ i: i, Config: c, } return ws, nil } func init() { ChooseSystem(windowsSystem{}) } func (l WindowsLogger) send(err error) error { if err == nil { return nil } if l.errs != nil { l.errs <- err } return err } // Error logs an error message. func (l WindowsLogger) Error(v ...interface{}) error { return l.send(l.ev.Error(3, fmt.Sprint(v...))) } // Warning logs an warning message. func (l WindowsLogger) Warning(v ...interface{}) error { return l.send(l.ev.Warning(2, fmt.Sprint(v...))) } // Info logs an info message. func (l WindowsLogger) Info(v ...interface{}) error { return l.send(l.ev.Info(1, fmt.Sprint(v...))) } // Errorf logs an error message. func (l WindowsLogger) Errorf(format string, a ...interface{}) error { return l.send(l.ev.Error(3, fmt.Sprintf(format, a...))) } // Warningf logs an warning message. func (l WindowsLogger) Warningf(format string, a ...interface{}) error { return l.send(l.ev.Warning(2, fmt.Sprintf(format, a...))) } // Infof logs an info message. func (l WindowsLogger) Infof(format string, a ...interface{}) error { return l.send(l.ev.Info(1, fmt.Sprintf(format, a...))) } // NError logs an error message and an event ID. func (l WindowsLogger) NError(eventID uint32, v ...interface{}) error { return l.send(l.ev.Error(eventID, fmt.Sprint(v...))) } // NWarning logs an warning message and an event ID. func (l WindowsLogger) NWarning(eventID uint32, v ...interface{}) error { return l.send(l.ev.Warning(eventID, fmt.Sprint(v...))) } // NInfo logs an info message and an event ID. func (l WindowsLogger) NInfo(eventID uint32, v ...interface{}) error { return l.send(l.ev.Info(eventID, fmt.Sprint(v...))) } // NErrorf logs an error message and an event ID. func (l WindowsLogger) NErrorf(eventID uint32, format string, a ...interface{}) error { return l.send(l.ev.Error(eventID, fmt.Sprintf(format, a...))) } // NWarningf logs an warning message and an event ID. func (l WindowsLogger) NWarningf(eventID uint32, format string, a ...interface{}) error { return l.send(l.ev.Warning(eventID, fmt.Sprintf(format, a...))) } // NInfof logs an info message and an event ID. func (l WindowsLogger) NInfof(eventID uint32, format string, a ...interface{}) error { return l.send(l.ev.Info(eventID, fmt.Sprintf(format, a...))) } var interactive = false func init() { isService, err := svc.IsWindowsService() if err != nil { panic(err) } interactive = !isService } func (ws *windowsService) String() string { if len(ws.DisplayName) > 0 { return ws.DisplayName } return ws.Name } func (ws *windowsService) Platform() string { return version } func (ws *windowsService) setError(err error) { ws.errSync.Lock() defer ws.errSync.Unlock() ws.stopStartErr = err } func (ws *windowsService) getError() error { ws.errSync.Lock() defer ws.errSync.Unlock() return ws.stopStartErr } 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.i.Start(ws); err != nil { ws.setError(err) return true, 1 } changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} loop: for { c := <-r switch c.Cmd { case svc.Interrogate: changes <- c.CurrentStatus case svc.Stop: changes <- svc.Status{State: svc.StopPending} if err := ws.i.Stop(ws); err != nil { ws.setError(err) return true, 2 } break loop case svc.Shutdown: changes <- svc.Status{State: svc.StopPending} var err error if wsShutdown, ok := ws.i.(Shutdowner); ok { err = wsShutdown.Shutdown(ws) } else { err = ws.i.Stop(ws) } if err != nil { ws.setError(err) return true, 2 } break loop default: continue loop } } return false, 0 } func lowPrivMgr() (*mgr.Mgr, error) { h, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CONNECT|windows.SC_MANAGER_ENUMERATE_SERVICE) if err != nil { return nil, err } return &mgr.Mgr{Handle: h}, nil } func lowPrivSvc(m *mgr.Mgr, name string) (*mgr.Service, error) { h, err := windows.OpenService( m.Handle, syscall.StringToUTF16Ptr(name), windows.SERVICE_QUERY_CONFIG|windows.SERVICE_QUERY_STATUS|windows.SERVICE_START|windows.SERVICE_STOP) if err != nil { return nil, err } return &mgr.Service{Handle: h, Name: name}, nil } func (ws *windowsService) setEnvironmentVariablesInRegistry() error { if len(ws.EnvVars) == 0 { return nil } k, _, err := registry.CreateKey( registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Services\`+ws.Name, registry.QUERY_VALUE|registry.SET_VALUE|registry.CREATE_SUB_KEY) if err != nil { return fmt.Errorf("failed creating env var registry key, err = %v", err) } envStrings := make([]string, 0, len(ws.EnvVars)) for k, v := range ws.EnvVars { envStrings = append(envStrings, k+"="+v) } if err := k.SetStringsValue("Environment", envStrings); err != nil { return fmt.Errorf("failed setting env var registry key, err = %v", err) } if err := k.Close(); err != nil { return fmt.Errorf("failed closing env var registry key, err = %v", err) } return nil } func (ws *windowsService) Install() error { exepath, err := ws.execPath() if err != nil { return err } m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() if err := ws.setEnvironmentVariablesInRegistry(); err != nil { return err } s, err := m.OpenService(ws.Name) if err == nil { s.Close() return fmt.Errorf("service %s already exists", ws.Name) } var startType int32 switch ws.Option.string(StartType, ServiceStartAutomatic) { case ServiceStartAutomatic: startType = mgr.StartAutomatic case ServiceStartManual: startType = mgr.StartManual case ServiceStartDisabled: startType = mgr.StartDisabled } serviceType := windows.SERVICE_WIN32_OWN_PROCESS if ws.Option.bool("Interactive", false) { serviceType = serviceType | windows.SERVICE_INTERACTIVE_PROCESS } s, err = m.CreateService(ws.Name, exepath, mgr.Config{ DisplayName: ws.DisplayName, Description: ws.Description, StartType: uint32(startType), ServiceStartName: ws.UserName, Password: ws.Option.string("Password", ""), Dependencies: ws.Dependencies, DelayedAutoStart: ws.Option.bool("DelayedAutoStart", false), ServiceType: uint32(serviceType), }, ws.Arguments...) if err != nil { return err } if onFailure := ws.Option.string(OnFailure, ""); onFailure != "" { var delay = 1 * time.Second if d, err := time.ParseDuration(ws.Option.string(OnFailureDelayDuration, "1s")); err == nil { delay = d } var actionType int switch onFailure { case OnFailureReboot: actionType = mgr.ComputerReboot case OnFailureRestart: actionType = mgr.ServiceRestart case OnFailureNoAction: actionType = mgr.NoAction default: actionType = mgr.ServiceRestart } if err := s.SetRecoveryActions([]mgr.RecoveryAction{ { Type: actionType, Delay: delay, }, }, uint32(ws.Option.int(OnFailureResetPeriod, 10))); err != nil { return err } } defer s.Close() err = eventlog.InstallAsEventCreate(ws.Name, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { if !strings.Contains(err.Error(), "exists") { s.Delete() return fmt.Errorf("SetupEventLogSource() failed: %s", err) } } return nil } func (ws *windowsService) Uninstall() error { m, err := mgr.Connect() if err != nil { return err } defer m.Disconnect() s, err := m.OpenService(ws.Name) if err != nil { 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) if err != nil { return fmt.Errorf("RemoveEventLogSource() failed: %s", err) } return nil } func (ws *windowsService) Run() error { ws.setError(nil) if !interactive { // Return error messages from start and stop routines // that get executed in the Execute method. // Guarded with a mutex as it may run a different thread // (callback from windows). runErr := svc.Run(ws.Name, ws) startStopErr := ws.getError() if startStopErr != nil { return startStopErr } if runErr != nil { return runErr } return nil } err := ws.i.Start(ws) if err != nil { return err } sigChan := make(chan os.Signal) signal.Notify(sigChan, os.Interrupt) <-sigChan return ws.i.Stop(ws) } func (ws *windowsService) Status() (Status, error) { m, err := lowPrivMgr() if err != nil { return StatusUnknown, err } defer m.Disconnect() s, err := lowPrivSvc(m, ws.Name) if err != nil { if errno, ok := err.(syscall.Errno); ok && errno == errnoServiceDoesNotExist { return StatusUnknown, ErrNotInstalled } return StatusUnknown, err } defer s.Close() status, err := s.Query() if err != nil { return StatusUnknown, err } switch status.State { case svc.StartPending: fallthrough case svc.Running: return StatusRunning, nil case svc.PausePending: fallthrough case svc.Paused: fallthrough case svc.ContinuePending: fallthrough case svc.StopPending: fallthrough case svc.Stopped: return StatusStopped, nil default: return StatusUnknown, fmt.Errorf("unknown status %v", status) } } func (ws *windowsService) Start() error { m, err := lowPrivMgr() if err != nil { return err } defer m.Disconnect() s, err := lowPrivSvc(m, ws.Name) if err != nil { return err } defer s.Close() return s.Start() } func (ws *windowsService) Stop() error { m, err := lowPrivMgr() if err != nil { return err } defer m.Disconnect() s, err := lowPrivSvc(m, ws.Name) if err != nil { return err } defer s.Close() return ws.stopWait(s) } func (ws *windowsService) Restart() error { m, err := lowPrivMgr() if err != nil { return err } defer m.Disconnect() s, err := lowPrivSvc(m, ws.Name) if err != nil { return err } defer s.Close() err = ws.stopWait(s) if err != nil { return err } return s.Start() } func (ws *windowsService) stopWait(s *mgr.Service) error { // First stop the service. Then wait for the service to // actually stop before starting it. status, err := s.Control(svc.Stop) if err != nil { return err } timeDuration := time.Millisecond * 50 timeout := time.After(getStopTimeout() + (timeDuration * 2)) tick := time.NewTicker(timeDuration) defer tick.Stop() for status.State != svc.Stopped { select { case <-tick.C: status, err = s.Query() if err != nil { return err } case <-timeout: break } } return nil } // getStopTimeout fetches the time before windows will kill the service. func getStopTimeout() time.Duration { // For default and paths see https://support.microsoft.com/en-us/kb/146092 defaultTimeout := time.Millisecond * 20000 key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ) if err != nil { return defaultTimeout } sv, _, err := key.GetStringValue("WaitToKillServiceTimeout") if err != nil { return defaultTimeout } v, err := strconv.Atoi(sv) if err != nil { return defaultTimeout } return time.Millisecond * time.Duration(v) } func (ws *windowsService) Logger(errs chan<- error) (Logger, error) { if interactive { return ConsoleLogger, nil } return ws.SystemLogger(errs) } func (ws *windowsService) SystemLogger(errs chan<- error) (Logger, error) { el, err := eventlog.Open(ws.Name) if err != nil { return nil, err } return WindowsLogger{el, errs}, nil } service-1.2.2/service_windows_test.go000066400000000000000000000004361432111315600177540ustar00rootroot00000000000000// Copyright 2015 Daniel Theophanes. // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service import ( "testing" ) func TestTimeout(t *testing.T) { stopSpan := getStopTimeout() t.Log("Max Stop Duration", stopSpan) } service-1.2.2/servicetest_unix_test.go000066400000000000000000000010051432111315600201360ustar00rootroot00000000000000// Copyright 2016 Lawrence Woodman // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. // +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris package service_test import ( "os" "testing" ) func interruptProcess(t *testing.T) { pid := os.Getpid() p, err := os.FindProcess(pid) if err != nil { t.Fatalf("FindProcess: %s", err) } if err := p.Signal(os.Interrupt); err != nil { t.Fatalf("Signal: %s", err) } } service-1.2.2/servicetest_windows_test.go000066400000000000000000000015731432111315600206570ustar00rootroot00000000000000// Copyright 2016 Lawrence Woodman // Use of this source code is governed by a zlib-style // license that can be found in the LICENSE file. package service_test import ( "os" "syscall" "testing" ) func interruptProcess(t *testing.T) { dll, err := syscall.LoadDLL("kernel32.dll") if err != nil { t.Fatalf("LoadDLL(\"kernel32.dll\") err: %s", err) } p, err := dll.FindProc("GenerateConsoleCtrlEvent") if err != nil { t.Fatalf("FindProc(\"GenerateConsoleCtrlEvent\") err: %s", err) } // Send the CTRL_BREAK_EVENT to a console process group that shares // the console associated with the calling process. // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683155(v=vs.85).aspx pid := os.Getpid() r1, _, err := p.Call(syscall.CTRL_BREAK_EVENT, uintptr(pid)) if r1 == 0 { t.Fatalf("Call(CTRL_BREAK_EVENT, %d) err: %s", pid, err) } } service-1.2.2/version.go000066400000000000000000000023341432111315600151670ustar00rootroot00000000000000package service import ( "errors" "strconv" "strings" ) // versionAtMost will return true if the provided version is less than or equal to max func versionAtMost(version, max []int) (bool, error) { if comp, err := versionCompare(version, max); err != nil { return false, err } else if comp == 1 { return false, nil } return true, nil } // versionCompare take to versions split into integer arrays and attempts to compare them // An error will be returned if there is an array length mismatch. // Return values are as follows // -1 - v1 is less than v2 // 0 - v1 is equal to v2 // 1 - v1 is greater than v2 func versionCompare(v1, v2 []int) (int, error) { if len(v1) != len(v2) { return 0, errors.New("version length mismatch") } for idx, v2S := range v2 { v1S := v1[idx] if v1S > v2S { return 1, nil } if v1S < v2S { return -1, nil } } return 0, nil } // parseVersion will parse any integer type version separated by periods. // This does not fully support semver style versions. func parseVersion(v string) []int { version := make([]int, 3) for idx, vStr := range strings.Split(v, ".") { vS, err := strconv.Atoi(vStr) if err != nil { return nil } version[idx] = vS } return version } service-1.2.2/version_test.go000066400000000000000000000065241432111315600162330ustar00rootroot00000000000000package service import ( "reflect" "testing" ) func Test_versionCompare(t *testing.T) { type args struct { v1 []int v2 []int } tests := []struct { name string args args want int wantErr bool }{ {"segment-mismatch-1", args{[]int{0, 0, 0, 0}, []int{0, 0, 0}}, 0, true}, {"segment-mismatch-2", args{[]int{0, 0, 0}, []int{0, 0, 0, 0}}, 0, true}, {"equal-to-1", args{[]int{0, 0, 0}, []int{0, 0, 0}}, 0, false}, {"equal-to-2", args{[]int{0, 0, 1}, []int{0, 0, 1}}, 0, false}, {"equal-to-3", args{[]int{0, 1, 0}, []int{0, 1, 0}}, 0, false}, {"equal-to-4", args{[]int{1, 0, 0}, []int{1, 0, 0}}, 0, false}, {"equal-to-5", args{[]int{2, 2, 2}, []int{2, 2, 2}}, 0, false}, {"less-than-1", args{[]int{0, 0, 0}, []int{0, 0, 1}}, -1, false}, {"less-than-2", args{[]int{0, 0, 1}, []int{0, 1, 0}}, -1, false}, {"less-than-3", args{[]int{0, 1, 0}, []int{1, 0, 0}}, -1, false}, {"less-than-4", args{[]int{0, 1, 0}, []int{1, 0, 0}}, -1, false}, {"less-than-5", args{[]int{0, 8, 0}, []int{1, 5, 0}}, -1, false}, {"greater-than-1", args{[]int{0, 0, 1}, []int{0, 0, 0}}, 1, false}, {"greater-than-2", args{[]int{0, 1, 0}, []int{0, 0, 1}}, 1, false}, {"greater-than-3", args{[]int{1, 0, 0}, []int{0, 1, 0}}, 1, false}, {"greater-than-4", args{[]int{1, 0, 0}, []int{0, 1, 0}}, 1, false}, {"greater-than-5", args{[]int{1, 5, 0}, []int{0, 8, 0}}, 1, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := versionCompare(tt.args.v1, tt.args.v2) if (err != nil) != tt.wantErr { t.Errorf("versionCompare() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("versionCompare() = %v, want %v", got, tt.want) } }) } } func Test_parseVersion(t *testing.T) { type args struct { v string } tests := []struct { name string args args want []int }{ {"sanity-check", args{"0.0.0"}, []int{0, 0, 0}}, {"should-fail", args{"0.zero.0"}, nil}, {"should-fail-no-semver", args{"0.0.0-test+1"}, nil}, {"double-digits", args{"5.200.1"}, []int{5, 200, 1}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := parseVersion(tt.args.v); !reflect.DeepEqual(got, tt.want) { t.Errorf("parseVersion() = %v, want %v", got, tt.want) } }) } } func Test_versionAtMost(t *testing.T) { type args struct { version []int max []int } tests := []struct { name string args args want bool wantErr bool }{ {"segment-mismatch-1", args{[]int{0, 0, 0, 0}, []int{0, 0, 0}}, false, true}, {"segment-mismatch-2", args{[]int{0, 0, 0}, []int{0, 0, 0, 0}}, false, true}, {"test-1", args{[]int{0, 0, 0}, []int{0, 0, 1}}, true, false}, {"test-2", args{[]int{0, 1, 0}, []int{0, 1, 0}}, true, false}, {"test-3", args{[]int{1, 0, 0}, []int{1, 0, 0}}, true, false}, {"negative-test-1", args{[]int{0, 0, 1}, []int{0, 0, 0}}, false, false}, {"negative-test-2", args{[]int{0, 1, 0}, []int{0, 0, 1}}, false, false}, {"negative-test-3", args{[]int{1, 0, 0}, []int{0, 1, 0}}, false, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := versionAtMost(tt.args.version, tt.args.max) if (err != nil) != tt.wantErr { t.Errorf("versionAtMost() error = %v, wantErr %v", err, tt.wantErr) return } if got != tt.want { t.Errorf("versionAtMost() = %v, want %v", got, tt.want) } }) } }