147 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package service
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"os"
 | |
| 	"path/filepath"
 | |
| 	"strings"
 | |
| )
 | |
| 
 | |
| // Service should describe the service well-enough for it to
 | |
| // run on Mac, Linux, and Windows.
 | |
| //
 | |
| // 	&Service{
 | |
| // 		// A human-friendy name
 | |
| // 		Title: "Foobar App",
 | |
| // 		// A computer-friendly name
 | |
| // 		Name: "foobar-app",
 | |
| // 		// A name for OS X plist
 | |
| // 		ReverseDNS: "com.example.foobar-app",
 | |
| // 		// A human-friendly description
 | |
| // 		Desc: "Foobar App",
 | |
| // 		// The app /service homepage
 | |
| // 		URL: "https://example.com/foobar-app/",
 | |
| // 		// The full path of the interpreter, if any (ruby, python, node, etc)
 | |
| // 		Interpreter: "/opt/node/bin/node",
 | |
| // 		// The name of the executable (or script)
 | |
| // 		Exec: "foobar-app.js",
 | |
| // 		// An array of arguments
 | |
| // 		Argv: []string{"-c", "/path/to/config.json"},
 | |
| // 		// A map of Environment variables that should be set
 | |
| // 		Envs: map[string]string{
 | |
| // 			PORT: "8080",
 | |
| // 			ENV: "development",
 | |
| // 		},
 | |
| // 		// The user (Linux & Mac only).
 | |
| // 		// This does not apply to userspace services.
 | |
| // 		// There may be special considerations
 | |
| // 		User: "www-data",
 | |
| // 		// If different from User
 | |
| // 		Group: "",
 | |
| // 		// Whether to install as a system or user service
 | |
| // 		System: false,
 | |
| // 		// Whether or not the service may need privileged ports
 | |
| // 		PrivilegedPorts: false,
 | |
| // 	}
 | |
| //
 | |
| // Note that some fields are exported for templating,
 | |
| // but not intended to be set by you.
 | |
| // These are documented as omitted from JSON.
 | |
| // Try to stick to what's outlined above.
 | |
| type Service struct {
 | |
| 	Title               string            `json:"title,omitempty"`
 | |
| 	Name                string            `json:"name"`
 | |
| 	Desc                string            `json:"desc,omitempty"`
 | |
| 	URL                 string            `json:"url,omitempty"`
 | |
| 	ReverseDNS          string            `json:"reverse_dns"`           // i.e. com.example.foo-app
 | |
| 	Interpreter         string            `json:"interpreter,omitempty"` // i.e. node, python
 | |
| 	Exec                string            `json:"exec"`
 | |
| 	Argv                []string          `json:"argv,omitempty"`
 | |
| 	Workdir             string            `json:"workdir,omitempty"`
 | |
| 	Envs                map[string]string `json:"envs,omitempty"`
 | |
| 	User                string            `json:"user,omitempty"`
 | |
| 	Group               string            `json:"group,omitempty"`
 | |
| 	Home                string            `json:"-"`
 | |
| 	Local               string            `json:"-"`
 | |
| 	Logdir              string            `json:"logdir"`
 | |
| 	System              bool              `json:"system"`
 | |
| 	Restart             bool              `json:"restart"`
 | |
| 	Production          bool              `json:"production,omitempty"`
 | |
| 	PrivilegedPorts     bool              `json:"privileged_ports,omitempty"`
 | |
| 	MultiuserProtection bool              `json:"multiuser_protection,omitempty"`
 | |
| }
 | |
| 
 | |
| func (s *Service) NormalizeWithoutPath() {
 | |
| 	if "" == s.Name {
 | |
| 		ext := filepath.Ext(s.Exec)
 | |
| 		base := filepath.Base(s.Exec[:len(s.Exec)-len(ext)])
 | |
| 		s.Name = strings.ToLower(base)
 | |
| 	}
 | |
| 	if "" == s.Title {
 | |
| 		s.Title = s.Name
 | |
| 	}
 | |
| 	if "" == s.ReverseDNS {
 | |
| 		// technically should be something more like "com.example." + s.Name,
 | |
| 		// but whatever
 | |
| 		s.ReverseDNS = s.Name
 | |
| 	}
 | |
| 
 | |
| 	if !s.System {
 | |
| 		home, err := os.UserHomeDir()
 | |
| 		if nil != err {
 | |
| 			fmt.Fprintf(os.Stderr, "Unrecoverable Error: %s", err)
 | |
| 			os.Exit(4)
 | |
| 			return
 | |
| 		}
 | |
| 		s.Home = home
 | |
| 		s.Local = filepath.Join(home, ".local")
 | |
| 		s.Logdir = filepath.Join(home, ".local", "share", s.Name, "var", "log")
 | |
| 	} else {
 | |
| 		s.Logdir = "/var/log/" + s.Name
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (s *Service) Normalize(force bool) {
 | |
| 	s.NormalizeWithoutPath()
 | |
| 
 | |
| 	// 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(s.Exec)
 | |
| 	if nil != err {
 | |
| 		bad := true
 | |
| 		if !strings.Contains(filepath.ToSlash(s.Exec), "/") {
 | |
| 			optpath := filepath.Join(s.Local, "/opt", s.Name, s.Exec)
 | |
| 			_, err := os.Stat(optpath)
 | |
| 			if nil == err {
 | |
| 				bad = false
 | |
| 				//fmt.Fprintf(os.Stderr, "Using '%s' for '%s'\n", optpath, s.Exec)
 | |
| 				s.Exec = optpath
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		if bad {
 | |
| 			// TODO look for it in WorkDir?
 | |
| 			fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", s.Exec)
 | |
| 			if !force {
 | |
| 				os.Exit(5)
 | |
| 				return
 | |
| 			}
 | |
| 			execpath, err := filepath.Abs(s.Exec)
 | |
| 			if nil == err {
 | |
| 				s.Exec = execpath
 | |
| 			}
 | |
| 			fmt.Fprintf(os.Stderr, "Using '%s' anyway.\n", s.Exec)
 | |
| 		}
 | |
| 	} else {
 | |
| 		execpath, err := filepath.Abs(s.Exec)
 | |
| 		if nil != err {
 | |
| 			fmt.Fprintf(os.Stderr, "Unrecoverable Error: %s", err)
 | |
| 			os.Exit(4)
 | |
| 		} else {
 | |
| 			s.Exec = execpath
 | |
| 		}
 | |
| 	}
 | |
| }
 |