diff --git a/npm/.gitignore b/npm/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/npm/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 0000000..50d6a99 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,28 @@ +# 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 diff --git a/npm/package-lock.json b/npm/package-lock.json new file mode 100644 index 0000000..206cacc --- /dev/null +++ b/npm/package-lock.json @@ -0,0 +1,18 @@ +{ + "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==" + } + } +} diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..4f2c6af --- /dev/null +++ b/npm/package.json @@ -0,0 +1,30 @@ +{ + "name": "serviceman", + "version": "0.5.0", + "description": "A cross-platform service manager", + "main": "index.js", + "scripts": { + "postinstall": "bin/install-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 (https://coolaj86.com/)", + "license": "MPL-2.0", + "dependencies": { + "@root/mkdirp": "^1.0.0", + "@root/request": "^1.3.11" + } +} diff --git a/npm/scripts/fetch-serviceman.js b/npm/scripts/fetch-serviceman.js new file mode 100644 index 0000000..3e46144 --- /dev/null +++ b/npm/scripts/fetch-serviceman.js @@ -0,0 +1,197 @@ +'use strict'; +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 = 'windows' === 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(/[\.\-\+]/); + oldVer = oldVer.replace(/^v/, '').split(/[\.\-\+]/); + //console.log(oldVer, newVer); + + if (newVer[3] && !oldVer[3]) { + // don't install beta over stable + return false; + } + + 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; + } + } + 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; + } + } + + //console.log(oldVer, newVer); + 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')); +*/ + +exec('serviceman version', { windowsHide: true }, function(err, stdout) { + var oldVer = (stdout || '').split(' ')[0]; + console.log(oldVer, newVer); + if (!needsUpdate(oldVer, newVer)) { + console.info( + 'Current serviceman version is new enough:', + 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; + } + + //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); + 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'); + return mkdirp(bindir, function(err) { + if (err) { + console.error(err); + return; + } + + var localsrv = path.join(bindir, serviceman); + return fs.writeFile(localsrv, resp.body, function(err) { + if (err) { + console.error(err); + return; + } + fs.chmodSync(localsrv, parseInt('0755', 8)); + console.info('Wrote', serviceman, 'to', bindir); + }); + }); + }); + }); +});