From 84f1dacbaca8d9267e6d75f491ddee9020c37aeb Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Mon, 1 Jul 2019 02:44:48 -0600 Subject: [PATCH] basic functionality works --- README.md | 16 +- go.mod | 10 + go.sum | 52 +++++ installer/b0x.toml | 24 ++ .../Library/LaunchDaemons/_rdns_.plist.tmpl | 71 ++++++ .../etc/systemd/system/_name_.service.tmpl | 87 ++++++++ installer/doc.go | 7 + installer/filesystem.go | 23 ++ installer/install.go | 128 +++++++++++ installer/install_darwin.go | 64 ++++++ installer/install_linux.go | 76 +++++++ installer/install_notwindows.go | 17 ++ installer/install_other.go | 7 + installer/install_windows.go | 95 ++++++++ installer/static/ab0x.go | 207 ++++++++++++++++++ installer/whoami.go | 15 ++ installer/whoami_windows.go | 43 ++++ serviceman.go | 105 +++++++++ tools/tools.go | 8 + 19 files changed, 1054 insertions(+), 1 deletion(-) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 installer/b0x.toml create mode 100644 installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl create mode 100644 installer/dist/etc/systemd/system/_name_.service.tmpl create mode 100644 installer/doc.go create mode 100644 installer/filesystem.go create mode 100644 installer/install.go create mode 100644 installer/install_darwin.go create mode 100644 installer/install_linux.go create mode 100644 installer/install_notwindows.go create mode 100644 installer/install_other.go create mode 100644 installer/install_windows.go create mode 100644 installer/static/ab0x.go create mode 100644 installer/whoami.go create mode 100644 installer/whoami_windows.go create mode 100644 serviceman.go create mode 100644 tools/tools.go diff --git a/README.md b/README.md index 1eeed86..54c9332 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ # go-serviceman -A cross-platform service manager. \ No newline at end of file +A cross-platform service manager. + +Goal: + +```bash +serviceman install [options] [interpreter] [-- [options]] +``` + +```bash +serviceman install --user ./foo-app -- -c ./ +``` + +```bash +serviceman install --user /usr/local/bin/node ./whatever.js -- -c ./ +``` diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a182622 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module git.rootprojects.org/root/go-serviceman + +go 1.12 + +require ( + git.rootprojects.org/root/go-gitver v1.1.2 + github.com/UnnoTed/fileb0x v1.1.3 + golang.org/x/net v0.0.0-20180921000356-2f5d2388922f + golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..b9a1259 --- /dev/null +++ b/go.sum @@ -0,0 +1,52 @@ +git.rootprojects.org/root/go-gitver v1.1.2 h1:AQhr8ktJyP+X+jFbtLavCi/FQLSmB6xvdG2Nfp+J2JA= +git.rootprojects.org/root/go-gitver v1.1.2/go.mod h1:Rj1v3TBhvdaSphFEqMynUYwAz/4f+wY/+syBTvRrmlI= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/UnnoTed/fileb0x v1.1.3 h1:TUfJRey+psXuivBqasgp7Du3iXB4hzjI5UXDl+BCrzE= +github.com/UnnoTed/fileb0x v1.1.3/go.mod h1:AyTnLP7elx6MM4eHxahl5sBEWBw0QLf6TM/s64LtM4s= +github.com/airking05/termui v2.2.0+incompatible h1:S3j2WJzr70u8KjUktaQ0Cmja+R0edOXChltFoQSGG8I= +github.com/airking05/termui v2.2.0+incompatible/go.mod h1:B/M5sgOwSZlvGm3TsR98s1BSzlSH4wPQzUUNwZG+uUM= +github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/karrick/godirwalk v1.7.8 h1:VfG72pyIxgtC7+3X9CMHI0AOl4LwyRAg98WAgsvffi8= +github.com/karrick/godirwalk v1.7.8/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= +github.com/labstack/echo v3.2.1+incompatible h1:J2M7YArHx4gi8p/3fDw8tX19SXhBCoRpviyAZSN3I88= +github.com/labstack/echo v3.2.1+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= +github.com/labstack/gommon v0.2.7 h1:2qOPq/twXDrQ6ooBGrn3mrmVOC+biLlatwgIu8lbzRM= +github.com/labstack/gommon v0.2.7/go.mod h1:/tj9csK2iPSBvn+3NLM9e52usepMtrd5ilFYA+wQNJ4= +github.com/maruel/panicparse v1.1.1 h1:k62YPcEoLncEEpjMt92GtG5ugb8WL/510Ys3/h5IkRc= +github.com/maruel/panicparse v1.1.1/go.mod h1:nty42YY5QByNC5MM7q/nj938VbgPU7avs45z6NClpxI= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4= +github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= +github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY= +github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4 h1:gKMu1Bf6QINDnvyZuTaACm9ofY+PRh+5vFz4oxBZeF8= +github.com/valyala/fasttemplate v0.0.0-20170224212429-dcecefd839c4/go.mod h1:50wTf68f99/Zt14pr046Tgt3Lp2vLyFZKzbFXTOabXw= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b h1:2b9XGzhjiYsYPnKXoEfL7klWZQIt8IfyRCz62gCqqlQ= +golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/net v0.0.0-20180921000356-2f5d2388922f h1:QM2QVxvDoW9PFSPp/zy9FgxJLfaWTZlS61KEPtBwacM= +golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8 h1:R91KX5nmbbvEd7w370cbVzKC+EzCTGqZq63Zad5IcLM= +golang.org/x/sys v0.0.0-20181019160139-8e24a49d80f8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/installer/b0x.toml b/installer/b0x.toml new file mode 100644 index 0000000..d7d105c --- /dev/null +++ b/installer/b0x.toml @@ -0,0 +1,24 @@ +# all folders and files are relative to the path where fileb0x was run! + +pkg = "static" +dest = "./static/" +fmt = true + +# build tags for the main b0x.go file +tags = "" + +# default: ab0x.go (so that its init() sorts first) +output = "ab0x.go" + +[[custom]] + # everything inside the folder + # type: array of strings + files = ["./dist/"] + + # base is the path that will be removed from all files' path + # type: string + base = "" + + # prefix is the path that will be added to all files' path + # type: string + prefix = "" diff --git a/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl b/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl new file mode 100644 index 0000000..c75eed4 --- /dev/null +++ b/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl @@ -0,0 +1,71 @@ + + + + + Label + {{ .ReverseDNS }} + ProgramArguments + + {{- if .Interpreter }} + {{ .Interpreter }} + {{- end }} + {{ .Local }}/opt/{{ .Name }}/{{ .Exec }} + {{- if .Argv }} + {{- range $arg := .Argv }} + {{ $arg }} + {{- end }} + {{- end }} + + {{- if .Envs }} + EnvironmentVariables + + {{- range $key, $value := .Envs }} + {{ $key }} + {{ $value }} + {{- end }} + + {{- end }} + + {{if .User -}} + UserName + {{ .User }} + GroupName + {{ .Group }} + InitGroups + + + {{end -}} + RunAtLoad + + {{ if .Restart -}} + KeepAlive + + + + {{ end -}} + {{ if .Production -}} + SoftResourceLimits + + NumberOfFiles + 8192 + + HardResourceLimits + + + {{ end -}} + WorkingDirectory + {{ .Local }}/opt/{{ .Name }} + + StandardErrorPath + {{ .LogDir }}/{{ .Name }}.log + StandardOutPath + {{ .LogDir }}/{{ .Name }}.log + + diff --git a/installer/dist/etc/systemd/system/_name_.service.tmpl b/installer/dist/etc/systemd/system/_name_.service.tmpl new file mode 100644 index 0000000..e7cd86e --- /dev/null +++ b/installer/dist/etc/systemd/system/_name_.service.tmpl @@ -0,0 +1,87 @@ +# Pre-req +# sudo mkdir -p {{ .Local }}/opt/{{ .Name }}/ {{ .Local }}/var/log/{{ .Name }} +{{ if not .Local -}} +{{- if and .User ( ne "root" .User ) -}} +# sudo adduser {{ .User }} --home /opt/{{ .Name }} +# sudo chown -R {{ .User }}:{{ .Group }} /opt/{{ .Name }}/ /var/log/{{ .Name }} +{{- end }} +{{ end -}} +# Post-install +# sudo systemctl {{ if .Local -}} --user {{ end -}} daemon-reload +# sudo systemctl {{ if .Local -}} --user {{ end -}} restart {{ .Name }}.service +# sudo journalctl {{ if .Local -}} --user {{ end -}} -xefu {{ .Name }} + +[Unit] +Description={{ .Title }} - {{ .Desc }} +Documentation={{ .URL }} +{{ if not .Local -}} +After=network-online.target +Wants=network-online.target systemd-networkd-wait-online.service +{{- end }} + +[Service] +# Restart on crash (bad signal), but not on 'clean' failure (error exit code) +# Allow up to 3 restarts within 10 seconds +# (it's unlikely that a user or properly-running script will do this) +Restart=on-abnormal +StartLimitInterval=10 +StartLimitBurst=3 + +{{ if .User -}} +# User and group the process will run as +User={{ .User }} +Group={{ .Group }} + +{{ end -}} +WorkingDirectory={{ .Local }}/opt/{{ .Name }} +ExecStart={{if .Interpreter }}{{ .Interpreter }} {{ end }}{{ .Local }}/opt/{{ .Name }}/{{ .Name }} {{ .Args }} +ExecReload=/bin/kill -USR1 $MAINPID + +{{if .Production -}} +# Limit the number of file descriptors and processes; see `man systemd.exec` for more limit settings. +# These are reasonable defaults for a production system. +# Note: systemd "user units" do not support this +LimitNOFILE=1048576 +LimitNPROC=64 + +{{ end -}} +{{if .MultiuserProtection -}} +# Use private /tmp and /var/tmp, which are discarded after the service stops. +PrivateTmp=true +# Use a minimal /dev +PrivateDevices=true +# Hide /home, /root, and /run/user. Nobody will steal your SSH-keys. +ProtectHome=true +# Make /usr, /boot, /etc and possibly some more folders read-only. +ProtectSystem=full +# ... except /opt/{{ .Name }} because we want a place for the database +# and /var/log/{{ .Name }} because we want a place where logs can go. +# This merely retains r/w access rights, it does not add any new. +# Must still be writable on the host! +ReadWriteDirectories=/opt/{{ .Name }} /var/log/{{ .Name }} + +# Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories +; ReadWritePaths=/opt/{{ .Name }} /var/log/{{ .Name }} + +{{ end -}} +{{if .PrivilegedPorts -}} +# The following additional security directives only work with systemd v229 or later. +# They further retrict privileges that can be gained by the service. +# Note that you may have to add capabilities required by any plugins in use. +CapabilityBoundingSet=CAP_NET_BIND_SERVICE +AmbientCapabilities=CAP_NET_BIND_SERVICE +NoNewPrivileges=true + +# Caveat: Some features may need additional capabilities. +# For example an "upload" may need CAP_LEASE +; CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_LEASE +; AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_LEASE +; NoNewPrivileges=true + +{{ end -}} +[Install] +{{ if not .Local -}} +WantedBy=multi-user.target +{{- else -}} +WantedBy=default.target +{{- end }} diff --git a/installer/doc.go b/installer/doc.go new file mode 100644 index 0000000..ec2b059 --- /dev/null +++ b/installer/doc.go @@ -0,0 +1,7 @@ +// Package installer can be used cross-platform to install apps +// as either userspace or system services for fairly simple applications. +// This is not intended for complex installers. +// +// I'm prototyping this out to be useful for more than just watchdog +// hence there are a few unnecessary things for the sake of the trying it out +package installer diff --git a/installer/filesystem.go b/installer/filesystem.go new file mode 100644 index 0000000..d0395b5 --- /dev/null +++ b/installer/filesystem.go @@ -0,0 +1,23 @@ +package installer + +import ( + "io" + "os" +) + +// "A little copying is better than a little dependency" +// These are here so that we don't need a dependency on http.FileSystem and http.File + +// FileSystem is the same as http.FileSystem +type FileSystem interface { + Open(name string) (File, error) +} + +// File is the same as http.File +type File interface { + io.Closer + io.Reader + io.Seeker + Readdir(count int) ([]os.FileInfo, error) + Stat() (os.FileInfo, error) +} diff --git a/installer/install.go b/installer/install.go new file mode 100644 index 0000000..01ab1fc --- /dev/null +++ b/installer/install.go @@ -0,0 +1,128 @@ +//go:generate go run -mod=vendor github.com/UnnoTed/fileb0x b0x.toml + +package installer + +import ( + "os" + "path/filepath" + "strings" +) + +// Config should describe the service well-enough for it to +// run on Mac, Linux, and Windows. +// +// &Config{ +// // 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 Config struct { + Title string `json:"title"` + Name string `json:"name"` + Desc string `json:"desc"` + URL string `json:"url"` + ReverseDNS string `json:"reverse_dns"` // i.e. com.example.foo-app + Interpreter string `json:"interpreter"` // i.e. node, python + Exec string `json:"exec"` + Argv []string `json:"argv"` + Args string `json:"-"` + Envs map[string]string `json:"envs"` + User string `json:"user"` + Group string `json:"group"` + home string `json:"-"` + Local string `json:"-"` + LogDir string `json:"-"` + System bool `json:"system"` + Restart bool `json:"restart"` + Production bool `json:"production"` + PrivilegedPorts bool `json:"privileged_ports"` + MultiuserProtection bool `json:"multiuser_protection"` +} + +// Install will do a best-effort attempt to install a start-on-startup +// user or system service via systemd, launchd, or reg.exe +func Install(c *Config) error { + if "" == c.Exec { + 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 { + home, err := os.UserHomeDir() + if nil != err { + return err + } + 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) + if nil != err { + return err + } + + err = os.MkdirAll(c.LogDir, 0750) + if nil != err { + return err + } + + return nil +} + +// Returns true if we suspect that the current user (or process) will be able +// to write to system folders, bind to privileged ports, and otherwise +// successfully run a system service. +func IsPrivileged() bool { + return isPrivileged() +} + +func WhereIs(exec string) (string, error) { + exec = filepath.ToSlash(exec) + if strings.Contains(exec, "/") { + // filepath.Clean(exec) + // it's a path (don't allow filenames with slashes) + // TODO stat + return exec, nil + } + return whereIs(exec) +} diff --git a/installer/install_darwin.go b/installer/install_darwin.go new file mode 100644 index 0000000..bad5feb --- /dev/null +++ b/installer/install_darwin.go @@ -0,0 +1,64 @@ +package installer + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + "text/template" + + "git.rootprojects.org/root/go-serviceman/installer/static" +) + +func install(c *Config) error { + // Darwin-specific config options + if c.PrivilegedPorts { + if !c.System { + return fmt.Errorf("You must use root-owned LaunchDaemons (not user-owned LaunchAgents) to use priveleged ports on OS X") + } + } + plistDir := "/Library/LaunchDaemons/" + if !c.System { + plistDir = filepath.Join(c.home, "Library/LaunchAgents") + } + + // Check paths first + err := os.MkdirAll(filepath.Dir(plistDir), 0750) + if nil != err { + return err + } + + // Create service file from template + b, err := static.ReadFile("dist/Library/LaunchDaemons/_rdns_.plist.tmpl") + if err != nil { + return err + } + s := string(b) + rw := &bytes.Buffer{} + // not sure what the template name does, but whatever + tmpl, err := template.New("service").Parse(s) + if err != nil { + return err + } + err = tmpl.Execute(rw, c) + if nil != err { + return err + } + + // Write the file out + // TODO rdns + plistName := c.Name + ".plist" + plistPath := filepath.Join(plistDir, plistName) + if err := ioutil.WriteFile(plistPath, rw.Bytes(), 0644); err != nil { + fmt.Println("Use 'sudo' to install as a privileged system service.") + fmt.Println("Use '--userspace' to install as an user service.") + return fmt.Errorf("ioutil.WriteFile error: %v", err) + } + fmt.Printf("Installed. To start '%s' run the following:\n", c.Name) + // TODO template config file + fmt.Printf("\tlaunchctl load -w %s\n", strings.Replace(plistPath, c.home, "~", 1)) + + return nil +} diff --git a/installer/install_linux.go b/installer/install_linux.go new file mode 100644 index 0000000..4f06c43 --- /dev/null +++ b/installer/install_linux.go @@ -0,0 +1,76 @@ +package installer + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "text/template" + + "git.rootprojects.org/root/go-serviceman/installer/static" +) + +func install(c *Config) error { + // Linux-specific config options + if c.System { + if "" == c.User { + c.User = "root" + } + } + if "" == c.Group { + c.Group = c.User + } + serviceDir := "/etc/systemd/system/" + + // Check paths first + serviceName := c.Name + ".service" + if !c.System { + // Not sure which of these it's supposed to be... + // * ~/.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") + } + err := os.MkdirAll(filepath.Dir(serviceDir), 0750) + if nil != err { + return err + } + + // Create service file from template + b, err := static.ReadFile("dist/etc/systemd/system/_name_.service.tmpl") + if err != nil { + return err + } + s := string(b) + rw := &bytes.Buffer{} + // not sure what the template name does, but whatever + tmpl, err := template.New("service").Parse(s) + if err != nil { + return err + } + err = tmpl.Execute(rw, c) + if nil != err { + return err + } + + // Write the file out + servicePath := filepath.Join(serviceDir, serviceName) + if err := ioutil.WriteFile(servicePath, rw.Bytes(), 0644); err != nil { + return fmt.Errorf("ioutil.WriteFile error: %v", err) + } + + // TODO template this as well? + userspace := "" + sudo := "sudo " + if !c.System { + userspace = "--user " + sudo = "" + } + fmt.Printf("System service installed as '%s'.\n", servicePath) + fmt.Printf("Run the following to start '%s':\n", c.Name) + fmt.Printf("\t" + sudo + "systemctl " + userspace + "daemon-reload\n") + fmt.Printf("\t"+sudo+"systemctl "+userspace+"restart %s.service\n", c.Name) + fmt.Printf("\t"+sudo+"journalctl "+userspace+"-xefu %s\n", c.Name) + return nil +} diff --git a/installer/install_notwindows.go b/installer/install_notwindows.go new file mode 100644 index 0000000..236e7cf --- /dev/null +++ b/installer/install_notwindows.go @@ -0,0 +1,17 @@ +// +build !windows + +package installer + +import ( + "os/exec" + "strings" +) + +func whereIs(exe string) (string, error) { + cmd := exec.Command("command", "-v", exe) + out, err := cmd.Output() + if nil != err { + return "", err + } + return strings.TrimSpace(string(out)), nil +} diff --git a/installer/install_other.go b/installer/install_other.go new file mode 100644 index 0000000..f78efaf --- /dev/null +++ b/installer/install_other.go @@ -0,0 +1,7 @@ +// +build !windows,!linux,!darwin + +package installer + +func install(c *Config) error { + return nil, nil +} diff --git a/installer/install_windows.go b/installer/install_windows.go new file mode 100644 index 0000000..ee6b6fc --- /dev/null +++ b/installer/install_windows.go @@ -0,0 +1,95 @@ +package installer + +import ( + "fmt" + "log" + "os/exec" + "path/filepath" + "strings" + + "golang.org/x/sys/windows/registry" +) + +// TODO system service requires elevated privileges +// See https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/ +func install(c *Config) error { + //token := windows.Token(0) + /* + // LEAVE THIS DOCUMENTATION HERE + reg.exe + /V - "Telebit" + /T - "REG_SZ" - String + /D + /C - case sensitive + /F - not sure... + + // Special Note: + "/c" is similar to -- (*nix), and required within the data string + So instead of setting "do.exe --do-arg1 --do-arg2" + you must set "do.exe /c --do-arg1 --do-arg2" + + vars.telebitNode += '.exe'; + var cmd = 'reg.exe add "HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run"' + + ' /V "Telebit" /t REG_SZ /D ' + + '"' + things.argv[0] + ' /c ' // something like C:\Program Files (x64)\nodejs\node.exe + + [ path.join(__dirname, 'bin/telebitd.js') + , 'daemon' + , '--config' + , path.join(os.homedir(), '.config/telebit/telebitd.yml') + ].join(' ') + + '" /F' + ; + */ + autorunKey := `SOFTWARE\Microsoft\Windows\CurrentVersion\Run` + k, _, err := registry.CreateKey( + registry.CURRENT_USER, + autorunKey, + registry.SET_VALUE, + ) + if err != nil { + log.Fatal(err) + } + defer k.Close() + + setArgs := "" + args := c.Argv + exec := filepath.Join(c.home, ".local", "opt", c.Name, c.Exec) + bin := c.Interpreter + if "" != bin { + // If this is something like node or python, + // the interpeter must be called as "the main thing" + // and "the app" must be an argument + args = append([]string{exec}, args...) + } else { + // Otherwise, if "the app" is a true binary, + // it can be "the main thing" + bin = exec + } + if 0 != len(args) { + // On Windows the /c acts kinda like -- does on *nix, + // at least for commands in the registry that have arguments + setArgs = ` /c ` + } + + // The final string ends up looking something like one of these: + // "C:\Users\aj\.local\opt\appname\appname.js /c -p 8080" + // "C:\Program Files (x64)\nodejs\node.exe /c C:\Users\aj\.local\opt\appname\appname.js -p 8080" + regSZ := bin + setArgs + strings.Join(c.Argv, " ") + if len(regSZ) > 260 { + return fmt.Errorf("data value is too long for registry entry") + } + fmt.Println("Set Registry Key:") + fmt.Println(autorunKey, c.Title, regSZ) + k.SetStringValue(c.Title, regSZ) + + return nil +} + +func whereIs(exe string) (string, error) { + cmd := exec.Command("where.exe", exe) + out, err := cmd.Output() + if nil != err { + return "", err + } + return strings.TrimSpace(string(out)), nil +} diff --git a/installer/static/ab0x.go b/installer/static/ab0x.go new file mode 100644 index 0000000..344cd5d --- /dev/null +++ b/installer/static/ab0x.go @@ -0,0 +1,207 @@ +// Code generated by fileb0x at "2019-07-01 02:25:06.914859 -0600 MDT m=+0.005210050" from config file "b0x.toml" DO NOT EDIT. +// modification hash(23db194c43de8cc25ab29e0b867004fa.acdb557394f98d3c09c0bb4d4b9142f8) + +package static + +import ( + "bytes" + + "context" + "io" + "net/http" + "os" + "path" + + "golang.org/x/net/webdav" +) + +var ( + // CTX is a context for webdav vfs + CTX = context.Background() + + // FS is a virtual memory file system + FS = webdav.NewMemFS() + + // Handler is used to server files through a http handler + Handler *webdav.Handler + + // HTTP is the http file system + HTTP http.FileSystem = new(HTTPFS) +) + +// HTTPFS implements http.FileSystem +type HTTPFS struct { + // Prefix allows to limit the path of all requests. F.e. a prefix "css" would allow only calls to /css/* + Prefix string +} + +// FileDistLibraryLaunchDaemonsRdnsPlistTmpl is "dist/Library/LaunchDaemons/_rdns_.plist.tmpl" +var FileDistLibraryLaunchDaemonsRdnsPlistTmpl = []byte("\x3c\x3f\x78\x6d\x6c\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x30\x22\x20\x65\x6e\x63\x6f\x64\x69\x6e\x67\x3d\x22\x55\x54\x46\x2d\x38\x22\x3f\x3e\x0a\x3c\x21\x44\x4f\x43\x54\x59\x50\x45\x20\x70\x6c\x69\x73\x74\x20\x50\x55\x42\x4c\x49\x43\x20\x22\x2d\x2f\x2f\x41\x70\x70\x6c\x65\x2f\x2f\x44\x54\x44\x20\x50\x4c\x49\x53\x54\x20\x31\x2e\x30\x2f\x2f\x45\x4e\x22\x20\x22\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x61\x70\x70\x6c\x65\x2e\x63\x6f\x6d\x2f\x44\x54\x44\x73\x2f\x50\x72\x6f\x70\x65\x72\x74\x79\x4c\x69\x73\x74\x2d\x31\x2e\x30\x2e\x64\x74\x64\x22\x3e\x0a\x3c\x70\x6c\x69\x73\x74\x20\x76\x65\x72\x73\x69\x6f\x6e\x3d\x22\x31\x2e\x30\x22\x3e\x0a\x3c\x64\x69\x63\x74\x3e\x0a\x09\x3c\x6b\x65\x79\x3e\x4c\x61\x62\x65\x6c\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x52\x65\x76\x65\x72\x73\x65\x44\x4e\x53\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x3c\x6b\x65\x79\x3e\x50\x72\x6f\x67\x72\x61\x6d\x41\x72\x67\x75\x6d\x65\x6e\x74\x73\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x61\x72\x72\x61\x79\x3e\x0a\x09\x09\x7b\x7b\x2d\x20\x69\x66\x20\x2e\x49\x6e\x74\x65\x72\x70\x72\x65\x74\x65\x72\x20\x7d\x7d\x0a\x09\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x49\x6e\x74\x65\x72\x70\x72\x65\x74\x65\x72\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x09\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a\x09\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x7d\x7d\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x2f\x7b\x7b\x20\x2e\x45\x78\x65\x63\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x20\x20\x7b\x7b\x2d\x20\x69\x66\x20\x2e\x41\x72\x67\x76\x20\x7d\x7d\x0a\x09\x09\x7b\x7b\x2d\x20\x72\x61\x6e\x67\x65\x20\x24\x61\x72\x67\x20\x3a\x3d\x20\x2e\x41\x72\x67\x76\x20\x7d\x7d\x0a\x09\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x24\x61\x72\x67\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x09\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a\x09\x20\x20\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a\x09\x3c\x2f\x61\x72\x72\x61\x79\x3e\x0a\x09\x7b\x7b\x2d\x20\x69\x66\x20\x2e\x45\x6e\x76\x73\x20\x7d\x7d\x0a\x09\x3c\x6b\x65\x79\x3e\x45\x6e\x76\x69\x72\x6f\x6e\x6d\x65\x6e\x74\x56\x61\x72\x69\x61\x62\x6c\x65\x73\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x64\x69\x63\x74\x3e\x0a\x09\x09\x7b\x7b\x2d\x20\x72\x61\x6e\x67\x65\x20\x24\x6b\x65\x79\x2c\x20\x24\x76\x61\x6c\x75\x65\x20\x3a\x3d\x20\x2e\x45\x6e\x76\x73\x20\x7d\x7d\x0a\x09\x09\x3c\x6b\x65\x79\x3e\x7b\x7b\x20\x24\x6b\x65\x79\x20\x7d\x7d\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x24\x76\x61\x6c\x75\x65\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x09\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a\x09\x3c\x2f\x64\x69\x63\x74\x3e\x0a\x09\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a\x0a\x09\x7b\x7b\x69\x66\x20\x2e\x55\x73\x65\x72\x20\x2d\x7d\x7d\x0a\x09\x3c\x6b\x65\x79\x3e\x55\x73\x65\x72\x4e\x61\x6d\x65\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x55\x73\x65\x72\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x3c\x6b\x65\x79\x3e\x47\x72\x6f\x75\x70\x4e\x61\x6d\x65\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x47\x72\x6f\x75\x70\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x3c\x6b\x65\x79\x3e\x49\x6e\x69\x74\x47\x72\x6f\x75\x70\x73\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x74\x72\x75\x65\x2f\x3e\x0a\x0a\x09\x7b\x7b\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x09\x3c\x6b\x65\x79\x3e\x52\x75\x6e\x41\x74\x4c\x6f\x61\x64\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x74\x72\x75\x65\x2f\x3e\x0a\x09\x7b\x7b\x20\x69\x66\x20\x2e\x52\x65\x73\x74\x61\x72\x74\x20\x2d\x7d\x7d\x0a\x09\x3c\x6b\x65\x79\x3e\x4b\x65\x65\x70\x41\x6c\x69\x76\x65\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x74\x72\x75\x65\x2f\x3e\x0a\x09\x3c\x21\x2d\x2d\x64\x69\x63\x74\x3e\x0a\x09\x09\x3c\x6b\x65\x79\x3e\x43\x72\x61\x73\x68\x65\x64\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x09\x3c\x74\x72\x75\x65\x2f\x3e\x0a\x09\x09\x3c\x6b\x65\x79\x3e\x4e\x65\x74\x77\x6f\x72\x6b\x53\x74\x61\x74\x65\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x09\x3c\x74\x72\x75\x65\x2f\x3e\x0a\x09\x09\x3c\x6b\x65\x79\x3e\x53\x75\x63\x63\x65\x73\x73\x66\x75\x6c\x45\x78\x69\x74\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x09\x3c\x66\x61\x6c\x73\x65\x2f\x3e\x0a\x09\x3c\x2f\x64\x69\x63\x74\x2d\x2d\x3e\x0a\x0a\x09\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x09\x7b\x7b\x20\x69\x66\x20\x2e\x50\x72\x6f\x64\x75\x63\x74\x69\x6f\x6e\x20\x2d\x7d\x7d\x0a\x09\x3c\x6b\x65\x79\x3e\x53\x6f\x66\x74\x52\x65\x73\x6f\x75\x72\x63\x65\x4c\x69\x6d\x69\x74\x73\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x64\x69\x63\x74\x3e\x0a\x09\x09\x3c\x6b\x65\x79\x3e\x4e\x75\x6d\x62\x65\x72\x4f\x66\x46\x69\x6c\x65\x73\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x09\x3c\x69\x6e\x74\x65\x67\x65\x72\x3e\x38\x31\x39\x32\x3c\x2f\x69\x6e\x74\x65\x67\x65\x72\x3e\x0a\x09\x3c\x2f\x64\x69\x63\x74\x3e\x0a\x09\x3c\x6b\x65\x79\x3e\x48\x61\x72\x64\x52\x65\x73\x6f\x75\x72\x63\x65\x4c\x69\x6d\x69\x74\x73\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x64\x69\x63\x74\x2f\x3e\x0a\x0a\x09\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x09\x3c\x6b\x65\x79\x3e\x57\x6f\x72\x6b\x69\x6e\x67\x44\x69\x72\x65\x63\x74\x6f\x72\x79\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x7d\x7d\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x0a\x09\x3c\x6b\x65\x79\x3e\x53\x74\x61\x6e\x64\x61\x72\x64\x45\x72\x72\x6f\x72\x50\x61\x74\x68\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x4c\x6f\x67\x44\x69\x72\x20\x7d\x7d\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x2e\x6c\x6f\x67\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x09\x3c\x6b\x65\x79\x3e\x53\x74\x61\x6e\x64\x61\x72\x64\x4f\x75\x74\x50\x61\x74\x68\x3c\x2f\x6b\x65\x79\x3e\x0a\x09\x3c\x73\x74\x72\x69\x6e\x67\x3e\x7b\x7b\x20\x2e\x4c\x6f\x67\x44\x69\x72\x20\x7d\x7d\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x2e\x6c\x6f\x67\x3c\x2f\x73\x74\x72\x69\x6e\x67\x3e\x0a\x3c\x2f\x64\x69\x63\x74\x3e\x0a\x3c\x2f\x70\x6c\x69\x73\x74\x3e\x0a") + +// FileDistEtcSystemdSystemNameServiceTmpl is "dist/etc/systemd/system/_name_.service.tmpl" +var FileDistEtcSystemdSystemNameServiceTmpl = []byte("\x23\x20\x50\x72\x65\x2d\x72\x65\x71\x0a\x23\x20\x73\x75\x64\x6f\x20\x6d\x6b\x64\x69\x72\x20\x2d\x70\x20\x7b\x7b\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x7d\x7d\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x2f\x20\x7b\x7b\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x7d\x7d\x2f\x76\x61\x72\x2f\x6c\x6f\x67\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x0a\x7b\x7b\x20\x69\x66\x20\x6e\x6f\x74\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x2d\x7d\x7d\x0a\x7b\x7b\x2d\x20\x69\x66\x20\x61\x6e\x64\x20\x2e\x55\x73\x65\x72\x20\x28\x20\x6e\x65\x20\x22\x72\x6f\x6f\x74\x22\x20\x2e\x55\x73\x65\x72\x20\x29\x20\x2d\x7d\x7d\x0a\x23\x20\x73\x75\x64\x6f\x20\x61\x64\x64\x75\x73\x65\x72\x20\x7b\x7b\x20\x2e\x55\x73\x65\x72\x20\x7d\x7d\x20\x2d\x2d\x68\x6f\x6d\x65\x20\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x0a\x23\x20\x73\x75\x64\x6f\x20\x63\x68\x6f\x77\x6e\x20\x2d\x52\x20\x7b\x7b\x20\x2e\x55\x73\x65\x72\x20\x7d\x7d\x3a\x7b\x7b\x20\x2e\x47\x72\x6f\x75\x70\x20\x7d\x7d\x20\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x2f\x20\x2f\x76\x61\x72\x2f\x6c\x6f\x67\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x0a\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x23\x20\x50\x6f\x73\x74\x2d\x69\x6e\x73\x74\x61\x6c\x6c\x0a\x23\x20\x73\x75\x64\x6f\x20\x73\x79\x73\x74\x65\x6d\x63\x74\x6c\x20\x7b\x7b\x20\x69\x66\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x2d\x7d\x7d\x20\x2d\x2d\x75\x73\x65\x72\x20\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x20\x64\x61\x65\x6d\x6f\x6e\x2d\x72\x65\x6c\x6f\x61\x64\x0a\x23\x20\x73\x75\x64\x6f\x20\x73\x79\x73\x74\x65\x6d\x63\x74\x6c\x20\x7b\x7b\x20\x69\x66\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x2d\x7d\x7d\x20\x2d\x2d\x75\x73\x65\x72\x20\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x20\x72\x65\x73\x74\x61\x72\x74\x20\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x2e\x73\x65\x72\x76\x69\x63\x65\x0a\x23\x20\x73\x75\x64\x6f\x20\x6a\x6f\x75\x72\x6e\x61\x6c\x63\x74\x6c\x20\x7b\x7b\x20\x69\x66\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x2d\x7d\x7d\x20\x2d\x2d\x75\x73\x65\x72\x20\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x20\x2d\x78\x65\x66\x75\x20\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x0a\x0a\x5b\x55\x6e\x69\x74\x5d\x0a\x44\x65\x73\x63\x72\x69\x70\x74\x69\x6f\x6e\x3d\x7b\x7b\x20\x2e\x54\x69\x74\x6c\x65\x20\x7d\x7d\x20\x2d\x20\x7b\x7b\x20\x2e\x44\x65\x73\x63\x20\x7d\x7d\x0a\x44\x6f\x63\x75\x6d\x65\x6e\x74\x61\x74\x69\x6f\x6e\x3d\x7b\x7b\x20\x2e\x55\x52\x4c\x20\x7d\x7d\x0a\x7b\x7b\x20\x69\x66\x20\x6e\x6f\x74\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x2d\x7d\x7d\x0a\x41\x66\x74\x65\x72\x3d\x6e\x65\x74\x77\x6f\x72\x6b\x2d\x6f\x6e\x6c\x69\x6e\x65\x2e\x74\x61\x72\x67\x65\x74\x0a\x57\x61\x6e\x74\x73\x3d\x6e\x65\x74\x77\x6f\x72\x6b\x2d\x6f\x6e\x6c\x69\x6e\x65\x2e\x74\x61\x72\x67\x65\x74\x20\x73\x79\x73\x74\x65\x6d\x64\x2d\x6e\x65\x74\x77\x6f\x72\x6b\x64\x2d\x77\x61\x69\x74\x2d\x6f\x6e\x6c\x69\x6e\x65\x2e\x73\x65\x72\x76\x69\x63\x65\x0a\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a\x0a\x5b\x53\x65\x72\x76\x69\x63\x65\x5d\x0a\x23\x20\x52\x65\x73\x74\x61\x72\x74\x20\x6f\x6e\x20\x63\x72\x61\x73\x68\x20\x28\x62\x61\x64\x20\x73\x69\x67\x6e\x61\x6c\x29\x2c\x20\x62\x75\x74\x20\x6e\x6f\x74\x20\x6f\x6e\x20\x27\x63\x6c\x65\x61\x6e\x27\x20\x66\x61\x69\x6c\x75\x72\x65\x20\x28\x65\x72\x72\x6f\x72\x20\x65\x78\x69\x74\x20\x63\x6f\x64\x65\x29\x0a\x23\x20\x41\x6c\x6c\x6f\x77\x20\x75\x70\x20\x74\x6f\x20\x33\x20\x72\x65\x73\x74\x61\x72\x74\x73\x20\x77\x69\x74\x68\x69\x6e\x20\x31\x30\x20\x73\x65\x63\x6f\x6e\x64\x73\x0a\x23\x20\x28\x69\x74\x27\x73\x20\x75\x6e\x6c\x69\x6b\x65\x6c\x79\x20\x74\x68\x61\x74\x20\x61\x20\x75\x73\x65\x72\x20\x6f\x72\x20\x70\x72\x6f\x70\x65\x72\x6c\x79\x2d\x72\x75\x6e\x6e\x69\x6e\x67\x20\x73\x63\x72\x69\x70\x74\x20\x77\x69\x6c\x6c\x20\x64\x6f\x20\x74\x68\x69\x73\x29\x0a\x52\x65\x73\x74\x61\x72\x74\x3d\x6f\x6e\x2d\x61\x62\x6e\x6f\x72\x6d\x61\x6c\x0a\x53\x74\x61\x72\x74\x4c\x69\x6d\x69\x74\x49\x6e\x74\x65\x72\x76\x61\x6c\x3d\x31\x30\x0a\x53\x74\x61\x72\x74\x4c\x69\x6d\x69\x74\x42\x75\x72\x73\x74\x3d\x33\x0a\x0a\x7b\x7b\x20\x69\x66\x20\x2e\x55\x73\x65\x72\x20\x2d\x7d\x7d\x0a\x23\x20\x55\x73\x65\x72\x20\x61\x6e\x64\x20\x67\x72\x6f\x75\x70\x20\x74\x68\x65\x20\x70\x72\x6f\x63\x65\x73\x73\x20\x77\x69\x6c\x6c\x20\x72\x75\x6e\x20\x61\x73\x0a\x55\x73\x65\x72\x3d\x7b\x7b\x20\x2e\x55\x73\x65\x72\x20\x7d\x7d\x0a\x47\x72\x6f\x75\x70\x3d\x7b\x7b\x20\x2e\x47\x72\x6f\x75\x70\x20\x7d\x7d\x0a\x0a\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x57\x6f\x72\x6b\x69\x6e\x67\x44\x69\x72\x65\x63\x74\x6f\x72\x79\x3d\x7b\x7b\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x7d\x7d\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x0a\x45\x78\x65\x63\x53\x74\x61\x72\x74\x3d\x7b\x7b\x69\x66\x20\x2e\x49\x6e\x74\x65\x72\x70\x72\x65\x74\x65\x72\x20\x7d\x7d\x7b\x7b\x20\x2e\x49\x6e\x74\x65\x72\x70\x72\x65\x74\x65\x72\x20\x7d\x7d\x20\x7b\x7b\x20\x65\x6e\x64\x20\x7d\x7d\x7b\x7b\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x7d\x7d\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x20\x7b\x7b\x20\x2e\x41\x72\x67\x73\x20\x7d\x7d\x0a\x45\x78\x65\x63\x52\x65\x6c\x6f\x61\x64\x3d\x2f\x62\x69\x6e\x2f\x6b\x69\x6c\x6c\x20\x2d\x55\x53\x52\x31\x20\x24\x4d\x41\x49\x4e\x50\x49\x44\x0a\x0a\x7b\x7b\x69\x66\x20\x2e\x50\x72\x6f\x64\x75\x63\x74\x69\x6f\x6e\x20\x2d\x7d\x7d\x0a\x23\x20\x4c\x69\x6d\x69\x74\x20\x74\x68\x65\x20\x6e\x75\x6d\x62\x65\x72\x20\x6f\x66\x20\x66\x69\x6c\x65\x20\x64\x65\x73\x63\x72\x69\x70\x74\x6f\x72\x73\x20\x61\x6e\x64\x20\x70\x72\x6f\x63\x65\x73\x73\x65\x73\x3b\x20\x73\x65\x65\x20\x60\x6d\x61\x6e\x20\x73\x79\x73\x74\x65\x6d\x64\x2e\x65\x78\x65\x63\x60\x20\x66\x6f\x72\x20\x6d\x6f\x72\x65\x20\x6c\x69\x6d\x69\x74\x20\x73\x65\x74\x74\x69\x6e\x67\x73\x2e\x0a\x23\x20\x54\x68\x65\x73\x65\x20\x61\x72\x65\x20\x72\x65\x61\x73\x6f\x6e\x61\x62\x6c\x65\x20\x64\x65\x66\x61\x75\x6c\x74\x73\x20\x66\x6f\x72\x20\x61\x20\x70\x72\x6f\x64\x75\x63\x74\x69\x6f\x6e\x20\x73\x79\x73\x74\x65\x6d\x2e\x0a\x23\x20\x4e\x6f\x74\x65\x3a\x20\x73\x79\x73\x74\x65\x6d\x64\x20\x22\x75\x73\x65\x72\x20\x75\x6e\x69\x74\x73\x22\x20\x64\x6f\x20\x6e\x6f\x74\x20\x73\x75\x70\x70\x6f\x72\x74\x20\x74\x68\x69\x73\x0a\x4c\x69\x6d\x69\x74\x4e\x4f\x46\x49\x4c\x45\x3d\x31\x30\x34\x38\x35\x37\x36\x0a\x4c\x69\x6d\x69\x74\x4e\x50\x52\x4f\x43\x3d\x36\x34\x0a\x0a\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x7b\x7b\x69\x66\x20\x2e\x4d\x75\x6c\x74\x69\x75\x73\x65\x72\x50\x72\x6f\x74\x65\x63\x74\x69\x6f\x6e\x20\x2d\x7d\x7d\x0a\x23\x20\x55\x73\x65\x20\x70\x72\x69\x76\x61\x74\x65\x20\x2f\x74\x6d\x70\x20\x61\x6e\x64\x20\x2f\x76\x61\x72\x2f\x74\x6d\x70\x2c\x20\x77\x68\x69\x63\x68\x20\x61\x72\x65\x20\x64\x69\x73\x63\x61\x72\x64\x65\x64\x20\x61\x66\x74\x65\x72\x20\x74\x68\x65\x20\x73\x65\x72\x76\x69\x63\x65\x20\x73\x74\x6f\x70\x73\x2e\x0a\x50\x72\x69\x76\x61\x74\x65\x54\x6d\x70\x3d\x74\x72\x75\x65\x0a\x23\x20\x55\x73\x65\x20\x61\x20\x6d\x69\x6e\x69\x6d\x61\x6c\x20\x2f\x64\x65\x76\x0a\x50\x72\x69\x76\x61\x74\x65\x44\x65\x76\x69\x63\x65\x73\x3d\x74\x72\x75\x65\x0a\x23\x20\x48\x69\x64\x65\x20\x2f\x68\x6f\x6d\x65\x2c\x20\x2f\x72\x6f\x6f\x74\x2c\x20\x61\x6e\x64\x20\x2f\x72\x75\x6e\x2f\x75\x73\x65\x72\x2e\x20\x4e\x6f\x62\x6f\x64\x79\x20\x77\x69\x6c\x6c\x20\x73\x74\x65\x61\x6c\x20\x79\x6f\x75\x72\x20\x53\x53\x48\x2d\x6b\x65\x79\x73\x2e\x0a\x50\x72\x6f\x74\x65\x63\x74\x48\x6f\x6d\x65\x3d\x74\x72\x75\x65\x0a\x23\x20\x4d\x61\x6b\x65\x20\x2f\x75\x73\x72\x2c\x20\x2f\x62\x6f\x6f\x74\x2c\x20\x2f\x65\x74\x63\x20\x61\x6e\x64\x20\x70\x6f\x73\x73\x69\x62\x6c\x79\x20\x73\x6f\x6d\x65\x20\x6d\x6f\x72\x65\x20\x66\x6f\x6c\x64\x65\x72\x73\x20\x72\x65\x61\x64\x2d\x6f\x6e\x6c\x79\x2e\x0a\x50\x72\x6f\x74\x65\x63\x74\x53\x79\x73\x74\x65\x6d\x3d\x66\x75\x6c\x6c\x0a\x23\x20\x2e\x2e\x2e\x20\x65\x78\x63\x65\x70\x74\x20\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x20\x62\x65\x63\x61\x75\x73\x65\x20\x77\x65\x20\x77\x61\x6e\x74\x20\x61\x20\x70\x6c\x61\x63\x65\x20\x66\x6f\x72\x20\x74\x68\x65\x20\x64\x61\x74\x61\x62\x61\x73\x65\x0a\x23\x20\x61\x6e\x64\x20\x2f\x76\x61\x72\x2f\x6c\x6f\x67\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x20\x62\x65\x63\x61\x75\x73\x65\x20\x77\x65\x20\x77\x61\x6e\x74\x20\x61\x20\x70\x6c\x61\x63\x65\x20\x77\x68\x65\x72\x65\x20\x6c\x6f\x67\x73\x20\x63\x61\x6e\x20\x67\x6f\x2e\x0a\x23\x20\x54\x68\x69\x73\x20\x6d\x65\x72\x65\x6c\x79\x20\x72\x65\x74\x61\x69\x6e\x73\x20\x72\x2f\x77\x20\x61\x63\x63\x65\x73\x73\x20\x72\x69\x67\x68\x74\x73\x2c\x20\x69\x74\x20\x64\x6f\x65\x73\x20\x6e\x6f\x74\x20\x61\x64\x64\x20\x61\x6e\x79\x20\x6e\x65\x77\x2e\x0a\x23\x20\x4d\x75\x73\x74\x20\x73\x74\x69\x6c\x6c\x20\x62\x65\x20\x77\x72\x69\x74\x61\x62\x6c\x65\x20\x6f\x6e\x20\x74\x68\x65\x20\x68\x6f\x73\x74\x21\x0a\x52\x65\x61\x64\x57\x72\x69\x74\x65\x44\x69\x72\x65\x63\x74\x6f\x72\x69\x65\x73\x3d\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x20\x2f\x76\x61\x72\x2f\x6c\x6f\x67\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x0a\x0a\x23\x20\x4e\x6f\x74\x65\x3a\x20\x69\x6e\x20\x76\x32\x33\x31\x20\x61\x6e\x64\x20\x61\x62\x6f\x76\x65\x20\x52\x65\x61\x64\x57\x72\x69\x74\x65\x50\x61\x74\x68\x73\x20\x68\x61\x73\x20\x62\x65\x65\x6e\x20\x72\x65\x6e\x61\x6d\x65\x64\x20\x74\x6f\x20\x52\x65\x61\x64\x57\x72\x69\x74\x65\x44\x69\x72\x65\x63\x74\x6f\x72\x69\x65\x73\x0a\x3b\x20\x52\x65\x61\x64\x57\x72\x69\x74\x65\x50\x61\x74\x68\x73\x3d\x2f\x6f\x70\x74\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x20\x2f\x76\x61\x72\x2f\x6c\x6f\x67\x2f\x7b\x7b\x20\x2e\x4e\x61\x6d\x65\x20\x7d\x7d\x0a\x0a\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x7b\x7b\x69\x66\x20\x2e\x50\x72\x69\x76\x69\x6c\x65\x67\x65\x64\x50\x6f\x72\x74\x73\x20\x2d\x7d\x7d\x0a\x23\x20\x54\x68\x65\x20\x66\x6f\x6c\x6c\x6f\x77\x69\x6e\x67\x20\x61\x64\x64\x69\x74\x69\x6f\x6e\x61\x6c\x20\x73\x65\x63\x75\x72\x69\x74\x79\x20\x64\x69\x72\x65\x63\x74\x69\x76\x65\x73\x20\x6f\x6e\x6c\x79\x20\x77\x6f\x72\x6b\x20\x77\x69\x74\x68\x20\x73\x79\x73\x74\x65\x6d\x64\x20\x76\x32\x32\x39\x20\x6f\x72\x20\x6c\x61\x74\x65\x72\x2e\x0a\x23\x20\x54\x68\x65\x79\x20\x66\x75\x72\x74\x68\x65\x72\x20\x72\x65\x74\x72\x69\x63\x74\x20\x70\x72\x69\x76\x69\x6c\x65\x67\x65\x73\x20\x74\x68\x61\x74\x20\x63\x61\x6e\x20\x62\x65\x20\x67\x61\x69\x6e\x65\x64\x20\x62\x79\x20\x74\x68\x65\x20\x73\x65\x72\x76\x69\x63\x65\x2e\x0a\x23\x20\x4e\x6f\x74\x65\x20\x74\x68\x61\x74\x20\x79\x6f\x75\x20\x6d\x61\x79\x20\x68\x61\x76\x65\x20\x74\x6f\x20\x61\x64\x64\x20\x63\x61\x70\x61\x62\x69\x6c\x69\x74\x69\x65\x73\x20\x72\x65\x71\x75\x69\x72\x65\x64\x20\x62\x79\x20\x61\x6e\x79\x20\x70\x6c\x75\x67\x69\x6e\x73\x20\x69\x6e\x20\x75\x73\x65\x2e\x0a\x43\x61\x70\x61\x62\x69\x6c\x69\x74\x79\x42\x6f\x75\x6e\x64\x69\x6e\x67\x53\x65\x74\x3d\x43\x41\x50\x5f\x4e\x45\x54\x5f\x42\x49\x4e\x44\x5f\x53\x45\x52\x56\x49\x43\x45\x0a\x41\x6d\x62\x69\x65\x6e\x74\x43\x61\x70\x61\x62\x69\x6c\x69\x74\x69\x65\x73\x3d\x43\x41\x50\x5f\x4e\x45\x54\x5f\x42\x49\x4e\x44\x5f\x53\x45\x52\x56\x49\x43\x45\x0a\x4e\x6f\x4e\x65\x77\x50\x72\x69\x76\x69\x6c\x65\x67\x65\x73\x3d\x74\x72\x75\x65\x0a\x0a\x23\x20\x43\x61\x76\x65\x61\x74\x3a\x20\x53\x6f\x6d\x65\x20\x66\x65\x61\x74\x75\x72\x65\x73\x20\x6d\x61\x79\x20\x6e\x65\x65\x64\x20\x61\x64\x64\x69\x74\x69\x6f\x6e\x61\x6c\x20\x63\x61\x70\x61\x62\x69\x6c\x69\x74\x69\x65\x73\x2e\x0a\x23\x20\x46\x6f\x72\x20\x65\x78\x61\x6d\x70\x6c\x65\x20\x61\x6e\x20\x22\x75\x70\x6c\x6f\x61\x64\x22\x20\x6d\x61\x79\x20\x6e\x65\x65\x64\x20\x43\x41\x50\x5f\x4c\x45\x41\x53\x45\x0a\x3b\x20\x43\x61\x70\x61\x62\x69\x6c\x69\x74\x79\x42\x6f\x75\x6e\x64\x69\x6e\x67\x53\x65\x74\x3d\x43\x41\x50\x5f\x4e\x45\x54\x5f\x42\x49\x4e\x44\x5f\x53\x45\x52\x56\x49\x43\x45\x20\x43\x41\x50\x5f\x4c\x45\x41\x53\x45\x0a\x3b\x20\x41\x6d\x62\x69\x65\x6e\x74\x43\x61\x70\x61\x62\x69\x6c\x69\x74\x69\x65\x73\x3d\x43\x41\x50\x5f\x4e\x45\x54\x5f\x42\x49\x4e\x44\x5f\x53\x45\x52\x56\x49\x43\x45\x20\x43\x41\x50\x5f\x4c\x45\x41\x53\x45\x0a\x3b\x20\x4e\x6f\x4e\x65\x77\x50\x72\x69\x76\x69\x6c\x65\x67\x65\x73\x3d\x74\x72\x75\x65\x0a\x0a\x7b\x7b\x20\x65\x6e\x64\x20\x2d\x7d\x7d\x0a\x5b\x49\x6e\x73\x74\x61\x6c\x6c\x5d\x0a\x7b\x7b\x20\x69\x66\x20\x6e\x6f\x74\x20\x2e\x4c\x6f\x63\x61\x6c\x20\x2d\x7d\x7d\x0a\x57\x61\x6e\x74\x65\x64\x42\x79\x3d\x6d\x75\x6c\x74\x69\x2d\x75\x73\x65\x72\x2e\x74\x61\x72\x67\x65\x74\x0a\x7b\x7b\x2d\x20\x65\x6c\x73\x65\x20\x2d\x7d\x7d\x0a\x57\x61\x6e\x74\x65\x64\x42\x79\x3d\x64\x65\x66\x61\x75\x6c\x74\x2e\x74\x61\x72\x67\x65\x74\x0a\x7b\x7b\x2d\x20\x65\x6e\x64\x20\x7d\x7d\x0a") + +func init() { + err := CTX.Err() + if err != nil { + panic(err) + } + + err = FS.Mkdir(CTX, "dist/", 0777) + if err != nil && err != os.ErrExist { + panic(err) + } + + err = FS.Mkdir(CTX, "dist/etc/", 0777) + if err != nil && err != os.ErrExist { + panic(err) + } + + err = FS.Mkdir(CTX, "dist/etc/systemd/", 0777) + if err != nil && err != os.ErrExist { + panic(err) + } + + err = FS.Mkdir(CTX, "dist/etc/systemd/system/", 0777) + if err != nil && err != os.ErrExist { + panic(err) + } + + err = FS.Mkdir(CTX, "dist/Library/", 0777) + if err != nil && err != os.ErrExist { + panic(err) + } + + err = FS.Mkdir(CTX, "dist/Library/LaunchDaemons/", 0777) + if err != nil && err != os.ErrExist { + panic(err) + } + + var f webdav.File + + f, err = FS.OpenFile(CTX, "dist/Library/LaunchDaemons/_rdns_.plist.tmpl", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) + if err != nil { + panic(err) + } + + _, err = f.Write(FileDistLibraryLaunchDaemonsRdnsPlistTmpl) + if err != nil { + panic(err) + } + + err = f.Close() + if err != nil { + panic(err) + } + + f, err = FS.OpenFile(CTX, "dist/etc/systemd/system/_name_.service.tmpl", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) + if err != nil { + panic(err) + } + + _, err = f.Write(FileDistEtcSystemdSystemNameServiceTmpl) + if err != nil { + panic(err) + } + + err = f.Close() + if err != nil { + panic(err) + } + + Handler = &webdav.Handler{ + FileSystem: FS, + LockSystem: webdav.NewMemLS(), + } + +} + +// Open a file +func (hfs *HTTPFS) Open(path string) (http.File, error) { + path = hfs.Prefix + path + + f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644) + if err != nil { + return nil, err + } + + return f, nil +} + +// ReadFile is adapTed from ioutil +func ReadFile(path string) ([]byte, error) { + f, err := FS.OpenFile(CTX, path, os.O_RDONLY, 0644) + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(make([]byte, 0, bytes.MinRead)) + + // If the buffer overflows, we will get bytes.ErrTooLarge. + // Return that as an error. Any other panic remains. + defer func() { + e := recover() + if e == nil { + return + } + if panicErr, ok := e.(error); ok && panicErr == bytes.ErrTooLarge { + err = panicErr + } else { + panic(e) + } + }() + _, err = buf.ReadFrom(f) + return buf.Bytes(), err +} + +// WriteFile is adapTed from ioutil +func WriteFile(filename string, data []byte, perm os.FileMode) error { + f, err := FS.OpenFile(CTX, filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + n, err := f.Write(data) + if err == nil && n < len(data) { + err = io.ErrShortWrite + } + if err1 := f.Close(); err == nil { + err = err1 + } + return err +} + +// WalkDirs looks for files in the given dir and returns a list of files in it +// usage for all files in the b0x: WalkDirs("", false) +func WalkDirs(name string, includeDirsInList bool, files ...string) ([]string, error) { + f, err := FS.OpenFile(CTX, name, os.O_RDONLY, 0) + if err != nil { + return nil, err + } + + fileInfos, err := f.Readdir(0) + if err != nil { + return nil, err + } + + err = f.Close() + if err != nil { + return nil, err + } + + for _, info := range fileInfos { + filename := path.Join(name, info.Name()) + + if includeDirsInList || !info.IsDir() { + files = append(files, filename) + } + + if info.IsDir() { + files, err = WalkDirs(filename, includeDirsInList, files...) + if err != nil { + return nil, err + } + } + } + + return files, nil +} diff --git a/installer/whoami.go b/installer/whoami.go new file mode 100644 index 0000000..e3f5e71 --- /dev/null +++ b/installer/whoami.go @@ -0,0 +1,15 @@ +// +build !windows + +package installer + +import "os/user" + +func isPrivileged() bool { + u, err := user.Current() + if nil != err { + return false + } + + // not quite, but close enough for now + return "0" == u.Uid +} diff --git a/installer/whoami_windows.go b/installer/whoami_windows.go new file mode 100644 index 0000000..721da06 --- /dev/null +++ b/installer/whoami_windows.go @@ -0,0 +1,43 @@ +package installer + +import ( + "fmt" + "os" + + "golang.org/x/sys/windows" +) + +func isPrivileged() bool { + var sid *windows.SID + + // Although this looks scary, it is directly copied from the + // official windows documentation. The Go API for this is a + // direct wrap around the official C++ API. + // See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-checktokenmembership + err := windows.AllocateAndInitializeSid( + &windows.SECURITY_NT_AUTHORITY, + 2, + windows.SECURITY_BUILTIN_DOMAIN_RID, + windows.DOMAIN_ALIAS_RID_ADMINS, + 0, 0, 0, 0, 0, 0, + &sid) + if err != nil { + // we don't believe this _can_ return an error with the given inputs + // and if it does, the important info is still the false + fmt.Fprintf(os.Stderr, "warning: Unexpected Windows UserID Error: %s\n", err) + return false + } + + // This appears to cast a null pointer so I'm not sure why this + // works, but this guy says it does and it Works for Me™: + // https://github.com/golang/go/issues/28804#issuecomment-438838144 + token := windows.Token(0) + + isAdmin, err := token.IsMember(sid) + if err != nil { + fmt.Fprintf(os.Stderr, "warning: Unexpected Windows Permission ID Error: %s\n", err) + return false + } + + return isAdmin || token.IsElevated() +} diff --git a/serviceman.go b/serviceman.go new file mode 100644 index 0000000..7634c00 --- /dev/null +++ b/serviceman.go @@ -0,0 +1,105 @@ +//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver + +package main + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strings" + "time" + + "git.rootprojects.org/root/go-serviceman/installer" +) + +var GitRev = "000000000" +var GitVersion = "v0.0.0" +var GitTimestamp = time.Now().Format(time.RFC3339) + +func main() { + conf := &installer.Config{ + Restart: true, + } + + args := []string{} + for i := range os.Args { + if "--" == os.Args[i] { + if len(os.Args) > i+1 { + args = os.Args[i+1:] + } + os.Args = os.Args[:i] + break + } + } + conf.Argv = args + conf.Args = strings.Join(conf.Argv, " ") + + forUser := false + forSystem := false + 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.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.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(&forUser, "user", false, "install user space / user mode service even when admin/root/sudo/elevated") + flag.StringVar(&conf.User, "username", "", "run the service as this user") + 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.Parse() + args = flag.Args() + + if forUser && forSystem { + fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?") + os.Exit(1) + } + if forUser { + conf.System = false + } else if forSystem { + conf.System = true + } else { + conf.System = installer.IsPrivileged() + } + + n := len(args) + if 0 == n { + fmt.Println("Usage: serviceman install ./foo-app -- --foo-arg") + os.Exit(1) + } + + execpath, err := installer.WhereIs(args[0]) + if nil != err { + fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.", args[0]) + os.Exit(1) + } + args[0] = execpath + conf.Exec = args[0] + args = args[1:] + + if n >= 2 { + conf.Interpreter = conf.Exec + conf.Exec = args[0] + conf.Argv = append(args[1:], conf.Argv...) + } + + if "" == conf.Name { + ext := filepath.Ext(conf.Exec) + base := filepath.Base(conf.Exec[:len(conf.Exec)-len(ext)]) + conf.Name = strings.ToLower(base) + } + if "" == conf.Title { + conf.Title = conf.Name + } + if "" == conf.ReverseDNS { + conf.ReverseDNS = "com.example." + conf.Name + } + + fmt.Printf("\n%#v\n\n", conf) + + err = installer.Install(conf) + if nil != err { + log.Fatal(err) + } +} diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 0000000..3a01bb2 --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,8 @@ +// +build tools + +package tools + +import ( + _ "git.rootprojects.org/root/go-gitver" + _ "github.com/UnnoTed/fileb0x" +)