123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269 |
- // 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"
- "regexp"
- "strings"
- "syscall"
- "text/template"
- )
- func isUpstart() bool {
- if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
- return true
- }
- if _, err := os.Stat("/sbin/initctl"); err == nil {
- if _, out, err := runWithOutput("/sbin/initctl", "--version"); err == nil {
- if strings.Contains(out, "initctl (upstart") {
- return true
- }
- }
- }
- return false
- }
- type upstart struct {
- i Interface
- platform string
- *Config
- }
- func newUpstartService(i Interface, platform string, c *Config) (Service, error) {
- s := &upstart{
- i: i,
- platform: platform,
- Config: c,
- }
- return s, nil
- }
- func (s *upstart) String() string {
- if len(s.DisplayName) > 0 {
- return s.DisplayName
- }
- return s.Name
- }
- func (s *upstart) Platform() string {
- return s.platform
- }
- // 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
- version := s.getUpstartVersion()
- if version == nil {
- return defaultValue
- }
- maxVersion := []int{0, 6, 5}
- if matches, err := versionAtMost(version, maxVersion); err != nil || matches {
- return false
- }
- return defaultValue
- }
- func (s *upstart) hasSetUIDStanza() bool {
- defaultValue := true
- version := s.getUpstartVersion()
- if version == nil {
- return defaultValue
- }
- maxVersion := []int{1, 4, 0}
- if matches, err := versionAtMost(version, maxVersion); err != nil || matches {
- return false
- }
- return defaultValue
- }
- func (s *upstart) getUpstartVersion() []int {
- _, out, err := runWithOutput("/sbin/initctl", "--version")
- if err != nil {
- return nil
- }
- re := regexp.MustCompile(`initctl \(upstart (\d+.\d+.\d+)\)`)
- matches := re.FindStringSubmatch(out)
- if len(matches) != 2 {
- return nil
- }
- return parseVersion(matches[1])
- }
- func (s *upstart) template() *template.Template {
- customScript := s.Option.string(optionUpstartScript, "")
- if customScript != "" {
- return template.Must(template.New("").Funcs(tf).Parse(customScript))
- } else {
- 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
- HasSetUIDStanza bool
- LogOutput bool
- }{
- s.Config,
- path,
- s.hasKillStanza(),
- s.hasSetUIDStanza(),
- s.Option.bool(optionLogOutput, optionLogOutputDefault),
- }
- 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, syscall.SIGTERM, os.Interrupt)
- <-sigChan
- })()
- return s.i.Stop(s)
- }
- func (s *upstart) Status() (Status, error) {
- exitCode, out, err := runWithOutput("initctl", "status", s.Name)
- if exitCode == 0 && err != nil {
- return StatusUnknown, err
- }
- switch {
- case strings.HasPrefix(out, fmt.Sprintf("%s start/running", s.Name)):
- return StatusRunning, nil
- case strings.HasPrefix(out, fmt.Sprintf("%s stop/waiting", s.Name)):
- return StatusStopped, nil
- default:
- return StatusUnknown, ErrNotInstalled
- }
- }
- 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 {
- return run("initctl", "restart", s.Name)
- }
- // 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 and .UserName .HasSetUIDStanza}}setuid {{.UserName}}{{end}}
- respawn
- respawn limit 10 5
- umask 022
- console none
- pre-start script
- test -x {{.Path}} || { stop; exit 0; }
- end script
- # Start
- script
- {{if .LogOutput}}
- stdout_log="/var/log/{{.Name}}.out"
- stderr_log="/var/log/{{.Name}}.err"
- {{end}}
-
- if [ -f "/etc/sysconfig/{{.Name}}" ]; then
- set -a
- source /etc/sysconfig/{{.Name}}
- set +a
- fi
- exec {{if and .UserName (not .HasSetUIDStanza)}}sudo -E -u {{.UserName}} {{end}}{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}{{if .LogOutput}} >> $stdout_log 2>> $stderr_log{{end}}
- end script
- `
|