diff --git a/README.md b/README.md
index 6c2c600..74db9e5 100644
--- a/README.md
+++ b/README.md
@@ -2,34 +2,347 @@
A cross-platform service manager.
-Goal:
+Because debugging launchctl, systemd, etc absolutely sucks!
+
+...and I wanted a reasonable way to install [Telebit](https://telebit.io) on Windows.
+(see more in the **Why** section below)
+
+
+User Mode Services
+ * `sytemctl --user` on Linux
+ * `launchctl` on MacOS
+ * `HKEY_CURRENT_USER/.../Run` on Windows
+
+
+System Services
+ * `sudo sytemctl` on Linux
+ * `sudo launchctl` on MacOS
+ * _not yet implemented_ on Windows
+
+
+- **Install**
+- **Usage**
+- **Build**
+- **Examples**
+ - compiled programs
+ - scripts
+ - bash
+ - node
+ - python
+ - ruby
+- **Logging**
+- **Windows**
+- **Debugging**
+- **Why**
+- **Legal**
+
+# Install
+
+Download `serviceman` for
+
+- [MacOS (64-bit darwin)](https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman)
+- [Windows 10 (64-bit)](https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.exe)
+- [Windows 10 (32-bit)](https://rootprojects.org/serviceman/dist/windows/386/serviceman.exe)
+- [Linux (64-bit)](https://rootprojects.org/serviceman/dist/linux/amd64/serviceman)
+- [Linux (32-bit)](https://rootprojects.org/serviceman/dist/linux/386/serviceman)
+- [Raspberry Pi 4 (64-bit armv8)](https://rootprojects.org/serviceman/dist/linux/armv8/serviceman)
+- [Raspberry Pi 3 (armv7)](https://rootprojects.org/serviceman/dist/linux/armv7/serviceman)
+- [Raspberry Pi 2 (armv6)](https://rootprojects.org/serviceman/dist/linux/armv6/serviceman)
+- [Raspberry Pi Zero (armv5)](https://rootprojects.org/serviceman/dist/linux/armv5/serviceman)
+
+# Usage
+
+```bash
+serviceman add [options] [interpreter] -- [service options]
+```
+
+```bash
+serviceman add --help
+```
+
+```bash
+serviceman version
+```
+
+# Examples
+
+**Compiled Apps**
+
+Normally you might run your program something like this:
+
+```bash
+dinglehopper --port 8421
+```
+
+Adding a service for that program with `serviceman` would look like this:
+
+> **serviceman add** dinglehopper **--** --port 8421
+
+`serviceman` will find `dinglehopper` in your PATH, but if you have
+any arguments with relative paths, you should switch to using absolute paths.
+
+```bash
+dinglehopper --config ./conf.json
+```
+
+becomes
+
+> **serviceman add** dinglehopper **--** --config **/Users/aj/dinglehopper/conf.json**
+
+
+Using with scripts
+
+Although your text script may be executable, you'll need to specify the interpreter
+in order for `serviceman` to configure the service correctly.
+
+For example, if you had a bash script that you normally ran like this:
+
+```bash
+./snarfblat.sh --port 8421
+```
+
+You'd create a system service for it like this:
+
+> serviceman add **bash** ./snarfblat.sh **--** --port 8421
+
+`serviceman` will resolve `./snarfblat.sh` correctly because it comes
+before the **--**.
+
+**Background Information**
+
+An operating system can't "run" text files (even if the executable bit is set).
+
+Scripts require an _interpreter_. Often this is denoted at the top of
+"executable" scripts with something like one of these:
```bash
-serviceman install [options] [interpreter] [-- [options]]
+#!/usr/bin/env ruby
```
```bash
-serviceman install --user ./foo-app -- -c ./
+#!/usr/bin/python
```
+However, sometimes people get fancy and pass arguments to the interpreter,
+like this:
+
```bash
-serviceman install --user /usr/local/bin/node ./whatever.js -- -c ./
+#!/usr/local/bin/node --harmony --inspect
```
+
+
+
+Using with node.js
+
+If normally you run your node script something like this:
+
```bash
-serviceman run --config conf.json
+node ./demo.js --foo bar --baz
```
+Then you would add it as a system service like this:
+
+> **serviceman add** node ./demo.js **--** --foo bar --baz
+
+It is important that you specify `node ./demo.js` and not just `./demo.js`
+
+See **Using with scripts** for more detailed information.
+
+
+
+
+Using with python
+
+If normally you run your python script something like this:
+
+```bash
+python ./demo.py --foo bar --baz
+```
+
+Then you would add it as a system service like this:
+
+> **serviceman add** python ./demo.py **--** --foo bar --baz
+
+It is important that you specify `python ./demo.py` and not just `./demo.py`
+
+See **Using with scripts** for more detailed information.
+
+
+
+
+Using with ruby
+
+If normally you run your ruby script something like this:
+
+```bash
+ruby ./demo.rb --foo bar --baz
+```
+
+Then you would add it as a system service like this:
+
+> **serviceman add** ruby ./demo.rb **--** --foo bar --baz
+
+It is important that you specify `ruby ./demo.rb` and not just `./demo.rb`
+
+See **Using with scripts** for more detailed information.
+
+
+
+# Logging
+
+When you run `serviceman add` it will either give you an error or
+will print out the location where logs will be found.
+
+By default it's one of these:
+
+```txt
+~/.local/share//var/log/.log
+```
+
+```txt
+/var/log//var/log/.log
+```
+
+You set it with one of these:
+
+- `--logdir ` (cli)
+- `"logdir": ""` (json)
+- `Logdir: ""` (go)
+
+If anything about the logging sucks, tell me... unless they're your logs
+(which they probably are), in which case _you_ should fix them.
+
+That said, my goal is that it shouldn't take an IT genius to interpret
+why your app failed to start.
+
+# Peculiarities of Windows
+
+Windows doesn't have a userspace daemon launcher.
+This means that if your application crashes, it won't automatically restart.
+
+However, `serviceman` handles this by not directly adding your application
+to `HKEY_CURRENT_USER/.../Run`, but rather installing a copy of _itself_
+instead, which runs your application and automatically restarts it whenever it
+exits.
+
+If the application fails to start `serviceman` will retry continually,
+but it does have an exponential backoff of up to 1 minute between failed
+restart attempts.
+
+See the bit on `serviceman run` in the **Debugging** section down below for more information.
+
+# Debugging
+
+One of the most irritating problems with all of these launchers is that they're
+terrible to debug - it's often difficult to find the logs, and nearly impossible
+to interpret them, if they exist at all.
+
+The config files generate by `serviceman` are simple, template-generated and
+tested, and therefore gauranteed to work - **_if_** your
+application runs with the parameters given, which is big 'if'.
+
+`serviceman` tries to make sure that all necessary files and folders
+exist and give clear error messages if they don't (be sure to check the logs,
+mentioned above).
+
+There's also a `run` utility that can be used to test that the parameters
+you've given are being interpreted correctly (absolute paths and such).
+
+```bash
+serviceman run --config ./conf.json
+```
+
+Where `conf.json` looks something like
+
+**For Binaries**:
+
+```json
+{
+ "title": "Demo",
+ "exec": "/Users/aj/go-demo/demo",
+ "argv": ["--foo", "bar", "--baz", "qux"]
+}
+```
+
+**For Scripts**:
+
+Scripts can't be run directly. They require a binary `interpreter` - bash, node, ruby, python, etc.
+
+If you're running from the folder containing `./demo.js`,
+and `node.exe` is in your PATH, then you can use executable
+names and relative paths.
+
```json
{
- "interpreter": "/Program Files (x86)/node/node.exe",
- "exec": "/Users/aj/demo/demo.js",
- "argv": ["--foo", "bar", "--baz", "qux"]
+ "title": "Demo",
+ "interpreter": "node.exe",
+ "exec": "./bin/demo.js",
+ "argv": ["--foo", "bar", "--baz", "qux"]
}
```
+That's equivalent to this:
+
+```json
+{
+ "title": "Demo",
+
+ "name": "demo",
+
+ "exec": "node.exe",
+ "argv": ["./bin/demo.js", "--foo", "bar", "--baz", "qux"]
+}
+```
+
+Making `add` and `run` take the exact same arguments is on the TODO list.
+The fact that they don't is an artifact of `run` being created specifically
+for Windows.
+
+If you have gripes about it, tell me. It shouldn't suck. That's the goal anyway.
+
+# Building
+
+```bash
+git clone https://git.coolaj86.com/coolaj86/go-serviceman.git
+```
+
+```bash
+pushd ./go-serviceman
+```
+
```bash
go generate -mod=vendor ./...
-go build -mod=vendor -ldflags "-H=windowsgui"
-.\\go-serviceman node ./demo.js -- --foo bar --baz qux
-```
\ No newline at end of file
+```
+
+**Windows**:
+
+```bash
+go build -mod=vendor -ldflags "-H=windowsgui" -o serviceman.exe
+```
+
+**Linux, MacOS**:
+
+```bash
+go build -mod=vendor -o /usr/local/bin/serviceman
+```
+
+# Why
+
+I created this for two reasons:
+
+1. Too often I just run services in `screen -xRS foo` because systemd `.service` files are way too hard to get right and even harder to debug. I make stupid typos or config mistakes and get it wrong. Then I get a notice 18 months later from digital ocean that NYC region 3 is being rebooted and to expect 5 seconds of downtime... and I don't remember if I remembered to go back and set up that service with systemd or not.
+2. To make it easier for people to install [Telebit](https://telebit.io) on Windows.
+
+
+
+# Legal
+
+[serviceman](https://git.coolaj86.com/coolaj86/go-serviceman) |
+MPL-2.0 |
+[Terms of Use](https://therootcompany.com/legal/#terms) |
+[Privacy Policy](https://therootcompany.com/legal/#privacy)
+
+Copyright 2019 AJ ONeal.
+
+
diff --git a/build-all.sh b/build-all.sh
new file mode 100644
index 0000000..0eb5b5d
--- /dev/null
+++ b/build-all.sh
@@ -0,0 +1,41 @@
+#GOOS=windows GOARCH=amd64 go install
+#go tool dist list
+
+# TODO move this into tools/build.go
+
+export CGO_ENABLED=0
+exe=serviceman
+gocmd=.
+
+echo ""
+go generate -mod=vendor ./...
+
+echo ""
+echo "Windows amd64"
+GOOS=windows GOARCH=amd64 go build -mod=vendor -o dist/windows/amd64/${exe}.exe -ldflags "-H=windowsgui" $gocmd
+echo "Windows 386"
+GOOS=windows GOARCH=386 go build -mod=vendor -o dist/windows/386/${exe}.exe -ldflags "-H=windowsgui" $gocmd
+
+echo ""
+echo "Darwin (macOS) amd64"
+GOOS=darwin GOARCH=amd64 go build -mod=vendor -o dist/darwin/amd64/${exe} $gocmd
+
+echo ""
+echo "Linux amd64"
+GOOS=linux GOARCH=amd64 go build -mod=vendor -o dist/linux/amd64/${exe} $gocmd
+echo "Linux 386"
+GOOS=linux GOARCH=386 go build -mod=vendor -o dist/linux/386/${exe} $gocmd
+
+echo ""
+echo "RPi 4 (64-bit) ARMv8"
+GOOS=linux GOARCH=arm64 go build -mod=vendor -o dist/linux/armv8/${exe} $gocmd
+echo "RPi 3 B+ ARMv7"
+GOOS=linux GOARCH=arm GOARM=7 go build -mod=vendor -o dist/linux/armv7/${exe} $gocmd
+echo "ARMv6"
+GOOS=linux GOARCH=arm GOARM=6 go build -mod=vendor -o dist/linux/armv6/${exe} $gocmd
+echo "RPi Zero ARMv5"
+GOOS=linux GOARCH=arm GOARM=5 go build -mod=vendor -o dist/linux/armv5/${exe} $gocmd
+
+echo ""
+rsync -av ./dist/ ubuntu@rootprojects.org:/srv/www/rootprojects.org/serviceman/dist/
+# https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.exe
diff --git a/installer/b0x.toml b/manager/b0x.toml
similarity index 100%
rename from installer/b0x.toml
rename to manager/b0x.toml
diff --git a/installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl b/manager/dist/Library/LaunchDaemons/_rdns_.plist.tmpl
similarity index 100%
rename from installer/dist/Library/LaunchDaemons/_rdns_.plist.tmpl
rename to manager/dist/Library/LaunchDaemons/_rdns_.plist.tmpl
diff --git a/installer/dist/etc/systemd/system/_name_.service.tmpl b/manager/dist/etc/systemd/system/_name_.service.tmpl
similarity index 100%
rename from installer/dist/etc/systemd/system/_name_.service.tmpl
rename to manager/dist/etc/systemd/system/_name_.service.tmpl
diff --git a/installer/doc.go b/manager/doc.go
similarity index 58%
rename from installer/doc.go
rename to manager/doc.go
index ec2b059..9c757e8 100644
--- a/installer/doc.go
+++ b/manager/doc.go
@@ -1,7 +1,7 @@
-// Package installer can be used cross-platform to install apps
+// Package manager can be used cross-platform to add apps
// as either userspace or system services for fairly simple applications.
-// This is not intended for complex installers.
+// This is not intended for complex or highly platform-specific service 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
+package manager
diff --git a/installer/filesystem.go b/manager/filesystem.go
similarity index 96%
rename from installer/filesystem.go
rename to manager/filesystem.go
index d0395b5..baaebec 100644
--- a/installer/filesystem.go
+++ b/manager/filesystem.go
@@ -1,4 +1,4 @@
-package installer
+package manager
import (
"io"
diff --git a/installer/install.go b/manager/install.go
similarity index 98%
rename from installer/install.go
rename to manager/install.go
index 18ffd4d..70e0c25 100644
--- a/installer/install.go
+++ b/manager/install.go
@@ -1,6 +1,6 @@
//go:generate go run -mod=vendor github.com/UnnoTed/fileb0x b0x.toml
-package installer
+package manager
import (
"fmt"
diff --git a/installer/install_darwin.go b/manager/install_darwin.go
similarity index 95%
rename from installer/install_darwin.go
rename to manager/install_darwin.go
index 5ff21bd..a2ea38e 100644
--- a/installer/install_darwin.go
+++ b/manager/install_darwin.go
@@ -1,4 +1,4 @@
-package installer
+package manager
import (
"bytes"
@@ -9,7 +9,7 @@ import (
"strings"
"text/template"
- "git.rootprojects.org/root/go-serviceman/installer/static"
+ "git.rootprojects.org/root/go-serviceman/manager/static"
"git.rootprojects.org/root/go-serviceman/service"
)
diff --git a/installer/install_linux.go b/manager/install_linux.go
similarity index 95%
rename from installer/install_linux.go
rename to manager/install_linux.go
index 3aa2f39..5d29443 100644
--- a/installer/install_linux.go
+++ b/manager/install_linux.go
@@ -1,4 +1,4 @@
-package installer
+package manager
import (
"bytes"
@@ -8,7 +8,7 @@ import (
"path/filepath"
"text/template"
- "git.rootprojects.org/root/go-serviceman/installer/static"
+ "git.rootprojects.org/root/go-serviceman/manager/static"
"git.rootprojects.org/root/go-serviceman/service"
)
diff --git a/installer/install_other.go b/manager/install_other.go
similarity index 89%
rename from installer/install_other.go
rename to manager/install_other.go
index 3a88a5b..ff4c411 100644
--- a/installer/install_other.go
+++ b/manager/install_other.go
@@ -1,6 +1,6 @@
// +build !windows,!linux,!darwin
-package installer
+package manager
import (
"git.rootprojects.org/root/go-serviceman/service"
diff --git a/installer/install_windows.go b/manager/install_windows.go
similarity index 99%
rename from installer/install_windows.go
rename to manager/install_windows.go
index 2c2554c..e466644 100644
--- a/installer/install_windows.go
+++ b/manager/install_windows.go
@@ -1,4 +1,4 @@
-package installer
+package manager
import (
"encoding/json"
diff --git a/installer/static/ab0x.go b/manager/static/ab0x.go
similarity index 99%
rename from installer/static/ab0x.go
rename to manager/static/ab0x.go
index b2497db..686907b 100644
--- a/installer/static/ab0x.go
+++ b/manager/static/ab0x.go
@@ -1,4 +1,4 @@
-// Code generated by fileb0x at "2019-07-02 00:24:56.633505 -0600 MDT m=+0.003518020" from config file "b0x.toml" DO NOT EDIT.
+// Code generated by fileb0x at "2019-07-04 01:21:07.139664 -0600 MDT m=+0.003157447" from config file "b0x.toml" DO NOT EDIT.
// modification hash(7ce890c82a9c0a430fe55cf7f579e8b4.acdb557394f98d3c09c0bb4d4b9142f8)
package static
diff --git a/installer/whoami.go b/manager/whoami.go
similarity index 91%
rename from installer/whoami.go
rename to manager/whoami.go
index e3f5e71..eb54b02 100644
--- a/installer/whoami.go
+++ b/manager/whoami.go
@@ -1,6 +1,6 @@
// +build !windows
-package installer
+package manager
import "os/user"
diff --git a/installer/whoami_windows.go b/manager/whoami_windows.go
similarity index 98%
rename from installer/whoami_windows.go
rename to manager/whoami_windows.go
index 721da06..504e907 100644
--- a/installer/whoami_windows.go
+++ b/manager/whoami_windows.go
@@ -1,4 +1,4 @@
-package installer
+package manager
import (
"fmt"
diff --git a/serviceman.go b/serviceman.go
index 9d40a6e..0718eea 100644
--- a/serviceman.go
+++ b/serviceman.go
@@ -12,7 +12,7 @@ import (
"strings"
"time"
- "git.rootprojects.org/root/go-serviceman/installer"
+ "git.rootprojects.org/root/go-serviceman/manager"
"git.rootprojects.org/root/go-serviceman/runner"
"git.rootprojects.org/root/go-serviceman/service"
)
@@ -22,7 +22,7 @@ var GitVersion = "v0.0.0"
var GitTimestamp = time.Now().Format(time.RFC3339)
func usage() {
- fmt.Println("Usage: serviceman install ./foo-app -- --foo-arg")
+ fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
fmt.Println("Usage: serviceman run --config ./foo-app.json")
}
@@ -36,8 +36,10 @@ func main() {
top := os.Args[1]
os.Args = append(os.Args[:1], os.Args[2:]...)
switch top {
- case "install":
- install()
+ case "version":
+ fmt.Println(GitVersion, GitTimestamp, GitRev)
+ case "add":
+ add()
case "run":
run()
default:
@@ -47,7 +49,7 @@ func main() {
}
}
-func install() {
+func add() {
conf := &service.Service{
Restart: true,
}
@@ -73,8 +75,8 @@ func install() {
flag.StringVar(&conf.URL, "url", "", "the documentation on home page of the service")
//flag.StringVar(&conf.Workdir, "workdir", "", "the directory in which the service should be started")
flag.StringVar(&conf.ReverseDNS, "rdns", "", "a plist-friendly Reverse DNS name for launchctl (ex: com.example.foo-app)")
- flag.BoolVar(&forSystem, "system", false, "attempt to install system service as an unprivileged/unelevated user")
- flag.BoolVar(&forUser, "user", false, "install user space / user mode service even when admin/root/sudo/elevated")
+ flag.BoolVar(&forSystem, "system", false, "attempt to add system service as an unprivileged/unelevated user")
+ flag.BoolVar(&forUser, "user", false, "add user space / user mode service even when admin/root/sudo/elevated")
flag.BoolVar(&force, "force", false, "if the interpreter or executable doesn't exist, or things don't make sense, try anyway")
flag.StringVar(&conf.User, "username", "", "run the service as this user")
flag.StringVar(&conf.Group, "groupname", "", "run the service as this group")
@@ -92,17 +94,17 @@ func install() {
} else if forSystem {
conf.System = true
} else {
- conf.System = installer.IsPrivileged()
+ conf.System = manager.IsPrivileged()
}
n := len(args)
if 0 == n {
- fmt.Println("Usage: serviceman install ./foo-app -- --foo-arg")
+ fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
os.Exit(2)
return
}
- execpath, err := installer.WhereIs(args[0])
+ execpath, err := manager.WhereIs(args[0])
if nil != err {
fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", args[0])
if !force {
@@ -125,11 +127,11 @@ func install() {
//fmt.Printf("\n%#v\n\n", conf)
- err = installer.Install(conf)
+ err = manager.Install(conf)
if nil != err {
fmt.Fprintf(os.Stderr, "%s\n", err)
- fmt.Fprintf(os.Stderr, "Use 'sudo' to install as a privileged system service.\n")
- fmt.Fprintf(os.Stderr, "Use '--user' to install as an user service.\n")
+ fmt.Fprintf(os.Stderr, "Use 'sudo' to add service as a privileged system service.\n")
+ fmt.Fprintf(os.Stderr, "Use '--user' to add service as an user service.\n")
}
}