ready for more testing
This commit is contained in:
parent
870d149797
commit
20d460c70e
|
@ -9,12 +9,10 @@
|
||||||
{{- if .Interpreter }}
|
{{- if .Interpreter }}
|
||||||
<string>{{ .Interpreter }}</string>
|
<string>{{ .Interpreter }}</string>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
<string>{{ .Local }}/opt/{{ .Name }}/{{ .Exec }}</string>
|
<string>{{ .Exec }}</string>
|
||||||
{{- if .Argv }}
|
|
||||||
{{- range $arg := .Argv }}
|
{{- range $arg := .Argv }}
|
||||||
<string>{{ $arg }}</string>
|
<string>{{ $arg }}</string>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
|
||||||
</array>
|
</array>
|
||||||
{{- if .Envs }}
|
{{- if .Envs }}
|
||||||
<key>EnvironmentVariables</key>
|
<key>EnvironmentVariables</key>
|
||||||
|
@ -60,9 +58,11 @@
|
||||||
<dict/>
|
<dict/>
|
||||||
|
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
|
{{ if .Workdir -}}
|
||||||
<key>WorkingDirectory</key>
|
<key>WorkingDirectory</key>
|
||||||
<string>{{ .Local }}/opt/{{ .Name }}</string>
|
<string>{{ .Workdir }}</string>
|
||||||
|
|
||||||
|
{{ end -}}
|
||||||
<key>StandardErrorPath</key>
|
<key>StandardErrorPath</key>
|
||||||
<string>{{ .LogDir }}/{{ .Name }}.log</string>
|
<string>{{ .LogDir }}/{{ .Name }}.log</string>
|
||||||
<key>StandardOutPath</key>
|
<key>StandardOutPath</key>
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
# Pre-req
|
# Pre-req
|
||||||
# sudo mkdir -p {{ .Local }}/opt/{{ .Name }}/ {{ .Local }}/var/log/{{ .Name }}
|
# sudo mkdir -p {{ .Local }}/opt/{{ .Name }}/ {{ .Local }}/var/log/{{ .Name }}
|
||||||
{{ if not .Local -}}
|
{{ if .System -}}
|
||||||
{{- if and .User ( ne "root" .User ) -}}
|
{{- if and .User ( ne "root" .User ) -}}
|
||||||
# sudo adduser {{ .User }} --home /opt/{{ .Name }}
|
# sudo adduser {{ .User }} --home /opt/{{ .Name }}
|
||||||
# sudo chown -R {{ .User }}:{{ .Group }} /opt/{{ .Name }}/ /var/log/{{ .Name }}
|
# sudo chown -R {{ .User }}:{{ .Group }} /opt/{{ .Name }}/ /var/log/{{ .Name }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
# Post-install
|
# Post-install
|
||||||
# sudo systemctl {{ if .Local -}} --user {{ end -}} daemon-reload
|
# sudo systemctl {{ if not .System -}} --user {{ end -}} daemon-reload
|
||||||
# sudo systemctl {{ if .Local -}} --user {{ end -}} restart {{ .Name }}.service
|
# sudo systemctl {{ if not .System -}} --user {{ end -}} restart {{ .Name }}.service
|
||||||
# sudo journalctl {{ if .Local -}} --user {{ end -}} -xefu {{ .Name }}
|
# sudo journalctl {{ if not .System -}} --user {{ end -}} -xefu {{ .Name }}
|
||||||
|
|
||||||
[Unit]
|
[Unit]
|
||||||
Description={{ .Title }} - {{ .Desc }}
|
Description={{ .Title }} - {{ .Desc }}
|
||||||
Documentation={{ .URL }}
|
Documentation={{ .URL }}
|
||||||
{{ if not .Local -}}
|
{{ if .System -}}
|
||||||
After=network-online.target
|
After=network-online.target
|
||||||
Wants=network-online.target systemd-networkd-wait-online.service
|
Wants=network-online.target systemd-networkd-wait-online.service
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
@ -33,8 +33,10 @@ User={{ .User }}
|
||||||
Group={{ .Group }}
|
Group={{ .Group }}
|
||||||
|
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
WorkingDirectory={{ .Local }}/opt/{{ .Name }}
|
{{ if .Workdir -}}
|
||||||
ExecStart={{if .Interpreter }}{{ .Interpreter }} {{ end }}{{ .Local }}/opt/{{ .Name }}/{{ .Name }} {{ .Args }}
|
WorkingDirectory={{ .Workdir }}
|
||||||
|
{{ end -}}
|
||||||
|
ExecStart={{if .Interpreter }}{{ .Interpreter }} {{ end }}{{ .Exec }} {{- range $arg := .Argv }}{{ $arg }} {{- end }}
|
||||||
ExecReload=/bin/kill -USR1 $MAINPID
|
ExecReload=/bin/kill -USR1 $MAINPID
|
||||||
|
|
||||||
{{if .Production -}}
|
{{if .Production -}}
|
||||||
|
@ -80,7 +82,7 @@ NoNewPrivileges=true
|
||||||
|
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
[Install]
|
[Install]
|
||||||
{{ if not .Local -}}
|
{{ if .System -}}
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
WantedBy=default.target
|
WantedBy=default.target
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package installer
|
package installer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -58,7 +59,7 @@ type Config struct {
|
||||||
Interpreter string `json:"interpreter"` // i.e. node, python
|
Interpreter string `json:"interpreter"` // i.e. node, python
|
||||||
Exec string `json:"exec"`
|
Exec string `json:"exec"`
|
||||||
Argv []string `json:"argv"`
|
Argv []string `json:"argv"`
|
||||||
Args string `json:"-"`
|
Workdir string `json:"workdir"`
|
||||||
Envs map[string]string `json:"envs"`
|
Envs map[string]string `json:"envs"`
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Group string `json:"group"`
|
Group string `json:"group"`
|
||||||
|
@ -78,22 +79,16 @@ func Install(c *Config) error {
|
||||||
if "" == c.Exec {
|
if "" == c.Exec {
|
||||||
c.Exec = c.Name
|
c.Exec = c.Name
|
||||||
}
|
}
|
||||||
c.Args = strings.Join(c.Argv, " ")
|
|
||||||
|
|
||||||
// TODO handle non-system installs
|
|
||||||
// * ~/.local/opt/watchdog/watchdog
|
|
||||||
// * ~/.local/share/watchdog/var/log/
|
|
||||||
// * ~/.config/watchdog/watchdog.json
|
|
||||||
if !c.System {
|
if !c.System {
|
||||||
home, err := os.UserHomeDir()
|
home, err := os.UserHomeDir()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unrecoverable Error: %s", err)
|
||||||
|
os.Exit(4)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
c.home = home
|
|
||||||
c.Local = filepath.Join(c.home, ".local")
|
|
||||||
c.LogDir = filepath.Join(c.home, ".local", "share", c.Name, "var", "log")
|
|
||||||
} else {
|
} else {
|
||||||
c.LogDir = "/var/log/" + c.Name
|
c.home = home
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := install(c)
|
err := install(c)
|
||||||
|
@ -119,10 +114,15 @@ func IsPrivileged() bool {
|
||||||
func WhereIs(exec string) (string, error) {
|
func WhereIs(exec string) (string, error) {
|
||||||
exec = filepath.ToSlash(exec)
|
exec = filepath.ToSlash(exec)
|
||||||
if strings.Contains(exec, "/") {
|
if strings.Contains(exec, "/") {
|
||||||
// filepath.Clean(exec)
|
// it's a path (so we don't allow filenames with slashes)
|
||||||
// it's a path (don't allow filenames with slashes)
|
stat, err := os.Stat(exec)
|
||||||
// TODO stat
|
if nil != err {
|
||||||
return exec, nil
|
return "", err
|
||||||
|
}
|
||||||
|
if stat.IsDir() {
|
||||||
|
return "", fmt.Errorf("'%s' is not an executable file", exec)
|
||||||
|
}
|
||||||
|
return filepath.Abs(exec)
|
||||||
}
|
}
|
||||||
return whereIs(exec)
|
return whereIs(exec)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func install(c *Config) error {
|
||||||
|
|
||||||
// Write the file out
|
// Write the file out
|
||||||
// TODO rdns
|
// TODO rdns
|
||||||
plistName := c.Name + ".plist"
|
plistName := c.ReverseDNS + ".plist"
|
||||||
plistPath := filepath.Join(plistDir, plistName)
|
plistPath := filepath.Join(plistDir, plistName)
|
||||||
if err := ioutil.WriteFile(plistPath, rw.Bytes(), 0644); err != nil {
|
if err := ioutil.WriteFile(plistPath, rw.Bytes(), 0644); err != nil {
|
||||||
fmt.Println("Use 'sudo' to install as a privileged system service.")
|
fmt.Println("Use 'sudo' to install as a privileged system service.")
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -34,17 +34,19 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conf.Argv = args
|
conf.Argv = args
|
||||||
conf.Args = strings.Join(conf.Argv, " ")
|
|
||||||
|
|
||||||
|
force := false
|
||||||
forUser := false
|
forUser := false
|
||||||
forSystem := false
|
forSystem := false
|
||||||
flag.StringVar(&conf.Title, "title", "", "a human-friendly name for the service")
|
flag.StringVar(&conf.Title, "title", "", "a human-friendly name for the service")
|
||||||
flag.StringVar(&conf.Desc, "desc", "", "a human-friendly description of the service (ex: Foo App)")
|
flag.StringVar(&conf.Desc, "desc", "", "a human-friendly description of the service (ex: Foo App)")
|
||||||
flag.StringVar(&conf.Name, "name", "", "a computer-friendly name for the service (ex: foo-app)")
|
flag.StringVar(&conf.Name, "name", "", "a computer-friendly name for the service (ex: foo-app)")
|
||||||
flag.StringVar(&conf.URL, "url", "", "the documentation on home page of the service")
|
flag.StringVar(&conf.URL, "url", "", "the documentation on home page of the service")
|
||||||
|
//flag.StringVar(&conf.Workdir, "workdir", "", "the directory in which the service should be started")
|
||||||
flag.StringVar(&conf.ReverseDNS, "rdns", "", "a plist-friendly Reverse DNS name for launchctl (ex: com.example.foo-app)")
|
flag.StringVar(&conf.ReverseDNS, "rdns", "", "a plist-friendly Reverse DNS name for launchctl (ex: com.example.foo-app)")
|
||||||
flag.BoolVar(&forSystem, "system", false, "attempt to install system service as an unprivileged/unelevated user")
|
flag.BoolVar(&forSystem, "system", false, "attempt to install system service as an unprivileged/unelevated user")
|
||||||
flag.BoolVar(&forUser, "user", false, "install user space / user mode service even when admin/root/sudo/elevated")
|
flag.BoolVar(&forUser, "user", false, "install user space / user mode service even when admin/root/sudo/elevated")
|
||||||
|
flag.BoolVar(&force, "force", false, "if the interpreter or executable doesn't exist, or things don't make sense, try anyway")
|
||||||
flag.StringVar(&conf.User, "username", "", "run the service as this user")
|
flag.StringVar(&conf.User, "username", "", "run the service as this user")
|
||||||
flag.StringVar(&conf.Group, "groupname", "", "run the service as this group")
|
flag.StringVar(&conf.Group, "groupname", "", "run the service as this group")
|
||||||
flag.BoolVar(&conf.PrivilegedPorts, "cap-net-bind", false, "this service should have access to privileged ports")
|
flag.BoolVar(&conf.PrivilegedPorts, "cap-net-bind", false, "this service should have access to privileged ports")
|
||||||
|
@ -54,6 +56,7 @@ func main() {
|
||||||
if forUser && forSystem {
|
if forUser && forSystem {
|
||||||
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
if forUser {
|
if forUser {
|
||||||
conf.System = false
|
conf.System = false
|
||||||
|
@ -66,15 +69,20 @@ func main() {
|
||||||
n := len(args)
|
n := len(args)
|
||||||
if 0 == n {
|
if 0 == n {
|
||||||
fmt.Println("Usage: serviceman install ./foo-app -- --foo-arg")
|
fmt.Println("Usage: serviceman install ./foo-app -- --foo-arg")
|
||||||
os.Exit(1)
|
os.Exit(2)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
execpath, err := installer.WhereIs(args[0])
|
execpath, err := installer.WhereIs(args[0])
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.", args[0])
|
fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", args[0])
|
||||||
os.Exit(1)
|
if !force {
|
||||||
|
os.Exit(3)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
args[0] = execpath
|
args[0] = execpath
|
||||||
|
}
|
||||||
conf.Exec = args[0]
|
conf.Exec = args[0]
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
|
|
||||||
|
@ -93,7 +101,54 @@ func main() {
|
||||||
conf.Title = conf.Name
|
conf.Title = conf.Name
|
||||||
}
|
}
|
||||||
if "" == conf.ReverseDNS {
|
if "" == conf.ReverseDNS {
|
||||||
conf.ReverseDNS = "com.example." + conf.Name
|
// technically should be something more like "com.example." + conf.Name,
|
||||||
|
// but whatever
|
||||||
|
conf.ReverseDNS = conf.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conf.System {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unrecoverable Error: %s", err)
|
||||||
|
os.Exit(4)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conf.Local = filepath.Join(home, ".local")
|
||||||
|
conf.LogDir = filepath.Join(home, ".local", "share", conf.Name, "var", "log")
|
||||||
|
} else {
|
||||||
|
conf.LogDir = "/var/log/" + conf.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check to see if Exec exists
|
||||||
|
// /whatever => must exist exactly
|
||||||
|
// ./whatever => must exist in current or WorkDir(TODO)
|
||||||
|
// whatever => may also exist in {{ .Local }}/opt/{{ .Name }}/{{ .Exec }}
|
||||||
|
_, err = os.Stat(conf.Exec)
|
||||||
|
if nil != err {
|
||||||
|
bad := true
|
||||||
|
if !strings.Contains(filepath.ToSlash(conf.Exec), "/") {
|
||||||
|
optpath := filepath.Join(conf.Local, "/opt", conf.Name, conf.Exec)
|
||||||
|
_, err := os.Stat(optpath)
|
||||||
|
if nil == err {
|
||||||
|
bad = false
|
||||||
|
fmt.Fprintf(os.Stderr, "Using '%s' for '%s'\n", optpath, conf.Exec)
|
||||||
|
conf.Exec = optpath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bad {
|
||||||
|
// TODO look for it in WorkDir?
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", conf.Exec)
|
||||||
|
if !force {
|
||||||
|
os.Exit(5)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
execpath, err := filepath.Abs(conf.Exec)
|
||||||
|
if nil == err {
|
||||||
|
conf.Exec = execpath
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Using '%s' anyway.\n", conf.Exec)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\n%#v\n\n", conf)
|
fmt.Printf("\n%#v\n\n", conf)
|
||||||
|
|
Loading…
Reference in New Issue