Files
service/service_darwin.go
2015-03-14 08:14:25 -07:00

212 lines
4.2 KiB
Go

// 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
package service
import (
"fmt"
"os"
"os/signal"
"os/user"
"syscall"
"text/template"
"time"
"github.com/kardianos/osext"
)
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,
}
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
}
func (s *darwinLaunchdService) String() string {
if len(s.DisplayName) > 0 {
return s.DisplayName
}
return s.Name
}
func (s *darwinLaunchdService) getServiceFilePath() (string, error) {
if s.UserService {
u, err := user.Current()
if err != nil {
return "", err
}
return u.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)
}
f, err := os.Create(confPath)
if err != nil {
return err
}
defer f.Close()
path, err := osext.Executable()
if err != nil {
return err
}
var to = &struct {
*Config
Path string
KeepAlive, RunAtLoad bool
}{
Config: s.Config,
Path: path,
KeepAlive: s.Option.bool("KeepAlive", true),
RunAtLoad: s.Option.bool("RunAtLoad", false),
}
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
}
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>KeepAlive</key><{{bool .KeepAlive}}/>
<key>RunAtLoad</key><{{bool .RunAtLoad}}/>
<key>Disabled</key><false/>
</dict>
</plist>
`