From ae809d5d5ef087cae74f031d3cafb6c18a3fb012 Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Thu, 27 Jun 2019 02:54:51 -0600 Subject: [PATCH] WIP generates osx file as well --- .gitignore | 4 +- cmd/install/install.go | 23 ------ cmd/install/install_darwin.go | 7 -- cmd/install/install_linux.go | 60 ---------------- cmd/watchdog/install.go | 63 ++++++++++++++++ cmd/{install => watchdog/installer}/b0x.toml | 0 .../Library/LaunchDaemons/_rdns_.plist.tmpl | 71 +++++++++++++++++++ .../etc/systemd/system/_name_.service.tmpl} | 36 ++++++---- cmd/watchdog/installer/filesystem.go | 23 ++++++ cmd/watchdog/installer/install.go | 63 ++++++++++++++++ cmd/watchdog/installer/install_darwin.go | 64 +++++++++++++++++ cmd/watchdog/installer/install_linux.go | 65 +++++++++++++++++ .../installer}/install_windows.go | 8 +-- cmd/watchdog/installer/unknownos.go | 7 ++ cmd/watchdog/installer/watchdog.service | 29 ++++++++ cmd/watchdog/installer/whoami.go | 15 ++++ cmd/watchdog/installer/whoami_windows.go | 26 +++++++ cmd/watchdog/watchdog.go | 9 ++- cmd/watchdog/watchdog.service.json | 10 +++ 19 files changed, 472 insertions(+), 111 deletions(-) delete mode 100644 cmd/install/install.go delete mode 100644 cmd/install/install_darwin.go delete mode 100644 cmd/install/install_linux.go create mode 100644 cmd/watchdog/install.go rename cmd/{install => watchdog/installer}/b0x.toml (100%) create mode 100644 cmd/watchdog/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl rename cmd/{install/dist/etc/systemd/system/watchdog.service.tmpl => watchdog/installer/dist/etc/systemd/system/_name_.service.tmpl} (66%) create mode 100644 cmd/watchdog/installer/filesystem.go create mode 100644 cmd/watchdog/installer/install.go create mode 100644 cmd/watchdog/installer/install_darwin.go create mode 100644 cmd/watchdog/installer/install_linux.go rename cmd/{install => watchdog/installer}/install_windows.go (82%) create mode 100644 cmd/watchdog/installer/unknownos.go create mode 100644 cmd/watchdog/installer/watchdog.service create mode 100644 cmd/watchdog/installer/whoami.go create mode 100644 cmd/watchdog/installer/whoami_windows.go create mode 100644 cmd/watchdog/watchdog.service.json diff --git a/.gitignore b/.gitignore index d96d1c4..dbc89fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ -installer -watchdog.service -/cmd/install/static +/cmd/watchdog/installer/static /watchdog /cmd/watchdog/watchdog xversion.go diff --git a/cmd/install/install.go b/cmd/install/install.go deleted file mode 100644 index 6ed3143..0000000 --- a/cmd/install/install.go +++ /dev/null @@ -1,23 +0,0 @@ -//go:generate go run -mod=vendor github.com/UnnoTed/fileb0x b0x.toml - -// 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 main - -type Config struct { - Name string `json:"name"` - Desc string `json:"desc"` - URL string `json:"url"` - Exec string `json:"exec"` - Args string `json:"args"` - User string `json:"user"` - Group string `json:"group"` - Production bool `json:"production"` - PrivilegedPorts bool `json:"privileged_ports"` - MultiuserProtection bool `json:"multiuser_protection"` -} - -func main() { - - install() -} diff --git a/cmd/install/install_darwin.go b/cmd/install/install_darwin.go deleted file mode 100644 index bb61051..0000000 --- a/cmd/install/install_darwin.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "log" - -func install() { - log.Fatal("not yet implemented") -} diff --git a/cmd/install/install_linux.go b/cmd/install/install_linux.go deleted file mode 100644 index f5dbd6e..0000000 --- a/cmd/install/install_linux.go +++ /dev/null @@ -1,60 +0,0 @@ -// +build !windows !darwin -package main - -import ( - "bytes" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "text/template" - - "git.rootprojects.org/root/watchdog.go/cmd/install/static" -) - -func install() { - b, err := static.ReadFile("dist/etc/systemd/system/watchdog.service.tmpl") - if err != nil { - log.Fatal(err) - return - } - s := string(b) - - j, err := static.ReadFile("dist/etc/systemd/system/watchdog.service.json") - if err != nil { - log.Fatal(err) - return - } - - //conf := map[string]string{} - conf := &Config{} - err = json.Unmarshal(j, &conf) - if nil != err { - log.Fatal(err) - return - } - if "" == conf.Group { - conf.Group = conf.User - } - - serviceFile := conf.Exec + ".service" - - rw := &bytes.Buffer{} - // not sure what the template name does, but whatever - tmpl, err := template.New("service").Parse(s) - if err != nil { - log.Fatal(err) - return - } - - err = tmpl.Execute(rw, conf) - if nil != err { - log.Fatal(err) - return - } - - if err := ioutil.WriteFile(serviceFile, rw.Bytes(), 0644); err != nil { - log.Fatalf("ioutil.WriteFile error: %v", err) - } - fmt.Printf("Wrote %q\n", serviceFile) -} diff --git a/cmd/watchdog/install.go b/cmd/watchdog/install.go new file mode 100644 index 0000000..84a8d9b --- /dev/null +++ b/cmd/watchdog/install.go @@ -0,0 +1,63 @@ +package main + +import ( + "fmt" + "log" + "os" + "strings" + + "git.rootprojects.org/root/watchdog.go/cmd/watchdog/installer" +) + +func install(binpath string, args []string) { + system := true + production := false + config := "./config.json" + for i := range os.Args { + switch { + case strings.HasSuffix(os.Args[i], "userspace"): + system = false + case strings.HasSuffix(os.Args[i], "production"): + fmt.Println("Warning: production options don't work on all systems. If you have trouble, drop this first.") + production = false + case "-c" == os.Args[i]: + if len(os.Args) <= i+1 { + fmt.Println("-c requires a string path to the config file") + os.Exit(1) + } + config = os.Args[i+1] + } + } + /* + j, err := static.ReadFile("dist/etc/systemd/system/watchdog.service.json") + if err != nil { + log.Fatal(err) + return + } + + //conf := map[string]string{} + conf := &Config{} + err = json.Unmarshal(j, &conf) + if nil != err { + log.Fatal(err) + return + } + */ + err := installer.Install(&installer.Config{ + Title: "Watchdog", + Desc: "Get notified when sites go down", + URL: "https://git.rootprojects.org/root/watchdog.go", + Name: "watchdog", + Exec: "watchdog", + Local: "", + System: system, + Restart: true, + Argv: []string{"-c", config}, + PrivilegedPorts: false, + MultiuserProtection: false, + Production: production, + }) + if nil != err { + log.Fatal(err) + } +} diff --git a/cmd/install/b0x.toml b/cmd/watchdog/installer/b0x.toml similarity index 100% rename from cmd/install/b0x.toml rename to cmd/watchdog/installer/b0x.toml diff --git a/cmd/watchdog/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl b/cmd/watchdog/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl new file mode 100644 index 0000000..8971ccb --- /dev/null +++ b/cmd/watchdog/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl @@ -0,0 +1,71 @@ + + + + + Label + {{ .Title }} + ProgramArguments + + {{- if .Interpreter }} + {{ .Interpreter }} + {{- end }} + {{ .Exec }} + {{- if .Argv }} + {{- range $arg := .Argv }} + {{ $arg }} + {{- end }} + {{- end }} + + EnvironmentVariables + {{- if .Envs }} + + {{- 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/cmd/install/dist/etc/systemd/system/watchdog.service.tmpl b/cmd/watchdog/installer/dist/etc/systemd/system/_name_.service.tmpl similarity index 66% rename from cmd/install/dist/etc/systemd/system/watchdog.service.tmpl rename to cmd/watchdog/installer/dist/etc/systemd/system/_name_.service.tmpl index 438c903..2af4774 100644 --- a/cmd/install/dist/etc/systemd/system/watchdog.service.tmpl +++ b/cmd/watchdog/installer/dist/etc/systemd/system/_name_.service.tmpl @@ -1,18 +1,24 @@ # Pre-req -# sudo adduser {{ .Exec }} --home /opt/{{ .Exec }} -# sudo mkdir -p /opt/{{ .Exec }}/ /var/log/{{ .Exec }} -# sudo chown -R {{ .Exec }}:{{ .Exec }} /opt/{{ .Exec }}/ /var/log/{{ .Exec }} +# sudo mkdir -p {{ .Local }}/opt/{{ .Name }}/ {{ .Local }}/var/log/{{ .Name }} +{{ if not .Local -}} +{{ if .User -}} +# sudo adduser {{ .User }} --home /opt/{{ .Name }} +# sudo chown -R {{ .User }}:{{ .Group }} /opt/{{ .Name }}/ /var/log/{{ .Name }} +{{- end }} +{{- end }} # Post-install -# sudo systemctl daemon-reload -# sudo systemctl restart {{ .Exec }}.service -# sudo journalctl -xefu {{ .Exec }} +# 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={{ .Name }} - {{ .Desc }} +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) @@ -28,8 +34,8 @@ User={{ .User }} Group={{ .Group }} {{ end -}} -WorkingDirectory=/opt/{{ .Exec }} -ExecStart=/opt/{{ .Exec }}/{{ .Exec }} {{ .Args }} +WorkingDirectory={{ .Local }}/opt/{{ .Name }} +ExecStart={{if .Interpreter }}{{ .Interpreter }} {{ end }}{{ .Local }}/opt/{{ .Name }}/{{ .Name }} {{ .Args }} ExecReload=/bin/kill -USR1 $MAINPID {{if .Production -}} @@ -49,14 +55,14 @@ PrivateDevices=true ProtectHome=true # Make /usr, /boot, /etc and possibly some more folders read-only. ProtectSystem=full -# ... except /opt/{{ .Exec }} because we want a place for the database -# and /var/log/{{ .Exec }} because we want a place where logs can go. +# ... 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/{{ .Exec }} /var/log/{{ .Exec }} +ReadWriteDirectories=/opt/{{ .Name }} /var/log/{{ .Name }} # Note: in v231 and above ReadWritePaths has been renamed to ReadWriteDirectories -; ReadWritePaths=/opt/{{ .Exec }} /var/log/{{ .Exec }} +; ReadWritePaths=/opt/{{ .Name }} /var/log/{{ .Name }} {{ end -}} {{if .PrivilegedPorts -}} @@ -75,4 +81,8 @@ NoNewPrivileges=true {{ end -}} [Install] +{{ if not .Local -}} WantedBy=multi-user.target +{{- else -}} +WantedBy=default.target +{{- end }} diff --git a/cmd/watchdog/installer/filesystem.go b/cmd/watchdog/installer/filesystem.go new file mode 100644 index 0000000..c41f2b9 --- /dev/null +++ b/cmd/watchdog/installer/filesystem.go @@ -0,0 +1,23 @@ +package installer + +// "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 + +import ( + "io" + "os" +) + +// Same as http.FileSystem +type FileSystem interface { + Open(name string) (File, error) +} + +// 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/cmd/watchdog/installer/install.go b/cmd/watchdog/installer/install.go new file mode 100644 index 0000000..d12afb5 --- /dev/null +++ b/cmd/watchdog/installer/install.go @@ -0,0 +1,63 @@ +//go:generate go run -mod=vendor github.com/UnnoTed/fileb0x b0x.toml + +// 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 + +import ( + "os" + "path/filepath" + "strings" +) + +type Config struct { + Title string `json:"title"` + Name string `json:"name"` + Desc string `json:"desc"` + URL string `json:"url"` + 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:"local"` + 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"` +} + +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 + } + + return nil +} diff --git a/cmd/watchdog/installer/install_darwin.go b/cmd/watchdog/installer/install_darwin.go new file mode 100644 index 0000000..7e2eb16 --- /dev/null +++ b/cmd/watchdog/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/watchdog.go/cmd/watchdog/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/cmd/watchdog/installer/install_linux.go b/cmd/watchdog/installer/install_linux.go new file mode 100644 index 0000000..869dbda --- /dev/null +++ b/cmd/watchdog/installer/install_linux.go @@ -0,0 +1,65 @@ +package installer + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "text/template" + + "git.rootprojects.org/root/watchdog.go/cmd/watchdog/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) + } + fmt.Printf("Wrote %q\n", servicePath) + + return nil +} diff --git a/cmd/install/install_windows.go b/cmd/watchdog/installer/install_windows.go similarity index 82% rename from cmd/install/install_windows.go rename to cmd/watchdog/installer/install_windows.go index c461b81..2246e90 100644 --- a/cmd/install/install_windows.go +++ b/cmd/watchdog/installer/install_windows.go @@ -1,7 +1,7 @@ -package main +package installer import ( - "log" + "fmt" //"golang.org/x/sys/windows" ) @@ -11,7 +11,7 @@ import ( // https://stackoverflow.com/questions/27366298/check-if-application-is-running-as-administrator-in-golang // https://www.reddit.com/r/golang/comments/53dthc/way_to_detect_if_the_programs_running_with/ // https://play.golang.org/p/bBtRZrk4_p -func install() { +func install(c *Config) error { //token := windows.Token(0) - log.Fatal("not yet implemented") + return fmt.Errorf("not yet implemented") } diff --git a/cmd/watchdog/installer/unknownos.go b/cmd/watchdog/installer/unknownos.go new file mode 100644 index 0000000..f78efaf --- /dev/null +++ b/cmd/watchdog/installer/unknownos.go @@ -0,0 +1,7 @@ +// +build !windows,!linux,!darwin + +package installer + +func install(c *Config) error { + return nil, nil +} diff --git a/cmd/watchdog/installer/watchdog.service b/cmd/watchdog/installer/watchdog.service new file mode 100644 index 0000000..8a49114 --- /dev/null +++ b/cmd/watchdog/installer/watchdog.service @@ -0,0 +1,29 @@ +# Pre-req +# sudo adduser watchdog --home /opt/watchdog +# sudo mkdir -p /opt/watchdog/ /var/log/watchdog +# sudo chown -R watchdog:watchdog /opt/watchdog/ /var/log/watchdog + +[Unit] +Description=Watchdog - Get notified when sites go down +Documentation=https://git.rootprojects.org/root/watchdog.go +After=network-online.target +Wants=network-online.target systemd-networkd-wait-online.service + +[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 + +# User and group the process will run as +User=root +Group=root + +WorkingDirectory=/opt/watchdog +ExecStart=/opt/watchdog -c ./config.json +ExecReload=/bin/kill -USR1 $MAINPID + +[Install] +WantedBy=multi-user.target diff --git a/cmd/watchdog/installer/whoami.go b/cmd/watchdog/installer/whoami.go new file mode 100644 index 0000000..d5a73ac --- /dev/null +++ b/cmd/watchdog/installer/whoami.go @@ -0,0 +1,15 @@ +// +build !windows + +package installer + +import "os/user" + +func IsAdmin() bool { + u, err := user.Current() + if nil != err { + return false + } + + // not quite, but close enough for now + return "0" == u.Uid +} diff --git a/cmd/watchdog/installer/whoami_windows.go b/cmd/watchdog/installer/whoami_windows.go new file mode 100644 index 0000000..5b48f06 --- /dev/null +++ b/cmd/watchdog/installer/whoami_windows.go @@ -0,0 +1,26 @@ +package installer + +import "os/user" + +func IsAdmin() { + u, err := user.Current() + if nil != err { + return false + } + + // https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems + // not quite, but close enough for now + // BUILTIN\ADMINISTRATORS + if "S-1-5-32-544" == u.Uid || "S-1-5-32-544" == u.Gid { + return true + } + + ids := u.GroupIds() + for i := range ids { + if "S-1-5-32-544" == ids[i] { + return true + } + } + + return false +} diff --git a/cmd/watchdog/watchdog.go b/cmd/watchdog/watchdog.go index db98cf7..5202b6b 100644 --- a/cmd/watchdog/watchdog.go +++ b/cmd/watchdog/watchdog.go @@ -11,7 +11,7 @@ import ( "os" "strings" - watchdog "git.rootprojects.org/root/watchdog.go" + "git.rootprojects.org/root/watchdog.go" ) var GitRev, GitVersion, GitTimestamp string @@ -31,6 +31,13 @@ func main() { case strings.HasSuffix(os.Args[i], "help"): usage() os.Exit(0) + case os.Args[i] == "install": + args := []string{} + if len(os.Args) > i+1 { + args = os.Args[i+1:] + } + install(os.Args[0], args) + os.Exit(0) } } diff --git a/cmd/watchdog/watchdog.service.json b/cmd/watchdog/watchdog.service.json new file mode 100644 index 0000000..a83e3e6 --- /dev/null +++ b/cmd/watchdog/watchdog.service.json @@ -0,0 +1,10 @@ +{ + "name": "Watchdog", + "desc": "Get notified when sites go down", + "url": "https://git.rootprojects.org/root/watchdog.go", + "exec": "watchdog", + "args": "-c ./config.json", + "user": "root", + "privileged_ports": false, + "multiuser_protection": false +}