lxg 3 năm trước cách đây
commit
e6a4fba2f9
13 tập tin đã thay đổi với 1980 bổ sung0 xóa
  1. 20 0
      LICENSE
  2. 14 0
      README.md
  3. 21 0
      appveyor.yml
  4. 48 0
      console.go
  5. 15 0
      linux-test-su.sh
  6. 371 0
      service.go
  7. 240 0
      service_darwin.go
  8. 75 0
      service_linux.go
  9. 175 0
      service_systemd_linux.go
  10. 252 0
      service_sysv_linux.go
  11. 88 0
      service_unix.go
  12. 226 0
      service_upstart_linux.go
  13. 435 0
      service_windows.go

+ 20 - 0
LICENSE

@@ -0,0 +1,20 @@
+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.

+ 14 - 0
README.md

@@ -0,0 +1,14 @@
+# 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.

+ 21 - 0
appveyor.yml

@@ -0,0 +1,21 @@
+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 ./...

+ 48 - 0
console.go

@@ -0,0 +1,48 @@
+// 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
+}

+ 15 - 0
linux-test-su.sh

@@ -0,0 +1,15 @@
+#!/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 ./...

+ 371 - 0
service.go

@@ -0,0 +1,371 @@
+// 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/penggy/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/penggy/service"
+
+import (
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+)
+
+const (
+	optionKeepAlive            = "KeepAlive"
+	optionKeepAliveDefault     = true
+	optionRunAtLoad            = "RunAtLoad"
+	optionRunAtLoadDefault     = false
+	optionUserService          = "UserService"
+	optionUserServiceDefault   = false
+	optionSessionCreate        = "SessionCreate"
+	optionSessionCreateDefault = false
+
+	optionRunWait      = "RunWait"
+	optionReloadSignal = "ReloadSignal"
+	optionPIDFile      = "PIDFile"
+)
+
+// 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
+	//    - KeepAlive     bool (true)
+	//    - RunAtLoad     bool (false)
+	//    - UserService   bool (false) - Install as a current user service.
+	//    - SessionCreate bool (false) - Create a full user session.
+	//  * POSIX
+	//    - 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.
+	Option KeyValue
+}
+
+var (
+	system         System
+	systemRegistry []System
+)
+
+var (
+	// ErrNameFieldRequired is returned when Conifg.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.")
+)
+
+// 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
+}
+
+// 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
+}
+
+func (c *Config) execPath() (string, error) {
+	if len(c.Executable) != 0 {
+		return filepath.Abs(c.Executable)
+	}
+	return os.Executable()
+}

+ 240 - 0
service_darwin.go

@@ -0,0 +1,240 @@
+// 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"
+	"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) 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) 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),
+	}
+
+	functions := template.FuncMap{
+		"bool": func(v bool) string {
+			if v {
+				return "true"
+			}
+			return "false"
+		},
+	}
+	t := template.Must(template.New("launchdConfig").Funcs(functions).Parse(launchdConfig))
+	return t.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) 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 = `<?xml version='1.0' encoding='UTF-8'?>
+<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
+"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
+<plist version='1.0'>
+<dict>
+<key>Label</key><string>{{html .Name}}</string>
+<key>ProgramArguments</key>
+<array>
+        <string>{{html .Path}}</string>
+{{range .Config.Arguments}}
+        <string>{{html .}}</string>
+{{end}}
+</array>
+{{if .UserName}}<key>UserName</key><string>{{html .UserName}}</string>{{end}}
+{{if .ChRoot}}<key>RootDirectory</key><string>{{html .ChRoot}}</string>{{end}}
+{{if .WorkingDirectory}}<key>WorkingDirectory</key><string>{{html .WorkingDirectory}}</string>{{end}}
+<key>SessionCreate</key><{{bool .SessionCreate}}/>
+<key>KeepAlive</key><{{bool .KeepAlive}}/>
+<key>RunAtLoad</key><{{bool .RunAtLoad}}/>
+<key>Disabled</key><false/>
+</dict>
+</plist>
+`

+ 75 - 0
service_linux.go

@@ -0,0 +1,75 @@
+// 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, 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, 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)
+	},
+}

+ 175 - 0
service_systemd_linux.go

@@ -0,0 +1,175 @@
+// 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"
+	"syscall"
+	"text/template"
+)
+
+func isSystemd() bool {
+	if _, err := os.Stat("/run/systemd/system"); err == nil {
+		return true
+	}
+	return false
+}
+
+type systemd struct {
+	i Interface
+	*Config
+}
+
+func newSystemdService(i Interface, c *Config) (Service, error) {
+	s := &systemd{
+		i:      i,
+		Config: c,
+	}
+
+	return s, nil
+}
+
+func (s *systemd) String() string {
+	if len(s.DisplayName) > 0 {
+		return s.DisplayName
+	}
+	return s.Name
+}
+
+// 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) template() *template.Template {
+	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
+		ReloadSignal string
+		PIDFile      string
+	}{
+		s.Config,
+		path,
+		s.Option.string(optionReloadSignal, ""),
+		s.Option.string(optionPIDFile, ""),
+	}
+
+	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) 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}}
+Restart=always
+RestartSec=120
+EnvironmentFile=-/etc/sysconfig/{{.Name}}
+
+[Install]
+WantedBy=multi-user.target
+`

+ 252 - 0
service_sysv_linux.go

@@ -0,0 +1,252 @@
+// 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"
+	"syscall"
+	"text/template"
+	"time"
+)
+
+type sysv struct {
+	i Interface
+	*Config
+}
+
+func newSystemVService(i Interface, c *Config) (Service, error) {
+	s := &sysv{
+		i:      i,
+		Config: c,
+	}
+
+	return s, nil
+}
+
+func (s *sysv) String() string {
+	if len(s.DisplayName) > 0 {
+		return s.DisplayName
+	}
+	return s.Name
+}
+
+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 {
+	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) 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
+`

+ 88 - 0
service_unix.go

@@ -0,0 +1,88 @@
+// 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/ioutil"
+	"log/syslog"
+	"os/exec"
+)
+
+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 {
+	cmd := exec.Command(command, arguments...)
+
+	// Connect pipe to read Stderr
+	stderr, err := cmd.StderrPipe()
+
+	if err != nil {
+		// Failed to connect pipe
+		return 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 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 fmt.Errorf("%q failed with stderr: %s", command, slurp)
+		}
+	}
+
+	if err := cmd.Wait(); err != nil {
+		// Command didn't exit with a zero exit status.
+		return fmt.Errorf("%q failed: %v", command, err)
+	}
+
+	return nil
+}

+ 226 - 0
service_upstart_linux.go

@@ -0,0 +1,226 @@
+// 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/exec"
+	"os/signal"
+	"regexp"
+	"strconv"
+	"strings"
+	"text/template"
+	"time"
+)
+
+func isUpstart() bool {
+	if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
+		return true
+	}
+	if _, err := os.Stat("/sbin/init"); err == nil {
+		if out, err := exec.Command("/sbin/init", "--version").Output(); err == nil {
+			if strings.Contains(string(out), "init (upstart") {
+				return true
+			}
+		}
+	}
+	return false
+}
+
+type upstart struct {
+	i Interface
+	*Config
+}
+
+func newUpstartService(i Interface, c *Config) (Service, error) {
+	s := &upstart{
+		i:      i,
+		Config: c,
+	}
+
+	return s, nil
+}
+
+func (s *upstart) String() string {
+	if len(s.DisplayName) > 0 {
+		return s.DisplayName
+	}
+	return s.Name
+}
+
+// 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
+
+	out, err := exec.Command("/sbin/init", "--version").Output()
+	if err != nil {
+		return defaultValue
+	}
+
+	re := regexp.MustCompile(`init \(upstart (\d+.\d+.\d+)\)`)
+	matches := re.FindStringSubmatch(string(out))
+	if len(matches) != 2 {
+		return defaultValue
+	}
+
+	version := make([]int, 3)
+	for idx, vStr := range strings.Split(matches[1], ".") {
+		version[idx], err = strconv.Atoi(vStr)
+		if err != nil {
+			return defaultValue
+		}
+	}
+
+	maxVersion := []int{0, 6, 5}
+	if versionAtMost(version, maxVersion) {
+		return false
+	}
+
+	return defaultValue
+}
+
+func versionAtMost(version, max []int) bool {
+	for idx, m := range max {
+		v := version[idx]
+		if v > m {
+			return false
+		}
+	}
+	return true
+}
+
+func (s *upstart) template() *template.Template {
+	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
+	}{
+		s.Config,
+		path,
+		s.hasKillStanza(),
+	}
+
+	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, os.Interrupt, os.Kill)
+		<-sigChan
+	})()
+
+	return s.i.Stop(s)
+}
+
+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 .UserName}}setuid {{.UserName}}{{end}}
+
+respawn
+respawn limit 10 5
+umask 022
+
+console none
+
+pre-start script
+    test -x {{.Path}} || { stop; exit 0; }
+end script
+
+# Start
+exec {{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}
+`

+ 435 - 0
service_windows.go

@@ -0,0 +1,435 @@
+// 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"
+	"sync"
+	"time"
+	"unsafe"
+
+	"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"
+
+//penggy add for config restart on crash
+
+const (
+	SC_ACTION_NONE        = 0
+	SC_ACTION_RESTART     = 1
+	SC_ACTION_REBOOT      = 2
+	SC_ACTION_RUN_COMMAND = 3
+
+	SERVICE_CONFIG_FAILURE_ACTIONS = 2
+)
+
+type SERVICE_FAILURE_ACTIONS struct {
+	ResetPeriod  uint32
+	RebootMsg    *uint16
+	Command      *uint16
+	ActionsCount uint32
+	Actions      uintptr
+}
+
+type SC_ACTION struct {
+	Type  uint32
+	Delay uint32
+}
+
+func setServiceFailureActions(handle windows.Handle) error {
+	t := []SC_ACTION{
+		{Type: SC_ACTION_RESTART, Delay: uint32(1000)},
+		{Type: SC_ACTION_RESTART, Delay: uint32(10000)},
+		{Type: SC_ACTION_RESTART, Delay: uint32(60000)},
+	}
+	m := SERVICE_FAILURE_ACTIONS{ResetPeriod: uint32(60), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))}
+
+	return windows.ChangeServiceConfig2(handle, SERVICE_CONFIG_FAILURE_ACTIONS, (*byte)(unsafe.Pointer(&m)))
+}
+
+//-- penggy add for config restart on crash
+
+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) 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 {
+		s.Delete()
+		return fmt.Errorf("InstallAsEventCreate() failed: %s", err)
+	}
+
+	// penggy add for config restart on crash
+	err = setServiceFailureActions(s.Handle)
+	if err != nil {
+		panic(err)
+	}
+	//-- penggy add for config restart on crash
+	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, os.Kill)
+
+	<-sigChan
+
+	return ws.i.Stop(ws)
+}
+
+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
+}