123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312 |
- // 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 (
- "bytes"
- "errors"
- "fmt"
- "os"
- "os/signal"
- "path/filepath"
- "regexp"
- "strconv"
- "strings"
- "syscall"
- "text/template"
- )
- func isSystemd() bool {
- if _, err := os.Stat("/run/systemd/system"); err == nil {
- return true
- }
- if _, err := os.Stat("/proc/1/comm"); err == nil {
- filerc, err := os.Open("/proc/1/comm")
- if err != nil {
- return false
- }
- defer filerc.Close()
- buf := new(bytes.Buffer)
- buf.ReadFrom(filerc)
- contents := buf.String()
- if strings.Trim(contents, " \r\n") == "systemd" {
- return true
- }
- }
- return false
- }
- type systemd struct {
- i Interface
- platform string
- *Config
- }
- func newSystemdService(i Interface, platform string, c *Config) (Service, error) {
- s := &systemd{
- i: i,
- platform: platform,
- Config: c,
- }
- return s, nil
- }
- func (s *systemd) String() string {
- if len(s.DisplayName) > 0 {
- return s.DisplayName
- }
- return s.Name
- }
- func (s *systemd) Platform() string {
- return s.platform
- }
- func (s *systemd) configPath() (cp string, err error) {
- if !s.isUserService() {
- cp = "/etc/systemd/system/" + s.Config.Name + ".service"
- return
- }
- homeDir, err := os.UserHomeDir()
- if err != nil {
- return
- }
- systemdUserDir := filepath.Join(homeDir, ".config/systemd/user")
- err = os.MkdirAll(systemdUserDir, os.ModePerm)
- if err != nil {
- return
- }
- cp = filepath.Join(systemdUserDir, s.Config.Name+".service")
- return
- }
- func (s *systemd) getSystemdVersion() int64 {
- _, out, err := runWithOutput("systemctl", "--version")
- if err != nil {
- return -1
- }
- re := regexp.MustCompile(`systemd ([0-9]+)`)
- matches := re.FindStringSubmatch(out)
- if len(matches) != 2 {
- return -1
- }
- v, err := strconv.ParseInt(matches[1], 10, 64)
- if err != nil {
- return -1
- }
- return v
- }
- func (s *systemd) hasOutputFileSupport() bool {
- defaultValue := true
- version := s.getSystemdVersion()
- if version == -1 {
- return defaultValue
- }
- if version < 236 {
- return false
- }
- return defaultValue
- }
- func (s *systemd) template() *template.Template {
- customScript := s.Option.string(optionSystemdScript, "")
- if customScript != "" {
- return template.Must(template.New("").Funcs(tf).Parse(customScript))
- } else {
- return template.Must(template.New("").Funcs(tf).Parse(systemdScript))
- }
- }
- func (s *systemd) isUserService() bool {
- return s.Option.bool(optionUserService, optionUserServiceDefault)
- }
- func (s *systemd) 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.OpenFile(confPath, os.O_WRONLY|os.O_CREATE, 0644)
- if err != nil {
- return err
- }
- defer f.Close()
- path, err := s.execPath()
- if err != nil {
- return err
- }
- var to = &struct {
- *Config
- Path string
- HasOutputFileSupport bool
- ReloadSignal string
- PIDFile string
- LimitNOFILE int
- Restart string
- SuccessExitStatus string
- LogOutput bool
- }{
- s.Config,
- path,
- s.hasOutputFileSupport(),
- s.Option.string(optionReloadSignal, ""),
- s.Option.string(optionPIDFile, ""),
- s.Option.int(optionLimitNOFILE, optionLimitNOFILEDefault),
- s.Option.string(optionRestart, "always"),
- s.Option.string(optionSuccessExitStatus, ""),
- s.Option.bool(optionLogOutput, optionLogOutputDefault),
- }
- err = s.template().Execute(f, to)
- if err != nil {
- return err
- }
- err = s.runAction("enable")
- if err != nil {
- return err
- }
- return s.run("daemon-reload")
- }
- func (s *systemd) Uninstall() error {
- err := s.runAction("disable")
- if err != nil {
- return err
- }
- cp, err := s.configPath()
- if err != nil {
- return err
- }
- if err := os.Remove(cp); err != nil {
- return err
- }
- return nil
- }
- func (s *systemd) Logger(errs chan<- error) (Logger, error) {
- if system.Interactive() {
- return ConsoleLogger, nil
- }
- return s.SystemLogger(errs)
- }
- func (s *systemd) SystemLogger(errs chan<- error) (Logger, error) {
- return newSysLogger(s.Name, errs)
- }
- func (s *systemd) 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 *systemd) Status() (Status, error) {
- exitCode, out, err := runWithOutput("systemctl", "is-active", s.Name)
- if exitCode == 0 && err != nil {
- return StatusUnknown, err
- }
- switch {
- case strings.HasPrefix(out, "active"):
- return StatusRunning, nil
- case strings.HasPrefix(out, "inactive"):
- // inactive can also mean its not installed, check unit files
- exitCode, out, err := runWithOutput("systemctl", "list-unit-files", "-t", "service", s.Name)
- if exitCode == 0 && err != nil {
- return StatusUnknown, err
- }
- if strings.Contains(out, s.Name) {
- // unit file exists, installed but not running
- return StatusStopped, nil
- }
- // no unit file
- return StatusUnknown, ErrNotInstalled
- case strings.HasPrefix(out, "activating"):
- return StatusRunning, nil
- case strings.HasPrefix(out, "failed"):
- return StatusUnknown, errors.New("service in failed state")
- default:
- return StatusUnknown, ErrNotInstalled
- }
- }
- func (s *systemd) Start() error {
- return s.runAction("start")
- }
- func (s *systemd) Stop() error {
- return s.runAction("stop")
- }
- func (s *systemd) Restart() error {
- return s.runAction("restart")
- }
- func (s *systemd) run(action string, args ...string) error {
- if s.isUserService() {
- return run("systemctl", append([]string{action, "--user"}, args...)...)
- }
- return run("systemctl", append([]string{action}, args...)...)
- }
- func (s *systemd) runAction(action string) error {
- return s.run(action, s.Name+".service")
- }
- const systemdScript = `[Unit]
- Description={{.Description}}
- ConditionFileIsExecutable={{.Path|cmdEscape}}
- {{range $i, $dep := .Dependencies}}
- {{$dep}} {{end}}
- [Service]
- StartLimitInterval=5
- StartLimitBurst=10
- ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
- {{if .ChRoot}}RootDirectory={{.ChRoot|cmd}}{{end}}
- {{if .WorkingDirectory}}WorkingDirectory={{.WorkingDirectory|cmdEscape}}{{end}}
- {{if .UserName}}User={{.UserName}}{{end}}
- {{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
- {{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
- {{if and .LogOutput .HasOutputFileSupport -}}
- StandardOutput=file:/var/log/{{.Name}}.out
- StandardError=file:/var/log/{{.Name}}.err
- {{- end}}
- {{if gt .LimitNOFILE -1 }}LimitNOFILE={{.LimitNOFILE}}{{end}}
- {{if .Restart}}Restart={{.Restart}}{{end}}
- {{if .SuccessExitStatus}}SuccessExitStatus={{.SuccessExitStatus}}{{end}}
- RestartSec=120
- EnvironmentFile=-/etc/sysconfig/{{.Name}}
- [Install]
- WantedBy=multi-user.target
- `
|