WIP generates osx file as well

This commit is contained in:
AJ ONeal 2019-06-27 02:54:51 -06:00
parent 1e9f95295d
commit ae809d5d5e
19 changed files with 472 additions and 111 deletions

4
.gitignore vendored
View File

@ -1,6 +1,4 @@
installer
watchdog.service
/cmd/install/static
/cmd/watchdog/installer/static
/watchdog
/cmd/watchdog/watchdog
xversion.go

View File

@ -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()
}

View File

@ -1,7 +0,0 @@
package main
import "log"
func install() {
log.Fatal("not yet implemented")
}

View File

@ -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)
}

63
cmd/watchdog/install.go Normal file
View File

@ -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)
}
}

View File

@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>{{ .Title }}</string>
<key>ProgramArguments</key>
<array>
{{- if .Interpreter }}
<string>{{ .Interpreter }}</string>
{{- end }}
<string>{{ .Exec }}</string>
{{- if .Argv }}
{{- range $arg := .Argv }}
<string>{{ $arg }}</string>
{{- end }}
{{- end }}
</array>
<key>EnvironmentVariables</key>
{{- if .Envs }}
<dict>
{{- range $key, $value := .Envs }}
<key>{{ $key }}</key>
<string>{{ $value }}</string>
{{- end }}
</dict>
{{- end }}
{{if .User -}}
<key>UserName</key>
<string>{{ .User }}</string>
<key>GroupName</key>
<string>{{ .Group }}</string>
<key>InitGroups</key>
<true/>
{{end -}}
<key>RunAtLoad</key>
<true/>
{{ if .Restart -}}
<key>KeepAlive</key>
<true/>
<!--dict>
<key>Crashed</key>
<true/>
<key>NetworkState</key>
<true/>
<key>SuccessfulExit</key>
<false/>
</dict-->
{{ end -}}
{{ if .Production -}}
<key>SoftResourceLimits</key>
<dict>
<key>NumberOfFiles</key>
<integer>8192</integer>
</dict>
<key>HardResourceLimits</key>
<dict/>
{{ end -}}
<key>WorkingDirectory</key>
<string>{{ .Local }}/opt/{{ .Name }}</string>
<key>StandardErrorPath</key>
<string>{{ .LogDir }}/{{ .Name }}.log</string>
<key>StandardOutPath</key>
<string>{{ .LogDir }}/{{ .Name }}.log</string>
</dict>
</plist>

View File

@ -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 }}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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")
}

View File

@ -0,0 +1,7 @@
// +build !windows,!linux,!darwin
package installer
func install(c *Config) error {
return nil, nil
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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
}