MacOS: start on install

This commit is contained in:
AJ ONeal 2019-07-05 12:48:58 -06:00
parent 00749b3465
commit 2b7148c3ae
5 changed files with 319 additions and 16 deletions

View File

@ -6,13 +6,72 @@ import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"text/template"
"git.rootprojects.org/root/go-serviceman/manager/static"
"git.rootprojects.org/root/go-serviceman/service"
)
const (
srvExt = ".plist"
srvSysPath = "/Library/LaunchDaemons"
srvUserPath = "Library/LaunchAgents"
)
var srvLen int
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)
}
cmds := []Runnable{
Runnable{
Exec: "launchctl",
Args: []string{"unload", "-w", service},
Must: false,
},
Runnable{
Exec: "launchctl",
Args: []string{"load", "-w", service},
Must: true,
},
}
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 {
// Darwin-specific config options
if c.PrivilegedPorts {
@ -20,9 +79,9 @@ func install(c *service.Service) error {
return fmt.Errorf("You must use root-owned LaunchDaemons (not user-owned LaunchAgents) to use priveleged ports on OS X")
}
}
plistDir := "/Library/LaunchDaemons/"
plistDir := srvSysPath
if !c.System {
plistDir = filepath.Join(c.Home, "Library/LaunchAgents")
plistDir = filepath.Join(c.Home, srvUserPath)
}
// Check paths first
@ -56,16 +115,32 @@ func install(c *service.Service) error {
return fmt.Errorf("ioutil.WriteFile error: %v", err)
}
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)
// TODO --no-start
err = start(c.System, c.Home, c.ReverseDNS)
if nil != err {
fmt.Printf("If things don't go well you should be able to get additional logging from launchctl:\n")
fmt.Printf("\tsudo launchctl log level debug\n")
fmt.Printf("\ttail -f /var/log/system.log\n")
return err
}
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
*/
}

View File

@ -12,6 +12,17 @@ import (
"git.rootprojects.org/root/go-serviceman/service"
)
var (
srvLen int
srvExt = ".service"
srvSysPath = "/etc/systemd/system"
srvUserPath = ".local/share/systemd/user"
)
func init() {
srvLen = len(srvExt)
}
func install(c *service.Service) error {
// Linux-specific config options
if c.System {
@ -22,7 +33,7 @@ func install(c *service.Service) error {
if "" == c.Group {
c.Group = c.User
}
serviceDir := "/etc/systemd/system/"
serviceDir := srvSysPath
// Check paths first
serviceName := c.Name + ".service"
@ -31,7 +42,7 @@ func install(c *service.Service) error {
// * ~/.local/share/systemd/user/watchdog.service
// * ~/.config/systemd/user/watchdog.service
// https://wiki.archlinux.org/index.php/Systemd/User
serviceDir = filepath.Join(c.Home, ".local/share/systemd/user")
serviceDir = filepath.Join(c.Home, srvUserPath)
err := os.MkdirAll(filepath.Dir(serviceDir), 0755)
if nil != err {
return err

View File

@ -14,6 +14,17 @@ import (
"golang.org/x/sys/windows/registry"
)
var (
srvLen int
srvExt = ".json"
srvSysPath = "/opt/serviceman/etc"
srvUserPath = ".local/opt/serviceman/etc"
)
func init() {
srvLen = len(srvExt)
}
// TODO nab some goodness from https://github.com/takama/daemon
// TODO system service requires elevated privileges

205
manager/start.go Normal file
View File

@ -0,0 +1,205 @@
package manager
import (
"fmt"
"io/ioutil"
"os/exec"
"path/filepath"
"strings"
)
// Runnable defines a command to run, along with its arguments,
// and whether or not failing to exit successfully matters.
// It also defines whether certains words must exist (or not exist)
// in its output, apart from existing successfully, to determine
// whether or not it was actually successful.
type Runnable struct {
Exec string
Args []string
Must bool
Keywords []string
Badwords []string
}
func (x Runnable) Run() error {
cmd := exec.Command(x.Exec, x.Args...)
out, err := cmd.CombinedOutput()
if !x.Must {
return nil
}
good := true
str := string(out)
for j := range x.Keywords {
if !strings.Contains(str, x.Keywords[j]) {
good = false
break
}
}
if good && 0 != len(x.Badwords) {
for j := range x.Badwords {
if "" != x.Badwords[j] && !strings.Contains(str, x.Badwords[j]) {
good = false
break
}
}
}
if nil != err {
return fmt.Errorf("Failed to run %s %s\n%s\n", x.Exec, strings.Join(x.Args, " "), str)
}
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",
x.Exec,
strings.Join(x.Args, " "),
must,
comment,
))
}
func getSrvs(dir string) ([]string, error) {
plists := []string{}
infos, err := ioutil.ReadDir(dir)
if nil != err {
return nil, err
}
for i := range infos {
x := infos[i]
fname := strings.ToLower(x.Name())
if strings.HasSuffix(fname, srvExt) {
plists = append(plists, x.Name())
}
}
return plists, nil
}
func getSystemSrvs() ([]string, error) {
return getSrvs(srvSysPath)
}
func getUserSrvs(home string) ([]string, error) {
dir := filepath.Join(home, srvUserPath)
return getSrvs(dir)
}
func filterMatchingSrvs(plists []string, name string) []string {
filtered := []string{}
for i := range plists {
pname := plists[i]
lname := strings.ToLower(pname)
n := len(lname)
if strings.HasSuffix(lname[:n-srvLen], strings.ToLower(name)) {
filtered = append(filtered, pname)
}
}
return filtered
}
func getMatchingSrvs(home string, name string) ([]string, []string, error) {
sysPlists, err := getSystemSrvs()
if nil != err {
return nil, nil, err
}
var userPlists []string
if "" != home {
userPlists, err = getUserSrvs(home)
if nil != err {
return nil, nil, err
}
}
return filterMatchingSrvs(sysPlists, name), filterMatchingSrvs(userPlists, name), nil
}
func getExactSrvMatch(srvs []string, name string) string {
for i := range srvs {
srv := srvs[i]
n := len(srv)
if srv[:n-srvLen] == strings.ToLower(name) {
return srv
}
}
return ""
}
func getOneSysSrv(sys []string, user []string, name string) (string, error) {
service := getExactSrvMatch(user, name)
if "" != service {
return service, nil
}
var errstr string
// system service was wanted
n := len(sys)
switch {
case 0 == n:
errstr += fmt.Sprintf("Didn't find user service matching %q\n", name)
if 0 != len(user) {
errstr += fmt.Sprintf("Did you intend to run a user service instead?\n\t%s\n", strings.Join(user, "\n\t"))
}
case n > 1:
errstr += fmt.Sprintf("Found more than one matching service:\n\t%s\n", strings.Join(sys, "\n\t"))
default:
service = filepath.Join(srvSysPath, sys[0])
}
if "" != errstr {
return "", fmt.Errorf(errstr)
}
return service, nil
}
func getOneUserSrv(home string, sys []string, user []string, name string) (string, error) {
service := getExactSrvMatch(user, name)
if "" != service {
return service, nil
}
var errstr string
// user service was wanted
n := len(user)
switch {
case 0 == n:
errstr += fmt.Sprintf("Didn't find user service matching %q\n", name)
if 0 != len(sys) {
errstr += fmt.Sprintf("Did you intend to run a system service instead?\n\t%s\n", strings.Join(sys, "\n\t"))
}
case n > 1:
errstr += fmt.Sprintf("Found more than one matching service:\n\t%s\n", strings.Join(user, "\n\t"))
default:
service = filepath.Join(home, srvUserPath, user[0]+srvExt)
}
if "" != errstr {
return "", fmt.Errorf(errstr)
}
return service, nil
}

View File

@ -134,7 +134,8 @@ func add() {
fmt.Fprintf(os.Stderr, "Use '--user' to add service as an user service.\n")
}
fmt.Printf("Once started, logs will be found at:\n\t%s\n", conf.Logdir)
fmt.Printf("If all went well the logs should have been created at:\n\t%s\n", conf.Logdir)
fmt.Println()
}
func run() {