service_upstart_linux.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. // Copyright 2015 Daniel Theophanes.
  2. // Use of this source code is governed by a zlib-style
  3. // license that can be found in the LICENSE file.
  4. package service
  5. import (
  6. "errors"
  7. "fmt"
  8. "os"
  9. "os/signal"
  10. "regexp"
  11. "strings"
  12. "syscall"
  13. "text/template"
  14. )
  15. func isUpstart() bool {
  16. if _, err := os.Stat("/sbin/upstart-udev-bridge"); err == nil {
  17. return true
  18. }
  19. if _, err := os.Stat("/sbin/initctl"); err == nil {
  20. if _, out, err := runWithOutput("/sbin/initctl", "--version"); err == nil {
  21. if strings.Contains(out, "initctl (upstart") {
  22. return true
  23. }
  24. }
  25. }
  26. return false
  27. }
  28. type upstart struct {
  29. i Interface
  30. platform string
  31. *Config
  32. }
  33. func newUpstartService(i Interface, platform string, c *Config) (Service, error) {
  34. s := &upstart{
  35. i: i,
  36. platform: platform,
  37. Config: c,
  38. }
  39. return s, nil
  40. }
  41. func (s *upstart) String() string {
  42. if len(s.DisplayName) > 0 {
  43. return s.DisplayName
  44. }
  45. return s.Name
  46. }
  47. func (s *upstart) Platform() string {
  48. return s.platform
  49. }
  50. // Upstart has some support for user services in graphical sessions.
  51. // Due to the mix of actual support for user services over versions, just don't bother.
  52. // Upstart will be replaced by systemd in most cases anyway.
  53. var errNoUserServiceUpstart = errors.New("User services are not supported on Upstart.")
  54. func (s *upstart) configPath() (cp string, err error) {
  55. if s.Option.bool(optionUserService, optionUserServiceDefault) {
  56. err = errNoUserServiceUpstart
  57. return
  58. }
  59. cp = "/etc/init/" + s.Config.Name + ".conf"
  60. return
  61. }
  62. func (s *upstart) hasKillStanza() bool {
  63. defaultValue := true
  64. version := s.getUpstartVersion()
  65. if version == nil {
  66. return defaultValue
  67. }
  68. maxVersion := []int{0, 6, 5}
  69. if matches, err := versionAtMost(version, maxVersion); err != nil || matches {
  70. return false
  71. }
  72. return defaultValue
  73. }
  74. func (s *upstart) hasSetUIDStanza() bool {
  75. defaultValue := true
  76. version := s.getUpstartVersion()
  77. if version == nil {
  78. return defaultValue
  79. }
  80. maxVersion := []int{1, 4, 0}
  81. if matches, err := versionAtMost(version, maxVersion); err != nil || matches {
  82. return false
  83. }
  84. return defaultValue
  85. }
  86. func (s *upstart) getUpstartVersion() []int {
  87. _, out, err := runWithOutput("/sbin/initctl", "--version")
  88. if err != nil {
  89. return nil
  90. }
  91. re := regexp.MustCompile(`initctl \(upstart (\d+.\d+.\d+)\)`)
  92. matches := re.FindStringSubmatch(out)
  93. if len(matches) != 2 {
  94. return nil
  95. }
  96. return parseVersion(matches[1])
  97. }
  98. func (s *upstart) template() *template.Template {
  99. customScript := s.Option.string(optionUpstartScript, "")
  100. if customScript != "" {
  101. return template.Must(template.New("").Funcs(tf).Parse(customScript))
  102. } else {
  103. return template.Must(template.New("").Funcs(tf).Parse(upstartScript))
  104. }
  105. }
  106. func (s *upstart) Install() error {
  107. confPath, err := s.configPath()
  108. if err != nil {
  109. return err
  110. }
  111. _, err = os.Stat(confPath)
  112. if err == nil {
  113. return fmt.Errorf("Init already exists: %s", confPath)
  114. }
  115. f, err := os.Create(confPath)
  116. if err != nil {
  117. return err
  118. }
  119. defer f.Close()
  120. path, err := s.execPath()
  121. if err != nil {
  122. return err
  123. }
  124. var to = &struct {
  125. *Config
  126. Path string
  127. HasKillStanza bool
  128. HasSetUIDStanza bool
  129. LogOutput bool
  130. }{
  131. s.Config,
  132. path,
  133. s.hasKillStanza(),
  134. s.hasSetUIDStanza(),
  135. s.Option.bool(optionLogOutput, optionLogOutputDefault),
  136. }
  137. return s.template().Execute(f, to)
  138. }
  139. func (s *upstart) Uninstall() error {
  140. cp, err := s.configPath()
  141. if err != nil {
  142. return err
  143. }
  144. if err := os.Remove(cp); err != nil {
  145. return err
  146. }
  147. return nil
  148. }
  149. func (s *upstart) Logger(errs chan<- error) (Logger, error) {
  150. if system.Interactive() {
  151. return ConsoleLogger, nil
  152. }
  153. return s.SystemLogger(errs)
  154. }
  155. func (s *upstart) SystemLogger(errs chan<- error) (Logger, error) {
  156. return newSysLogger(s.Name, errs)
  157. }
  158. func (s *upstart) Run() (err error) {
  159. err = s.i.Start(s)
  160. if err != nil {
  161. return err
  162. }
  163. s.Option.funcSingle(optionRunWait, func() {
  164. var sigChan = make(chan os.Signal, 3)
  165. signal.Notify(sigChan, syscall.SIGTERM, os.Interrupt)
  166. <-sigChan
  167. })()
  168. return s.i.Stop(s)
  169. }
  170. func (s *upstart) Status() (Status, error) {
  171. exitCode, out, err := runWithOutput("initctl", "status", s.Name)
  172. if exitCode == 0 && err != nil {
  173. return StatusUnknown, err
  174. }
  175. switch {
  176. case strings.HasPrefix(out, fmt.Sprintf("%s start/running", s.Name)):
  177. return StatusRunning, nil
  178. case strings.HasPrefix(out, fmt.Sprintf("%s stop/waiting", s.Name)):
  179. return StatusStopped, nil
  180. default:
  181. return StatusUnknown, ErrNotInstalled
  182. }
  183. }
  184. func (s *upstart) Start() error {
  185. return run("initctl", "start", s.Name)
  186. }
  187. func (s *upstart) Stop() error {
  188. return run("initctl", "stop", s.Name)
  189. }
  190. func (s *upstart) Restart() error {
  191. return run("initctl", "restart", s.Name)
  192. }
  193. // The upstart script should stop with an INT or the Go runtime will terminate
  194. // the program before the Stop handler can run.
  195. const upstartScript = `# {{.Description}}
  196. {{if .DisplayName}}description "{{.DisplayName}}"{{end}}
  197. {{if .HasKillStanza}}kill signal INT{{end}}
  198. {{if .ChRoot}}chroot {{.ChRoot}}{{end}}
  199. {{if .WorkingDirectory}}chdir {{.WorkingDirectory}}{{end}}
  200. start on filesystem or runlevel [2345]
  201. stop on runlevel [!2345]
  202. {{if and .UserName .HasSetUIDStanza}}setuid {{.UserName}}{{end}}
  203. respawn
  204. respawn limit 10 5
  205. umask 022
  206. console none
  207. pre-start script
  208. test -x {{.Path}} || { stop; exit 0; }
  209. end script
  210. # Start
  211. script
  212. {{if .LogOutput}}
  213. stdout_log="/var/log/{{.Name}}.out"
  214. stderr_log="/var/log/{{.Name}}.err"
  215. {{end}}
  216. if [ -f "/etc/sysconfig/{{.Name}}" ]; then
  217. set -a
  218. source /etc/sysconfig/{{.Name}}
  219. set +a
  220. fi
  221. exec {{if and .UserName (not .HasSetUIDStanza)}}sudo -E -u {{.UserName}} {{end}}{{.Path}}{{range .Arguments}} {{.|cmd}}{{end}}{{if .LogOutput}} >> $stdout_log 2>> $stderr_log{{end}}
  222. end script
  223. `