// 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 = ` 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}} KeepAlive<{{bool .KeepAlive}}/> RunAtLoad<{{bool .RunAtLoad}}/> Disabled `