Compare commits
9 Commits
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 |
32
README.md
32
README.md
|
@ -1,4 +1,4 @@
|
||||||
# go-serviceman
|
# [go-serviceman](https://git.rootprojects.org/root/serviceman)
|
||||||
|
|
||||||
Cross-platform service management made easy.
|
Cross-platform service management made easy.
|
||||||
|
|
||||||
|
@ -77,6 +77,24 @@ The **default** is to register a _user_ services. To register a _system_ service
|
||||||
|
|
||||||
# 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,
|
||||||
|
@ -84,8 +102,16 @@ 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)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -93,6 +119,8 @@ curl https://rootprojects.org/serviceman/dist/darwin/amd64/serviceman -o service
|
||||||
chmod +x ./serviceman
|
chmod +x ./serviceman
|
||||||
```
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
### Windows
|
### Windows
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
@ -125,6 +153,7 @@ powershell.exe "(New-Object Net.WebClient).DownloadFile('https://rootprojects.or
|
||||||
|
|
||||||
### Linux
|
### Linux
|
||||||
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>See download options</summary>
|
<summary>See download options</summary>
|
||||||
|
|
||||||
|
@ -620,4 +649,3 @@ MPL-2.0 |
|
||||||
Copyright 2019 AJ ONeal.
|
Copyright 2019 AJ ONeal.
|
||||||
|
|
||||||
<!-- {{ end }} -->
|
<!-- {{ end }} -->
|
||||||
<!-- {{ end }} -->
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -288,6 +289,13 @@ func installServiceman(c *service.Service) ([]string, error) {
|
||||||
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
|
||||||
|
|
|
@ -3,6 +3,7 @@ package manager
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -121,7 +122,12 @@ func getSystemSrvs() ([]string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUserSrvs(home string) ([]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"
|
// "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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
// Code generated by fileb0x at "2019-08-05 10:04:21.752835 -0600 MDT m=+0.004628888" from config file "b0x.toml" DO NOT EDIT.
|
// Code generated by fileb0x at "2019-08-05 10:09:44.205977 -0600 MDT m=+0.003863161" from config file "b0x.toml" DO NOT EDIT.
|
||||||
// modification hash(e0dad2aef9534d24be436f8d9dd5e9cc.acdb557394f98d3c09c0bb4d4b9142f8)
|
// modification hash(25df1985b6a67dc5b8f5c9281219965c.acdb557394f98d3c09c0bb4d4b9142f8)
|
||||||
|
|
||||||
package static
|
package static
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
# this will be replaced by the postinstall script
|
|
@ -1,10 +1,19 @@
|
||||||
{
|
{
|
||||||
"name": "serviceman",
|
"name": "serviceman",
|
||||||
"version": "0.5.2",
|
"version": "0.7.0",
|
||||||
"description": "A cross-platform service manager",
|
"description": "A cross-platform service manager",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
|
"homepage": "https://git.rootprojects.org/root/serviceman/src/branch/master/npm",
|
||||||
|
"files": [
|
||||||
|
"bin/",
|
||||||
|
"scripts/"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"serviceman": "bin/serviceman"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"postinstall": "scripts/fetch-serviceman.js",
|
"serviceman": "serviceman",
|
||||||
|
"postinstall": "node scripts/fetch-serviceman.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
var path = require('path');
|
||||||
var os = require('os');
|
var os = require('os');
|
||||||
|
|
||||||
// https://nodejs.org/api/os.html#os_os_arch
|
// 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
|
// https://nodejs.org/api/os.html#os_os_platform
|
||||||
// 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', 'win32'
|
// 'aix', 'darwin', 'freebsd', 'linux', 'openbsd', 'sunos', 'win32'
|
||||||
var platform = os.platform(); // process.platform
|
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
|
// This is _probably_ right. It's good enough for us
|
||||||
// https://github.com/nodejs/node/issues/13629
|
// https://github.com/nodejs/node/issues/13629
|
||||||
|
@ -53,15 +54,26 @@ var mkdirp = require('@root/mkdirp');
|
||||||
|
|
||||||
function needsUpdate(oldVer, newVer) {
|
function needsUpdate(oldVer, newVer) {
|
||||||
// "v1.0.0-pre" is BEHIND "v1.0.0"
|
// "v1.0.0-pre" is BEHIND "v1.0.0"
|
||||||
newVer = newVer.replace(/^v/, '').split(/[\.\-\+]/);
|
newVer = newVer
|
||||||
oldVer = oldVer.replace(/^v/, '').split(/[\.\-\+]/);
|
.replace(/^v/, '')
|
||||||
//console.log(oldVer, newVer);
|
.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]) {
|
if (newVer[3] && !oldVer[3]) {
|
||||||
// don't install beta over stable
|
// don't install beta over stable
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ex: old is v1.0.0-pre
|
||||||
if (oldVer[3]) {
|
if (oldVer[3]) {
|
||||||
if (oldVer[2] > 0) {
|
if (oldVer[2] > 0) {
|
||||||
oldVer[2] -= 1;
|
oldVer[2] -= 1;
|
||||||
|
@ -77,6 +89,8 @@ function needsUpdate(oldVer, newVer) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ex: v1.0.1 vs v1.0.0-pre
|
||||||
if (newVer[3]) {
|
if (newVer[3]) {
|
||||||
if (newVer[2] > 0) {
|
if (newVer[2] > 0) {
|
||||||
newVer[2] -= 1;
|
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]) {
|
if (oldVer[0] > newVer[0]) {
|
||||||
return false;
|
return false;
|
||||||
} else if (oldVer[0] < newVer[0]) {
|
} else if (oldVer[0] < newVer[0]) {
|
||||||
|
@ -128,72 +142,128 @@ console.log(false === needsUpdate('0.5.0', '0.5.0-pre1'));
|
||||||
console.log(false === needsUpdate('0.5.1', '0.5.0'));
|
console.log(false === needsUpdate('0.5.1', '0.5.0'));
|
||||||
*/
|
*/
|
||||||
|
|
||||||
exec('serviceman version', { windowsHide: true }, function(err, stdout) {
|
function install(name, bindirs, getVersion, parseVersion, urlTpl) {
|
||||||
var oldVer = (stdout || '').split(' ')[0];
|
exec(getVersion, { windowsHide: true }, function(err, stdout) {
|
||||||
console.log(oldVer, newVer);
|
var oldVer = parseVersion(stdout);
|
||||||
if (!needsUpdate(oldVer, newVer)) {
|
//console.log('old:', oldVer, 'new:', newVer);
|
||||||
console.info(
|
if (!needsUpdate(oldVer, newVer)) {
|
||||||
'Current serviceman version is new enough:',
|
console.info(
|
||||||
oldVer,
|
'Current ' + name + ' version is new enough:',
|
||||||
newVer
|
oldVer,
|
||||||
);
|
newVer
|
||||||
return;
|
);
|
||||||
//} else {
|
|
||||||
// console.info('Current serviceman version is older:', oldVer, newVer);
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = 'https://rootprojects.org/serviceman/dist/{{ .Platform }}/{{ .Arch }}/serviceman{{ .Ext }}'
|
|
||||||
.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;
|
return;
|
||||||
|
//} else {
|
||||||
|
// console.info('Current serviceman version is older:', oldVer, newVer);
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log(resp.body.byteLength);
|
var url = urlTpl
|
||||||
//console.log(typeof resp.body);
|
.replace(/{{ .Version }}/g, newVer)
|
||||||
var serviceman = 'serviceman' + ext;
|
.replace(/{{ .Platform }}/g, platform)
|
||||||
return fs.writeFile(serviceman, resp.body, null, function(err) {
|
.replace(/{{ .Arch }}/g, arch)
|
||||||
|
.replace(/{{ .Ext }}/g, ext);
|
||||||
|
|
||||||
|
console.info('Installing from', url);
|
||||||
|
return request({ uri: url, encoding: null }, function(err, resp) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fs.chmodSync(serviceman, parseInt('0755', 8));
|
|
||||||
|
|
||||||
var path = require('path');
|
//console.log(resp.body.byteLength);
|
||||||
var localdir = '/usr/local/bin';
|
//console.log(typeof resp.body);
|
||||||
fs.rename(serviceman, path.join(localdir, serviceman), function(
|
var bin = name + ext;
|
||||||
err
|
function next() {
|
||||||
) {
|
if (!bindirs.length) {
|
||||||
if (err) {
|
|
||||||
//console.error(err);
|
|
||||||
}
|
|
||||||
// ignore
|
|
||||||
});
|
|
||||||
|
|
||||||
var homedir = require('os').homedir();
|
|
||||||
var bindir = path.join(homedir, '.local', 'bin');
|
|
||||||
return mkdirp(bindir, function(err) {
|
|
||||||
if (err) {
|
|
||||||
console.error(err);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var bindir = bindirs.pop();
|
||||||
var localsrv = path.join(bindir, serviceman);
|
return mkdirp(bindir, function(err) {
|
||||||
return fs.writeFile(localsrv, resp.body, function(err) {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fs.chmodSync(localsrv, parseInt('0755', 8));
|
|
||||||
console.info('Wrote', serviceman, 'to', bindir);
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -230,7 +230,7 @@ func add() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ass) > 0 {
|
if len(ass) > 0 {
|
||||||
fmt.Println("OPTIONS: Making some assumptions...\n")
|
fmt.Printf("OPTIONS: Making some assumptions...\n\n")
|
||||||
for i := range ass {
|
for i := range ass {
|
||||||
fmt.Println("\t" + ass[i])
|
fmt.Println("\t" + ass[i])
|
||||||
}
|
}
|
||||||
|
@ -350,7 +350,7 @@ func list() {
|
||||||
fmt.Fprintf(os.Stderr, "\n")
|
fmt.Fprintf(os.Stderr, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("serviceman-managed services:\n")
|
fmt.Printf("serviceman-managed services:\n\n")
|
||||||
for i := range managed {
|
for i := range managed {
|
||||||
fmt.Println("\t" + managed[i])
|
fmt.Println("\t" + managed[i])
|
||||||
}
|
}
|
||||||
|
@ -360,7 +360,7 @@ func list() {
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
|
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Println("other services:\n")
|
fmt.Printf("other services:\n\n")
|
||||||
for i := range others {
|
for i := range others {
|
||||||
fmt.Println("\t" + others[i])
|
fmt.Println("\t" + others[i])
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue