MacOS: start on install
This commit is contained in:
parent
00749b3465
commit
2b7148c3ae
|
@ -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
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue