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 | ||||
|  | ||||
							
								
								
									
										205
									
								
								manager/start.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										205
									
								
								manager/start.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user