ready for more testing
This commit is contained in:
		
							parent
							
								
									870d149797
								
							
						
					
					
						commit
						20d460c70e
					
				| @ -9,11 +9,9 @@ | |||||||
| 		{{- 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 }} | ||||||
| @ -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 | ||||||
|  | 		} else { | ||||||
|  | 			c.home = home | ||||||
| 		} | 		} | ||||||
| 		c.home = home |  | ||||||
| 		c.Local = filepath.Join(c.home, ".local") |  | ||||||
| 		c.LogDir = filepath.Join(c.home, ".local", "share", c.Name, "var", "log") |  | ||||||
| 	} else { |  | ||||||
| 		c.LogDir = "/var/log/" + c.Name |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user