Commit 0cf1c2cb authored by Kamil Trzciński's avatar Kamil Trzciński 🔴

Fixed #34: Update kardianos service

parent e7f93152
......@@ -19,7 +19,7 @@
},
{
"ImportPath": "github.com/ayufan/golang-kardianos-service",
"Rev": "5fef5ff823c52f9c3535008bb99f0e3707921d2c"
"Rev": "d114a4d2061938adacad90dc698e9214e3ff69e9"
},
{
"ImportPath": "github.com/codegangsta/cli",
......
# service (BETA)
# 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.
......@@ -8,7 +8,6 @@ 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.
## TODO
## BUGS
* Dependencies field is not implemented for Linux systems and Launchd.
* OS X when running as a UserService Interactive will not be accurate.
* Determine if UserService should remain in main configuration.
* Hook up Dependencies field for Linux systems and Launchd.
......@@ -45,7 +45,6 @@ func (p *program) run() error {
return nil
}
}
return nil
}
func (p *program) Stop(s service.Service) error {
// Any work in Stop should be quick, usually a few seconds at most.
......
// 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
// 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/osext"
"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 := osext.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)
}
}
{
"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"
}
\ No newline at end of file
......@@ -69,30 +69,42 @@ import (
"github.com/kardianos/osext"
)
const (
optionKeepAlive = "KeepAlive"
optionKeepAliveDefault = true
optionRunAtLoad = "RunAtLoad"
optionRunAtLoadDefault = false
optionUserService = "UserService"
optionUserServiceDefault = false
optionSessionCreate = "SessionCreate"
optionSessionCreateDefault = true
)
// 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.
Dependencies []string // Array of service dependencies.
UserName string // Run as username.
Arguments []string // Run with arguments.
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
// Install as a current user service. Only supported on OS X.
UserService bool
// System specific options.
// * OS X
// - KeepAlive bool (true)
// - RunAtLoad bool (false)
// - KeepAlive bool (true)
// - RunAtLoad bool (false)
// - UserService bool (false) // Install as a current user service.
// - SessionCreate bool (true)
Option KeyValue
}
......
......@@ -9,6 +9,7 @@ import (
"os"
"os/signal"
"os/user"
"path/filepath"
"syscall"
"text/template"
"time"
......@@ -17,7 +18,7 @@ import (
const maxPathSize = 32 * 1024
const version = "Darwin Launchd"
const version = "darwin-launchd"
type darwinSystem struct{}
......@@ -34,6 +35,8 @@ func (darwinSystem) New(i Interface, c *Config) (Service, error) {
s := &darwinLaunchdService{
i: i,
Config: c,
userService: c.Option.bool(optionUserService, optionUserServiceDefault),
}
return s, nil
......@@ -61,6 +64,8 @@ func isInteractive() (bool, error) {
type darwinLaunchdService struct {
i Interface
*Config
userService bool
}
func (s *darwinLaunchdService) String() string {
......@@ -78,9 +83,6 @@ func (s *darwinLaunchdService) getHomeDir() (string, error) {
// alternate methods
homeDir := os.Getenv("HOME") // *nix
if homeDir == "" { // Windows
homeDir = os.Getenv("USERPROFILE")
}
if homeDir == "" {
return "", errors.New("User home directory not found.")
}
......@@ -88,7 +90,7 @@ func (s *darwinLaunchdService) getHomeDir() (string, error) {
}
func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
if s.UserService {
if s.userService {
homeDir, err := s.getHomeDir()
if err != nil {
return "", err
......@@ -108,6 +110,14 @@ func (s *darwinLaunchdService) Install() error {
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
......@@ -125,11 +135,11 @@ func (s *darwinLaunchdService) Install() error {
KeepAlive, RunAtLoad, SessionCreate bool
}{
Config: s.Config,
Path: path,
KeepAlive: s.Option.bool("KeepAlive", true),
RunAtLoad: s.Option.bool("RunAtLoad", false),
SessionCreate: s.Option.bool("SessionCreate", true),
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{
......
......@@ -5,25 +5,10 @@
package service
import (
"fmt"
"os"
"strings"
)
type linuxSystem struct {
interactive bool
selectedName string
selectedNew func(i Interface, c *Config) (Service, error)
}
func (ls linuxSystem) String() string {
return fmt.Sprintf("Linux %s", ls.selectedName)
}
func (ls linuxSystem) Interactive() bool {
return ls.interactive
}
type linuxSystemService struct {
name string
detect func() bool
......@@ -46,7 +31,7 @@ func (sc linuxSystemService) New(i Interface, c *Config) (Service, error) {
func init() {
ChooseSystem(linuxSystemService{
name: "systemd",
name: "linux-systemd",
detect: isSystemd,
interactive: func() bool {
is, _ := isInteractive()
......@@ -55,7 +40,7 @@ func init() {
new: newSystemdService,
},
linuxSystemService{
name: "Upstart",
name: "linux-upstart",
detect: isUpstart,
interactive: func() bool {
is, _ := isInteractive()
......@@ -64,7 +49,7 @@ func init() {
new: newUpstartService,
},
linuxSystemService{
name: "System-V",
name: "unix-systemv",
detect: func() bool { return true },
interactive: func() bool {
is, _ := isInteractive()
......
......@@ -46,7 +46,7 @@ func (s *systemd) String() string {
var errNoUserServiceSystemd = errors.New("User services are not supported on systemd.")
func (s *systemd) configPath() (cp string, err error) {
if s.Config.UserService {
if s.Option.bool(optionUserService, optionUserServiceDefault) {
err = errNoUserServiceSystemd
return
}
......
......@@ -55,7 +55,7 @@ func (s *sysv) String() string {
var errNoUserServiceSystemV = errors.New("User services are not supported on SystemV.")
func (s *sysv) configPath() (cp string, err error) {
if s.Config.UserService {
if s.Option.bool(optionUserService, optionUserServiceDefault) {
err = errNoUserServiceSystemV
return
}
......@@ -219,7 +219,7 @@ case "$1" in
echo "Already started"
else
echo "Starting $name"
{{if .WorkingDirectory}}cd {{.WorkingDirectory|cmd}}{{end}} \
{{if .WorkingDirectory}}cd '{{.WorkingDirectory}}'{{end}}
$cmd &>> $log_file &
echo $! > "$pid_file"
if ! is_running; then
......
......@@ -47,7 +47,7 @@ func (s *upstart) String() string {
var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.")
func (s *upstart) configPath() (cp string, err error) {
if s.Config.UserService {
if s.Option.bool(optionUserService, optionUserServiceDefault) {
err = errNoUserServiceUpstart
return
}
......@@ -149,6 +149,8 @@ const upstartScript = `# {{.Description}}
{{if .DisplayName}}description "{{.DisplayName}}"{{end}}
kill signal INT
{{if .ChRoot}}chroot {{.ChRoot}}{{end}}
{{if .WorkingDirectory}}chdir {{.WorkingDirectory}}{{end}}
start on filesystem or runlevel [2345]
stop on runlevel [!2345]
......
......@@ -5,20 +5,18 @@
package service
import (
"bytes"
"fmt"
"os"
"os/signal"
"strings"
"sync"
"time"
"code.google.com/p/winsvc/eventlog"
"code.google.com/p/winsvc/mgr"
"code.google.com/p/winsvc/svc"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/eventlog"
"golang.org/x/sys/windows/svc/mgr"
)
const version = "Windows Service"
const version = "windows-service"
type windowsService struct {
i Interface
......@@ -66,42 +64,65 @@ func (l WindowsLogger) send(err error) error {
}
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...)))
}
func (l WindowsLogger) NError(eventId uint32, v ...interface{}) error {
return l.send(l.ev.Error(eventId, fmt.Sprint(v...)))
// 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...)))
}
func (l WindowsLogger) NWarning(eventId uint32, v ...interface{}) error {
return l.send(l.ev.Warning(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...)))
}
func (l WindowsLogger) NInfo(eventId uint32, v ...interface{}) error {
return l.send(l.ev.Info(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...)))
}
func (l WindowsLogger) NErrorf(eventId uint32, format string, a ...interface{}) error {
return l.send(l.ev.Error(eventId, fmt.Sprintf(format, a...)))
// 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...)))
}
func (l WindowsLogger) NWarningf(eventId uint32, format string, a ...interface{}) error {
return l.send(l.ev.Warning(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...)))
}
func (l WindowsLogger) NInfof(eventId uint32, format string, a ...interface{}) error {
return l.send(l.ev.Info(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
......@@ -169,20 +190,6 @@ func (ws *windowsService) Install() error {
return err
}
binPath := &bytes.Buffer{}
// Quote exe path in case it contains a string.
binPath.WriteRune('"')
binPath.WriteString(exepath)
binPath.WriteRune('"')
// Arguments are encoded with the binary path to service.
// Enclose arguments in quotes. Escape quotes with a backslash.
for _, arg := range ws.Arguments {
binPath.WriteRune(' ')
binPath.WriteString(`"`)
binPath.WriteString(strings.Replace(arg, `"`, `\"`, -1))
binPath.WriteString(`"`)
}
m, err := mgr.Connect()
if err != nil {
return err
......@@ -193,14 +200,14 @@ func (ws *windowsService) Install() error {
s.Close()
return fmt.Errorf("service %s already exists", ws.Name)
}
s, err = m.CreateService(ws.Name, binPath.String(), mgr.Config{
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
}
......@@ -278,7 +285,7 @@ func (ws *windowsService) Start() error {
return err
}
defer s.Close()
return s.Start([]string{})
return s.Start()
}
func (ws *windowsService) Stop() error {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment