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 `