pax_global_header 0000666 0000000 0000000 00000000064 13427336164 0014523 g ustar 00root root 0000000 0000000 52 comment=56787a3ea05e9b262708192e7ce3b500aba73561
service-1.0.0/ 0000775 0000000 0000000 00000000000 13427336164 0013161 5 ustar 00root root 0000000 0000000 service-1.0.0/.gitignore 0000664 0000000 0000000 00000000000 13427336164 0015137 0 ustar 00root root 0000000 0000000 service-1.0.0/.travis.yml 0000664 0000000 0000000 00000000521 13427336164 0015270 0 ustar 00root root 0000000 0000000 language: go
go_import_path: github.com/kardianos/service
sudo: required
go:
- 1.10.x
- 1.11.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.0.0/LICENSE 0000664 0000000 0000000 00000001546 13427336164 0014174 0 ustar 00root root 0000000 0000000 Copyright (c) 2015 Daniel Theophanes
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
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.0.0/README.md 0000664 0000000 0000000 00000001361 13427336164 0014441 0 ustar 00root root 0000000 0000000 # service [](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.0.0/appveyor.yml 0000664 0000000 0000000 00000000430 13427336164 0015546 0 ustar 00root root 0000000 0000000 version: "{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.0.0/console.go 0000664 0000000 0000000 00000002142 13427336164 0015151 0 ustar 00root root 0000000 0000000 // 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.0.0/example/ 0000775 0000000 0000000 00000000000 13427336164 0014614 5 ustar 00root root 0000000 0000000 service-1.0.0/example/logging/ 0000775 0000000 0000000 00000000000 13427336164 0016242 5 ustar 00root root 0000000 0000000 service-1.0.0/example/logging/main.go 0000664 0000000 0000000 00000004144 13427336164 0017520 0 ustar 00root root 0000000 0000000 // 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()
svcConfig := &service.Config{
Name: "GoServiceExampleLogging",
DisplayName: "Go Service Example for Logging",
Description: "This is an example Go service that outputs log messages.",
}
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.0.0/example/runner/ 0000775 0000000 0000000 00000000000 13427336164 0016125 5 ustar 00root root 0000000 0000000 service-1.0.0/example/runner/runner.go 0000664 0000000 0000000 00000006671 13427336164 0017777 0 ustar 00root root 0000000 0000000 // 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.ProcessState.Exited() == false {
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.0.0/example/runner/runner.json 0000664 0000000 0000000 00000000744 13427336164 0020336 0 ustar 00root root 0000000 0000000 {
"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.0.0/example/simple/ 0000775 0000000 0000000 00000000000 13427336164 0016105 5 ustar 00root root 0000000 0000000 service-1.0.0/example/simple/main.go 0000664 0000000 0000000 00000001763 13427336164 0017367 0 ustar 00root root 0000000 0000000 // 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.0.0/example/stopPause/ 0000775 0000000 0000000 00000000000 13427336164 0016577 5 ustar 00root root 0000000 0000000 service-1.0.0/example/stopPause/main.go 0000664 0000000 0000000 00000002267 13427336164 0020061 0 ustar 00root root 0000000 0000000 // 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.0.0/go.mod 0000664 0000000 0000000 00000000152 13427336164 0014265 0 ustar 00root root 0000000 0000000 module github.com/kardianos/service
go 1.10
require golang.org/x/sys v0.0.0-20190204203706-41f3e6584952
service-1.0.0/go.sum 0000664 0000000 0000000 00000000317 13427336164 0014315 0 ustar 00root root 0000000 0000000 golang.org/x/sys v0.0.0-20190204203706-41f3e6584952 h1:FDfvYgoVsA7TTZSbgiqjAbfPbK47CNHdWl3h/PJtii0=
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
service-1.0.0/linux-test-su.sh 0000775 0000000 0000000 00000000546 13427336164 0016266 0 ustar 00root root 0000000 0000000 #!/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.0.0/linux_test/ 0000775 0000000 0000000 00000000000 13427336164 0015357 5 ustar 00root root 0000000 0000000 service-1.0.0/linux_test/Makefile 0000664 0000000 0000000 00000001531 13427336164 0017017 0 ustar 00root root 0000000 0000000
all: sysv systemd upstart clean
test:
@go test -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
service-1.0.0/linux_test/README.md 0000664 0000000 0000000 00000000263 13427336164 0016637 0 ustar 00root root 0000000 0000000 ## 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.0.0/linux_test/systemd/ 0000775 0000000 0000000 00000000000 13427336164 0017047 5 ustar 00root root 0000000 0000000 service-1.0.0/linux_test/systemd/Dockerfile 0000664 0000000 0000000 00000000132 13427336164 0021035 0 ustar 00root root 0000000 0000000 FROM minimum2scp/systemd:latest
ADD service.test /tmp/
CMD /tmp/service.test -test.v=true
service-1.0.0/linux_test/sysv/ 0000775 0000000 0000000 00000000000 13427336164 0016363 5 ustar 00root root 0000000 0000000 service-1.0.0/linux_test/sysv/Dockerfile 0000664 0000000 0000000 00000000134 13427336164 0020353 0 ustar 00root root 0000000 0000000 FROM minimum2scp/baseimage:latest
ADD service.test /tmp/
CMD /tmp/service.test -test.v=true
service-1.0.0/linux_test/upstart/ 0000775 0000000 0000000 00000000000 13427336164 0017061 5 ustar 00root root 0000000 0000000 service-1.0.0/linux_test/upstart/Dockerfile 0000664 0000000 0000000 00000000114 13427336164 0021047 0 ustar 00root root 0000000 0000000 FROM ubuntu:14.04
ADD service.test /tmp/
CMD /tmp/service.test -test.v=true
service-1.0.0/name_test.go 0000664 0000000 0000000 00000000670 13427336164 0015472 0 ustar 00root root 0000000 0000000 // 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.0.0/service.go 0000664 0000000 0000000 00000030112 13427336164 0015145 0 ustar 00root root 0000000 0000000 // Copyright 2015 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.
// Package service provides a simple way to create a system service.
// Currently supports Windows, Linux/(systemd | Upstart | SysV), and OSX/Launchd.
//
// Windows controls services by setting up callbacks that is non-trivial. This
// is very different then other systems. This package provides the same API
// despite the substantial differences.
// It also can be used to detect how a program is called, from an interactive
// terminal or from a service manager.
//
// Examples in the example/ folder.
//
// package main
//
// import (
// "log"
//
// "github.com/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
optionRunWait = "RunWait"
optionReloadSignal = "ReloadSignal"
optionPIDFile = "PIDFile"
optionSystemdScript = "SystemdScript"
optionSysvScript = "SysvScript"
optionUpstartScript = "UpstartScript"
optionLaunchdConfig = "LaunchdConfig"
)
// 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 implemented on Linux or OS X.
Dependencies []string
// The following fields are not supported on Windows.
WorkingDirectory string // Initial working directory.
ChRoot string
// System specific options.
// * OS X
// - LaunchdConfig string () - Use custom launchd config
// - KeepAlive bool (true)
// - RunAtLoad bool (false)
// - UserService bool (false) - Install as a current user service.
// - SessionCreate bool (false) - Create a full user session.
// * POSIX
// - SystemdScript string () - Use custom systemd script
// - UpstartScript string () - Use custom upstart script
// - SysvScript string () - Use custom sysv script
// - RunWait func() (wait for SIGNAL) - Do not install signal but wait for this function to return.
// - ReloadSignal string () [USR1, ...] - Signal to send on reaload.
// - PIDFile string () [/run/prog.pid] - Location of the PID file.
// - LogOutput bool (false) - Redirect StdErr & StdOut to files.
Option KeyValue
}
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 platform specific options. See platform docs for
// more details.
type KeyValue map[string]interface{}
// bool returns the value of the given name, assuming the value is a boolean.
// If the value isn't found or is not of the type, the defaultValue is returned.
func (kv KeyValue) bool(name string, defaultValue bool) bool {
if v, found := kv[name]; found {
if castValue, is := v.(bool); is {
return castValue
}
}
return defaultValue
}
// int returns the value of the given name, assuming the value is an int.
// If the value isn't found or is not of the type, the defaultValue is returned.
func (kv KeyValue) int(name string, defaultValue int) int {
if v, found := kv[name]; found {
if castValue, is := v.(int); is {
return castValue
}
}
return defaultValue
}
// string returns the value of the given name, assuming the value is a string.
// If the value isn't found or is not of the type, the defaultValue is returned.
func (kv KeyValue) string(name string, defaultValue string) string {
if v, found := kv[name]; found {
if castValue, is := v.(string); is {
return castValue
}
}
return defaultValue
}
// float64 returns the value of the given name, assuming the value is a float64.
// If the value isn't found or is not of the type, the defaultValue is returned.
func (kv KeyValue) float64(name string, defaultValue float64) float64 {
if v, found := kv[name]; found {
if castValue, is := v.(float64); is {
return castValue
}
}
return defaultValue
}
// funcSingle returns the value of the given name, assuming the value is a float64.
// If the value isn't found or is not of the type, the defaultValue is returned.
func (kv KeyValue) funcSingle(name string, defaultValue func()) func() {
if v, found := kv[name]; found {
if castValue, is := v.(func()); is {
return castValue
}
}
return defaultValue
}
// Platform returns a description of the system service.
func Platform() string {
if system == nil {
return ""
}
return system.String()
}
// Interactive returns false if running under the OS service manager
// and true otherwise.
func Interactive() bool {
if system == nil {
return true
}
return system.Interactive()
}
func newSystem() System {
for _, choice := range systemRegistry {
if choice.Detect() == false {
continue
}
return choice
}
return nil
}
// ChooseSystem chooses a system from the given system services.
// SystemServices are considered in the order they are suggested.
// Calling this may change what Interactive and Platform return.
func ChooseSystem(a ...System) {
systemRegistry = a
system = newSystem()
}
// ChosenSystem returns the system that service will use.
func ChosenSystem() System {
return system
}
// AvailableSystems returns the list of system services considered
// when choosing the system service.
func AvailableSystems() []System {
return systemRegistry
}
// System represents the service manager that is available.
type System interface {
// String returns a description of the system.
String() string
// Detect returns true if the system is available to use.
Detect() bool
// Interactive returns false if running under the system service manager
// and true otherwise.
Interactive() bool
// New creates a new service for this system.
New(i Interface, c *Config) (Service, error)
}
// Interface represents the service interface for a program. Start runs before
// the hosting process is granted control and Stop runs when control is returned.
//
// 1. OS service manager executes user program.
// 2. User program sees it is executed from a service manager (IsInteractive is false).
// 3. User program calls Service.Run() which blocks.
// 4. Interface.Start() is called and quickly returns.
// 5. User program runs.
// 6. OS service manager signals the user program to stop.
// 7. Interface.Stop() is called and quickly returns.
// - For a successful exit, os.Exit should not be called in Interface.Stop().
// 8. Service.Run returns.
// 9. User program should quickly exit.
type Interface interface {
// Start provides a place to initiate the service. The service doesn't not
// signal a completed start until after this function returns, so the
// Start function must not take more then a few seconds at most.
Start(s Service) error
// Stop provides a place to clean up program execution before it is terminated.
// It should not take more then a few seconds to execute.
// Stop should not call os.Exit directly in the function.
Stop(s Service) error
}
// TODO: Add Configure to Service interface.
// Service represents a service that can be run or controlled.
type Service interface {
// Run should be called shortly after the program entry point.
// After Interface.Stop has finished running, Run will stop blocking.
// After Run stops blocking, the program must exit shortly after.
Run() error
// Start signals to the OS service manager the given service should start.
Start() error
// Stop signals to the OS service manager the given service should stop.
Stop() error
// Restart signals to the OS service manager the given service should stop then start.
Restart() error
// Install setups up the given service in the OS service manager. This may require
// greater rights. Will return an error if it is already installed.
Install() error
// Uninstall removes the given service from the OS service manager. This may require
// greater rights. Will return an error if the service is not present.
Uninstall() error
// Opens and returns a system logger. If the user program is running
// interactively rather then as a service, the returned logger will write to
// os.Stderr. If errs is non-nil errors will be sent on errs as well as
// returned from Logger's functions.
Logger(errs chan<- error) (Logger, error)
// SystemLogger opens and returns a system logger. If errs is non-nil errors
// will be sent on errs as well as returned from Logger's functions.
SystemLogger(errs chan<- error) (Logger, error)
// String displays the name of the service. The display name if present,
// otherwise the name.
String() string
// 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.0.0/service_darwin.go 0000664 0000000 0000000 00000013656 13427336164 0016527 0 ustar 00root root 0000000 0000000 // 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"
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) 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))
} else {
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
}
var to = &struct {
*Config
Path string
KeepAlive, RunAtLoad bool
SessionCreate bool
}{
Config: s.Config,
Path: path,
KeepAlive: s.Option.bool(optionKeepAlive, optionKeepAliveDefault),
RunAtLoad: s.Option.bool(optionRunAtLoad, optionRunAtLoadDefault),
SessionCreate: s.Option.bool(optionSessionCreate, optionSessionCreateDefault),
}
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 {
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 *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 = `
Label{{html .Name}}
ProgramArguments
{{html .Path}}
{{range .Config.Arguments}}
{{html .}}
{{end}}
{{if .UserName}}UserName{{html .UserName}}{{end}}
{{if .ChRoot}}RootDirectory{{html .ChRoot}}{{end}}
{{if .WorkingDirectory}}WorkingDirectory{{html .WorkingDirectory}}{{end}}
SessionCreate<{{bool .SessionCreate}}/>
KeepAlive<{{bool .KeepAlive}}/>
RunAtLoad<{{bool .RunAtLoad}}/>
Disabled
`
service-1.0.0/service_go1.8.go 0000664 0000000 0000000 00000000320 13427336164 0016057 0 ustar 00root root 0000000 0000000 //+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.0.0/service_linux.go 0000664 0000000 0000000 00000003115 13427336164 0016367 0 ustar 00root root 0000000 0000000 // 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 (
"os"
"strings"
)
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: "unix-systemv",
detect: func() bool { return true },
interactive: func() bool {
is, _ := isInteractive()
return is
},
new: newSystemVService,
},
)
}
func isInteractive() (bool, error) {
// TODO: This is not true for user services.
return os.Getppid() != 1, 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.0.0/service_nosu_test.go 0000664 0000000 0000000 00000000535 13427336164 0017256 0 ustar 00root root 0000000 0000000 // 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.0.0/service_su_test.go 0000664 0000000 0000000 00000007153 13427336164 0016724 0 ustar 00root root 0000000 0000000 // 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.0.0/service_systemd_linux.go 0000664 0000000 0000000 00000012251 13427336164 0020140 0 ustar 00root root 0000000 0000000 // 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"
"strconv"
"strings"
"syscall"
"text/template"
)
func isSystemd() bool {
if _, err := os.Stat("/run/systemd/system"); err == nil {
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
}
// Systemd services should be supported, but are not currently.
var errNoUserServiceSystemd = errors.New("User services are not supported on systemd.")
func (s *systemd) configPath() (cp string, err error) {
if s.Option.bool(optionUserService, optionUserServiceDefault) {
err = errNoUserServiceSystemd
return
}
cp = "/etc/systemd/system/" + s.Config.Name + ".service"
return
}
func (s *systemd) getSystemdVersion() int64 {
_, out, err := 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))
} else {
return template.Must(template.New("").Funcs(tf).Parse(systemdScript))
}
}
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.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
HasOutputFileSupport bool
ReloadSignal string
PIDFile string
LogOutput bool
}{
s.Config,
path,
s.hasOutputFileSupport(),
s.Option.string(optionReloadSignal, ""),
s.Option.string(optionPIDFile, ""),
s.Option.bool(optionLogOutput, optionLogOutputDefault),
}
err = s.template().Execute(f, to)
if err != nil {
return err
}
err = run("systemctl", "enable", s.Name+".service")
if err != nil {
return err
}
return run("systemctl", "daemon-reload")
}
func (s *systemd) Uninstall() error {
err := run("systemctl", "disable", s.Name+".service")
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 := runWithOutput("systemctl", "is-active", s.Name)
if exitCode == 0 && err != nil {
return StatusUnknown, err
}
switch {
case strings.HasPrefix(out, "active"):
return StatusRunning, nil
case strings.HasPrefix(out, "inactive"):
return StatusStopped, nil
case strings.HasPrefix(out, "failed"):
return StatusUnknown, errors.New("service in failed state")
default:
return StatusUnknown, ErrNotInstalled
}
}
func (s *systemd) Start() error {
return run("systemctl", "start", s.Name+".service")
}
func (s *systemd) Stop() error {
return run("systemctl", "stop", s.Name+".service")
}
func (s *systemd) Restart() error {
return run("systemctl", "restart", s.Name+".service")
}
const systemdScript = `[Unit]
Description={{.Description}}
ConditionFileIsExecutable={{.Path|cmdEscape}}
[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:/var/log/{{.Name}}.out
StandardError=file:/var/log/{{.Name}}.err
{{- end}}
Restart=always
RestartSec=120
EnvironmentFile=-/etc/sysconfig/{{.Name}}
[Install]
WantedBy=multi-user.target
`
service-1.0.0/service_sysv_linux.go 0000664 0000000 0000000 00000013077 13427336164 0017463 0 ustar 00root root 0000000 0000000 // 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))
} else {
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
}{
s.Config,
path,
}
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="/var/log/$name.log"
stderr_log="/var/log/$name.err"
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name
get_pid() {
cat "$pid_file"
}
is_running() {
[ -f "$pid_file" ] && ps $(get_pid) > /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.0.0/service_test.go 0000664 0000000 0000000 00000001734 13427336164 0016214 0 ustar 00root root 0000000 0000000 // 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 (
"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)
}
}
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.0.0/service_unix.go 0000664 0000000 0000000 00000006462 13427336164 0016223 0 ustar 00root root 0000000 0000000 // Copyright 2015 Daniel Theophanes.
// Use of this source code is governed by a zlib-style
// license that can be found in the LICENSE file.
// +build linux darwin
package service
import (
"fmt"
"io"
"io/ioutil"
"log/syslog"
"os/exec"
"syscall"
)
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 {
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.0.0/service_upstart_linux.go 0000664 0000000 0000000 00000013355 13427336164 0020160 0 ustar 00root root 0000000 0000000 // 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"
"time"
)
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
}{
s.Config,
path,
s.hasKillStanza(),
s.hasSetUIDStanza(),
s.Option.bool(optionLogOutput, optionLogOutputDefault),
}
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 {
err := s.Stop()
if err != nil {
return err
}
time.Sleep(50 * time.Millisecond)
return s.Start()
}
// 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="/var/log/{{.Name}}.out"
stderr_log="/var/log/{{.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.0.0/service_windows.go 0000664 0000000 0000000 00000022536 13427336164 0016732 0 ustar 00root root 0000000 0000000 // 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"
"time"
"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"
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() {
var err error
interactive, err = svc.IsAnInteractiveSession()
if err != nil {
panic(err)
}
}
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, svc.Shutdown:
changes <- svc.Status{State: svc.StopPending}
if err := ws.i.Stop(ws); err != nil {
ws.setError(err)
return true, 2
}
break loop
default:
continue loop
}
}
return false, 0
}
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()
s, err := m.OpenService(ws.Name)
if err == nil {
s.Close()
return fmt.Errorf("service %s already exists", ws.Name)
}
s, err = m.CreateService(ws.Name, exepath, mgr.Config{
DisplayName: ws.DisplayName,
Description: ws.Description,
StartType: mgr.StartAutomatic,
ServiceStartName: ws.UserName,
Password: ws.Option.string("Password", ""),
Dependencies: ws.Dependencies,
}, ws.Arguments...)
if 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 := mgr.Connect()
if err != nil {
return StatusUnknown, err
}
defer m.Disconnect()
s, err := m.OpenService(ws.Name)
if err != nil {
if err.Error() == "The specified service does not exist as an installed service." {
return StatusUnknown, ErrNotInstalled
}
return StatusUnknown, err
}
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 := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.Name)
if err != nil {
return err
}
defer s.Close()
return s.Start()
}
func (ws *windowsService) Stop() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(ws.Name)
if err != nil {
return err
}
defer s.Close()
return ws.stopWait(s)
}
func (ws *windowsService) Restart() error {
m, err := mgr.Connect()
if err != nil {
return err
}
defer m.Disconnect()
s, err := m.OpenService(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.0.0/service_windows_test.go 0000664 0000000 0000000 00000000436 13427336164 0017764 0 ustar 00root root 0000000 0000000 // 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.0.0/servicetest_unix_test.go 0000664 0000000 0000000 00000001005 13427336164 0020146 0 ustar 00root root 0000000 0000000 // 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.0.0/servicetest_windows_test.go 0000664 0000000 0000000 00000001573 13427336164 0020667 0 ustar 00root root 0000000 0000000 // 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.0.0/version.go 0000664 0000000 0000000 00000002334 13427336164 0015177 0 ustar 00root root 0000000 0000000 package 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 seperated 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.0.0/version_test.go 0000664 0000000 0000000 00000006524 13427336164 0016243 0 ustar 00root root 0000000 0000000 package 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)
}
})
}
}