Compare commits
No commits in common. "master" and "v0.2.9" have entirely different histories.
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,5 +1,3 @@
|
|||||||
*~
|
|
||||||
.*~
|
|
||||||
# ---> Go
|
# ---> Go
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
|
346
README.md
346
README.md
@ -1,100 +1,69 @@
|
|||||||
# [go-serviceman](https://git.rootprojects.org/root/serviceman)
|
# go-serviceman
|
||||||
|
|
||||||
Cross-platform service management made easy.
|
A cross-platform service manager.
|
||||||
|
|
||||||
> sudo serviceman add --name foo ./serve.js --port 3000
|
Because debugging launchctl, systemd, etc absolutely sucks!
|
||||||
|
|
||||||
> Success: "foo" started as a "launchd" SYSTEM service, running as "root"
|
...and I wanted a reasonable way to install [Telebit](https://telebit.io) on Windows.
|
||||||
|
(see more in the **Why** section below)
|
||||||
## 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
|
## Features
|
||||||
|
|
||||||
- Unprivileged (User Mode) Services with `--user` (_Default_)
|
- Unprivileged (User Mode) Services
|
||||||
- [x] Linux (`sytemctl --user`)
|
- [x] Linux (`sytemctl --user`)
|
||||||
- [x] MacOS (`launchctl`)
|
- [x] MacOS (`launchctl`)
|
||||||
- [x] Windows (`HKEY_CURRENT_USER/.../Run`)
|
- [x] Windows (`HKEY_CURRENT_USER/.../Run`)
|
||||||
- Privileged (System) Services with `--system` (_Default_ for `root`)
|
- Privileged (System) Services
|
||||||
- [x] Linux (`sudo sytemctl`)
|
- [x] Linux (`sudo sytemctl`)
|
||||||
- [x] MacOS (`sudo launchctl`)
|
- [x] MacOS (`sudo launchctl`)
|
||||||
- [ ] Windows (_not yet implemented_)
|
- [ ] Windows (_not yet implemented_)
|
||||||
|
|
||||||
# Table of Contents
|
# Table of Contents
|
||||||
|
|
||||||
- Usage
|
- Usage
|
||||||
- Install
|
- Install
|
||||||
- Examples
|
- Examples
|
||||||
- compiled programs
|
- compiled programs
|
||||||
- scripts
|
- scripts
|
||||||
- bash
|
- bash
|
||||||
- node
|
- node
|
||||||
- python
|
- python
|
||||||
- ruby
|
- ruby
|
||||||
- PATH
|
- Logging
|
||||||
- Logging
|
- Debugging
|
||||||
- Debugging
|
- Windows
|
||||||
- Windows
|
- Building
|
||||||
- Building
|
- Why
|
||||||
- More Why
|
- Legal
|
||||||
- Legal
|
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
The basic pattern of usage:
|
The basic pattern of usage, and what that might look like:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
sudo serviceman add --name "foobar" [options] [interpreter] <service> [--] [service options]
|
serviceman add [options] [interpreter] <service> -- [service options]
|
||||||
sudo serviceman start <service>
|
|
||||||
sudo serviceman stop <service>
|
|
||||||
sudo serviceman list --all
|
|
||||||
serviceman version
|
|
||||||
```
|
```
|
||||||
|
|
||||||
And what that might look like:
|
```
|
||||||
|
serviceman add foo.exe
|
||||||
```bash
|
|
||||||
sudo serviceman add --name "foo" foo.exe -c ./config.json
|
|
||||||
```
|
```
|
||||||
|
|
||||||
You can also view the help:
|
```
|
||||||
|
serviceman add --title "Foo App" node ./foo.js -- --bar
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also view the help and the version:
|
||||||
|
|
||||||
```
|
```
|
||||||
serviceman add --help
|
serviceman add --help
|
||||||
```
|
```
|
||||||
|
|
||||||
# System Services VS User Mode Services
|
```
|
||||||
|
serviceman version
|
||||||
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
|
# Install
|
||||||
|
|
||||||
You can install `serviceman` directly from the official git releases with [`webi`](https://webinstall.dev/serviceman):
|
|
||||||
|
|
||||||
**Mac**, **Linux**:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -sL https://webinstall.dev/serviceman | bash
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows 10**:
|
|
||||||
|
|
||||||
```pwsh
|
|
||||||
curl.exe -sLA "MS" https://webinstall.dev/serviceman | powershell
|
|
||||||
```
|
|
||||||
|
|
||||||
You can run this from cmd.exe or PowerShell (curl.exe is a native part of Windows 10).
|
|
||||||
|
|
||||||
## Manual Install
|
|
||||||
|
|
||||||
There are a number of pre-built binaries.
|
There are a number of pre-built binaries.
|
||||||
|
|
||||||
If none of them work for you, or you prefer to build from source,
|
If none of them work for you, or you prefer to build from source,
|
||||||
@ -102,25 +71,14 @@ see the instructions for building far down below.
|
|||||||
|
|
||||||
## Downloads
|
## Downloads
|
||||||
|
|
||||||
```
|
|
||||||
curl -fsSL "https://rootprojects.org/serviceman/dist/$(uname -s)/$(uname -m)/serviceman" -o serviceman
|
|
||||||
chmod +x ./serviceman
|
|
||||||
```
|
|
||||||
|
|
||||||
### MacOS
|
### MacOS
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>See download options</summary>
|
|
||||||
|
|
||||||
MacOS (darwin): [64-bit Download ](https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman)
|
MacOS (darwin): [64-bit Download ](https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman)
|
||||||
|
|
||||||
```
|
```
|
||||||
curl https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman -o serviceman
|
curl https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman -o serviceman
|
||||||
chmod +x ./serviceman
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@ -153,7 +111,6 @@ powershell.exe "(New-Object Net.WebClient).DownloadFile('https://rootprojects.or
|
|||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>See download options</summary>
|
<summary>See download options</summary>
|
||||||
|
|
||||||
@ -161,14 +118,12 @@ Linux (64-bit): [Download](https://rootprojects.org/serviceman/dist/linux/amd64/
|
|||||||
|
|
||||||
```
|
```
|
||||||
curl https://rootprojects.org/serviceman/dist/linux/amd64/serviceman -o 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)
|
Linux (32-bit): [Download](https://rootprojects.org/serviceman/dist/linux/386/serviceman)
|
||||||
|
|
||||||
```
|
```
|
||||||
curl https://rootprojects.org/serviceman/dist/linux/386/serviceman -o serviceman
|
curl https://rootprojects.org/serviceman/dist/linux/386/serviceman -o serviceman
|
||||||
chmod +x ./serviceman
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@ -182,28 +137,24 @@ RPi 4 (64-bit armv8): [Download](https://rootprojects.org/serviceman/dist/linux/
|
|||||||
|
|
||||||
```
|
```
|
||||||
curl https://rootprojects.org/serviceman/dist/linux/armv8/serviceman -o 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)
|
RPi 3 (armv7): [Download](https://rootprojects.org/serviceman/dist/linux/armv7/serviceman)
|
||||||
|
|
||||||
```
|
```
|
||||||
curl https://rootprojects.org/serviceman/dist/linux/armv7/serviceman -o 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)
|
ARMv6: [Download](https://rootprojects.org/serviceman/dist/linux/armv6/serviceman)
|
||||||
|
|
||||||
```
|
```
|
||||||
curl https://rootprojects.org/serviceman/dist/linux/armv6/serviceman -o 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)
|
RPi Zero (armv5): [Download](https://rootprojects.org/serviceman/dist/linux/armv5/serviceman)
|
||||||
|
|
||||||
```
|
```
|
||||||
curl https://rootprojects.org/serviceman/dist/linux/armv5/serviceman -o serviceman
|
curl https://rootprojects.org/serviceman/dist/linux/armv5/serviceman -o serviceman
|
||||||
chmod +x ./serviceman
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@ -214,112 +165,56 @@ chmod +x ./serviceman
|
|||||||
|
|
||||||
```
|
```
|
||||||
mkdir %userprofile%\bin
|
mkdir %userprofile%\bin
|
||||||
move serviceman.exe %userprofile%\bin\serviceman.exe
|
|
||||||
reg add HKEY_CURRENT_USER\Environment /v PATH /d "%PATH%;%userprofile%\bin"
|
reg add HKEY_CURRENT_USER\Environment /v PATH /d "%PATH%;%userprofile%\bin"
|
||||||
|
move serviceman.exe %userprofile%\bin\serviceman.exe
|
||||||
```
|
```
|
||||||
|
|
||||||
**All Others**
|
**All Others**
|
||||||
|
|
||||||
```
|
```
|
||||||
|
chmod a+x ./serviceman
|
||||||
sudo mv ./serviceman /usr/local/bin/
|
sudo mv ./serviceman /usr/local/bin/
|
||||||
```
|
```
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
|
||||||
```bash
|
> **serviceman add** <program> **--** <program options>
|
||||||
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>
|
<details>
|
||||||
<summary>Compiled Programs</summary>
|
<summary>Compiled Programs</summary>
|
||||||
|
|
||||||
Normally you might your program somewhat like this:
|
Normally you might your program somewhat like this:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
gizmo run --port 8421 --config envs/prod.ini
|
dinglehopper --port 8421
|
||||||
```
|
```
|
||||||
|
|
||||||
Adding a service for that program with `serviceman` would look like this:
|
Adding a service for that program with `serviceman` would look like this:
|
||||||
|
|
||||||
```bash
|
> **serviceman add** dinglehopper **--** --port 8421
|
||||||
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.
|
serviceman will find dinglehopper in your PATH.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Using with scripts</summary>
|
<summary>Using with scripts</summary>
|
||||||
|
|
||||||
```bash
|
|
||||||
./snarfblat.sh --port 8421
|
|
||||||
```
|
|
||||||
|
|
||||||
Although your text script may be executable, you'll need to specify the interpreter
|
Although your text script may be executable, you'll need to specify the interpreter
|
||||||
in order for `serviceman` to configure the service correctly.
|
in order for `serviceman` to configure the service correctly.
|
||||||
|
|
||||||
This can be done in two ways:
|
For example, if you had a bash script that you normally ran like this:
|
||||||
|
|
||||||
1. Put a **hashbang** in your script, such as `#!/bin/bash`.
|
```
|
||||||
2. Prepend the **interpreter** explicitly to your command, such as `bash ./dinglehopper.sh`.
|
./snarfblat.sh --port 8421
|
||||||
|
|
||||||
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:
|
You'd create a system service for it like this:
|
||||||
|
|
||||||
```bash
|
> serviceman add **bash** ./snarfblat.sh **--** --port 8421
|
||||||
./imok.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
So you'd either need to modify the script to include a hashbang:
|
`serviceman` will resolve `./snarfblat.sh` correctly because it comes
|
||||||
|
before the **--**.
|
||||||
```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**
|
**Background Information**
|
||||||
|
|
||||||
@ -343,8 +238,6 @@ like this:
|
|||||||
#!/usr/local/bin/node --harmony --inspect
|
#!/usr/local/bin/node --harmony --inspect
|
||||||
```
|
```
|
||||||
|
|
||||||
Serviceman understands all 3 of those approaches.
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@ -353,37 +246,14 @@ Serviceman understands all 3 of those approaches.
|
|||||||
If normally you run your node script something like this:
|
If normally you run your node script something like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pushd ~/my-node-project/
|
node ./demo.js --foo bar --baz
|
||||||
npm start
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you would add it as a system service like this:
|
Then you would add it as a system service like this:
|
||||||
|
|
||||||
```bash
|
> **serviceman add** node ./demo.js **--** --foo bar --baz
|
||||||
sudo serviceman add npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
If normally you run your node script something like this:
|
It is important that you specify `node ./demo.js` and not just `./demo.js`
|
||||||
|
|
||||||
```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.
|
See **Using with scripts** for more detailed information.
|
||||||
|
|
||||||
@ -395,15 +265,14 @@ See **Using with scripts** for more detailed information.
|
|||||||
If normally you run your python script something like this:
|
If normally you run your python script something like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pushd ~/my-python-project/
|
python ./demo.py --foo bar --baz
|
||||||
python ./serve.py --config ./config.ini
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you would add it as a system service like this:
|
Then you would add it as a system service like this:
|
||||||
|
|
||||||
```bash
|
> **serviceman add** python ./demo.py **--** --foo bar --baz
|
||||||
sudo serviceman add python ./serve.py --config ./config.ini
|
|
||||||
```
|
It is important that you specify `python ./demo.py` and not just `./demo.py`
|
||||||
|
|
||||||
See **Using with scripts** for more detailed information.
|
See **Using with scripts** for more detailed information.
|
||||||
|
|
||||||
@ -415,65 +284,35 @@ See **Using with scripts** for more detailed information.
|
|||||||
If normally you run your ruby script something like this:
|
If normally you run your ruby script something like this:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pushd ~/my-ruby-project/
|
ruby ./demo.rb --foo bar --baz
|
||||||
ruby ./serve.rb --config ./config.yaml
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Then you would add it as a system service like this:
|
Then you would add it as a system service like this:
|
||||||
|
|
||||||
```bash
|
> **serviceman add** ruby ./demo.rb **--** --foo bar --baz
|
||||||
sudo serviceman add ruby ./serve.rb --config ./config.yaml
|
|
||||||
```
|
It is important that you specify `ruby ./demo.rb` and not just `./demo.rb`
|
||||||
|
|
||||||
See **Using with scripts** for more detailed information.
|
See **Using with scripts** for more detailed information.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details>
|
## Relative vs Absolute Paths
|
||||||
<summary>Setting PATH</summary>
|
|
||||||
|
|
||||||
You can set the `$PATH` (`%PATH%` on Windows) for your service like this:
|
Although serviceman can expand the executable's path,
|
||||||
|
if you have any arguments with relative paths
|
||||||
```bash
|
you should switch to using absolute paths.
|
||||||
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
|
dinglehopper --config ./conf.json
|
||||||
# (it needs to be escaped with --)
|
```
|
||||||
sudo serviceman add dinglehopper config/prod -- --category color/blue
|
|
||||||
|
```
|
||||||
|
serviceman add dinglehopper -- --config /Users/me/dinglehopper/conf.json
|
||||||
```
|
```
|
||||||
|
|
||||||
# Logging
|
# 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
|
When you run `serviceman add` it will either give you an error or
|
||||||
will print out the location where logs will be found.
|
will print out the location where logs will be found.
|
||||||
|
|
||||||
@ -484,14 +323,14 @@ By default it's one of these:
|
|||||||
```
|
```
|
||||||
|
|
||||||
```txt
|
```txt
|
||||||
/opt/<NAME>/var/log/<NAME>.log
|
/var/log/<NAME>/var/log/<NAME>.log
|
||||||
```
|
```
|
||||||
|
|
||||||
You set it with one of these:
|
You set it with one of these:
|
||||||
|
|
||||||
- `--logdir <path>` (cli)
|
- `--logdir <path>` (cli)
|
||||||
- `"logdir": "<path>"` (json)
|
- `"logdir": "<path>"` (json)
|
||||||
- `Logdir: "<path>"` (go)
|
- `Logdir: "<path>"` (go)
|
||||||
|
|
||||||
If anything about the logging sucks, tell me... unless they're your logs
|
If anything about the logging sucks, tell me... unless they're your logs
|
||||||
(which they probably are), in which case _you_ should fix them.
|
(which they probably are), in which case _you_ should fix them.
|
||||||
@ -501,9 +340,6 @@ why your app failed to start.
|
|||||||
|
|
||||||
# Debugging
|
# 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
|
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
|
terrible to debug - it's often difficult to find the logs, and nearly impossible
|
||||||
to interpret them, if they exist at all.
|
to interpret them, if they exist at all.
|
||||||
@ -529,9 +365,9 @@ Where `conf.json` looks something like
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"title": "Demo",
|
"title": "Demo",
|
||||||
"exec": "/Users/me/go-demo/demo",
|
"exec": "/Users/me/go-demo/demo",
|
||||||
"argv": ["--foo", "bar", "--baz", "qux"]
|
"argv": ["--foo", "bar", "--baz", "qux"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -545,10 +381,10 @@ names and relative paths.
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"title": "Demo",
|
"title": "Demo",
|
||||||
"interpreter": "node.exe",
|
"interpreter": "node.exe",
|
||||||
"exec": "./bin/demo.js",
|
"exec": "./bin/demo.js",
|
||||||
"argv": ["--foo", "bar", "--baz", "qux"]
|
"argv": ["--foo", "bar", "--baz", "qux"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -556,12 +392,12 @@ That's equivalent to this:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"title": "Demo",
|
"title": "Demo",
|
||||||
|
|
||||||
"name": "demo",
|
"name": "demo",
|
||||||
|
|
||||||
"exec": "node.exe",
|
"exec": "node.exe",
|
||||||
"argv": ["./bin/demo.js", "--foo", "bar", "--baz", "qux"]
|
"argv": ["./bin/demo.js", "--foo", "bar", "--baz", "qux"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -630,7 +466,7 @@ go build -mod=vendor -ldflags "-H=windowsgui" -o serviceman.exe
|
|||||||
go build -mod=vendor -o /usr/local/bin/serviceman
|
go build -mod=vendor -o /usr/local/bin/serviceman
|
||||||
```
|
```
|
||||||
|
|
||||||
# More Why
|
# Why
|
||||||
|
|
||||||
I created this for two reasons:
|
I created this for two reasons:
|
||||||
|
|
||||||
|
@ -39,5 +39,5 @@ echo "RPi Zero ARMv5"
|
|||||||
GOOS=linux GOARCH=arm GOARM=5 go build -mod=vendor -o dist/linux/armv5/${exe} $gocmd
|
GOOS=linux GOARCH=arm GOARM=5 go build -mod=vendor -o dist/linux/armv5/${exe} $gocmd
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
#rsync -av ./dist/ ubuntu@rootprojects.org:/srv/www/rootprojects.org/serviceman/dist/
|
rsync -av ./dist/ ubuntu@rootprojects.org:/srv/www/rootprojects.org/serviceman/dist/
|
||||||
# https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.exe
|
# https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.exe
|
||||||
|
1
go.mod
1
go.mod
@ -5,7 +5,6 @@ go 1.12
|
|||||||
require (
|
require (
|
||||||
git.rootprojects.org/root/go-gitver v1.1.2
|
git.rootprojects.org/root/go-gitver v1.1.2
|
||||||
github.com/UnnoTed/fileb0x v1.1.3
|
github.com/UnnoTed/fileb0x v1.1.3
|
||||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
|
|
||||||
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f
|
golang.org/x/net v0.0.0-20180921000356-2f5d2388922f
|
||||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
|
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -26,8 +26,6 @@ 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-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 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8BzLR4=
|
||||||
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936 h1:kw1v0NlnN+GZcU8Ma8CLF2Zzgjfx95gs3/GN3vYAPpo=
|
|
||||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4=
|
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/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 h1:fvw0uluMptljaRKSU8459cJ4bmi3qUYyMs5kzpic2fY=
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated for serviceman. Edit as you wish, but leave this line. -->
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
@ -28,8 +27,6 @@
|
|||||||
{{if .User -}}
|
{{if .User -}}
|
||||||
<key>UserName</key>
|
<key>UserName</key>
|
||||||
<string>{{ .User }}</string>
|
<string>{{ .User }}</string>
|
||||||
{{end -}}
|
|
||||||
{{if .Group -}}
|
|
||||||
<key>GroupName</key>
|
<key>GroupName</key>
|
||||||
<string>{{ .Group }}</string>
|
<string>{{ .Group }}</string>
|
||||||
<key>InitGroups</key>
|
<key>InitGroups</key>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
# Generated for serviceman. Edit as you wish, but leave this line.
|
|
||||||
# Pre-req
|
# Pre-req
|
||||||
# sudo mkdir -p {{ .Local }}/opt/{{ .Name }}/ {{ .Local }}/var/log/{{ .Name }}
|
# sudo mkdir -p {{ .Local }}/opt/{{ .Name }}/ {{ .Local }}/var/log/{{ .Name }}
|
||||||
{{ if .System -}}
|
{{ if .System -}}
|
||||||
@ -36,9 +35,6 @@ User={{ .User }}
|
|||||||
Group={{ .Group }}
|
Group={{ .Group }}
|
||||||
|
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
{{- if .Envs }}
|
|
||||||
Environment="{{- range $key, $value := .Envs }}{{ $key }}={{ $value }};{{- end }}"
|
|
||||||
{{- end }}
|
|
||||||
{{ if .Workdir -}}
|
{{ if .Workdir -}}
|
||||||
WorkingDirectory={{ .Workdir }}
|
WorkingDirectory={{ .Workdir }}
|
||||||
{{ end -}}
|
{{ end -}}
|
||||||
|
@ -7,14 +7,13 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.rootprojects.org/root/go-serviceman/service"
|
"git.rootprojects.org/root/go-serviceman/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Install will do a best-effort attempt to install a start-on-startup
|
// Install will do a best-effort attempt to install a start-on-startup
|
||||||
// user or system service via systemd, launchd, or reg.exe
|
// user or system service via systemd, launchd, or reg.exe
|
||||||
func Install(c *service.Service) (string, error) {
|
func Install(c *service.Service) error {
|
||||||
if "" == c.Exec {
|
if "" == c.Exec {
|
||||||
c.Exec = c.Name
|
c.Exec = c.Name
|
||||||
}
|
}
|
||||||
@ -24,35 +23,23 @@ func Install(c *service.Service) (string, error) {
|
|||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Fprintf(os.Stderr, "Unrecoverable Error: %s", err)
|
fmt.Fprintf(os.Stderr, "Unrecoverable Error: %s", err)
|
||||||
os.Exit(4)
|
os.Exit(4)
|
||||||
return "", err
|
return err
|
||||||
} else {
|
} else {
|
||||||
c.Home = home
|
c.Home = home
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
name, err := install(c)
|
err := install(c)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.MkdirAll(c.Logdir, 0755)
|
err = os.MkdirAll(c.Logdir, 0755)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return name, nil
|
return nil
|
||||||
}
|
|
||||||
|
|
||||||
func Start(conf *service.Service) error {
|
|
||||||
return start(conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Stop(conf *service.Service) error {
|
|
||||||
return stop(conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func List(conf *service.Service) ([]string, []string, []error) {
|
|
||||||
return list(conf)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsPrivileged returns true if we suspect that the current user (or process) will be able
|
// IsPrivileged returns true if we suspect that the current user (or process) will be able
|
||||||
@ -70,22 +57,3 @@ func WhereIs(exe string) (string, error) {
|
|||||||
}
|
}
|
||||||
return filepath.Abs(filepath.ToSlash(exepath))
|
return filepath.Abs(filepath.ToSlash(exepath))
|
||||||
}
|
}
|
||||||
|
|
||||||
type ManageError struct {
|
|
||||||
Name string
|
|
||||||
Hint string
|
|
||||||
Parent error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ManageError) Error() string {
|
|
||||||
return e.Name + ": " + e.Hint + ": " + e.Parent.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
type ErrDaemonize struct {
|
|
||||||
DaemonArgs []string
|
|
||||||
error string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ErrDaemonize) Error() string {
|
|
||||||
return e.error + "\nYou need to switch on ErrDaemonize, and use .DaemonArgs, which would run this:" + strings.Join(e.DaemonArgs, " ")
|
|
||||||
}
|
|
||||||
|
@ -24,16 +24,27 @@ func init() {
|
|||||||
srvLen = len(srvExt)
|
srvLen = len(srvExt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(conf *service.Service) error {
|
func start(system bool, home string, name string) error {
|
||||||
system := conf.System
|
sys, user, err := getMatchingSrvs(home, name)
|
||||||
home := conf.Home
|
|
||||||
rdns := conf.ReverseDNS
|
|
||||||
|
|
||||||
service, err := getService(system, home, rdns)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var service string
|
||||||
|
if system {
|
||||||
|
service, err = getOneSysSrv(sys, user, name)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service = filepath.Join(srvSysPath, service)
|
||||||
|
} else {
|
||||||
|
service, err = getOneUserSrv(home, sys, user, name)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service = filepath.Join(home, srvUserPath, service)
|
||||||
|
}
|
||||||
|
|
||||||
cmds := []Runnable{
|
cmds := []Runnable{
|
||||||
Runnable{
|
Runnable{
|
||||||
Exec: "launchctl",
|
Exec: "launchctl",
|
||||||
@ -41,20 +52,16 @@ func start(conf *service.Service) error {
|
|||||||
Must: false,
|
Must: false,
|
||||||
},
|
},
|
||||||
Runnable{
|
Runnable{
|
||||||
Exec: "launchctl",
|
Exec: "launchctl",
|
||||||
Args: []string{"load", "-w", service},
|
Args: []string{"load", "-w", service},
|
||||||
Must: true,
|
Must: true,
|
||||||
Badwords: []string{"No such file or directory", "service already loaded"},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cmds = adjustPrivs(system, cmds)
|
cmds = adjustPrivs(system, cmds)
|
||||||
|
|
||||||
typ := "USER"
|
fmt.Println()
|
||||||
if system {
|
fmt.Println("Starting launchd service...")
|
||||||
typ = "SYSTEM"
|
|
||||||
}
|
|
||||||
fmt.Printf("Starting launchd %s service...\n\n", typ)
|
|
||||||
for i := range cmds {
|
for i := range cmds {
|
||||||
exe := cmds[i]
|
exe := cmds[i]
|
||||||
fmt.Println("\t" + exe.String())
|
fmt.Println("\t" + exe.String())
|
||||||
@ -68,73 +75,11 @@ func start(conf *service.Service) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop(conf *service.Service) error {
|
func install(c *service.Service) error {
|
||||||
system := conf.System
|
|
||||||
home := conf.Home
|
|
||||||
rdns := conf.ReverseDNS
|
|
||||||
|
|
||||||
service, err := getService(system, home, rdns)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds := []Runnable{
|
|
||||||
Runnable{
|
|
||||||
Exec: "launchctl",
|
|
||||||
Args: []string{"unload", service},
|
|
||||||
Must: false,
|
|
||||||
Badwords: []string{"No such file or directory", "Cound not find specified service"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds = adjustPrivs(system, cmds)
|
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
typ := "USER"
|
|
||||||
if system {
|
|
||||||
typ = "SYSTEM"
|
|
||||||
}
|
|
||||||
fmt.Printf("Stopping launchd %s service...\n", typ)
|
|
||||||
for i := range cmds {
|
|
||||||
exe := cmds[i]
|
|
||||||
fmt.Println("\t" + exe.String())
|
|
||||||
err := exe.Run()
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render will create a launchd .plist file using the simple internal template
|
|
||||||
func Render(c *service.Service) ([]byte, error) {
|
|
||||||
// Create service file from template
|
|
||||||
b, err := static.ReadFile("dist/Library/LaunchDaemons/_rdns_.plist.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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 nil, err
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(rw, c)
|
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rw.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func install(c *service.Service) (string, error) {
|
|
||||||
// Darwin-specific config options
|
// Darwin-specific config options
|
||||||
if c.PrivilegedPorts {
|
if c.PrivilegedPorts {
|
||||||
if !c.System {
|
if !c.System {
|
||||||
return "", fmt.Errorf("You must use root-owned LaunchDaemons (not user-owned LaunchAgents) to use priveleged ports on OS X")
|
return fmt.Errorf("You must use root-owned LaunchDaemons (not user-owned LaunchAgents) to use priveleged ports on OS X")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
plistDir := srvSysPath
|
plistDir := srvSysPath
|
||||||
@ -143,32 +88,45 @@ func install(c *service.Service) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check paths first
|
// Check paths first
|
||||||
err := os.MkdirAll(plistDir, 0755)
|
err := os.MkdirAll(filepath.Dir(plistDir), 0755)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := Render(c)
|
// 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 {
|
if nil != err {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the file out
|
// Write the file out
|
||||||
// TODO rdns
|
// TODO rdns
|
||||||
plistName := c.ReverseDNS + ".plist"
|
plistName := c.ReverseDNS + ".plist"
|
||||||
plistPath := filepath.Join(plistDir, plistName)
|
plistPath := filepath.Join(plistDir, plistName)
|
||||||
if err := ioutil.WriteFile(plistPath, b, 0644); err != nil {
|
if err := ioutil.WriteFile(plistPath, rw.Bytes(), 0644); err != nil {
|
||||||
return "", fmt.Errorf("Error writing %s: %v", plistPath, err)
|
return fmt.Errorf("Error writing %s: %v", plistPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO --no-start
|
// TODO --no-start
|
||||||
err = start(c)
|
err = start(c.System, c.Home, c.ReverseDNS)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Printf("If things don't go well you should be able to get additional logging from launchctl:\n")
|
fmt.Printf("If things don't go well you should be able to get additional logging from launchctl:\n")
|
||||||
fmt.Printf("\tsudo launchctl log level debug\n")
|
fmt.Printf("\tsudo launchctl log level debug\n")
|
||||||
fmt.Printf("\ttail -f /var/log/system.log\n")
|
fmt.Printf("\ttail -f /var/log/system.log\n")
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return "launchd", nil
|
fmt.Printf("Added and started '%s' as a launchctl service.\n", c.Name)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -29,16 +29,27 @@ func init() {
|
|||||||
srvLen = len(srvExt)
|
srvLen = len(srvExt)
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(conf *service.Service) error {
|
func start(system bool, home string, name string) error {
|
||||||
system := conf.System
|
sys, user, err := getMatchingSrvs(home, name)
|
||||||
home := conf.Home
|
|
||||||
name := conf.ReverseDNS
|
|
||||||
|
|
||||||
_, err := getService(system, home, name)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var service string
|
||||||
|
if system {
|
||||||
|
service, err = getOneSysSrv(sys, user, name)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service = filepath.Join(srvSysPath, service)
|
||||||
|
} else {
|
||||||
|
service, err = getOneUserSrv(home, sys, user, name)
|
||||||
|
if nil != err {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
service = filepath.Join(home, srvUserPath, service)
|
||||||
|
}
|
||||||
|
|
||||||
var cmds []Runnable
|
var cmds []Runnable
|
||||||
if system {
|
if system {
|
||||||
cmds = []Runnable{
|
cmds = []Runnable{
|
||||||
@ -52,12 +63,6 @@ func start(conf *service.Service) error {
|
|||||||
Args: []string{"stop", name + ".service"},
|
Args: []string{"stop", name + ".service"},
|
||||||
Must: false,
|
Must: false,
|
||||||
},
|
},
|
||||||
Runnable{
|
|
||||||
Exec: "systemctl",
|
|
||||||
Args: []string{"enable", name + ".service"},
|
|
||||||
Badwords: []string{"not found", "failed"},
|
|
||||||
Must: true,
|
|
||||||
},
|
|
||||||
Runnable{
|
Runnable{
|
||||||
Exec: "systemctl",
|
Exec: "systemctl",
|
||||||
Args: []string{"start", name + ".service"},
|
Args: []string{"start", name + ".service"},
|
||||||
@ -88,11 +93,8 @@ func start(conf *service.Service) error {
|
|||||||
|
|
||||||
cmds = adjustPrivs(system, cmds)
|
cmds = adjustPrivs(system, cmds)
|
||||||
|
|
||||||
typ := "USER MODE"
|
fmt.Println()
|
||||||
if system {
|
fmt.Println("Starting systemd service unit...")
|
||||||
typ = "SYSTEM"
|
|
||||||
}
|
|
||||||
fmt.Printf("Starting systemd %s service unit...\n\n", typ)
|
|
||||||
for i := range cmds {
|
for i := range cmds {
|
||||||
exe := cmds[i]
|
exe := cmds[i]
|
||||||
fmt.Println("\t" + exe.String())
|
fmt.Println("\t" + exe.String())
|
||||||
@ -106,85 +108,16 @@ func start(conf *service.Service) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop(conf *service.Service) error {
|
func install(c *service.Service) error {
|
||||||
system := conf.System
|
// Linux-specific config options
|
||||||
home := conf.Home
|
if c.System {
|
||||||
name := conf.ReverseDNS
|
if "" == c.User {
|
||||||
|
c.User = "root"
|
||||||
_, err := getService(system, home, name)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var cmds []Runnable
|
|
||||||
badwords := []string{"Failed to stop"}
|
|
||||||
if system {
|
|
||||||
cmds = []Runnable{
|
|
||||||
Runnable{
|
|
||||||
Exec: "systemctl",
|
|
||||||
Args: []string{"stop", name + ".service"},
|
|
||||||
Must: true,
|
|
||||||
Badwords: badwords,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cmds = []Runnable{
|
|
||||||
Runnable{
|
|
||||||
Exec: "systemctl",
|
|
||||||
Args: []string{"stop", "--user", name + ".service"},
|
|
||||||
Must: true,
|
|
||||||
Badwords: badwords,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if "" == c.Group {
|
||||||
cmds = adjustPrivs(system, cmds)
|
c.Group = c.User
|
||||||
|
|
||||||
fmt.Println()
|
|
||||||
typ := "USER MODE"
|
|
||||||
if system {
|
|
||||||
typ = "SYSTEM"
|
|
||||||
}
|
}
|
||||||
fmt.Printf("Stopping systemd %s service...\n", typ)
|
|
||||||
for i := range cmds {
|
|
||||||
exe := cmds[i]
|
|
||||||
fmt.Println("\t" + exe.String())
|
|
||||||
err := exe.Run()
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render will create a systemd .service file using the simple internal template
|
|
||||||
func Render(c *service.Service) ([]byte, error) {
|
|
||||||
defaultUserGroup(c)
|
|
||||||
|
|
||||||
// Create service file from template
|
|
||||||
b, err := static.ReadFile("dist/etc/systemd/system/_name_.service.tmpl")
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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 nil, err
|
|
||||||
}
|
|
||||||
err = tmpl.Execute(rw, c)
|
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return rw.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func install(c *service.Service) (string, error) {
|
|
||||||
defaultUserGroup(c)
|
|
||||||
|
|
||||||
// Check paths first
|
// Check paths first
|
||||||
serviceDir := srvSysPath
|
serviceDir := srvSysPath
|
||||||
@ -192,24 +125,36 @@ func install(c *service.Service) (string, error) {
|
|||||||
serviceDir = filepath.Join(c.Home, srvUserPath)
|
serviceDir = filepath.Join(c.Home, srvUserPath)
|
||||||
err := os.MkdirAll(serviceDir, 0755)
|
err := os.MkdirAll(serviceDir, 0755)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := Render(c)
|
// 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 {
|
if nil != err {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the file out
|
// Write the file out
|
||||||
serviceName := c.Name + ".service"
|
serviceName := c.Name + ".service"
|
||||||
servicePath := filepath.Join(serviceDir, serviceName)
|
servicePath := filepath.Join(serviceDir, serviceName)
|
||||||
if err := ioutil.WriteFile(servicePath, b, 0644); err != nil {
|
if err := ioutil.WriteFile(servicePath, rw.Bytes(), 0644); err != nil {
|
||||||
return "", fmt.Errorf("Error writing %s: %v", servicePath, err)
|
return fmt.Errorf("Error writing %s: %v", servicePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO --no-start
|
// TODO --no-start
|
||||||
err = start(c)
|
err = start(c.System, c.Home, c.Name)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
sudo := ""
|
sudo := ""
|
||||||
// --user-unit rather than --user --unit for older systemd
|
// --user-unit rather than --user --unit for older systemd
|
||||||
@ -220,20 +165,9 @@ func install(c *service.Service) (string, error) {
|
|||||||
}
|
}
|
||||||
fmt.Printf("If things don't go well you should be able to get additional logging from journalctl:\n")
|
fmt.Printf("If things don't go well you should be able to get additional logging from journalctl:\n")
|
||||||
fmt.Printf("\t%sjournalctl -xe %s %s.service\n", sudo, unit, c.Name)
|
fmt.Printf("\t%sjournalctl -xe %s %s.service\n", sudo, unit, c.Name)
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return "systemd", nil
|
fmt.Printf("Added and started '%s' as a systemd service.\n", c.Name)
|
||||||
}
|
return nil
|
||||||
|
|
||||||
func defaultUserGroup(c *service.Service) {
|
|
||||||
// Linux-specific config options
|
|
||||||
if c.System {
|
|
||||||
if "" == c.User {
|
|
||||||
c.User = "root"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if "" == c.Group {
|
|
||||||
c.Group = c.User
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
// +build !windows
|
|
||||||
|
|
||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"git.rootprojects.org/root/go-serviceman/service"
|
|
||||||
)
|
|
||||||
|
|
||||||
// this code is shared between Mac and Linux, but may diverge in the future
|
|
||||||
func list(c *service.Service) ([]string, []string, []error) {
|
|
||||||
confDir := srvSysPath
|
|
||||||
if !c.System {
|
|
||||||
confDir = filepath.Join(c.Home, srvUserPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enuser path exists
|
|
||||||
err := os.MkdirAll(confDir, 0755)
|
|
||||||
if nil != err {
|
|
||||||
return nil, nil, []error{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
fis, err := ioutil.ReadDir(confDir)
|
|
||||||
if nil != err {
|
|
||||||
return nil, nil, []error{err}
|
|
||||||
}
|
|
||||||
|
|
||||||
managed := []string{}
|
|
||||||
others := []string{}
|
|
||||||
errs := []error{}
|
|
||||||
b := make([]byte, 256)
|
|
||||||
for i := range fis {
|
|
||||||
fi := fis[i]
|
|
||||||
if !strings.HasSuffix(strings.ToLower(fi.Name()), srvExt) || len(fi.Name()) <= srvLen {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
confFile := filepath.Join(confDir, fi.Name())
|
|
||||||
r, err := os.Open(confFile)
|
|
||||||
if nil != err {
|
|
||||||
errs = append(errs, &ManageError{
|
|
||||||
Name: confFile,
|
|
||||||
Hint: "Open file",
|
|
||||||
Parent: err,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := r.Read(b)
|
|
||||||
if nil != err {
|
|
||||||
errs = append(errs, &ManageError{
|
|
||||||
Name: confFile,
|
|
||||||
Hint: "Read file",
|
|
||||||
Parent: err,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
b = b[:n]
|
|
||||||
|
|
||||||
name := fi.Name()[:len(fi.Name())-srvLen]
|
|
||||||
if bytes.Contains(b, []byte("for serviceman.")) {
|
|
||||||
managed = append(managed, name)
|
|
||||||
} else {
|
|
||||||
others = append(others, name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return managed, others, errs
|
|
||||||
}
|
|
@ -6,10 +6,6 @@ import (
|
|||||||
"git.rootprojects.org/root/go-serviceman/service"
|
"git.rootprojects.org/root/go-serviceman/service"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Render(c *service.Service) ([]byte, error) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func install(c *service.Service) error {
|
func install(c *service.Service) error {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -6,11 +6,9 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.rootprojects.org/root/go-serviceman/runner"
|
|
||||||
"git.rootprojects.org/root/go-serviceman/service"
|
"git.rootprojects.org/root/go-serviceman/service"
|
||||||
|
|
||||||
"golang.org/x/sys/windows/registry"
|
"golang.org/x/sys/windows/registry"
|
||||||
@ -31,7 +29,7 @@ func init() {
|
|||||||
|
|
||||||
// TODO system service requires elevated privileges
|
// TODO system service requires elevated privileges
|
||||||
// See https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
|
// See https://coolaj86.com/articles/golang-and-windows-and-admins-oh-my/
|
||||||
func install(c *service.Service) (string, error) {
|
func install(c *service.Service) error {
|
||||||
/*
|
/*
|
||||||
// LEAVE THIS DOCUMENTATION HERE
|
// LEAVE THIS DOCUMENTATION HERE
|
||||||
reg.exe
|
reg.exe
|
||||||
@ -69,12 +67,9 @@ func install(c *service.Service) (string, error) {
|
|||||||
}
|
}
|
||||||
defer k.Close()
|
defer k.Close()
|
||||||
|
|
||||||
// Try to stop before trying to copy the file
|
|
||||||
_ = runner.Stop(c)
|
|
||||||
|
|
||||||
args, err := installServiceman(c)
|
args, err := installServiceman(c)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -101,7 +96,7 @@ func install(c *service.Service) (string, error) {
|
|||||||
|
|
||||||
regSZ := fmt.Sprintf(`"%s" %s`, args[0], strings.Join(args[1:], " "))
|
regSZ := fmt.Sprintf(`"%s" %s`, args[0], strings.Join(args[1:], " "))
|
||||||
if len(regSZ) > 260 {
|
if len(regSZ) > 260 {
|
||||||
return "", fmt.Errorf("data value is too long for registry entry")
|
return fmt.Errorf("data value is too long for registry entry")
|
||||||
}
|
}
|
||||||
// In order for a windows gui program to not show a console,
|
// In order for a windows gui program to not show a console,
|
||||||
// it has to not output any messages?
|
// it has to not output any messages?
|
||||||
@ -109,193 +104,28 @@ func install(c *service.Service) (string, error) {
|
|||||||
//fmt.Println(autorunKey, c.Title, regSZ)
|
//fmt.Println(autorunKey, c.Title, regSZ)
|
||||||
k.SetStringValue(c.Title, regSZ)
|
k.SetStringValue(c.Title, regSZ)
|
||||||
|
|
||||||
err = start(c)
|
return nil
|
||||||
return "serviceman", err
|
|
||||||
}
|
|
||||||
|
|
||||||
func Render(c *service.Service) ([]byte, error) {
|
|
||||||
b, err := json.Marshal(c)
|
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func start(conf *service.Service) error {
|
|
||||||
args := getRunnerArgs(conf)
|
|
||||||
args = append(args, "--daemon")
|
|
||||||
return Run(args[0], args[1:]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop(conf *service.Service) error {
|
|
||||||
return runner.Stop(conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func list(c *service.Service) ([]string, []string, []error) {
|
|
||||||
var errs []error
|
|
||||||
|
|
||||||
regs, err := listRegistry(c)
|
|
||||||
if nil != err {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cfgs, errors := listConfigs(c)
|
|
||||||
if 0 != len(errors) {
|
|
||||||
errs = append(errs, errors...)
|
|
||||||
}
|
|
||||||
|
|
||||||
managed := []string{}
|
|
||||||
for i := range cfgs {
|
|
||||||
managed = append(managed, cfgs[i].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
others := []string{}
|
|
||||||
for i := range regs {
|
|
||||||
reg := regs[i]
|
|
||||||
if 0 == len(cfgs) {
|
|
||||||
others = append(others, reg)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var found bool
|
|
||||||
for j := range cfgs {
|
|
||||||
cfg := cfgs[j]
|
|
||||||
// Registry Value Names are case-insensitive
|
|
||||||
if strings.ToLower(reg) == strings.ToLower(cfg.Title) {
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !found {
|
|
||||||
others = append(others, reg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return managed, others, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func getRunnerArgs(c *service.Service) []string {
|
|
||||||
self := os.Args[0]
|
|
||||||
debug := ""
|
|
||||||
if strings.Contains(self, "debug.exe") {
|
|
||||||
debug = "debug."
|
|
||||||
}
|
|
||||||
|
|
||||||
smdir := `\opt\serviceman`
|
|
||||||
// TODO support service level services (which probably wouldn't need serviceman)
|
|
||||||
smdir = filepath.Join(c.Home, ".local", smdir)
|
|
||||||
// for now we'll scope the runner to the name of the application
|
|
||||||
smbin := filepath.Join(smdir, `bin\serviceman.`+debug+c.Name+`.exe`)
|
|
||||||
|
|
||||||
confpath := filepath.Join(smdir, `etc`)
|
|
||||||
conffile := filepath.Join(confpath, c.Name+`.json`)
|
|
||||||
|
|
||||||
return []string{
|
|
||||||
smbin,
|
|
||||||
"run",
|
|
||||||
"--config",
|
|
||||||
conffile,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type winConf struct {
|
|
||||||
Filename string `json:"-"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func listConfigs(c *service.Service) ([]winConf, []error) {
|
|
||||||
var errs []error
|
|
||||||
|
|
||||||
smdir := `\opt\serviceman`
|
|
||||||
if !c.System {
|
|
||||||
smdir = filepath.Join(c.Home, ".local", smdir)
|
|
||||||
}
|
|
||||||
confpath := filepath.Join(smdir, `etc`)
|
|
||||||
|
|
||||||
infos, err := ioutil.ReadDir(confpath)
|
|
||||||
if nil != err {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
errs = append(errs, &ManageError{
|
|
||||||
Name: confpath,
|
|
||||||
Hint: "Read directory",
|
|
||||||
Parent: err,
|
|
||||||
})
|
|
||||||
return nil, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO report active status
|
|
||||||
srvs := []winConf{}
|
|
||||||
for i := range infos {
|
|
||||||
filename := strings.ToLower(infos[i].Name())
|
|
||||||
if len(filename) <= srvLen || !strings.HasSuffix(filename, srvExt) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
name := filename[:len(filename)-srvLen]
|
|
||||||
b, err := ioutil.ReadFile(filepath.Join(confpath, filename))
|
|
||||||
if nil != err {
|
|
||||||
errs = append(errs, &ManageError{
|
|
||||||
Name: name,
|
|
||||||
Hint: "Read file",
|
|
||||||
Parent: err,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cfg := winConf{Filename: filename}
|
|
||||||
err = json.Unmarshal(b, &cfg)
|
|
||||||
if nil != err {
|
|
||||||
errs = append(errs, &ManageError{
|
|
||||||
Name: name,
|
|
||||||
Hint: "Parse JSON",
|
|
||||||
Parent: err,
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
srvs = append(srvs, cfg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return srvs, errs
|
|
||||||
}
|
|
||||||
|
|
||||||
func listRegistry(c *service.Service) ([]string, error) {
|
|
||||||
autorunKey := `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`
|
|
||||||
k, _, err := registry.CreateKey(
|
|
||||||
registry.CURRENT_USER,
|
|
||||||
autorunKey,
|
|
||||||
registry.QUERY_VALUE,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer k.Close()
|
|
||||||
|
|
||||||
return k.ReadValueNames(-1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// copies self to install path and returns config path
|
// copies self to install path and returns config path
|
||||||
func installServiceman(c *service.Service) ([]string, error) {
|
func installServiceman(c *service.Service) ([]string, error) {
|
||||||
// TODO check version and upgrade or dismiss
|
// TODO check version and upgrade or dismiss
|
||||||
self := os.Args[0]
|
self := os.Args[0]
|
||||||
|
debug := ""
|
||||||
args := getRunnerArgs(c)
|
if strings.Contains(self, "debug.exe") {
|
||||||
smbin := args[0]
|
debug = "debug."
|
||||||
conffile := args[len(args)-1]
|
}
|
||||||
|
smdir := `\opt\serviceman`
|
||||||
|
// TODO support service level services (which probably wouldn't need serviceman)
|
||||||
|
smdir = filepath.Join(c.Home, ".local", smdir)
|
||||||
|
// for now we'll scope the runner to the name of the application
|
||||||
|
smbin := filepath.Join(smdir, `bin\serviceman.`+debug+c.Name+`.exe`)
|
||||||
|
|
||||||
if smbin != self {
|
if smbin != self {
|
||||||
err := os.MkdirAll(filepath.Dir(smbin), 0755)
|
err := os.MkdirAll(filepath.Dir(smbin), 0755)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Note: self may be the short name, in which case
|
|
||||||
// we should just use whatever is closest in the path
|
|
||||||
// exec.LookPath will handle this correctly
|
|
||||||
self, err = exec.LookPath(self)
|
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bin, err := ioutil.ReadFile(self)
|
bin, err := ioutil.ReadFile(self)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -306,19 +136,26 @@ func installServiceman(c *service.Service) ([]string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := Render(c)
|
b, err := json.Marshal(c)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
// this should be impossible, so we'll just panic
|
// this should be impossible, so we'll just panic
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
err = os.MkdirAll(filepath.Dir(conffile), 0755)
|
confpath := filepath.Join(smdir, `etc`)
|
||||||
|
err = os.MkdirAll(confpath, 0755)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
conffile := filepath.Join(confpath, c.Name+`.json`)
|
||||||
err = ioutil.WriteFile(conffile, b, 0640)
|
err = ioutil.WriteFile(conffile, b, 0640)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return args, nil
|
return []string{
|
||||||
|
smbin,
|
||||||
|
"run",
|
||||||
|
"--config",
|
||||||
|
conffile,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -3,34 +3,11 @@ package manager
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getService(system bool, home string, name string) (string, error) {
|
|
||||||
sys, user, err := getMatchingSrvs(home, name)
|
|
||||||
if nil != err {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
var service string
|
|
||||||
if system {
|
|
||||||
service, err = getOneSysSrv(sys, user, name)
|
|
||||||
if nil != err {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
service, err = getOneUserSrv(home, sys, user, name)
|
|
||||||
if nil != err {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return service, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runnable defines a command to run, along with its arguments,
|
// Runnable defines a command to run, along with its arguments,
|
||||||
// and whether or not failing to exit successfully matters.
|
// and whether or not failing to exit successfully matters.
|
||||||
// It also defines whether certains words must exist (or not exist)
|
// It also defines whether certains words must exist (or not exist)
|
||||||
@ -122,15 +99,10 @@ func getSystemSrvs() ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getUserSrvs(home string) ([]string, error) {
|
func getUserSrvs(home string) ([]string, error) {
|
||||||
confDir := filepath.Join(home, srvUserPath)
|
dir := filepath.Join(home, srvUserPath)
|
||||||
err := os.MkdirAll(confDir, 0755)
|
return getSrvs(dir)
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return getSrvs(confDir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "come.example.foo.plist" matches "foo"
|
|
||||||
func filterMatchingSrvs(plists []string, name string) []string {
|
func filterMatchingSrvs(plists []string, name string) []string {
|
||||||
filtered := []string{}
|
filtered := []string{}
|
||||||
|
|
||||||
@ -176,45 +148,59 @@ func getExactSrvMatch(srvs []string, name string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getOneSysSrv(sys []string, user []string, name string) (string, error) {
|
func getOneSysSrv(sys []string, user []string, name string) (string, error) {
|
||||||
if service := getExactSrvMatch(user, name); "" != service {
|
service := getExactSrvMatch(user, name)
|
||||||
return filepath.Join(srvSysPath, service), nil
|
if "" != service {
|
||||||
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errstr string
|
||||||
|
// system service was wanted
|
||||||
n := len(sys)
|
n := len(sys)
|
||||||
switch {
|
switch {
|
||||||
case 0 == n:
|
case 0 == n:
|
||||||
errstr := fmt.Sprintf("Didn't find user service matching %q\n", name)
|
errstr += fmt.Sprintf("Didn't find user service matching %q\n", name)
|
||||||
if 0 != len(user) {
|
if 0 != len(user) {
|
||||||
errstr += fmt.Sprintf("Did you intend to run a user service instead?\n\t%s\n", strings.Join(user, "\n\t"))
|
errstr += fmt.Sprintf("Did you intend to run a user service instead?\n\t%s\n", strings.Join(user, "\n\t"))
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf(errstr)
|
|
||||||
case n > 1:
|
case n > 1:
|
||||||
errstr := fmt.Sprintf("Found more than one matching service:\n\t%s\n", strings.Join(sys, "\n\t"))
|
errstr += fmt.Sprintf("Found more than one matching service:\n\t%s\n", strings.Join(sys, "\n\t"))
|
||||||
return "", fmt.Errorf(errstr)
|
|
||||||
default:
|
default:
|
||||||
return filepath.Join(srvSysPath, sys[0]), nil
|
service = filepath.Join(srvSysPath, sys[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "" != errstr {
|
||||||
|
return "", fmt.Errorf(errstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getOneUserSrv(home string, sys []string, user []string, name string) (string, error) {
|
func getOneUserSrv(home string, sys []string, user []string, name string) (string, error) {
|
||||||
if service := getExactSrvMatch(user, name); "" != service {
|
service := getExactSrvMatch(user, name)
|
||||||
return filepath.Join(home, srvUserPath, service), nil
|
if "" != service {
|
||||||
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var errstr string
|
||||||
|
// user service was wanted
|
||||||
n := len(user)
|
n := len(user)
|
||||||
switch {
|
switch {
|
||||||
case 0 == n:
|
case 0 == n:
|
||||||
errstr := fmt.Sprintf("Didn't find user service matching %q\n", name)
|
errstr += fmt.Sprintf("Didn't find user service matching %q\n", name)
|
||||||
if 0 != len(sys) {
|
if 0 != len(sys) {
|
||||||
errstr += fmt.Sprintf("Did you intend to run a system service instead?\n\t%s\n", strings.Join(sys, "\n\t"))
|
errstr += fmt.Sprintf("Did you intend to run a system service instead?\n\t%s\n", strings.Join(sys, "\n\t"))
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf(errstr)
|
|
||||||
case n > 1:
|
case n > 1:
|
||||||
errstr := fmt.Sprintf("Found more than one matching service:\n\t%s\n", strings.Join(user, "\n\t"))
|
errstr += fmt.Sprintf("Found more than one matching service:\n\t%s\n", strings.Join(user, "\n\t"))
|
||||||
return "", fmt.Errorf(errstr)
|
|
||||||
default:
|
default:
|
||||||
return filepath.Join(home, srvUserPath, user[0]), nil
|
service = filepath.Join(home, srvUserPath, user[0]+srvExt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "" != errstr {
|
||||||
|
return "", fmt.Errorf(errstr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return service, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustPrivs(system bool, cmds []Runnable) []Runnable {
|
func adjustPrivs(system bool, cmds []Runnable) []Runnable {
|
||||||
@ -233,21 +219,3 @@ func adjustPrivs(system bool, cmds []Runnable) []Runnable {
|
|||||||
|
|
||||||
return cmds
|
return cmds
|
||||||
}
|
}
|
||||||
|
|
||||||
func Run(bin string, args ...string) error {
|
|
||||||
cmd := exec.Command(bin, args...)
|
|
||||||
// for debugging
|
|
||||||
/*
|
|
||||||
out, err := cmd.CombinedOutput()
|
|
||||||
if nil != err {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
fmt.Println(string(out))
|
|
||||||
*/
|
|
||||||
|
|
||||||
err := cmd.Start()
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
package manager
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEmptyUserServicePath(t *testing.T) {
|
|
||||||
srvs, err := getUserSrvs("/tmp/fakeuser")
|
|
||||||
if nil != err {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(srvs) > 0 {
|
|
||||||
t.Fatal(fmt.Errorf("sanity fail: shouldn't get services from empty directory"))
|
|
||||||
}
|
|
||||||
|
|
||||||
dirs, err := ioutil.ReadDir(filepath.Join("/tmp/fakeuser", srvUserPath))
|
|
||||||
if nil != err {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if len(dirs) > 0 {
|
|
||||||
t.Fatal(fmt.Errorf("sanity fail: shouldn't get listing from empty directory"))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.RemoveAll("/tmp/fakeuser")
|
|
||||||
if nil != err {
|
|
||||||
panic("couldn't remove /tmp/fakeuser")
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
1
npm/.gitignore
vendored
1
npm/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
node_modules
|
|
@ -1,28 +0,0 @@
|
|||||||
# serviceman
|
|
||||||
|
|
||||||
A cross-platform service manager
|
|
||||||
|
|
||||||
```bash
|
|
||||||
serviceman add --name "my-project" node ./serve.js --port 3000
|
|
||||||
serviceman stop my-project
|
|
||||||
serviceman start my-project
|
|
||||||
```
|
|
||||||
|
|
||||||
Works with launchd (Mac), systemd (Linux), or standalone (Windows).
|
|
||||||
|
|
||||||
## Meta Package
|
|
||||||
|
|
||||||
This is a meta-package to fetch and install the correction version of
|
|
||||||
[go-serviceman](https://git.rootprojects.org/root/serviceman)
|
|
||||||
for your architecture and platform.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install serviceman
|
|
||||||
```
|
|
||||||
|
|
||||||
## How does it work?
|
|
||||||
|
|
||||||
1. Resolves executable from PATH, or hashbang (ex: `#!/usr/bin/env node`)
|
|
||||||
2. Resolves file and directory paths to absolute paths (ex: `/Users/me/my-project/serve.js`)
|
|
||||||
3. Creates a template `.plist` (Mac), `.service` (Linux), or `.json` (Windows) file
|
|
||||||
4. Calls `launchd` (Mac), `systemd` (Linux), or `serviceman-runner` (Windows) to enable/start/stop/etc
|
|
@ -1 +0,0 @@
|
|||||||
# this will be replaced by the postinstall script
|
|
18
npm/package-lock.json
generated
18
npm/package-lock.json
generated
@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "serviceman",
|
|
||||||
"version": "0.5.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@root/mkdirp": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@root/mkdirp/-/mkdirp-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-hxGAYUx5029VggfG+U9naAhQkoMSXtOeXtbql97m3Hi6/sQSRL/4khKZPyOF6w11glyCOU38WCNLu9nUcSjOfA=="
|
|
||||||
},
|
|
||||||
"@root/request": {
|
|
||||||
"version": "1.3.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/@root/request/-/request-1.3.11.tgz",
|
|
||||||
"integrity": "sha512-3a4Eeghcjsfe6zh7EJ+ni1l8OK9Fz2wL1OjP4UCa0YdvtH39kdXB9RGWuzyNv7dZi0+Ffkc83KfH0WbPMiuJFw=="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "serviceman",
|
|
||||||
"version": "0.7.0",
|
|
||||||
"description": "A cross-platform service manager",
|
|
||||||
"main": "index.js",
|
|
||||||
"homepage": "https://git.rootprojects.org/root/serviceman/src/branch/master/npm",
|
|
||||||
"files": [
|
|
||||||
"bin/",
|
|
||||||
"scripts/"
|
|
||||||
],
|
|
||||||
"bin": {
|
|
||||||
"serviceman": "bin/serviceman"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"serviceman": "serviceman",
|
|
||||||
"postinstall": "node scripts/fetch-serviceman.js",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://git.rootprojects.org/root/serviceman.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"launchd",
|
|
||||||
"systemd",
|
|
||||||
"winsvc",
|
|
||||||
"launchctl",
|
|
||||||
"systemctl",
|
|
||||||
"HKEY_CURRENT_USER",
|
|
||||||
"HKCU",
|
|
||||||
"Run"
|
|
||||||
],
|
|
||||||
"author": "AJ ONeal <coolaj86@gmail.com> (https://coolaj86.com/)",
|
|
||||||
"license": "MPL-2.0",
|
|
||||||
"dependencies": {
|
|
||||||
"@root/mkdirp": "^1.0.0",
|
|
||||||
"@root/request": "^1.3.11"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,269 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
var path = require('path');
|
|
||||||
var os = require('os');
|
|
||||||
|
|
||||||
// https://nodejs.org/api/os.html#os_os_arch
|
|
||||||
// 'arm', 'arm64', 'ia32', 'mips', 'mipsel', 'ppc', 'ppc64', 's390', 's390x', 'x32', and 'x64'
|
|
||||||
var arch = os.arch(); // process.arch
|
|
||||||
|
|
||||||
// https://nodejs.org/api/os.html#os_os_platform
|
|
||||||
// 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', 'win32'
|
|
||||||
var platform = os.platform(); // process.platform
|
|
||||||
var ext = /^win/i.test(platform) ? '.exe' : '';
|
|
||||||
|
|
||||||
// This is _probably_ right. It's good enough for us
|
|
||||||
// https://github.com/nodejs/node/issues/13629
|
|
||||||
if ('arm' === arch) {
|
|
||||||
arch += 'v' + process.config.variables.arm_version;
|
|
||||||
}
|
|
||||||
|
|
||||||
var map = {
|
|
||||||
// arches
|
|
||||||
armv6: 'armv6',
|
|
||||||
armv7: 'armv7',
|
|
||||||
arm64: 'armv8',
|
|
||||||
ia32: '386',
|
|
||||||
x32: '386',
|
|
||||||
x64: 'amd64',
|
|
||||||
// platforms
|
|
||||||
darwin: 'darwin',
|
|
||||||
linux: 'linux',
|
|
||||||
win32: 'windows'
|
|
||||||
};
|
|
||||||
|
|
||||||
arch = map[arch];
|
|
||||||
platform = map[platform];
|
|
||||||
|
|
||||||
if (!arch || !platform) {
|
|
||||||
console.error(
|
|
||||||
"'" + os.platform() + "' on '" + os.arch() + "' isn't supported yet."
|
|
||||||
);
|
|
||||||
console.error(
|
|
||||||
'Please open an issue at https://git.rootprojects.org/root/serviceman/issues'
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newVer = require('../package.json').version;
|
|
||||||
var fs = require('fs');
|
|
||||||
var exec = require('child_process').exec;
|
|
||||||
var request = require('@root/request');
|
|
||||||
var mkdirp = require('@root/mkdirp');
|
|
||||||
|
|
||||||
function needsUpdate(oldVer, newVer) {
|
|
||||||
// "v1.0.0-pre" is BEHIND "v1.0.0"
|
|
||||||
newVer = newVer
|
|
||||||
.replace(/^v/, '')
|
|
||||||
.split(/[\.\-\+]/)
|
|
||||||
.filter(Boolean);
|
|
||||||
oldVer = oldVer
|
|
||||||
.replace(/^v/, '')
|
|
||||||
.split(/[\.\-\+]/)
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
if (!oldVer.length) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ex: v1.0.0-pre vs v1.0.0
|
|
||||||
if (newVer[3] && !oldVer[3]) {
|
|
||||||
// don't install beta over stable
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ex: old is v1.0.0-pre
|
|
||||||
if (oldVer[3]) {
|
|
||||||
if (oldVer[2] > 0) {
|
|
||||||
oldVer[2] -= 1;
|
|
||||||
} else if (oldVer[1] > 0) {
|
|
||||||
oldVer[2] = 999;
|
|
||||||
oldVer[1] -= 1;
|
|
||||||
} else if (oldVer[0] > 0) {
|
|
||||||
oldVer[2] = 999;
|
|
||||||
oldVer[1] = 999;
|
|
||||||
oldVer[0] -= 1;
|
|
||||||
} else {
|
|
||||||
// v0.0.0
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ex: v1.0.1 vs v1.0.0-pre
|
|
||||||
if (newVer[3]) {
|
|
||||||
if (newVer[2] > 0) {
|
|
||||||
newVer[2] -= 1;
|
|
||||||
} else if (newVer[1] > 0) {
|
|
||||||
newVer[2] = 999;
|
|
||||||
newVer[1] -= 1;
|
|
||||||
} else if (newVer[0] > 0) {
|
|
||||||
newVer[2] = 999;
|
|
||||||
newVer[1] = 999;
|
|
||||||
newVer[0] -= 1;
|
|
||||||
} else {
|
|
||||||
// v0.0.0
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ex: v1.0.1 vs v1.0.0
|
|
||||||
if (oldVer[0] > newVer[0]) {
|
|
||||||
return false;
|
|
||||||
} else if (oldVer[0] < newVer[0]) {
|
|
||||||
return true;
|
|
||||||
} else if (oldVer[1] > newVer[1]) {
|
|
||||||
return false;
|
|
||||||
} else if (oldVer[1] < newVer[1]) {
|
|
||||||
return true;
|
|
||||||
} else if (oldVer[2] > newVer[2]) {
|
|
||||||
return false;
|
|
||||||
} else if (oldVer[2] < newVer[2]) {
|
|
||||||
return true;
|
|
||||||
} else if (!oldVer[3] && newVer[3]) {
|
|
||||||
return false;
|
|
||||||
} else if (oldVer[3] && !newVer[3]) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Same version
|
|
||||||
console.log(false === needsUpdate('0.5.0', '0.5.0'));
|
|
||||||
// No previous version
|
|
||||||
console.log(true === needsUpdate('', '0.5.1'));
|
|
||||||
// The new version is slightly newer
|
|
||||||
console.log(true === needsUpdate('0.5.0', '0.5.1'));
|
|
||||||
console.log(true === needsUpdate('0.4.999-pre1', '0.5.0-pre1'));
|
|
||||||
// The new version is slightly older
|
|
||||||
console.log(false === needsUpdate('0.5.0', '0.5.0-pre1'));
|
|
||||||
console.log(false === needsUpdate('0.5.1', '0.5.0'));
|
|
||||||
*/
|
|
||||||
|
|
||||||
function install(name, bindirs, getVersion, parseVersion, urlTpl) {
|
|
||||||
exec(getVersion, { windowsHide: true }, function(err, stdout) {
|
|
||||||
var oldVer = parseVersion(stdout);
|
|
||||||
//console.log('old:', oldVer, 'new:', newVer);
|
|
||||||
if (!needsUpdate(oldVer, newVer)) {
|
|
||||||
console.info(
|
|
||||||
'Current ' + name + ' version is new enough:',
|
|
||||||
oldVer,
|
|
||||||
newVer
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
//} else {
|
|
||||||
// console.info('Current serviceman version is older:', oldVer, newVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = urlTpl
|
|
||||||
.replace(/{{ .Version }}/g, newVer)
|
|
||||||
.replace(/{{ .Platform }}/g, platform)
|
|
||||||
.replace(/{{ .Arch }}/g, arch)
|
|
||||||
.replace(/{{ .Ext }}/g, ext);
|
|
||||||
|
|
||||||
console.info('Installing from', url);
|
|
||||||
return request({ uri: url, encoding: null }, function(err, resp) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//console.log(resp.body.byteLength);
|
|
||||||
//console.log(typeof resp.body);
|
|
||||||
var bin = name + ext;
|
|
||||||
function next() {
|
|
||||||
if (!bindirs.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var bindir = bindirs.pop();
|
|
||||||
return mkdirp(bindir, function(err) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var localsrv = path.join(bindir, bin);
|
|
||||||
return fs.writeFile(localsrv, resp.body, function(err) {
|
|
||||||
next();
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fs.chmodSync(localsrv, parseInt('0755', 8));
|
|
||||||
console.info('Wrote', bin, 'to', bindir);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function winstall(name, bindir) {
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(bindir, name),
|
|
||||||
'#!/usr/bin/env bash\n"$(dirname "$0")/serviceman.exe" "$@"\nexit $?'
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
// because bugs in npm + git bash oddities, of course
|
|
||||||
// https://npm.community/t/globally-installed-package-does-not-execute-in-git-bash-on-windows/9394
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(path.join(__dirname, '../../.bin'), name),
|
|
||||||
[
|
|
||||||
'#!/bin/sh',
|
|
||||||
'# manual bugfix patch for npm on windows',
|
|
||||||
'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")',
|
|
||||||
'"$basedir/../' + name + '/bin/' + name + '" "$@"',
|
|
||||||
'exit $?'
|
|
||||||
].join('\n')
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(path.join(__dirname, '../../..'), name),
|
|
||||||
[
|
|
||||||
'#!/bin/sh',
|
|
||||||
'# manual bugfix patch for npm on windows',
|
|
||||||
'basedir=$(dirname "$(echo "$0" | sed -e \'s,\\\\,/,g\')")',
|
|
||||||
'"$basedir/node_modules/' + name + '/bin/' + name + '" "$@"',
|
|
||||||
'exit $?'
|
|
||||||
].join('\n')
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
// end bugfix
|
|
||||||
}
|
|
||||||
|
|
||||||
function run() {
|
|
||||||
//var homedir = require('os').homedir();
|
|
||||||
//var bindir = path.join(homedir, '.local', 'bin');
|
|
||||||
var bindir = path.resolve(__dirname, '..', 'bin');
|
|
||||||
var name = 'serviceman';
|
|
||||||
if ('.exe' === ext) {
|
|
||||||
winstall(name, bindir);
|
|
||||||
}
|
|
||||||
|
|
||||||
return install(
|
|
||||||
name,
|
|
||||||
[bindir],
|
|
||||||
'serviceman version',
|
|
||||||
function parseVersion(stdout) {
|
|
||||||
return (stdout || '').split(' ')[0];
|
|
||||||
},
|
|
||||||
'https://rootprojects.org/serviceman/dist/{{ .Platform }}/{{ .Arch }}/serviceman{{ .Ext }}'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (require.main === module) {
|
|
||||||
run();
|
|
||||||
}
|
|
147
runner/runner.go
147
runner/runner.go
@ -2,17 +2,13 @@ package runner
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.rootprojects.org/root/go-serviceman/service"
|
"git.rootprojects.org/root/go-serviceman/service"
|
||||||
|
|
||||||
ps "github.com/mitchellh/go-ps"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Filled in on init by runner_windows.go
|
// Filled in on init by runner_windows.go
|
||||||
@ -21,9 +17,7 @@ var shellArgs = []string{}
|
|||||||
// Notes on spawning a child process
|
// Notes on spawning a child process
|
||||||
// https://groups.google.com/forum/#!topic/golang-nuts/shST-SDqIp4
|
// https://groups.google.com/forum/#!topic/golang-nuts/shST-SDqIp4
|
||||||
|
|
||||||
// Start will execute the service, and write the PID and logs out to the log directory
|
func Run(conf *service.Service) {
|
||||||
func Start(conf *service.Service) error {
|
|
||||||
pid := os.Getpid()
|
|
||||||
originalBackoff := 1 * time.Second
|
originalBackoff := 1 * time.Second
|
||||||
maxBackoff := 1 * time.Minute
|
maxBackoff := 1 * time.Minute
|
||||||
threshold := 5 * time.Second
|
threshold := 5 * time.Second
|
||||||
@ -32,17 +26,6 @@ func Start(conf *service.Service) error {
|
|||||||
failures := 0
|
failures := 0
|
||||||
logfile := filepath.Join(conf.Logdir, conf.Name+".log")
|
logfile := filepath.Join(conf.Logdir, conf.Name+".log")
|
||||||
|
|
||||||
if oldPid, exename, err := getProcess(conf); nil == err {
|
|
||||||
return fmt.Errorf("%q may already be running as %q (pid %d)", conf.Name, exename, oldPid)
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
maybeWritePidFile(pid, conf)
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
binpath := conf.Exec
|
binpath := conf.Exec
|
||||||
args := []string{}
|
args := []string{}
|
||||||
if "" != conf.Interpreter {
|
if "" != conf.Interpreter {
|
||||||
@ -78,11 +61,6 @@ func Start(conf *service.Service) error {
|
|||||||
if "" != conf.Workdir {
|
if "" != conf.Workdir {
|
||||||
cmd.Dir = conf.Workdir
|
cmd.Dir = conf.Workdir
|
||||||
}
|
}
|
||||||
if len(conf.Envs) > 0 {
|
|
||||||
for k, v := range conf.Envs {
|
|
||||||
cmd.Env = append(cmd.Env, k+"="+v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
err = cmd.Start()
|
err = cmd.Start()
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Fprintf(lf, "[%s] Could not start %q process: %s\n", time.Now(), conf.Name, err)
|
fmt.Fprintf(lf, "[%s] Could not start %q process: %s\n", time.Now(), conf.Name, err)
|
||||||
@ -106,7 +84,7 @@ func Start(conf *service.Service) error {
|
|||||||
backoff = originalBackoff
|
backoff = originalBackoff
|
||||||
failures = 0
|
failures = 0
|
||||||
} else {
|
} else {
|
||||||
failures++
|
failures += 1
|
||||||
fmt.Fprintf(lf, "Waiting %s to restart %q (%d consequtive immediate exits)\n", backoff, conf.Name, failures)
|
fmt.Fprintf(lf, "Waiting %s to restart %q (%d consequtive immediate exits)\n", backoff, conf.Name, failures)
|
||||||
time.Sleep(backoff)
|
time.Sleep(backoff)
|
||||||
backoff *= 2
|
backoff *= 2
|
||||||
@ -115,125 +93,4 @@ func Start(conf *service.Service) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop will find and stop another serviceman runner instance by it's PID
|
|
||||||
func Stop(conf *service.Service) error {
|
|
||||||
i := 0
|
|
||||||
var err error
|
|
||||||
for {
|
|
||||||
if i >= 3 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
oldPid, exename, err2 := getProcess(conf)
|
|
||||||
err = err2
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
fmt.Printf("killing old process %q with pid %d\n", exename, oldPid)
|
|
||||||
err := kill(oldPid)
|
|
||||||
if nil != err {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return waitForProcessToDie(oldPid)
|
|
||||||
case ErrNoPidFile:
|
|
||||||
return err
|
|
||||||
case ErrNoProcess:
|
|
||||||
return err
|
|
||||||
case ErrInvalidPidFile:
|
|
||||||
fallthrough
|
|
||||||
default:
|
|
||||||
// waiting a little bit since the PID is written every second
|
|
||||||
time.Sleep(400 * time.Millisecond)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("unexpected error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restart calls Stop, ignoring any failure, and then Start, returning any failure
|
|
||||||
func Restart(conf *service.Service) error {
|
|
||||||
_ = Stop(conf)
|
|
||||||
return Start(conf)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ErrNoPidFile = fmt.Errorf("no pid file")
|
|
||||||
var ErrInvalidPidFile = fmt.Errorf("malformed pid file")
|
|
||||||
var ErrNoProcess = fmt.Errorf("process not found by pid")
|
|
||||||
|
|
||||||
func waitForProcessToDie(pid int) error {
|
|
||||||
exename := "unknown"
|
|
||||||
for i := 0; i < 10; i++ {
|
|
||||||
px, err := ps.FindProcess(pid)
|
|
||||||
if nil != err {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if nil == px {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
exename = px.Executable()
|
|
||||||
time.Sleep(1 * time.Second)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("process %q (%d) just won't die", exename, pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getProcess(conf *service.Service) (int, string, error) {
|
|
||||||
// TODO make Pidfile() a property of conf?
|
|
||||||
pidFile := filepath.Join(conf.Logdir, conf.Name+".pid")
|
|
||||||
b, err := ioutil.ReadFile(pidFile)
|
|
||||||
if nil != err {
|
|
||||||
return 0, "", ErrNoPidFile
|
|
||||||
}
|
|
||||||
|
|
||||||
s := strings.TrimSpace(string(b))
|
|
||||||
oldPid, err := strconv.Atoi(s)
|
|
||||||
if nil != err {
|
|
||||||
return 0, "", ErrInvalidPidFile
|
|
||||||
}
|
|
||||||
|
|
||||||
px, err := ps.FindProcess(oldPid)
|
|
||||||
if nil != err {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
if nil == px {
|
|
||||||
return 0, "", ErrNoProcess
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = os.FindProcess(oldPid)
|
|
||||||
if nil != err {
|
|
||||||
return 0, "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
exename := px.Executable()
|
|
||||||
return oldPid, exename, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO error out if can't write to PID or log
|
|
||||||
func maybeWritePidFile(pid int, conf *service.Service) bool {
|
|
||||||
newPid := []byte(strconv.Itoa(pid))
|
|
||||||
|
|
||||||
// TODO use a specific PID dir? meh...
|
|
||||||
pidFile := filepath.Join(conf.Logdir, conf.Name+".pid")
|
|
||||||
b, err := ioutil.ReadFile(pidFile)
|
|
||||||
if nil != err {
|
|
||||||
ioutil.WriteFile(pidFile, newPid, 0644)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
s := strings.TrimSpace(string(b))
|
|
||||||
oldPid, err := strconv.Atoi(s)
|
|
||||||
if nil != err {
|
|
||||||
ioutil.WriteFile(pidFile, newPid, 0644)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if oldPid != pid {
|
|
||||||
Stop(conf)
|
|
||||||
ioutil.WriteFile(pidFile, newPid, 0644)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,7 @@
|
|||||||
|
|
||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import "os/exec"
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func backgroundCmd(cmd *exec.Cmd) {
|
func backgroundCmd(cmd *exec.Cmd) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func kill(pid int) error {
|
|
||||||
p, err := os.FindProcess(pid)
|
|
||||||
// already died
|
|
||||||
if nil != err {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return p.Kill()
|
|
||||||
}
|
|
||||||
|
@ -1,23 +1,10 @@
|
|||||||
package runner
|
package runner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strconv"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
func backgroundCmd(cmd *exec.Cmd) {
|
func backgroundCmd(cmd *exec.Cmd) {
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
func kill(pid int) error {
|
|
||||||
// Kill the whole processes tree (all children and grandchildren)
|
|
||||||
cmd := exec.Command("taskkill", "/pid", strconv.Itoa(pid), "/T", "/F")
|
|
||||||
b, err := cmd.CombinedOutput()
|
|
||||||
if nil != err {
|
|
||||||
return fmt.Errorf("%s: %s", err.Error(), string(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
@ -71,7 +71,7 @@ type Service struct {
|
|||||||
MultiuserProtection bool `json:"multiuser_protection,omitempty"`
|
MultiuserProtection bool `json:"multiuser_protection,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) NormalizeWithoutPath() {
|
func (s *Service) Normalize(force bool) {
|
||||||
if "" == s.Name {
|
if "" == s.Name {
|
||||||
ext := filepath.Ext(s.Exec)
|
ext := filepath.Ext(s.Exec)
|
||||||
base := filepath.Base(s.Exec[:len(s.Exec)-len(ext)])
|
base := filepath.Base(s.Exec[:len(s.Exec)-len(ext)])
|
||||||
@ -93,16 +93,11 @@ func (s *Service) NormalizeWithoutPath() {
|
|||||||
os.Exit(4)
|
os.Exit(4)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s.Home = home
|
|
||||||
s.Local = filepath.Join(home, ".local")
|
s.Local = filepath.Join(home, ".local")
|
||||||
s.Logdir = filepath.Join(home, ".local", "share", s.Name, "var", "log")
|
s.Logdir = filepath.Join(home, ".local", "share", s.Name, "var", "log")
|
||||||
} else {
|
} else {
|
||||||
s.Logdir = "/var/log/" + s.Name
|
s.Logdir = "/var/log/" + s.Name
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) Normalize(force bool) {
|
|
||||||
s.NormalizeWithoutPath()
|
|
||||||
|
|
||||||
// Check to see if Exec exists
|
// Check to see if Exec exists
|
||||||
// /whatever => must exist exactly
|
// /whatever => must exist exactly
|
||||||
@ -116,7 +111,7 @@ func (s *Service) Normalize(force bool) {
|
|||||||
_, err := os.Stat(optpath)
|
_, err := os.Stat(optpath)
|
||||||
if nil == err {
|
if nil == err {
|
||||||
bad = false
|
bad = false
|
||||||
//fmt.Fprintf(os.Stderr, "Using '%s' for '%s'\n", optpath, s.Exec)
|
fmt.Fprintf(os.Stderr, "Using '%s' for '%s'\n", optpath, s.Exec)
|
||||||
s.Exec = optpath
|
s.Exec = optpath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
521
serviceman.go
521
serviceman.go
@ -1,6 +1,5 @@
|
|||||||
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver
|
//go:generate go run -mod=vendor git.rootprojects.org/root/go-gitver
|
||||||
|
|
||||||
// main runs the things and does the stuff
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -10,11 +9,8 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"os/user"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"git.rootprojects.org/root/go-serviceman/manager"
|
"git.rootprojects.org/root/go-serviceman/manager"
|
||||||
"git.rootprojects.org/root/go-serviceman/runner"
|
"git.rootprojects.org/root/go-serviceman/runner"
|
||||||
@ -22,17 +18,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var GitRev = "000000000"
|
var GitRev = "000000000"
|
||||||
var GitVersion = "v0.5.3-pre+dirty"
|
var GitVersion = "v0.0.0"
|
||||||
var GitTimestamp = time.Now().Format(time.RFC3339)
|
var GitTimestamp = time.Now().Format(time.RFC3339)
|
||||||
|
|
||||||
func usage() {
|
func usage() {
|
||||||
fmt.Println("Usage:")
|
fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
|
||||||
fmt.Println("\tserviceman <command> --help")
|
fmt.Println("Usage: serviceman run --config ./foo-app.json")
|
||||||
fmt.Println("\tserviceman add ./foo-app -- --foo-arg")
|
|
||||||
fmt.Println("\tserviceman run --config ./foo-app.json")
|
|
||||||
fmt.Println("\tserviceman list --all")
|
|
||||||
fmt.Println("\tserviceman start <name>")
|
|
||||||
fmt.Println("\tserviceman stop <name>")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -47,16 +38,10 @@ func main() {
|
|||||||
switch top {
|
switch top {
|
||||||
case "version":
|
case "version":
|
||||||
fmt.Println(GitVersion, GitTimestamp, GitRev)
|
fmt.Println(GitVersion, GitTimestamp, GitRev)
|
||||||
case "run":
|
|
||||||
run()
|
|
||||||
case "add":
|
case "add":
|
||||||
add()
|
add()
|
||||||
case "start":
|
case "run":
|
||||||
start()
|
run()
|
||||||
case "stop":
|
|
||||||
stop()
|
|
||||||
case "list":
|
|
||||||
list()
|
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
|
fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
|
||||||
usage()
|
usage()
|
||||||
@ -69,488 +54,89 @@ func add() {
|
|||||||
Restart: true,
|
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
|
||||||
|
|
||||||
force := false
|
force := false
|
||||||
forUser := false
|
forUser := false
|
||||||
forSystem := false
|
forSystem := false
|
||||||
dryrun := false
|
|
||||||
pathEnv := ""
|
|
||||||
flag.StringVar(&conf.Title, "title", "", "a human-friendly name for the service")
|
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.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.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.URL, "url", "", "the documentation on home page of the service")
|
||||||
flag.StringVar(&conf.Workdir, "workdir", "", "the directory in which the service should be started (if supported)")
|
//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.StringVar(&conf.ReverseDNS, "rdns", "", "a plist-friendly Reverse DNS name for launchctl (ex: com.example.foo-app)")
|
||||||
flag.BoolVar(&forSystem, "system", false, "attempt to add system service as an unprivileged/unelevated user")
|
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(&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.BoolVar(&force, "force", false, "if the interpreter or executable doesn't exist, or things don't make sense, try anyway")
|
||||||
flag.StringVar(&pathEnv, "path", "", "set the path for the resulting systemd service")
|
|
||||||
flag.StringVar(&conf.User, "username", "", "run the service as this user")
|
flag.StringVar(&conf.User, "username", "", "run the service as this user")
|
||||||
flag.StringVar(&conf.Group, "groupname", "", "run the service as this group")
|
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.BoolVar(&conf.PrivilegedPorts, "cap-net-bind", false, "this service should have access to privileged ports")
|
||||||
flag.BoolVar(&dryrun, "dryrun", false, "output the service file without modifying anything on disk")
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
flagargs := flag.Args()
|
args = flag.Args()
|
||||||
|
|
||||||
// You must have something to run, duh
|
|
||||||
n := len(flagargs)
|
|
||||||
if 0 == n {
|
|
||||||
fmt.Println("Usage: serviceman add ./foo-app --foo-arg")
|
|
||||||
os.Exit(2)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if forUser && forSystem {
|
if forUser && forSystem {
|
||||||
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are three groups of flags
|
|
||||||
// serviceman --flag1 arg1 non-flag-arg --child1 -- --raw1 -- --raw2
|
|
||||||
// serviceman --flag1 arg1 // these belong to serviceman
|
|
||||||
// non-flag-arg --child1 // these will be interpretted
|
|
||||||
// -- // separator
|
|
||||||
// --raw1 -- --raw2 // after the separater (including additional separators) will be ignored
|
|
||||||
rawargs := []string{}
|
|
||||||
for i := range flagargs {
|
|
||||||
if "--" == flagargs[i] {
|
|
||||||
if len(flagargs) > i+1 {
|
|
||||||
rawargs = flagargs[i+1:]
|
|
||||||
}
|
|
||||||
flagargs = flagargs[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assumptions
|
|
||||||
ass := []string{}
|
|
||||||
if forUser {
|
if forUser {
|
||||||
conf.System = false
|
conf.System = false
|
||||||
} else if forSystem {
|
} else if forSystem {
|
||||||
conf.System = true
|
conf.System = true
|
||||||
} else {
|
} else {
|
||||||
conf.System = manager.IsPrivileged()
|
conf.System = manager.IsPrivileged()
|
||||||
if conf.System {
|
|
||||||
ass = append(ass, "# Because you're a privileged user")
|
|
||||||
ass = append(ass, " --system")
|
|
||||||
ass = append(ass, "")
|
|
||||||
} else {
|
|
||||||
ass = append(ass, "# Because you're a unprivileged user")
|
|
||||||
ass = append(ass, " --user")
|
|
||||||
ass = append(ass, "")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if "" == conf.Workdir {
|
|
||||||
dir, _ := os.Getwd()
|
|
||||||
conf.Workdir = dir
|
|
||||||
ass = append(ass, "# Because this is your current working directory")
|
|
||||||
ass = append(ass, fmt.Sprintf(" --workdir %s", conf.Workdir))
|
|
||||||
ass = append(ass, "")
|
|
||||||
}
|
|
||||||
if "" == conf.Name {
|
|
||||||
name, _ := os.Getwd()
|
|
||||||
base := filepath.Base(name)
|
|
||||||
ext := filepath.Ext(base)
|
|
||||||
n := (len(base) - len(ext))
|
|
||||||
name = base[:n]
|
|
||||||
if "" == name {
|
|
||||||
name = base
|
|
||||||
}
|
|
||||||
conf.Name = name
|
|
||||||
ass = append(ass, "# Because this is the name of your current working directory")
|
|
||||||
ass = append(ass, fmt.Sprintf(" --name %s", conf.Name))
|
|
||||||
ass = append(ass, "")
|
|
||||||
}
|
|
||||||
if "" != pathEnv {
|
|
||||||
conf.Envs = make(map[string]string)
|
|
||||||
conf.Envs["PATH"] = pathEnv
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exepath, err := findExec(flagargs[0], force)
|
n := len(args)
|
||||||
if nil != err {
|
if 0 == n {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
fmt.Println("Usage: serviceman add ./foo-app -- --foo-arg")
|
||||||
os.Exit(3)
|
os.Exit(2)
|
||||||
return
|
|
||||||
}
|
|
||||||
flagargs[0] = exepath
|
|
||||||
|
|
||||||
exeargs, err := testScript(flagargs[0], force)
|
|
||||||
if nil != err {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
||||||
os.Exit(3)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
flagargs = append(exeargs, flagargs...)
|
execpath, err := manager.WhereIs(args[0])
|
||||||
// TODO
|
if nil != err {
|
||||||
for i := range flagargs {
|
fmt.Fprintf(os.Stderr, "Error: '%s' could not be found.\n", args[0])
|
||||||
arg := flagargs[i]
|
if !force {
|
||||||
arg = filepath.ToSlash(arg)
|
os.Exit(3)
|
||||||
// Paths considered to be anything starting with ./, .\, /, \, C:
|
return
|
||||||
if "." == arg || strings.Contains(arg, "/") {
|
|
||||||
//if "." == arg || (len(arg) >= 2 && "./" == arg[:2] || '/' == arg[0] || "C:" == strings.ToUpper(arg[:1])) {
|
|
||||||
var err error
|
|
||||||
arg, err = filepath.Abs(arg)
|
|
||||||
if nil == err {
|
|
||||||
_, err = os.Stat(arg)
|
|
||||||
}
|
|
||||||
if nil != err {
|
|
||||||
fmt.Printf("%q appears to be a file path, but %q could not be read\n", flagargs[i], arg)
|
|
||||||
if !force {
|
|
||||||
os.Exit(7)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if '\\' != os.PathSeparator {
|
|
||||||
// Convert paths back to .\ for Windows
|
|
||||||
arg = filepath.FromSlash(arg)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookin' good
|
|
||||||
flagargs[i] = arg
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
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...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We won't bother with Interpreter here
|
conf.Normalize(force)
|
||||||
// (it's really just for documentation),
|
|
||||||
// but we will add any and all unchecked args to the full slice
|
|
||||||
conf.Exec = flagargs[0]
|
|
||||||
conf.Argv = append(flagargs[1:], rawargs...)
|
|
||||||
|
|
||||||
// TODO update docs: go to the work directory
|
|
||||||
// TODO test with "npm start"
|
|
||||||
|
|
||||||
conf.NormalizeWithoutPath()
|
|
||||||
|
|
||||||
//fmt.Printf("\n%#v\n\n", conf)
|
//fmt.Printf("\n%#v\n\n", conf)
|
||||||
if conf.System && !manager.IsPrivileged() {
|
if conf.System && !manager.IsPrivileged() {
|
||||||
fmt.Fprintf(os.Stderr, "Warning: You may need to use 'sudo' to add %q as a privileged system service.\n", conf.Name)
|
fmt.Fprintf(os.Stderr, "Warning: You may need to use 'sudo' to add %q as a privileged system service.\n", conf.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ass) > 0 {
|
err = manager.Install(conf)
|
||||||
fmt.Printf("OPTIONS: Making some assumptions...\n\n")
|
|
||||||
for i := range ass {
|
|
||||||
fmt.Println("\t" + ass[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find who this is running as
|
|
||||||
// And pretty print the command to run
|
|
||||||
runAs := conf.User
|
|
||||||
var wasflag bool
|
|
||||||
fmt.Printf("COMMAND: Service %q will be run like this (more or less):\n\n", conf.Title)
|
|
||||||
if conf.System {
|
|
||||||
if "" == runAs {
|
|
||||||
runAs = "root"
|
|
||||||
}
|
|
||||||
fmt.Printf("\t# Starts on system boot, as %q\n", runAs)
|
|
||||||
} else {
|
|
||||||
u, _ := user.Current()
|
|
||||||
runAs = u.Name
|
|
||||||
if "" == runAs {
|
|
||||||
runAs = u.Username
|
|
||||||
}
|
|
||||||
fmt.Printf("\t# Starts as %q, when %q logs in\n", runAs, u.Username)
|
|
||||||
}
|
|
||||||
//fmt.Printf("\tpushd %s\n", conf.Workdir)
|
|
||||||
fmt.Printf("\t%s\n", conf.Exec)
|
|
||||||
for i := range conf.Argv {
|
|
||||||
arg := conf.Argv[i]
|
|
||||||
if '-' == arg[0] {
|
|
||||||
if wasflag {
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
wasflag = true
|
|
||||||
fmt.Printf("\t\t%s", arg)
|
|
||||||
} else {
|
|
||||||
if wasflag {
|
|
||||||
fmt.Printf(" %s\n", arg)
|
|
||||||
} else {
|
|
||||||
fmt.Printf("\t\t%s\n", arg)
|
|
||||||
}
|
|
||||||
wasflag = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if wasflag {
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
|
|
||||||
// TODO output config without installing
|
|
||||||
if dryrun {
|
|
||||||
b, err := manager.Render(conf)
|
|
||||||
if nil != err {
|
|
||||||
fmt.Fprintf(os.Stderr, "Error rendering: %s\n", err)
|
|
||||||
os.Exit(10)
|
|
||||||
}
|
|
||||||
fmt.Println(string(b))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("LAUNCHER: ")
|
|
||||||
servicetype, err := manager.Install(conf)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
os.Exit(500)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("LOGS: ")
|
|
||||||
printLogMessage(conf)
|
printLogMessage(conf)
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
|
||||||
servicemode := "USER MODE"
|
|
||||||
if conf.System {
|
|
||||||
servicemode = "SYSTEM"
|
|
||||||
}
|
|
||||||
fmt.Printf(
|
|
||||||
"SUCCESS:\n\n\t%q started as a %s %s service, running as %q\n",
|
|
||||||
conf.Name,
|
|
||||||
servicetype,
|
|
||||||
servicemode,
|
|
||||||
runAs,
|
|
||||||
)
|
|
||||||
fmt.Println()
|
|
||||||
}
|
|
||||||
|
|
||||||
func list() {
|
|
||||||
var verbose bool
|
|
||||||
forUser := false
|
|
||||||
forSystem := false
|
|
||||||
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(&verbose, "all", false, "show all services (even those not managed by serviceman)")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if forUser && forSystem {
|
|
||||||
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
|
||||||
os.Exit(1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &service.Service{}
|
|
||||||
if forUser {
|
|
||||||
conf.System = false
|
|
||||||
} else if forSystem {
|
|
||||||
conf.System = true
|
|
||||||
} else {
|
|
||||||
conf.System = manager.IsPrivileged()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pretty much just for HomeDir
|
|
||||||
conf.NormalizeWithoutPath()
|
|
||||||
|
|
||||||
managed, others, errs := manager.List(conf)
|
|
||||||
for i := range errs {
|
|
||||||
fmt.Fprintf(os.Stderr, "possible error: %s\n", errs[i])
|
|
||||||
}
|
|
||||||
if len(errs) > 0 {
|
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("serviceman-managed services:\n\n")
|
|
||||||
for i := range managed {
|
|
||||||
fmt.Println("\t" + managed[i])
|
|
||||||
}
|
|
||||||
if 0 == len(managed) {
|
|
||||||
fmt.Println("\t(none)")
|
|
||||||
}
|
|
||||||
fmt.Println("")
|
|
||||||
|
|
||||||
if verbose {
|
|
||||||
fmt.Printf("other services:\n\n")
|
|
||||||
for i := range others {
|
|
||||||
fmt.Println("\t" + others[i])
|
|
||||||
}
|
|
||||||
if 0 == len(others) {
|
|
||||||
fmt.Println("\t(none)")
|
|
||||||
}
|
|
||||||
fmt.Println("")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findExec(exe string, force bool) (string, error) {
|
|
||||||
// ex: node => /usr/local/bin/node
|
|
||||||
// ex: ./demo.js => /Users/aj/project/demo.js
|
|
||||||
exepath, err := exec.LookPath(exe)
|
|
||||||
if nil != err {
|
|
||||||
var msg string
|
|
||||||
if strings.Contains(filepath.ToSlash(exe), "/") {
|
|
||||||
if _, err := os.Stat(exe); err != nil {
|
|
||||||
msg = fmt.Sprintf("Error: '%s' could not be found in PATH or working directory.\n", exe)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("Error: '%s' is not an executable.\nYou may be able to fix that. Try running this:\n\tchmod a+x %s\n", exe, exe)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if _, err := os.Stat(exe); err != nil {
|
|
||||||
msg = fmt.Sprintf("Error: '%s' could not be found in PATH", exe)
|
|
||||||
} else {
|
|
||||||
msg = fmt.Sprintf("Error: '%s' could not be found in PATH, did you mean './%s'?\n", exe, exe)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !force {
|
|
||||||
return "", fmt.Errorf(msg)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
|
||||||
return exe, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ex: \Users\aj\project\demo.js => /Users/aj/project/demo.js
|
|
||||||
// Can't have an error here when lookpath succeeded
|
|
||||||
exepath, _ = filepath.Abs(filepath.ToSlash(exepath))
|
|
||||||
return exepath, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func testScript(exepath string, force bool) ([]string, error) {
|
|
||||||
f, err := os.Open(exepath)
|
|
||||||
b := make([]byte, 256)
|
|
||||||
if nil == err {
|
|
||||||
_, err = f.Read(b)
|
|
||||||
}
|
|
||||||
if nil != err || len(b) < len("#!/x") {
|
|
||||||
msg := fmt.Sprintf("Error when testing if '%s' is a binary or script: could not read file: %s\n", exepath, err)
|
|
||||||
if !force {
|
|
||||||
return nil, fmt.Errorf(msg)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nott sure if this is more readable and idiomatic as if else or switch
|
|
||||||
// However, the order matters
|
|
||||||
switch {
|
|
||||||
case utf8.Valid(b):
|
|
||||||
// Looks like an executable script
|
|
||||||
if "#!/" == string(b[:3]) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := fmt.Sprintf("Error: %q looks like a script, but we don't know the interpreter.\nYou can probably fix this by...\n"+
|
|
||||||
"\tExplicitly naming the interpreter (ex: 'python my-script.py' instead of just 'my-script.py')\n"+
|
|
||||||
"\tPlacing a hashbang at the top of the script (ex: '#!/usr/bin/env python')", exepath)
|
|
||||||
|
|
||||||
if !force {
|
|
||||||
return nil, fmt.Errorf(msg)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
case "#!/" != string(b[:3]):
|
|
||||||
// Looks like a normal binary
|
|
||||||
return nil, nil
|
|
||||||
default:
|
|
||||||
// Looks like a corrupt script file
|
|
||||||
msg := "Error: It looks like you've specified a corrupt script file."
|
|
||||||
if !force {
|
|
||||||
return nil, fmt.Errorf(msg)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deal with #!/whatever
|
|
||||||
|
|
||||||
// Get that first line
|
|
||||||
// "#!/usr/bin/env node" => ["/usr/bin/env", "node"]
|
|
||||||
// "#!/usr/bin/node --harmony => ["/usr/bin/node", "--harmony"]
|
|
||||||
s := string(b[2:]) // strip leading #!
|
|
||||||
s = strings.Split(strings.Replace(s, "\r\n", "\n", -1), "\n")[0]
|
|
||||||
allargs := strings.Split(strings.TrimSpace(s), " ")
|
|
||||||
args := []string{}
|
|
||||||
for i := range allargs {
|
|
||||||
arg := strings.TrimSpace(allargs[i])
|
|
||||||
if "" != arg {
|
|
||||||
args = append(args, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if strings.HasSuffix(args[0], "/env") && len(args) > 1 {
|
|
||||||
// TODO warn that "env" is probably not an executable if 1 = len(args)?
|
|
||||||
args = args[1:]
|
|
||||||
}
|
|
||||||
exepath, err = findExec(args[0], force)
|
|
||||||
if nil != err {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
args[0] = exepath
|
|
||||||
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func start() {
|
|
||||||
forUser := false
|
|
||||||
forSystem := false
|
|
||||||
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.Parse()
|
|
||||||
|
|
||||||
args := flag.Args()
|
|
||||||
if 1 != len(args) {
|
|
||||||
fmt.Println("Usage: serviceman start <name>")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if forUser && forSystem {
|
|
||||||
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
|
||||||
os.Exit(1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &service.Service{
|
|
||||||
Name: args[0],
|
|
||||||
Restart: false,
|
|
||||||
}
|
|
||||||
if forUser {
|
|
||||||
conf.System = false
|
|
||||||
} else if forSystem {
|
|
||||||
conf.System = true
|
|
||||||
} else {
|
|
||||||
conf.System = manager.IsPrivileged()
|
|
||||||
}
|
|
||||||
conf.NormalizeWithoutPath()
|
|
||||||
|
|
||||||
err := manager.Start(conf)
|
|
||||||
if nil != err {
|
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
|
||||||
os.Exit(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func stop() {
|
|
||||||
forUser := false
|
|
||||||
forSystem := false
|
|
||||||
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.Parse()
|
|
||||||
|
|
||||||
args := flag.Args()
|
|
||||||
if 1 != len(args) {
|
|
||||||
fmt.Println("Usage: serviceman stop <name>")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if forUser && forSystem {
|
|
||||||
fmt.Println("Pfff! You can't --user AND --system! What are you trying to pull?")
|
|
||||||
os.Exit(1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conf := &service.Service{
|
|
||||||
Name: args[0],
|
|
||||||
Restart: false,
|
|
||||||
}
|
|
||||||
if forUser {
|
|
||||||
conf.System = false
|
|
||||||
} else if forSystem {
|
|
||||||
conf.System = true
|
|
||||||
} else {
|
|
||||||
conf.System = manager.IsPrivileged()
|
|
||||||
}
|
|
||||||
conf.NormalizeWithoutPath()
|
|
||||||
|
|
||||||
if err := manager.Stop(conf); nil != err {
|
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(127)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func run() {
|
func run() {
|
||||||
@ -561,7 +147,7 @@ func run() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if "" == confpath {
|
if "" == confpath {
|
||||||
fmt.Fprintf(os.Stderr, "%s\n", strings.Join(flag.Args(), " "))
|
fmt.Fprintf(os.Stderr, "%s", strings.Join(flag.Args(), " "))
|
||||||
fmt.Fprintf(os.Stderr, "--config /path/to/config.json is required\n")
|
fmt.Fprintf(os.Stderr, "--config /path/to/config.json is required\n")
|
||||||
usage()
|
usage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -597,8 +183,7 @@ func run() {
|
|||||||
os.Exit(400)
|
os.Exit(400)
|
||||||
}
|
}
|
||||||
|
|
||||||
force := false
|
s.Normalize(false)
|
||||||
s.Normalize(force)
|
|
||||||
fmt.Printf("All output will be directed to the logs at:\n\t%s\n", s.Logdir)
|
fmt.Printf("All output will be directed to the logs at:\n\t%s\n", s.Logdir)
|
||||||
err = os.MkdirAll(s.Logdir, 0755)
|
err = os.MkdirAll(s.Logdir, 0755)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
@ -608,11 +193,23 @@ func run() {
|
|||||||
|
|
||||||
if !daemonize {
|
if !daemonize {
|
||||||
//fmt.Fprintf(os.Stdout, "Running %s %s %s\n", s.Interpreter, s.Exec, strings.Join(s.Argv, " "))
|
//fmt.Fprintf(os.Stdout, "Running %s %s %s\n", s.Interpreter, s.Exec, strings.Join(s.Argv, " "))
|
||||||
if err := runner.Start(s); nil != err {
|
runner.Run(s)
|
||||||
fmt.Println("Error:", err)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
manager.Run(os.Args[0], "run", "--config", confpath)
|
cmd := exec.Command(os.Args[0], "run", "--config", confpath)
|
||||||
|
// for debugging
|
||||||
|
/*
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if nil != err {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(out))
|
||||||
|
*/
|
||||||
|
|
||||||
|
err = cmd.Start()
|
||||||
|
if nil != err {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
os.Exit(500)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func printLogMessage(conf *service.Service) {
|
func printLogMessage(conf *service.Service) {
|
||||||
fmt.Printf("If all went well the logs should have been created at:\n\n\t%s\n", conf.Logdir)
|
fmt.Printf("If all went well the logs should have been created at:\n\t%s\n", conf.Logdir)
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ func printLogMessage(conf *service.Service) {
|
|||||||
} else {
|
} else {
|
||||||
unit = "--user-unit"
|
unit = "--user-unit"
|
||||||
}
|
}
|
||||||
fmt.Println("If all went well you should be able to see some goodies in the logs:\n")
|
fmt.Println("If all went well you should be able to see some goodies in the logs:")
|
||||||
fmt.Printf("\t%sjournalctl -xe %s %s.service\n", sudo, unit, conf.Name)
|
fmt.Printf("\t%sjournalctl -xe %s %s.service\n", sudo, unit, conf.Name)
|
||||||
if !conf.System {
|
if !conf.System {
|
||||||
fmt.Println("\nIf that's not the case, see https://unix.stackexchange.com/a/486566/45554.")
|
fmt.Println("\nIf that's not the case, see https://unix.stackexchange.com/a/486566/45554.")
|
||||||
|
@ -7,5 +7,5 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func printLogMessage(conf *service.Service) {
|
func printLogMessage(conf *service.Service) {
|
||||||
fmt.Printf("If all went well the logs should have been created at:\n\n\t%s\n", conf.Logdir)
|
fmt.Printf("If all went well the logs should have been created at:\n\t%s\n", conf.Logdir)
|
||||||
}
|
}
|
||||||
|
1
vendor/github.com/mitchellh/go-ps/.gitignore
generated
vendored
1
vendor/github.com/mitchellh/go-ps/.gitignore
generated
vendored
@ -1 +0,0 @@
|
|||||||
.vagrant/
|
|
4
vendor/github.com/mitchellh/go-ps/.travis.yml
generated
vendored
4
vendor/github.com/mitchellh/go-ps/.travis.yml
generated
vendored
@ -1,4 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.2.1
|
|
21
vendor/github.com/mitchellh/go-ps/LICENSE.md
generated
vendored
21
vendor/github.com/mitchellh/go-ps/LICENSE.md
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Mitchell Hashimoto
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
34
vendor/github.com/mitchellh/go-ps/README.md
generated
vendored
34
vendor/github.com/mitchellh/go-ps/README.md
generated
vendored
@ -1,34 +0,0 @@
|
|||||||
# Process List Library for Go
|
|
||||||
|
|
||||||
go-ps is a library for Go that implements OS-specific APIs to list and
|
|
||||||
manipulate processes in a platform-safe way. The library can find and
|
|
||||||
list processes on Linux, Mac OS X, Solaris, and Windows.
|
|
||||||
|
|
||||||
If you're new to Go, this library has a good amount of advanced Go educational
|
|
||||||
value as well. It uses some advanced features of Go: build tags, accessing
|
|
||||||
DLL methods for Windows, cgo for Darwin, etc.
|
|
||||||
|
|
||||||
How it works:
|
|
||||||
|
|
||||||
* **Darwin** uses the `sysctl` syscall to retrieve the process table.
|
|
||||||
* **Unix** uses the procfs at `/proc` to inspect the process tree.
|
|
||||||
* **Windows** uses the Windows API, and methods such as
|
|
||||||
`CreateToolhelp32Snapshot` to get a point-in-time snapshot of
|
|
||||||
the process table.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Install using standard `go get`:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ go get github.com/mitchellh/go-ps
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
Want to contribute? Here is a short TODO list of things that aren't
|
|
||||||
implemented for this library that would be nice:
|
|
||||||
|
|
||||||
* FreeBSD support
|
|
||||||
* Plan9 support
|
|
43
vendor/github.com/mitchellh/go-ps/Vagrantfile
generated
vendored
43
vendor/github.com/mitchellh/go-ps/Vagrantfile
generated
vendored
@ -1,43 +0,0 @@
|
|||||||
# -*- mode: ruby -*-
|
|
||||||
# vi: set ft=ruby :
|
|
||||||
|
|
||||||
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
|
|
||||||
VAGRANTFILE_API_VERSION = "2"
|
|
||||||
|
|
||||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|
||||||
config.vm.box = "chef/ubuntu-12.04"
|
|
||||||
|
|
||||||
config.vm.provision "shell", inline: $script
|
|
||||||
|
|
||||||
["vmware_fusion", "vmware_workstation"].each do |p|
|
|
||||||
config.vm.provider "p" do |v|
|
|
||||||
v.vmx["memsize"] = "1024"
|
|
||||||
v.vmx["numvcpus"] = "2"
|
|
||||||
v.vmx["cpuid.coresPerSocket"] = "1"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
$script = <<SCRIPT
|
|
||||||
SRCROOT="/opt/go"
|
|
||||||
|
|
||||||
# Install Go
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y build-essential mercurial
|
|
||||||
sudo hg clone -u release https://code.google.com/p/go ${SRCROOT}
|
|
||||||
cd ${SRCROOT}/src
|
|
||||||
sudo ./all.bash
|
|
||||||
|
|
||||||
# Setup the GOPATH
|
|
||||||
sudo mkdir -p /opt/gopath
|
|
||||||
cat <<EOF >/tmp/gopath.sh
|
|
||||||
export GOPATH="/opt/gopath"
|
|
||||||
export PATH="/opt/go/bin:\$GOPATH/bin:\$PATH"
|
|
||||||
EOF
|
|
||||||
sudo mv /tmp/gopath.sh /etc/profile.d/gopath.sh
|
|
||||||
sudo chmod 0755 /etc/profile.d/gopath.sh
|
|
||||||
|
|
||||||
# Make sure the gopath is usable by bamboo
|
|
||||||
sudo chown -R vagrant:vagrant $SRCROOT
|
|
||||||
sudo chown -R vagrant:vagrant /opt/gopath
|
|
||||||
SCRIPT
|
|
40
vendor/github.com/mitchellh/go-ps/process.go
generated
vendored
40
vendor/github.com/mitchellh/go-ps/process.go
generated
vendored
@ -1,40 +0,0 @@
|
|||||||
// ps provides an API for finding and listing processes in a platform-agnostic
|
|
||||||
// way.
|
|
||||||
//
|
|
||||||
// NOTE: If you're reading these docs online via GoDocs or some other system,
|
|
||||||
// you might only see the Unix docs. This project makes heavy use of
|
|
||||||
// platform-specific implementations. We recommend reading the source if you
|
|
||||||
// are interested.
|
|
||||||
package ps
|
|
||||||
|
|
||||||
// Process is the generic interface that is implemented on every platform
|
|
||||||
// and provides common operations for processes.
|
|
||||||
type Process interface {
|
|
||||||
// Pid is the process ID for this process.
|
|
||||||
Pid() int
|
|
||||||
|
|
||||||
// PPid is the parent process ID for this process.
|
|
||||||
PPid() int
|
|
||||||
|
|
||||||
// Executable name running this process. This is not a path to the
|
|
||||||
// executable.
|
|
||||||
Executable() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Processes returns all processes.
|
|
||||||
//
|
|
||||||
// This of course will be a point-in-time snapshot of when this method was
|
|
||||||
// called. Some operating systems don't provide snapshot capability of the
|
|
||||||
// process table, in which case the process table returned might contain
|
|
||||||
// ephemeral entities that happened to be running when this was called.
|
|
||||||
func Processes() ([]Process, error) {
|
|
||||||
return processes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FindProcess looks up a single process by pid.
|
|
||||||
//
|
|
||||||
// Process will be nil and error will be nil if a matching process is
|
|
||||||
// not found.
|
|
||||||
func FindProcess(pid int) (Process, error) {
|
|
||||||
return findProcess(pid)
|
|
||||||
}
|
|
138
vendor/github.com/mitchellh/go-ps/process_darwin.go
generated
vendored
138
vendor/github.com/mitchellh/go-ps/process_darwin.go
generated
vendored
@ -1,138 +0,0 @@
|
|||||||
// +build darwin
|
|
||||||
|
|
||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DarwinProcess struct {
|
|
||||||
pid int
|
|
||||||
ppid int
|
|
||||||
binary string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DarwinProcess) Pid() int {
|
|
||||||
return p.pid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DarwinProcess) PPid() int {
|
|
||||||
return p.ppid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *DarwinProcess) Executable() string {
|
|
||||||
return p.binary
|
|
||||||
}
|
|
||||||
|
|
||||||
func findProcess(pid int) (Process, error) {
|
|
||||||
ps, err := processes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range ps {
|
|
||||||
if p.Pid() == pid {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func processes() ([]Process, error) {
|
|
||||||
buf, err := darwinSyscall()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
procs := make([]*kinfoProc, 0, 50)
|
|
||||||
k := 0
|
|
||||||
for i := _KINFO_STRUCT_SIZE; i < buf.Len(); i += _KINFO_STRUCT_SIZE {
|
|
||||||
proc := &kinfoProc{}
|
|
||||||
err = binary.Read(bytes.NewBuffer(buf.Bytes()[k:i]), binary.LittleEndian, proc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
k = i
|
|
||||||
procs = append(procs, proc)
|
|
||||||
}
|
|
||||||
|
|
||||||
darwinProcs := make([]Process, len(procs))
|
|
||||||
for i, p := range procs {
|
|
||||||
darwinProcs[i] = &DarwinProcess{
|
|
||||||
pid: int(p.Pid),
|
|
||||||
ppid: int(p.PPid),
|
|
||||||
binary: darwinCstring(p.Comm),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return darwinProcs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinCstring(s [16]byte) string {
|
|
||||||
i := 0
|
|
||||||
for _, b := range s {
|
|
||||||
if b != 0 {
|
|
||||||
i++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(s[:i])
|
|
||||||
}
|
|
||||||
|
|
||||||
func darwinSyscall() (*bytes.Buffer, error) {
|
|
||||||
mib := [4]int32{_CTRL_KERN, _KERN_PROC, _KERN_PROC_ALL, 0}
|
|
||||||
size := uintptr(0)
|
|
||||||
|
|
||||||
_, _, errno := syscall.Syscall6(
|
|
||||||
syscall.SYS___SYSCTL,
|
|
||||||
uintptr(unsafe.Pointer(&mib[0])),
|
|
||||||
4,
|
|
||||||
0,
|
|
||||||
uintptr(unsafe.Pointer(&size)),
|
|
||||||
0,
|
|
||||||
0)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
bs := make([]byte, size)
|
|
||||||
_, _, errno = syscall.Syscall6(
|
|
||||||
syscall.SYS___SYSCTL,
|
|
||||||
uintptr(unsafe.Pointer(&mib[0])),
|
|
||||||
4,
|
|
||||||
uintptr(unsafe.Pointer(&bs[0])),
|
|
||||||
uintptr(unsafe.Pointer(&size)),
|
|
||||||
0,
|
|
||||||
0)
|
|
||||||
|
|
||||||
if errno != 0 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.NewBuffer(bs[0:size]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
_CTRL_KERN = 1
|
|
||||||
_KERN_PROC = 14
|
|
||||||
_KERN_PROC_ALL = 0
|
|
||||||
_KINFO_STRUCT_SIZE = 648
|
|
||||||
)
|
|
||||||
|
|
||||||
type kinfoProc struct {
|
|
||||||
_ [40]byte
|
|
||||||
Pid int32
|
|
||||||
_ [199]byte
|
|
||||||
Comm [16]byte
|
|
||||||
_ [301]byte
|
|
||||||
PPid int32
|
|
||||||
_ [84]byte
|
|
||||||
}
|
|
260
vendor/github.com/mitchellh/go-ps/process_freebsd.go
generated
vendored
260
vendor/github.com/mitchellh/go-ps/process_freebsd.go
generated
vendored
@ -1,260 +0,0 @@
|
|||||||
// +build freebsd,amd64
|
|
||||||
|
|
||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// copied from sys/sysctl.h
|
|
||||||
const (
|
|
||||||
CTL_KERN = 1 // "high kernel": proc, limits
|
|
||||||
KERN_PROC = 14 // struct: process entries
|
|
||||||
KERN_PROC_PID = 1 // by process id
|
|
||||||
KERN_PROC_PROC = 8 // only return procs
|
|
||||||
KERN_PROC_PATHNAME = 12 // path to executable
|
|
||||||
)
|
|
||||||
|
|
||||||
// copied from sys/user.h
|
|
||||||
type Kinfo_proc struct {
|
|
||||||
Ki_structsize int32
|
|
||||||
Ki_layout int32
|
|
||||||
Ki_args int64
|
|
||||||
Ki_paddr int64
|
|
||||||
Ki_addr int64
|
|
||||||
Ki_tracep int64
|
|
||||||
Ki_textvp int64
|
|
||||||
Ki_fd int64
|
|
||||||
Ki_vmspace int64
|
|
||||||
Ki_wchan int64
|
|
||||||
Ki_pid int32
|
|
||||||
Ki_ppid int32
|
|
||||||
Ki_pgid int32
|
|
||||||
Ki_tpgid int32
|
|
||||||
Ki_sid int32
|
|
||||||
Ki_tsid int32
|
|
||||||
Ki_jobc [2]byte
|
|
||||||
Ki_spare_short1 [2]byte
|
|
||||||
Ki_tdev int32
|
|
||||||
Ki_siglist [16]byte
|
|
||||||
Ki_sigmask [16]byte
|
|
||||||
Ki_sigignore [16]byte
|
|
||||||
Ki_sigcatch [16]byte
|
|
||||||
Ki_uid int32
|
|
||||||
Ki_ruid int32
|
|
||||||
Ki_svuid int32
|
|
||||||
Ki_rgid int32
|
|
||||||
Ki_svgid int32
|
|
||||||
Ki_ngroups [2]byte
|
|
||||||
Ki_spare_short2 [2]byte
|
|
||||||
Ki_groups [64]byte
|
|
||||||
Ki_size int64
|
|
||||||
Ki_rssize int64
|
|
||||||
Ki_swrss int64
|
|
||||||
Ki_tsize int64
|
|
||||||
Ki_dsize int64
|
|
||||||
Ki_ssize int64
|
|
||||||
Ki_xstat [2]byte
|
|
||||||
Ki_acflag [2]byte
|
|
||||||
Ki_pctcpu int32
|
|
||||||
Ki_estcpu int32
|
|
||||||
Ki_slptime int32
|
|
||||||
Ki_swtime int32
|
|
||||||
Ki_cow int32
|
|
||||||
Ki_runtime int64
|
|
||||||
Ki_start [16]byte
|
|
||||||
Ki_childtime [16]byte
|
|
||||||
Ki_flag int64
|
|
||||||
Ki_kiflag int64
|
|
||||||
Ki_traceflag int32
|
|
||||||
Ki_stat [1]byte
|
|
||||||
Ki_nice [1]byte
|
|
||||||
Ki_lock [1]byte
|
|
||||||
Ki_rqindex [1]byte
|
|
||||||
Ki_oncpu [1]byte
|
|
||||||
Ki_lastcpu [1]byte
|
|
||||||
Ki_ocomm [17]byte
|
|
||||||
Ki_wmesg [9]byte
|
|
||||||
Ki_login [18]byte
|
|
||||||
Ki_lockname [9]byte
|
|
||||||
Ki_comm [20]byte
|
|
||||||
Ki_emul [17]byte
|
|
||||||
Ki_sparestrings [68]byte
|
|
||||||
Ki_spareints [36]byte
|
|
||||||
Ki_cr_flags int32
|
|
||||||
Ki_jid int32
|
|
||||||
Ki_numthreads int32
|
|
||||||
Ki_tid int32
|
|
||||||
Ki_pri int32
|
|
||||||
Ki_rusage [144]byte
|
|
||||||
Ki_rusage_ch [144]byte
|
|
||||||
Ki_pcb int64
|
|
||||||
Ki_kstack int64
|
|
||||||
Ki_udata int64
|
|
||||||
Ki_tdaddr int64
|
|
||||||
Ki_spareptrs [48]byte
|
|
||||||
Ki_spareint64s [96]byte
|
|
||||||
Ki_sflag int64
|
|
||||||
Ki_tdflags int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnixProcess is an implementation of Process that contains Unix-specific
|
|
||||||
// fields and information.
|
|
||||||
type UnixProcess struct {
|
|
||||||
pid int
|
|
||||||
ppid int
|
|
||||||
state rune
|
|
||||||
pgrp int
|
|
||||||
sid int
|
|
||||||
|
|
||||||
binary string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UnixProcess) Pid() int {
|
|
||||||
return p.pid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UnixProcess) PPid() int {
|
|
||||||
return p.ppid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UnixProcess) Executable() string {
|
|
||||||
return p.binary
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh reloads all the data associated with this process.
|
|
||||||
func (p *UnixProcess) Refresh() error {
|
|
||||||
|
|
||||||
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PID, int32(p.pid)}
|
|
||||||
|
|
||||||
buf, length, err := call_syscall(mib)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
proc_k := Kinfo_proc{}
|
|
||||||
if length != uint64(unsafe.Sizeof(proc_k)) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
k, err := parse_kinfo_proc(buf)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copy_params(k *Kinfo_proc) (int, int, int, string) {
|
|
||||||
n := -1
|
|
||||||
for i, b := range k.Ki_comm {
|
|
||||||
if b == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
n = i + 1
|
|
||||||
}
|
|
||||||
comm := string(k.Ki_comm[:n])
|
|
||||||
|
|
||||||
return int(k.Ki_ppid), int(k.Ki_pgid), int(k.Ki_sid), comm
|
|
||||||
}
|
|
||||||
|
|
||||||
func findProcess(pid int) (Process, error) {
|
|
||||||
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, int32(pid)}
|
|
||||||
|
|
||||||
_, _, err := call_syscall(mib)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newUnixProcess(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func processes() ([]Process, error) {
|
|
||||||
results := make([]Process, 0, 50)
|
|
||||||
|
|
||||||
mib := []int32{CTL_KERN, KERN_PROC, KERN_PROC_PROC, 0}
|
|
||||||
buf, length, err := call_syscall(mib)
|
|
||||||
if err != nil {
|
|
||||||
return results, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// get kinfo_proc size
|
|
||||||
k := Kinfo_proc{}
|
|
||||||
procinfo_len := int(unsafe.Sizeof(k))
|
|
||||||
count := int(length / uint64(procinfo_len))
|
|
||||||
|
|
||||||
// parse buf to procs
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
b := buf[i*procinfo_len : i*procinfo_len+procinfo_len]
|
|
||||||
k, err := parse_kinfo_proc(b)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p, err := newUnixProcess(int(k.Ki_pid))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
p.ppid, p.pgrp, p.sid, p.binary = copy_params(&k)
|
|
||||||
|
|
||||||
results = append(results, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse_kinfo_proc(buf []byte) (Kinfo_proc, error) {
|
|
||||||
var k Kinfo_proc
|
|
||||||
br := bytes.NewReader(buf)
|
|
||||||
err := binary.Read(br, binary.LittleEndian, &k)
|
|
||||||
if err != nil {
|
|
||||||
return k, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return k, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func call_syscall(mib []int32) ([]byte, uint64, error) {
|
|
||||||
miblen := uint64(len(mib))
|
|
||||||
|
|
||||||
// get required buffer size
|
|
||||||
length := uint64(0)
|
|
||||||
_, _, err := syscall.RawSyscall6(
|
|
||||||
syscall.SYS___SYSCTL,
|
|
||||||
uintptr(unsafe.Pointer(&mib[0])),
|
|
||||||
uintptr(miblen),
|
|
||||||
0,
|
|
||||||
uintptr(unsafe.Pointer(&length)),
|
|
||||||
0,
|
|
||||||
0)
|
|
||||||
if err != 0 {
|
|
||||||
b := make([]byte, 0)
|
|
||||||
return b, length, err
|
|
||||||
}
|
|
||||||
if length == 0 {
|
|
||||||
b := make([]byte, 0)
|
|
||||||
return b, length, err
|
|
||||||
}
|
|
||||||
// get proc info itself
|
|
||||||
buf := make([]byte, length)
|
|
||||||
_, _, err = syscall.RawSyscall6(
|
|
||||||
syscall.SYS___SYSCTL,
|
|
||||||
uintptr(unsafe.Pointer(&mib[0])),
|
|
||||||
uintptr(miblen),
|
|
||||||
uintptr(unsafe.Pointer(&buf[0])),
|
|
||||||
uintptr(unsafe.Pointer(&length)),
|
|
||||||
0,
|
|
||||||
0)
|
|
||||||
if err != 0 {
|
|
||||||
return buf, length, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf, length, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUnixProcess(pid int) (*UnixProcess, error) {
|
|
||||||
p := &UnixProcess{pid: pid}
|
|
||||||
return p, p.Refresh()
|
|
||||||
}
|
|
35
vendor/github.com/mitchellh/go-ps/process_linux.go
generated
vendored
35
vendor/github.com/mitchellh/go-ps/process_linux.go
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
// +build linux
|
|
||||||
|
|
||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Refresh reloads all the data associated with this process.
|
|
||||||
func (p *UnixProcess) Refresh() error {
|
|
||||||
statPath := fmt.Sprintf("/proc/%d/stat", p.pid)
|
|
||||||
dataBytes, err := ioutil.ReadFile(statPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, parse out the image name
|
|
||||||
data := string(dataBytes)
|
|
||||||
binStart := strings.IndexRune(data, '(') + 1
|
|
||||||
binEnd := strings.IndexRune(data[binStart:], ')')
|
|
||||||
p.binary = data[binStart : binStart+binEnd]
|
|
||||||
|
|
||||||
// Move past the image name and start parsing the rest
|
|
||||||
data = data[binStart+binEnd+2:]
|
|
||||||
_, err = fmt.Sscanf(data,
|
|
||||||
"%c %d %d %d",
|
|
||||||
&p.state,
|
|
||||||
&p.ppid,
|
|
||||||
&p.pgrp,
|
|
||||||
&p.sid)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
96
vendor/github.com/mitchellh/go-ps/process_solaris.go
generated
vendored
96
vendor/github.com/mitchellh/go-ps/process_solaris.go
generated
vendored
@ -1,96 +0,0 @@
|
|||||||
// +build solaris
|
|
||||||
|
|
||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ushort_t uint16
|
|
||||||
|
|
||||||
type id_t int32
|
|
||||||
type pid_t int32
|
|
||||||
type uid_t int32
|
|
||||||
type gid_t int32
|
|
||||||
|
|
||||||
type dev_t uint64
|
|
||||||
type size_t uint64
|
|
||||||
type uintptr_t uint64
|
|
||||||
|
|
||||||
type timestruc_t [16]byte
|
|
||||||
|
|
||||||
// This is copy from /usr/include/sys/procfs.h
|
|
||||||
type psinfo_t struct {
|
|
||||||
Pr_flag int32 /* process flags (DEPRECATED; do not use) */
|
|
||||||
Pr_nlwp int32 /* number of active lwps in the process */
|
|
||||||
Pr_pid pid_t /* unique process id */
|
|
||||||
Pr_ppid pid_t /* process id of parent */
|
|
||||||
Pr_pgid pid_t /* pid of process group leader */
|
|
||||||
Pr_sid pid_t /* session id */
|
|
||||||
Pr_uid uid_t /* real user id */
|
|
||||||
Pr_euid uid_t /* effective user id */
|
|
||||||
Pr_gid gid_t /* real group id */
|
|
||||||
Pr_egid gid_t /* effective group id */
|
|
||||||
Pr_addr uintptr_t /* address of process */
|
|
||||||
Pr_size size_t /* size of process image in Kbytes */
|
|
||||||
Pr_rssize size_t /* resident set size in Kbytes */
|
|
||||||
Pr_pad1 size_t
|
|
||||||
Pr_ttydev dev_t /* controlling tty device (or PRNODEV) */
|
|
||||||
|
|
||||||
// Guess this following 2 ushort_t values require a padding to properly
|
|
||||||
// align to the 64bit mark.
|
|
||||||
Pr_pctcpu ushort_t /* % of recent cpu time used by all lwps */
|
|
||||||
Pr_pctmem ushort_t /* % of system memory used by process */
|
|
||||||
Pr_pad64bit [4]byte
|
|
||||||
|
|
||||||
Pr_start timestruc_t /* process start time, from the epoch */
|
|
||||||
Pr_time timestruc_t /* usr+sys cpu time for this process */
|
|
||||||
Pr_ctime timestruc_t /* usr+sys cpu time for reaped children */
|
|
||||||
Pr_fname [16]byte /* name of execed file */
|
|
||||||
Pr_psargs [80]byte /* initial characters of arg list */
|
|
||||||
Pr_wstat int32 /* if zombie, the wait() status */
|
|
||||||
Pr_argc int32 /* initial argument count */
|
|
||||||
Pr_argv uintptr_t /* address of initial argument vector */
|
|
||||||
Pr_envp uintptr_t /* address of initial environment vector */
|
|
||||||
Pr_dmodel [1]byte /* data model of the process */
|
|
||||||
Pr_pad2 [3]byte
|
|
||||||
Pr_taskid id_t /* task id */
|
|
||||||
Pr_projid id_t /* project id */
|
|
||||||
Pr_nzomb int32 /* number of zombie lwps in the process */
|
|
||||||
Pr_poolid id_t /* pool id */
|
|
||||||
Pr_zoneid id_t /* zone id */
|
|
||||||
Pr_contract id_t /* process contract */
|
|
||||||
Pr_filler int32 /* reserved for future use */
|
|
||||||
Pr_lwp [128]byte /* information for representative lwp */
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UnixProcess) Refresh() error {
|
|
||||||
var psinfo psinfo_t
|
|
||||||
|
|
||||||
path := fmt.Sprintf("/proc/%d/psinfo", p.pid)
|
|
||||||
fh, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer fh.Close()
|
|
||||||
|
|
||||||
err = binary.Read(fh, binary.LittleEndian, &psinfo)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.ppid = int(psinfo.Pr_ppid)
|
|
||||||
p.binary = toString(psinfo.Pr_fname[:], 16)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func toString(array []byte, len int) string {
|
|
||||||
for i := 0; i < len; i++ {
|
|
||||||
if array[i] == 0 {
|
|
||||||
return string(array[:i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(array[:])
|
|
||||||
}
|
|
101
vendor/github.com/mitchellh/go-ps/process_unix.go
generated
vendored
101
vendor/github.com/mitchellh/go-ps/process_unix.go
generated
vendored
@ -1,101 +0,0 @@
|
|||||||
// +build linux solaris
|
|
||||||
|
|
||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UnixProcess is an implementation of Process that contains Unix-specific
|
|
||||||
// fields and information.
|
|
||||||
type UnixProcess struct {
|
|
||||||
pid int
|
|
||||||
ppid int
|
|
||||||
state rune
|
|
||||||
pgrp int
|
|
||||||
sid int
|
|
||||||
|
|
||||||
binary string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UnixProcess) Pid() int {
|
|
||||||
return p.pid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UnixProcess) PPid() int {
|
|
||||||
return p.ppid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *UnixProcess) Executable() string {
|
|
||||||
return p.binary
|
|
||||||
}
|
|
||||||
|
|
||||||
func findProcess(pid int) (Process, error) {
|
|
||||||
dir := fmt.Sprintf("/proc/%d", pid)
|
|
||||||
_, err := os.Stat(dir)
|
|
||||||
if err != nil {
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newUnixProcess(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func processes() ([]Process, error) {
|
|
||||||
d, err := os.Open("/proc")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer d.Close()
|
|
||||||
|
|
||||||
results := make([]Process, 0, 50)
|
|
||||||
for {
|
|
||||||
fis, err := d.Readdir(10)
|
|
||||||
if err == io.EOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, fi := range fis {
|
|
||||||
// We only care about directories, since all pids are dirs
|
|
||||||
if !fi.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// We only care if the name starts with a numeric
|
|
||||||
name := fi.Name()
|
|
||||||
if name[0] < '0' || name[0] > '9' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// From this point forward, any errors we just ignore, because
|
|
||||||
// it might simply be that the process doesn't exist anymore.
|
|
||||||
pid, err := strconv.ParseInt(name, 10, 0)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := newUnixProcess(int(pid))
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
results = append(results, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newUnixProcess(pid int) (*UnixProcess, error) {
|
|
||||||
p := &UnixProcess{pid: pid}
|
|
||||||
return p, p.Refresh()
|
|
||||||
}
|
|
119
vendor/github.com/mitchellh/go-ps/process_windows.go
generated
vendored
119
vendor/github.com/mitchellh/go-ps/process_windows.go
generated
vendored
@ -1,119 +0,0 @@
|
|||||||
// +build windows
|
|
||||||
|
|
||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"syscall"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Windows API functions
|
|
||||||
var (
|
|
||||||
modKernel32 = syscall.NewLazyDLL("kernel32.dll")
|
|
||||||
procCloseHandle = modKernel32.NewProc("CloseHandle")
|
|
||||||
procCreateToolhelp32Snapshot = modKernel32.NewProc("CreateToolhelp32Snapshot")
|
|
||||||
procProcess32First = modKernel32.NewProc("Process32FirstW")
|
|
||||||
procProcess32Next = modKernel32.NewProc("Process32NextW")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Some constants from the Windows API
|
|
||||||
const (
|
|
||||||
ERROR_NO_MORE_FILES = 0x12
|
|
||||||
MAX_PATH = 260
|
|
||||||
)
|
|
||||||
|
|
||||||
// PROCESSENTRY32 is the Windows API structure that contains a process's
|
|
||||||
// information.
|
|
||||||
type PROCESSENTRY32 struct {
|
|
||||||
Size uint32
|
|
||||||
CntUsage uint32
|
|
||||||
ProcessID uint32
|
|
||||||
DefaultHeapID uintptr
|
|
||||||
ModuleID uint32
|
|
||||||
CntThreads uint32
|
|
||||||
ParentProcessID uint32
|
|
||||||
PriorityClassBase int32
|
|
||||||
Flags uint32
|
|
||||||
ExeFile [MAX_PATH]uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// WindowsProcess is an implementation of Process for Windows.
|
|
||||||
type WindowsProcess struct {
|
|
||||||
pid int
|
|
||||||
ppid int
|
|
||||||
exe string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WindowsProcess) Pid() int {
|
|
||||||
return p.pid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WindowsProcess) PPid() int {
|
|
||||||
return p.ppid
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *WindowsProcess) Executable() string {
|
|
||||||
return p.exe
|
|
||||||
}
|
|
||||||
|
|
||||||
func newWindowsProcess(e *PROCESSENTRY32) *WindowsProcess {
|
|
||||||
// Find when the string ends for decoding
|
|
||||||
end := 0
|
|
||||||
for {
|
|
||||||
if e.ExeFile[end] == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
|
|
||||||
return &WindowsProcess{
|
|
||||||
pid: int(e.ProcessID),
|
|
||||||
ppid: int(e.ParentProcessID),
|
|
||||||
exe: syscall.UTF16ToString(e.ExeFile[:end]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findProcess(pid int) (Process, error) {
|
|
||||||
ps, err := processes()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, p := range ps {
|
|
||||||
if p.Pid() == pid {
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func processes() ([]Process, error) {
|
|
||||||
handle, _, _ := procCreateToolhelp32Snapshot.Call(
|
|
||||||
0x00000002,
|
|
||||||
0)
|
|
||||||
if handle < 0 {
|
|
||||||
return nil, syscall.GetLastError()
|
|
||||||
}
|
|
||||||
defer procCloseHandle.Call(handle)
|
|
||||||
|
|
||||||
var entry PROCESSENTRY32
|
|
||||||
entry.Size = uint32(unsafe.Sizeof(entry))
|
|
||||||
ret, _, _ := procProcess32First.Call(handle, uintptr(unsafe.Pointer(&entry)))
|
|
||||||
if ret == 0 {
|
|
||||||
return nil, fmt.Errorf("Error retrieving process info.")
|
|
||||||
}
|
|
||||||
|
|
||||||
results := make([]Process, 0, 50)
|
|
||||||
for {
|
|
||||||
results = append(results, newWindowsProcess(&entry))
|
|
||||||
|
|
||||||
ret, _, _ := procProcess32Next.Call(handle, uintptr(unsafe.Pointer(&entry)))
|
|
||||||
if ret == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results, nil
|
|
||||||
}
|
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -32,8 +32,6 @@ github.com/mattn/go-colorable
|
|||||||
github.com/mattn/go-isatty
|
github.com/mattn/go-isatty
|
||||||
# github.com/mattn/go-runewidth v0.0.3
|
# github.com/mattn/go-runewidth v0.0.3
|
||||||
github.com/mattn/go-runewidth
|
github.com/mattn/go-runewidth
|
||||||
# github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936
|
|
||||||
github.com/mitchellh/go-ps
|
|
||||||
# github.com/mitchellh/go-wordwrap v1.0.0
|
# github.com/mitchellh/go-wordwrap v1.0.0
|
||||||
github.com/mitchellh/go-wordwrap
|
github.com/mitchellh/go-wordwrap
|
||||||
# github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e
|
# github.com/nsf/termbox-go v0.0.0-20180819125858-b66b20ab708e
|
||||||
|
Loading…
x
Reference in New Issue
Block a user