github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/zutil/daemon/daemon_linux.go (about)

     1  package daemon
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"strings"
     9  	"text/template"
    10  )
    11  
    12  type (
    13  	linuxSystemService struct {
    14  		detect      func() bool
    15  		interactive func() bool
    16  		new         func(i Iface, c *Config) (ServiceIface, error)
    17  		name        string
    18  	}
    19  	systemd struct {
    20  		i Iface
    21  		*Config
    22  	}
    23  )
    24  
    25  const (
    26  	optionReloadSignal = "ReloadSignal"
    27  	optionPIDFile      = "PIDFile"
    28  )
    29  
    30  var errNoUserServiceSystemd = errors.New("user services are not supported on systemd")
    31  
    32  func init() {
    33  	chooseSystem(linuxSystemService{
    34  		name:   "linux-systemd",
    35  		detect: isSystemd,
    36  		interactive: func() bool {
    37  			is, _ := isInteractive()
    38  			return is
    39  		},
    40  		new: newSystemdService,
    41  	})
    42  }
    43  
    44  func (sc linuxSystemService) String() string {
    45  	return sc.name
    46  }
    47  
    48  func (sc linuxSystemService) Detect() bool {
    49  	return sc.detect()
    50  }
    51  
    52  func (sc linuxSystemService) Interactive() bool {
    53  	return sc.interactive()
    54  }
    55  
    56  func (sc linuxSystemService) New(i Iface, c *Config) (s ServiceIface, err error) {
    57  	s, err = sc.new(i, c)
    58  	if err == nil {
    59  		err = isSudo()
    60  	}
    61  	return
    62  }
    63  
    64  func isInteractive() (bool, error) {
    65  	return os.Getppid() != 1, nil
    66  }
    67  
    68  var tf = map[string]interface{}{
    69  	"cmd": func(s string) string {
    70  		return `"` + strings.Replace(s, `"`, `\"`, -1) + `"`
    71  	},
    72  	"cmdEscape": func(s string) string {
    73  		return strings.Replace(s, " ", `\x20`, -1)
    74  	},
    75  }
    76  
    77  func isSystemd() bool {
    78  	if _, err := os.Stat("/run/systemd/system"); err == nil {
    79  		return true
    80  	}
    81  	return false
    82  }
    83  
    84  func newSystemdService(i Iface, c *Config) (ServiceIface, error) {
    85  	if c.Context == nil {
    86  		c.Context = context.Background()
    87  	}
    88  
    89  	s := &systemd{
    90  		i:      i,
    91  		Config: c,
    92  	}
    93  
    94  	return s, nil
    95  }
    96  
    97  func (s *systemd) String() string {
    98  	if len(s.DisplayName) > 0 {
    99  		return s.DisplayName
   100  	}
   101  	return s.Name
   102  }
   103  
   104  func (s *systemd) configPath() (cp string, err error) {
   105  	userService := optionUserServiceDefault
   106  	if u, ok := s.Options[optionUserService]; ok {
   107  		userService = u.(bool)
   108  	}
   109  	if userService {
   110  		err = errNoUserServiceSystemd
   111  		return
   112  	}
   113  	cp = "/etc/systemd/system/" + s.Config.Name + ".service"
   114  	return
   115  }
   116  func (s *systemd) template() *template.Template {
   117  	return template.Must(template.New("").Funcs(tf).Parse(systemdScript))
   118  }
   119  
   120  func (s *systemd) Install() error {
   121  	confPath, err := s.configPath()
   122  	if err != nil {
   123  		return err
   124  	}
   125  	_, err = os.Stat(confPath)
   126  	if err == nil {
   127  		return fmt.Errorf("init already exists: %s", confPath)
   128  	}
   129  
   130  	f, err := os.Create(confPath)
   131  	if err != nil {
   132  		return err
   133  	}
   134  	defer f.Close()
   135  	reloadSignal := ""
   136  	if v, ok := s.Options[optionReloadSignal]; ok {
   137  		reloadSignal, _ = v.(string)
   138  	}
   139  	pidFile := ""
   140  	if v, ok := s.Options[optionPIDFile]; ok {
   141  		pidFile, _ = v.(string)
   142  	}
   143  	path := s.execPath()
   144  	var to = &struct {
   145  		*Config
   146  		Path         string
   147  		ReloadSignal string
   148  		PIDFile      string
   149  	}{
   150  		s.Config,
   151  		path,
   152  		reloadSignal,
   153  		pidFile,
   154  	}
   155  
   156  	err = s.template().Execute(f, to)
   157  	if err != nil {
   158  		return err
   159  	}
   160  
   161  	err = run("systemctl", "enable", s.Name+".service")
   162  	if err != nil {
   163  		return err
   164  	}
   165  	return run("systemctl", "daemon-reload")
   166  }
   167  
   168  func (s *systemd) Uninstall() error {
   169  	_ = run("systemctl", "stop", s.Name+".service")
   170  	err := run("systemctl", "disable", s.Name+".service")
   171  	if err != nil {
   172  		return err
   173  	}
   174  	cp, err := s.configPath()
   175  	if err != nil {
   176  		return err
   177  	}
   178  	if err := os.Remove(cp); err != nil {
   179  		return err
   180  	}
   181  	return nil
   182  }
   183  
   184  func (s *systemd) Run() (err error) {
   185  	err = s.i.Start(s)
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	runWait := func() {
   191  		select {
   192  		case <-SingleKillSignal():
   193  		case <-s.Config.Context.Done():
   194  		}
   195  	}
   196  	if v, ok := s.Options[optionRunWait]; ok {
   197  		runWait, _ = v.(func())
   198  	}
   199  
   200  	runWait()
   201  
   202  	return s.i.Stop(s)
   203  }
   204  
   205  func (s *systemd) Start() error {
   206  	if os.Getuid() == 0 {
   207  		return run("systemctl", "start", s.Name+".service")
   208  	} else {
   209  		return run("sudo", "-n", "systemctl", "start", s.Name+".service")
   210  	}
   211  }
   212  
   213  func (s *systemd) Stop() error {
   214  	if os.Getuid() == 0 {
   215  		return run("systemctl", "stop", s.Name+".service")
   216  	} else {
   217  		return run("sudo", "-n", "systemctl", "stop", s.Name+".service")
   218  	}
   219  }
   220  
   221  func (s *systemd) Restart() error {
   222  	if os.Getuid() == 0 {
   223  		return run("systemctl", "restart", s.Name+".service")
   224  	} else {
   225  		return run("sudo", "-n", "systemctl", "restart", s.Name+".service")
   226  	}
   227  }
   228  
   229  func (s *systemd) Status() string {
   230  	var res string
   231  	if os.Getuid() == 0 {
   232  		res, _ = runGrep("running", "systemctl", "status", s.Name+".service")
   233  	} else {
   234  		res, _ = runGrep("running", "sudo", "-n", "systemctl", "status", s.Name+".service")
   235  	}
   236  	if res != "" {
   237  		return "Running"
   238  	}
   239  	return "Stop"
   240  }
   241  
   242  const systemdScript = `[Unit]
   243  Description={{.Description}}
   244  ConditionFileIsExecutable={{.Path|cmdEscape}}
   245  
   246  [Service]
   247  StartLimitInterval=5
   248  StartLimitBurst=10
   249  ExecStart={{.Path|cmdEscape}}{{range .Arguments}} {{.|cmd}}{{end}}
   250  {{if .RootDir}}RootDirectory={{.RootDir|cmd}}{{end}}
   251  {{if .WorkingDir}}WorkingDirectory={{.WorkingDir|cmdEscape}}{{end}}
   252  {{if .UserName}}User={{.UserName}}{{end}}
   253  {{if .ReloadSignal}}ExecReload=/bin/kill -{{.ReloadSignal}} "$MAINPID"{{end}}
   254  {{if .PIDFile}}PIDFile={{.PIDFile|cmd}}{{end}}
   255  Restart=always
   256  RestartSec=120ms
   257  EnvironmentFile=-/etc/sysconfig/{{.Name}}
   258  KillMode=process
   259  
   260  [Install]
   261  WantedBy=multi-user.target
   262  `