Browse Source

Ubuntu: start on install

pull/4/head
AJ ONeal 5 years ago
parent
commit
04e162b20f
  1. 23
      README.md
  2. 10
      manager/dist/etc/systemd/system/_name_.service.tmpl
  3. 21
      manager/install_darwin.go
  4. 130
      manager/install_linux.go
  5. 40
      manager/start.go
  6. 6
      manager/static/ab0x.go
  7. 7
      serviceman.go
  8. 11
      serviceman_darwin.go
  9. 26
      serviceman_linux.go
  10. 11
      serviceman_windows.go

23
README.md

@ -172,6 +172,7 @@ move serviceman.exe %userprofile%\bin\serviceman.exe
**All Others**
```
chmod a+x ./serviceman
sudo mv ./serviceman /usr/local/bin/
```
@ -364,9 +365,9 @@ Where `conf.json` looks something like
```json
{
"title": "Demo",
"exec": "/Users/me/go-demo/demo",
"argv": ["--foo", "bar", "--baz", "qux"]
"title": "Demo",
"exec": "/Users/me/go-demo/demo",
"argv": ["--foo", "bar", "--baz", "qux"]
}
```
@ -380,10 +381,10 @@ names and relative paths.
```json
{
"title": "Demo",
"interpreter": "node.exe",
"exec": "./bin/demo.js",
"argv": ["--foo", "bar", "--baz", "qux"]
"title": "Demo",
"interpreter": "node.exe",
"exec": "./bin/demo.js",
"argv": ["--foo", "bar", "--baz", "qux"]
}
```
@ -391,12 +392,12 @@ That's equivalent to this:
```json
{
"title": "Demo",
"title": "Demo",
"name": "demo",
"name": "demo",
"exec": "node.exe",
"argv": ["./bin/demo.js", "--foo", "bar", "--baz", "qux"]
"exec": "node.exe",
"argv": ["./bin/demo.js", "--foo", "bar", "--baz", "qux"]
}
```

10
manager/dist/etc/systemd/system/_name_.service.tmpl

@ -12,18 +12,20 @@
# sudo journalctl {{ if not .System -}} --user {{ end -}} -xefu {{ .Name }}
[Unit]
Description={{ .Title }} - {{ .Desc }}
Description={{ .Title }} {{ if .Desc }}- {{ .Desc }}{{ end }}
{{ if .URL -}}
Documentation={{ .URL }}
{{ end -}}
{{ if .System -}}
After=network-online.target
Wants=network-online.target systemd-networkd-wait-online.service
{{- end }}
{{ end -}}
[Service]
# Restart on crash (bad signal), but not on 'clean' failure (error exit code)
# Allow up to 3 restarts within 10 seconds
# (it's unlikely that a user or properly-running script will do this)
Restart=on-abnormal
Restart=always
StartLimitInterval=10
StartLimitBurst=3
@ -36,7 +38,7 @@ Group={{ .Group }}
{{ if .Workdir -}}
WorkingDirectory={{ .Workdir }}
{{ end -}}
ExecStart={{if .Interpreter }}{{ .Interpreter }} {{ end }}{{ .Exec }} {{- range $arg := .Argv }}{{ $arg }} {{- end }}
ExecStart={{if .Interpreter }}{{ .Interpreter }} {{ end }}{{ .Exec }}{{ range $arg := .Argv }} {{ $arg }}{{ end }}
ExecReload=/bin/kill -USR1 $MAINPID
{{if .Production -}}

21
manager/install_darwin.go

@ -58,6 +58,8 @@ func start(system bool, home string, name string) error {
},
}
cmds = adjustPrivs(system, cmds)
fmt.Println()
for i := range cmds {
exe := cmds[i]
@ -112,8 +114,7 @@ func install(c *service.Service) error {
plistName := c.ReverseDNS + ".plist"
plistPath := filepath.Join(plistDir, plistName)
if err := ioutil.WriteFile(plistPath, rw.Bytes(), 0644); err != nil {
return fmt.Errorf("ioutil.WriteFile error: %v", err)
return fmt.Errorf("Error writing %s: %v", plistPath, err)
}
// TODO --no-start
@ -127,20 +128,4 @@ func install(c *service.Service) error {
fmt.Printf("Added and started '%s' as a launchctl service.\n", c.Name)
return nil
/*
fmt.Printf("Installed. To start '%s' run the following:\n", c.Name)
// TODO template config file
if "" != c.Home {
plistPath = strings.Replace(plistPath, c.Home, "~", 1)
}
sudo := ""
if c.System {
sudo = "sudo "
}
fmt.Printf("\t%slaunchctl load -w %s\n", sudo, plistPath)
return nil
*/
}

130
manager/install_linux.go

@ -13,16 +13,100 @@ import (
)
var (
srvLen int
srvExt = ".service"
srvSysPath = "/etc/systemd/system"
srvUserPath = ".local/share/systemd/user"
srvLen int
srvExt = ".service"
srvSysPath = "/etc/systemd/system"
// Not sure which of these it's supposed to be...
// * ~/.local/share/systemd/user/watchdog.service
// * ~/.config/systemd/user/watchdog.service
// https://wiki.archlinux.org/index.php/Systemd/User
// This seems to work on Ubuntu
srvUserPath = ".config/systemd/user"
)
func init() {
srvLen = len(srvExt)
}
func start(system bool, home string, name string) error {
sys, user, err := getMatchingSrvs(home, name)
if nil != err {
return err
}
var service string
if system {
service, err = getOneSysSrv(sys, user, name)
if nil != err {
return err
}
service = filepath.Join(srvSysPath, service)
} else {
service, err = getOneUserSrv(home, sys, user, name)
if nil != err {
return err
}
service = filepath.Join(home, srvUserPath, service)
}
var cmds []Runnable
if system {
cmds = []Runnable{
Runnable{
Exec: "systemctl",
Args: []string{"daemon-reload"},
Must: false,
},
Runnable{
Exec: "systemctl",
Args: []string{"stop", name + ".service"},
Must: false,
},
Runnable{
Exec: "systemctl",
Args: []string{"start", name + ".service"},
Badwords: []string{"not found", "failed"},
Must: true,
},
}
} else {
cmds = []Runnable{
Runnable{
Exec: "systemctl",
Args: []string{"--user", "daemon-reload"},
Must: false,
},
Runnable{
Exec: "systemctl",
Args: []string{"stop", "--user", name + ".service"},
Must: false,
},
Runnable{
Exec: "systemctl",
Args: []string{"start", "--user", name + ".service"},
Badwords: []string{"not found", "failed"},
Must: true,
},
}
}
cmds = adjustPrivs(system, cmds)
fmt.Println()
for i := range cmds {
exe := cmds[i]
fmt.Println(exe.String())
err := exe.Run()
if nil != err {
return err
}
}
fmt.Println()
return nil
}
func install(c *service.Service) error {
// Linux-specific config options
if c.System {
@ -33,17 +117,12 @@ func install(c *service.Service) error {
if "" == c.Group {
c.Group = c.User
}
serviceDir := srvSysPath
// Check paths first
serviceName := c.Name + ".service"
serviceDir := srvSysPath
if !c.System {
// Not sure which of these it's supposed to be...
// * ~/.local/share/systemd/user/watchdog.service
// * ~/.config/systemd/user/watchdog.service
// https://wiki.archlinux.org/index.php/Systemd/User
serviceDir = filepath.Join(c.Home, srvUserPath)
err := os.MkdirAll(filepath.Dir(serviceDir), 0755)
err := os.MkdirAll(serviceDir, 0755)
if nil != err {
return err
}
@ -67,22 +146,27 @@ func install(c *service.Service) error {
}
// Write the file out
serviceName := c.Name + ".service"
servicePath := filepath.Join(serviceDir, serviceName)
if err := ioutil.WriteFile(servicePath, rw.Bytes(), 0644); err != nil {
return fmt.Errorf("ioutil.WriteFile error: %v", err)
return fmt.Errorf("Error writing %s: %v", servicePath, err)
}
// TODO template this as well?
userspace := ""
sudo := "sudo "
if !c.System {
userspace = "--user "
sudo = ""
// TODO --no-start
err = start(c.System, c.Home, c.Name)
if nil != err {
sudo := ""
// --user-unit rather than --user --unit for older systemd
unit := "--user-unit"
if c.System {
sudo = "sudo "
unit = "--unit"
}
fmt.Printf("If things don't go well you should be able to get additional logging from journalctl:\n")
fmt.Printf("\t%sjournalctl -xe %s %s.service\n", sudo, unit, c.Name)
return err
}
fmt.Printf("System service installed as '%s'.\n", servicePath)
fmt.Printf("Run the following to start '%s':\n", c.Name)
fmt.Printf("\t" + sudo + "systemctl " + userspace + "daemon-reload\n")
fmt.Printf("\t"+sudo+"systemctl "+userspace+"restart %s.service\n", c.Name)
fmt.Printf("\t"+sudo+"journalctl "+userspace+"-xefu %s\n", c.Name)
fmt.Printf("Added and started '%s' as a systemd service.\n", c.Name)
return nil
}

40
manager/start.go

@ -45,34 +45,33 @@ func (x Runnable) Run() error {
}
}
if nil != err {
return fmt.Errorf("Failed to run %s %s\n%s\n", x.Exec, strings.Join(x.Args, " "), str)
var comment string
if len(x.Keywords) > 0 {
comment += "# output must match all of:\n"
comment += "# \t" + strings.Join(x.Keywords, "\n#\t") + "\n"
}
if len(x.Badwords) > 0 {
comment += "# output must not match any of:\n"
comment += "# \t" + strings.Join(x.Badwords, "\n#\t") + "\n"
}
return fmt.Errorf("Failed to run %s %s\n%s\n%s\n", x.Exec, strings.Join(x.Args, " "), str, comment)
}
return nil
}
func (x Runnable) String() string {
var comment string
var must = "true"
if x.Must {
must = "exit"
if len(x.Keywords) > 0 {
comment += "# output must match all of:\n"
comment += "\t" + strings.Join(x.Keywords, "#\t \n") + "\n"
}
if len(x.Badwords) > 0 {
comment += "# output must not match any of:\n"
comment += "\t" + strings.Join(x.Keywords, "#\t \n") + "\n"
}
}
return strings.TrimSpace(fmt.Sprintf(
"%s %s || %s\n%s",
"%s %s || %s\n",
x.Exec,
strings.Join(x.Args, " "),
must,
comment,
))
}
@ -203,3 +202,20 @@ func getOneUserSrv(home string, sys []string, user []string, name string) (strin
return service, nil
}
func adjustPrivs(system bool, cmds []Runnable) []Runnable {
if !system || isPrivileged() {
return cmds
}
sudos := cmds
cmds = []Runnable{}
for i := range sudos {
exe := sudos[i]
exe.Args = append([]string{exe.Exec}, exe.Args...)
exe.Exec = "sudo"
cmds = append(cmds, exe)
}
return cmds
}

6
manager/static/ab0x.go

File diff suppressed because one or more lines are too long

7
serviceman.go

@ -126,15 +126,16 @@ func add() {
conf.Normalize(force)
//fmt.Printf("\n%#v\n\n", conf)
if conf.System && !manager.IsPrivileged() {
fmt.Fprintf(os.Stderr, "Warning: You may need to use 'sudo' to add %q as a privileged system service.\n", conf.Name)
}
err = manager.Install(conf)
if nil != err {
fmt.Fprintf(os.Stderr, "%s\n", err)
fmt.Fprintf(os.Stderr, "Use 'sudo' to add service as a privileged system service.\n")
fmt.Fprintf(os.Stderr, "Use '--user' to add service as an user service.\n")
}
fmt.Printf("If all went well the logs should have been created at:\n\t%s\n", conf.Logdir)
printLogMessage(conf)
fmt.Println()
}

11
serviceman_darwin.go

@ -0,0 +1,11 @@
package main
import (
"fmt"
"git.rootprojects.org/root/go-serviceman/service"
)
func printLogMessage(conf *service.Service) {
fmt.Printf("If all went well the logs should have been created at:\n\t%s\n", conf.Logdir)
}

26
serviceman_linux.go

@ -0,0 +1,26 @@
package main
import (
"fmt"
"git.rootprojects.org/root/go-serviceman/manager"
"git.rootprojects.org/root/go-serviceman/service"
)
func printLogMessage(conf *service.Service) {
sudo := ""
unit := "--unit"
if conf.System {
if !manager.IsPrivileged() {
sudo = "sudo"
}
} else {
unit = "--user-unit"
}
fmt.Println("If all went well you should be able to see some goodies in the logs:")
fmt.Printf("\t%sjournalctl -xe %s %s.service\n", sudo, unit, conf.Name)
if !conf.System {
fmt.Println("\nIf that's not the case, see https://unix.stackexchange.com/a/486566/45554.")
fmt.Println("(you may need to run `systemctl restart systemd-journald`)")
}
}

11
serviceman_windows.go

@ -0,0 +1,11 @@
package main
import (
"fmt"
"git.rootprojects.org/root/go-serviceman/service"
)
func printLogMessage(conf *service.Service) {
fmt.Printf("If all went well the logs should have been created at:\n\t%s\n", conf.Logdir)
}
Loading…
Cancel
Save