mirror of
https://github.com/kardianos/service.git
synced 2025-10-05 08:46:51 +08:00
212 lines
4.2 KiB
Go
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>
|
|
`
|