Compare commits
17 Commits
smaller-si
...
master
Author | SHA1 | Date |
---|---|---|
AJ ONeal | 32c71bd698 | |
AJ ONeal | 8277fa7ac6 | |
AJ ONeal | 3b6c4bbb7d | |
AJ ONeal | 1196f1d389 | |
AJ ONeal | 2824ee4c62 | |
AJ ONeal | e7a02191d8 | |
AJ ONeal | 693e61d7d4 | |
AJ ONeal | 258623ae44 | |
AJ ONeal | 8b6479dc95 | |
AJ ONeal | 3513b64aa7 | |
AJ ONeal | b64dbc6ca6 | |
AJ ONeal | 6c6c0123ed | |
AJ ONeal | b7989893cd | |
AJ ONeal | c84dc517a9 | |
AJ ONeal | 34ed9cc065 | |
AJ ONeal | f03a0755af | |
AJ ONeal | cc0176e058 |
33
README.md
33
README.md
|
@ -1,4 +1,4 @@
|
|||
# go-serviceman
|
||||
# [go-serviceman](https://git.rootprojects.org/root/serviceman)
|
||||
|
||||
Cross-platform service management made easy.
|
||||
|
||||
|
@ -51,6 +51,7 @@ The basic pattern of usage:
|
|||
sudo serviceman add --name "foobar" [options] [interpreter] <service> [--] [service options]
|
||||
sudo serviceman start <service>
|
||||
sudo serviceman stop <service>
|
||||
sudo serviceman list --all
|
||||
serviceman version
|
||||
```
|
||||
|
||||
|
@ -76,6 +77,24 @@ The **default** is to register a _user_ services. To register a _system_ service
|
|||
|
||||
# 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.
|
||||
|
||||
If none of them work for you, or you prefer to build from source,
|
||||
|
@ -83,8 +102,16 @@ see the instructions for building far down below.
|
|||
|
||||
## Downloads
|
||||
|
||||
```
|
||||
curl -fsSL "https://rootprojects.org/serviceman/dist/$(uname -s)/$(uname -m)/serviceman" -o serviceman
|
||||
chmod +x ./serviceman
|
||||
```
|
||||
|
||||
### MacOS
|
||||
|
||||
<details>
|
||||
<summary>See download options</summary>
|
||||
|
||||
MacOS (darwin): [64-bit Download ](https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman)
|
||||
|
||||
```
|
||||
|
@ -92,6 +119,8 @@ curl https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman -o service
|
|||
chmod +x ./serviceman
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Windows
|
||||
|
||||
<details>
|
||||
|
@ -124,6 +153,7 @@ powershell.exe "(New-Object Net.WebClient).DownloadFile('https://rootprojects.or
|
|||
|
||||
### Linux
|
||||
|
||||
|
||||
<details>
|
||||
<summary>See download options</summary>
|
||||
|
||||
|
@ -619,4 +649,3 @@ MPL-2.0 |
|
|||
Copyright 2019 AJ ONeal.
|
||||
|
||||
<!-- {{ end }} -->
|
||||
<!-- {{ end }} -->
|
||||
|
|
|
@ -39,5 +39,5 @@ echo "RPi Zero ARMv5"
|
|||
GOOS=linux GOARCH=arm GOARM=5 go build -mod=vendor -o dist/linux/armv5/${exe} $gocmd
|
||||
|
||||
echo ""
|
||||
rsync -av ./dist/ ubuntu@rootprojects.org:/srv/www/rootprojects.org/serviceman/dist/
|
||||
#rsync -av ./dist/ ubuntu@rootprojects.org:/srv/www/rootprojects.org/serviceman/dist/
|
||||
# https://rootprojects.org/serviceman/dist/windows/amd64/serviceman.exe
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
<?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">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# Generated for serviceman. Edit as you wish, but leave this line.
|
||||
# Pre-req
|
||||
# sudo mkdir -p {{ .Local }}/opt/{{ .Name }}/ {{ .Local }}/var/log/{{ .Name }}
|
||||
{{ if .System -}}
|
||||
|
|
|
@ -51,6 +51,10 @@ 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
|
||||
// to write to system folders, bind to privileged ports, and otherwise
|
||||
// successfully run a system service.
|
||||
|
@ -67,6 +71,16 @@ func WhereIs(exe string) (string, error) {
|
|||
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
|
||||
|
|
|
@ -108,6 +108,7 @@ func stop(conf *service.Service) error {
|
|||
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")
|
||||
|
@ -142,7 +143,7 @@ func install(c *service.Service) (string, error) {
|
|||
}
|
||||
|
||||
// Check paths first
|
||||
err := os.MkdirAll(filepath.Dir(plistDir), 0755)
|
||||
err := os.MkdirAll(plistDir, 0755)
|
||||
if nil != err {
|
||||
return "", err
|
||||
}
|
||||
|
|
|
@ -159,7 +159,7 @@ func stop(conf *service.Service) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Render will create a systemd .service file using a simple the internal template
|
||||
// Render will create a systemd .service file using the simple internal template
|
||||
func Render(c *service.Service) ([]byte, error) {
|
||||
defaultUserGroup(c)
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
// +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,6 +6,7 @@ import (
|
|||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
|
@ -130,6 +131,48 @@ 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 := ""
|
||||
|
@ -154,6 +197,84 @@ func getRunnerArgs(c *service.Service) []string {
|
|||
}
|
||||
}
|
||||
|
||||
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
|
||||
func installServiceman(c *service.Service) ([]string, error) {
|
||||
// TODO check version and upgrade or dismiss
|
||||
|
@ -168,6 +289,13 @@ func installServiceman(c *service.Service) ([]string, error) {
|
|||
if 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)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
|
|
|
@ -3,6 +3,7 @@ package manager
|
|||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
@ -121,7 +122,12 @@ func getSystemSrvs() ([]string, error) {
|
|||
}
|
||||
|
||||
func getUserSrvs(home string) ([]string, error) {
|
||||
return getSrvs(filepath.Join(home, srvUserPath))
|
||||
confDir := filepath.Join(home, srvUserPath)
|
||||
err := os.MkdirAll(confDir, 0755)
|
||||
if nil != err {
|
||||
return nil, err
|
||||
}
|
||||
return getSrvs(confDir)
|
||||
}
|
||||
|
||||
// "come.example.foo.plist" matches "foo"
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
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
|
@ -0,0 +1 @@
|
|||
# this will be replaced by the postinstall script
|
|
@ -1,10 +1,19 @@
|
|||
{
|
||||
"name": "serviceman",
|
||||
"version": "0.5.2",
|
||||
"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": {
|
||||
"postinstall": "scripts/fetch-serviceman.js",
|
||||
"serviceman": "serviceman",
|
||||
"postinstall": "node scripts/fetch-serviceman.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
var path = require('path');
|
||||
var os = require('os');
|
||||
|
||||
// https://nodejs.org/api/os.html#os_os_arch
|
||||
|
@ -10,7 +11,7 @@ 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 = 'windows' === platform ? '.exe' : '';
|
||||
var ext = /^win/i.test(platform) ? '.exe' : '';
|
||||
|
||||
// This is _probably_ right. It's good enough for us
|
||||
// https://github.com/nodejs/node/issues/13629
|
||||
|
@ -53,15 +54,26 @@ var mkdirp = require('@root/mkdirp');
|
|||
|
||||
function needsUpdate(oldVer, newVer) {
|
||||
// "v1.0.0-pre" is BEHIND "v1.0.0"
|
||||
newVer = newVer.replace(/^v/, '').split(/[\.\-\+]/);
|
||||
oldVer = oldVer.replace(/^v/, '').split(/[\.\-\+]/);
|
||||
//console.log(oldVer, newVer);
|
||||
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;
|
||||
|
@ -77,6 +89,8 @@ function needsUpdate(oldVer, newVer) {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ex: v1.0.1 vs v1.0.0-pre
|
||||
if (newVer[3]) {
|
||||
if (newVer[2] > 0) {
|
||||
newVer[2] -= 1;
|
||||
|
@ -93,7 +107,7 @@ function needsUpdate(oldVer, newVer) {
|
|||
}
|
||||
}
|
||||
|
||||
//console.log(oldVer, newVer);
|
||||
// ex: v1.0.1 vs v1.0.0
|
||||
if (oldVer[0] > newVer[0]) {
|
||||
return false;
|
||||
} else if (oldVer[0] < newVer[0]) {
|
||||
|
@ -128,12 +142,13 @@ console.log(false === needsUpdate('0.5.0', '0.5.0-pre1'));
|
|||
console.log(false === needsUpdate('0.5.1', '0.5.0'));
|
||||
*/
|
||||
|
||||
exec('serviceman version', { windowsHide: true }, function(err, stdout) {
|
||||
var oldVer = (stdout || '').split(' ')[0];
|
||||
console.log(oldVer, newVer);
|
||||
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 serviceman version is new enough:',
|
||||
'Current ' + name + ' version is new enough:',
|
||||
oldVer,
|
||||
newVer
|
||||
);
|
||||
|
@ -142,7 +157,7 @@ exec('serviceman version', { windowsHide: true }, function(err, stdout) {
|
|||
// console.info('Current serviceman version is older:', oldVer, newVer);
|
||||
}
|
||||
|
||||
var url = 'https://rootprojects.org/serviceman/dist/{{ .Platform }}/{{ .Arch }}/serviceman{{ .Ext }}'
|
||||
var url = urlTpl
|
||||
.replace(/{{ .Version }}/g, newVer)
|
||||
.replace(/{{ .Platform }}/g, platform)
|
||||
.replace(/{{ .Arch }}/g, arch)
|
||||
|
@ -157,43 +172,98 @@ exec('serviceman version', { windowsHide: true }, function(err, stdout) {
|
|||
|
||||
//console.log(resp.body.byteLength);
|
||||
//console.log(typeof resp.body);
|
||||
var serviceman = 'serviceman' + ext;
|
||||
return fs.writeFile(serviceman, resp.body, null, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
var bin = name + ext;
|
||||
function next() {
|
||||
if (!bindirs.length) {
|
||||
return;
|
||||
}
|
||||
fs.chmodSync(serviceman, parseInt('0755', 8));
|
||||
|
||||
var path = require('path');
|
||||
var localdir = '/usr/local/bin';
|
||||
fs.rename(serviceman, path.join(localdir, serviceman), function(
|
||||
err
|
||||
) {
|
||||
if (err) {
|
||||
//console.error(err);
|
||||
}
|
||||
// ignore
|
||||
});
|
||||
|
||||
var homedir = require('os').homedir();
|
||||
var bindir = path.join(homedir, '.local', 'bin');
|
||||
var bindir = bindirs.pop();
|
||||
return mkdirp(bindir, function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
|
||||
var localsrv = path.join(bindir, serviceman);
|
||||
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', serviceman, 'to', bindir);
|
||||
});
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ func usage() {
|
|||
fmt.Println("\tserviceman <command> --help")
|
||||
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>")
|
||||
}
|
||||
|
@ -54,6 +55,8 @@ func main() {
|
|||
start()
|
||||
case "stop":
|
||||
stop()
|
||||
case "list":
|
||||
list()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown argument %s\n", top)
|
||||
usage()
|
||||
|
@ -85,13 +88,6 @@ func add() {
|
|||
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(&dryrun, "dryrun", false, "output the service file without modifying anything on disk")
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0])
|
||||
|
||||
flag.PrintDefaults()
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Flags and arguments after \"--\" will be completely ignored by serviceman\n", os.Args[0])
|
||||
}
|
||||
flag.Parse()
|
||||
flagargs := flag.Args()
|
||||
|
||||
|
@ -234,7 +230,7 @@ func add() {
|
|||
}
|
||||
|
||||
if len(ass) > 0 {
|
||||
fmt.Println("OPTIONS: Making some assumptions...\n")
|
||||
fmt.Printf("OPTIONS: Making some assumptions...\n\n")
|
||||
for i := range ass {
|
||||
fmt.Println("\t" + ass[i])
|
||||
}
|
||||
|
@ -310,7 +306,7 @@ func add() {
|
|||
servicemode = "SYSTEM"
|
||||
}
|
||||
fmt.Printf(
|
||||
"SUCCESS:\n\n\t%q started as a %q %s service, running as %q\n",
|
||||
"SUCCESS:\n\n\t%q started as a %s %s service, running as %q\n",
|
||||
conf.Name,
|
||||
servicetype,
|
||||
servicemode,
|
||||
|
@ -319,6 +315,62 @@ func add() {
|
|||
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
|
||||
|
|
Loading…
Reference in New Issue