623 lines
15 KiB
Markdown
623 lines
15 KiB
Markdown
# go-serviceman
|
|
|
|
Cross-platform service management made easy.
|
|
|
|
> sudo serviceman add --name foo ./serve.js --port 3000
|
|
|
|
> Success: "foo" started as a "launchd" SYSTEM service, running as "root"
|
|
|
|
## Why?
|
|
|
|
Because it sucks to debug launchctl, systemd, etc.
|
|
|
|
Also, I wanted a reasonable way to install [Telebit](https://telebit.io) on Windows.
|
|
(see more in the **More Why** section below)
|
|
|
|
## Features
|
|
|
|
- Unprivileged (User Mode) Services with `--user` (_Default_)
|
|
- [x] Linux (`sytemctl --user`)
|
|
- [x] MacOS (`launchctl`)
|
|
- [x] Windows (`HKEY_CURRENT_USER/.../Run`)
|
|
- Privileged (System) Services with `--system` (_Default_ for `root`)
|
|
- [x] Linux (`sudo sytemctl`)
|
|
- [x] MacOS (`sudo launchctl`)
|
|
- [ ] Windows (_not yet implemented_)
|
|
|
|
# Table of Contents
|
|
|
|
- Usage
|
|
- Install
|
|
- Examples
|
|
- compiled programs
|
|
- scripts
|
|
- bash
|
|
- node
|
|
- python
|
|
- ruby
|
|
- PATH
|
|
- Logging
|
|
- Debugging
|
|
- Windows
|
|
- Building
|
|
- More Why
|
|
- Legal
|
|
|
|
# Usage
|
|
|
|
The basic pattern of usage:
|
|
|
|
```bash
|
|
sudo serviceman add --name "foobar" [options] [interpreter] <service> [--] [service options]
|
|
sudo serviceman start <service>
|
|
sudo serviceman stop <service>
|
|
serviceman version
|
|
```
|
|
|
|
And what that might look like:
|
|
|
|
```bash
|
|
sudo serviceman add --name "foo" foo.exe -c ./config.json
|
|
```
|
|
|
|
You can also view the help:
|
|
|
|
```
|
|
serviceman add --help
|
|
```
|
|
|
|
# System Services VS User Mode Services
|
|
|
|
User services start **on login**.
|
|
|
|
System services start **on boot**.
|
|
|
|
The **default** is to register a _user_ services. To register a _system_ service, use `sudo` or run as `root`.
|
|
|
|
# Install
|
|
|
|
There are a number of pre-built binaries.
|
|
|
|
If none of them work for you, or you prefer to build from source,
|
|
see the instructions for building far down below.
|
|
|
|
## Downloads
|
|
|
|
### MacOS
|
|
|
|
MacOS (darwin): [64-bit Download ](https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman)
|
|
|
|
```
|
|
curl https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman -o serviceman
|
|
chmod +x ./serviceman
|
|
```
|
|
|
|
### Windows
|
|
|
|
<details>
|
|
<summary>See download options</summary>
|
|
Windows 10: [64-bit Download](https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.exe)
|
|
|
|
```
|
|
powershell.exe $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.exe -OutFile serviceman.exe
|
|
```
|
|
|
|
**Debug version**:
|
|
|
|
```
|
|
powershell.exe $ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.debug.exe -OutFile serviceman.debug.exe
|
|
```
|
|
|
|
Windows 7: [32-bit Download](https://rootprojects.org/serviceman/dist/windows/386/serviceman.exe)
|
|
|
|
```
|
|
powershell.exe "(New-Object Net.WebClient).DownloadFile('https://rootprojects.org/serviceman/dist/windows/386/serviceman.exe', 'serviceman.exe')"
|
|
```
|
|
|
|
**Debug version**:
|
|
|
|
```
|
|
powershell.exe "(New-Object Net.WebClient).DownloadFile('https://rootprojects.org/serviceman/dist/windows/386/serviceman.debug.exe', 'serviceman.debug.exe')"
|
|
```
|
|
|
|
</details>
|
|
|
|
### Linux
|
|
|
|
<details>
|
|
<summary>See download options</summary>
|
|
|
|
Linux (64-bit): [Download](https://rootprojects.org/serviceman/dist/linux/amd64/serviceman)
|
|
|
|
```
|
|
curl https://rootprojects.org/serviceman/dist/linux/amd64/serviceman -o serviceman
|
|
chmod +x ./serviceman
|
|
```
|
|
|
|
Linux (32-bit): [Download](https://rootprojects.org/serviceman/dist/linux/386/serviceman)
|
|
|
|
```
|
|
curl https://rootprojects.org/serviceman/dist/linux/386/serviceman -o serviceman
|
|
chmod +x ./serviceman
|
|
```
|
|
|
|
</details>
|
|
|
|
### Raspberry Pi (Linux ARM)
|
|
|
|
<details>
|
|
<summary>See download options</summary>
|
|
|
|
RPi 4 (64-bit armv8): [Download](https://rootprojects.org/serviceman/dist/linux/armv8/serviceman)
|
|
|
|
```
|
|
curl https://rootprojects.org/serviceman/dist/linux/armv8/serviceman -o serviceman`
|
|
chmod +x ./serviceman
|
|
```
|
|
|
|
RPi 3 (armv7): [Download](https://rootprojects.org/serviceman/dist/linux/armv7/serviceman)
|
|
|
|
```
|
|
curl https://rootprojects.org/serviceman/dist/linux/armv7/serviceman -o serviceman
|
|
chmod +x ./serviceman
|
|
```
|
|
|
|
ARMv6: [Download](https://rootprojects.org/serviceman/dist/linux/armv6/serviceman)
|
|
|
|
```
|
|
curl https://rootprojects.org/serviceman/dist/linux/armv6/serviceman -o serviceman
|
|
chmod +x ./serviceman
|
|
```
|
|
|
|
RPi Zero (armv5): [Download](https://rootprojects.org/serviceman/dist/linux/armv5/serviceman)
|
|
|
|
```
|
|
curl https://rootprojects.org/serviceman/dist/linux/armv5/serviceman -o serviceman
|
|
chmod +x ./serviceman
|
|
```
|
|
|
|
</details>
|
|
|
|
### Add to PATH
|
|
|
|
**Windows**
|
|
|
|
```
|
|
mkdir %userprofile%\bin
|
|
move serviceman.exe %userprofile%\bin\serviceman.exe
|
|
reg add HKEY_CURRENT_USER\Environment /v PATH /d "%PATH%;%userprofile%\bin"
|
|
```
|
|
|
|
**All Others**
|
|
|
|
```
|
|
sudo mv ./serviceman /usr/local/bin/
|
|
```
|
|
|
|
# Examples
|
|
|
|
```bash
|
|
sudo serviceman add --name <name> <program> [options] [--] [raw options]
|
|
|
|
# Example
|
|
sudo serviceman add --name "gizmo" gizmo --foo bar/baz
|
|
```
|
|
|
|
Anything that looks like file or directory will be **resolved to its absolute path**:
|
|
|
|
```bash
|
|
# Example of path resolution
|
|
gizmo --foo /User/me/gizmo/bar/baz
|
|
```
|
|
|
|
Use `--` to prevent this behavior:
|
|
|
|
```bash
|
|
# Complex Example
|
|
sudo serviceman add --name "gizmo" gizmo -c ./config.ini -- --separator .
|
|
```
|
|
|
|
For native **Windows** programs that use `/` for flags, you'll need to resolve some paths yourself:
|
|
|
|
```bash
|
|
# Windows Example
|
|
serviceman add --name "gizmo" gizmo.exe .\input.txt -- /c \User\me\gizmo\config.ini /q /s .
|
|
```
|
|
|
|
In this case `./config.ini` would still be resolved (before `--`), but `.` would not (after `--`)
|
|
|
|
<details>
|
|
<summary>Compiled Programs</summary>
|
|
|
|
Normally you might your program somewhat like this:
|
|
|
|
```bash
|
|
gizmo run --port 8421 --config envs/prod.ini
|
|
```
|
|
|
|
Adding a service for that program with `serviceman` would look like this:
|
|
|
|
```bash
|
|
sudo serviceman add --name "gizmo" gizmo run --port 8421 --config envs/prod.ini
|
|
```
|
|
|
|
serviceman will find `gizmo` in your PATH and resolve `envs/prod.ini` to its absolute path.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Using with scripts</summary>
|
|
|
|
```bash
|
|
./snarfblat.sh --port 8421
|
|
```
|
|
|
|
Although your text script may be executable, you'll need to specify the interpreter
|
|
in order for `serviceman` to configure the service correctly.
|
|
|
|
This can be done in two ways:
|
|
|
|
1. Put a **hashbang** in your script, such as `#!/bin/bash`.
|
|
2. Prepend the **interpreter** explicitly to your command, such as `bash ./dinglehopper.sh`.
|
|
|
|
For example, suppose you had a script like this:
|
|
|
|
`iamok.sh`:
|
|
|
|
```bash
|
|
while true; do
|
|
sleep 1; echo "Still Alive, Still Alive!"
|
|
done
|
|
```
|
|
|
|
Normally you would run the script like this:
|
|
|
|
```bash
|
|
./imok.sh
|
|
```
|
|
|
|
So you'd either need to modify the script to include a hashbang:
|
|
|
|
```bash
|
|
#!/usr/bin/env bash
|
|
while true; do
|
|
sleep 1; echo "I'm Ok!"
|
|
done
|
|
```
|
|
|
|
Or you'd need to prepend it with `bash` when creating a service for it:
|
|
|
|
```bash
|
|
sudo serviceman add --name "imok" bash ./imok.sh
|
|
```
|
|
|
|
**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:
|
|
|
|
```
|
|
#!/usr/bin/env ruby
|
|
```
|
|
|
|
```bash
|
|
#!/usr/bin/python
|
|
```
|
|
|
|
However, sometimes people get fancy and pass arguments to the interpreter,
|
|
like this:
|
|
|
|
```bash
|
|
#!/usr/local/bin/node --harmony --inspect
|
|
```
|
|
|
|
Serviceman understands all 3 of those approaches.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Using with node.js</summary>
|
|
|
|
If normally you run your node script something like this:
|
|
|
|
```bash
|
|
pushd ~/my-node-project/
|
|
npm start
|
|
```
|
|
|
|
Then you would add it as a system service like this:
|
|
|
|
```bash
|
|
sudo serviceman add npm start
|
|
```
|
|
|
|
If normally you run your node script something like this:
|
|
|
|
```bash
|
|
pushd ~/my-node-project/
|
|
node ./serve.js --foo bar --baz
|
|
```
|
|
|
|
Then you would add it as a system service like this:
|
|
|
|
```bash
|
|
sudo serviceman add node ./serve.js --foo bar --baz
|
|
```
|
|
|
|
It's important that any paths start with `./` and have the `.js`
|
|
so that serviceman knows to resolve the full path.
|
|
|
|
```bash
|
|
# Bad Examples
|
|
sudo serviceman add node ./demo # Wouldn't work for 'demo.js' - not a real filename
|
|
sudo serviceman add node demo # Wouldn't work for './demo/' - doesn't look like a directory
|
|
```
|
|
|
|
See **Using with scripts** for more detailed information.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Using with python</summary>
|
|
|
|
If normally you run your python script something like this:
|
|
|
|
```bash
|
|
pushd ~/my-python-project/
|
|
python ./serve.py --config ./config.ini
|
|
```
|
|
|
|
Then you would add it as a system service like this:
|
|
|
|
```bash
|
|
sudo serviceman add python ./serve.py --config ./config.ini
|
|
```
|
|
|
|
See **Using with scripts** for more detailed information.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Using with ruby</summary>
|
|
|
|
If normally you run your ruby script something like this:
|
|
|
|
```bash
|
|
pushd ~/my-ruby-project/
|
|
ruby ./serve.rb --config ./config.yaml
|
|
```
|
|
|
|
Then you would add it as a system service like this:
|
|
|
|
```bash
|
|
sudo serviceman add ruby ./serve.rb --config ./config.yaml
|
|
```
|
|
|
|
See **Using with scripts** for more detailed information.
|
|
|
|
</details>
|
|
|
|
<details>
|
|
<summary>Setting PATH</summary>
|
|
|
|
You can set the `$PATH` (`%PATH%` on Windows) for your service like this:
|
|
|
|
```bash
|
|
sudo serviceman add ./myservice --path "/home/myuser/bin"
|
|
```
|
|
|
|
Snapshot your actual path like this:
|
|
|
|
```bash
|
|
sudo serviceman add ./myservice --path "$PATH"
|
|
```
|
|
|
|
Remember that this takes a snapshot and sets it in the configuration, it's not
|
|
a live reference to your path.
|
|
|
|
</details>
|
|
|
|
## Hints
|
|
|
|
- If something goes wrong, read the output **completely** - it'll probably be helpful
|
|
- Run `serviceman` from your **project directory**, just as you would run it normally
|
|
- Otherwise specify `--name <service-name>` and `--workdir <project directory>`
|
|
- Use `--` in front of arguments that should not be resolved as paths
|
|
- This also holds true if you need `--` as an argument, such as `-- --foo -- --bar`
|
|
|
|
```
|
|
# Example of a / that isn't a path
|
|
# (it needs to be escaped with --)
|
|
sudo serviceman add dinglehopper config/prod -- --category color/blue
|
|
```
|
|
|
|
# Logging
|
|
|
|
### Linux
|
|
|
|
```bash
|
|
sudo journalctl -xef --unit <NAME>
|
|
sudo journalctl -xef --user-unit <NAME>
|
|
```
|
|
|
|
### Mac, Windows
|
|
|
|
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/<NAME>/var/log/<NAME>.log
|
|
```
|
|
|
|
```txt
|
|
/opt/<NAME>/var/log/<NAME>.log
|
|
```
|
|
|
|
You set it with one of these:
|
|
|
|
- `--logdir <path>` (cli)
|
|
- `"logdir": "<path>"` (json)
|
|
- `Logdir: "<path>"` (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.
|
|
|
|
# Debugging
|
|
|
|
- `serviceman add --dryrun <normal options>`
|
|
- `serviceman run --config <special config>`
|
|
|
|
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/me/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
|
|
{
|
|
"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.
|
|
|
|
## Peculiarities of Windows
|
|
|
|
# Console vs No Console
|
|
|
|
Windows binaries can be built either for the console or the GUI.
|
|
|
|
When they're built for the console they can hide themselves when they start.
|
|
They must open up a terminal window.
|
|
|
|
When they're built for the GUI they can't print any output - even if they're started in the terminal.
|
|
|
|
This is why there's a **Debug version** for the windows binaries -
|
|
so that you can get your arguments correct with the one and then
|
|
switch to the other.
|
|
|
|
There's probably a clever way to work around this, but I don't know what it is yet.
|
|
|
|
# No userspace launcher
|
|
|
|
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 up above for more information.
|
|
|
|
# Building
|
|
|
|
```bash
|
|
git clone https://git.coolaj86.com/coolaj86/go-serviceman.git
|
|
```
|
|
|
|
```bash
|
|
pushd ./go-serviceman
|
|
```
|
|
|
|
```bash
|
|
go generate -mod=vendor ./...
|
|
```
|
|
|
|
**Windows**:
|
|
|
|
```bash
|
|
go build -mod=vendor -ldflags "-H=windowsgui" -o serviceman.exe
|
|
```
|
|
|
|
**Linux, MacOS**:
|
|
|
|
```bash
|
|
go build -mod=vendor -o /usr/local/bin/serviceman
|
|
```
|
|
|
|
# More 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.
|
|
|
|
<!-- {{ if .Legal }} -->
|
|
|
|
# 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.
|
|
|
|
<!-- {{ end }} -->
|
|
<!-- {{ end }} -->
|