From 242382d90b699e2fd5c54a77239a71a53701864b Mon Sep 17 00:00:00 2001 From: Bryan Roe Date: Fri, 11 Jan 2019 13:56:43 -0800 Subject: [PATCH] Added MacOS support --- modules/service-host.js | 239 +++++++++++++++++++------------------ modules/service-manager.js | 102 +++++++++++++++- 2 files changed, 221 insertions(+), 120 deletions(-) diff --git a/modules/service-host.js b/modules/service-host.js index 7bfe0e4..a409839 100644 --- a/modules/service-host.js +++ b/modules/service-host.js @@ -187,9 +187,9 @@ function serviceHost(serviceName) console.log(e); process.exit(); } - if (process.platform == 'win32') + if (process.platform == 'win32' || process.platform == 'darwin') { - // Only do this on Windows, becuase Linux is async... It'll complete later + // Only do this on Windows/MacOS, becuase Linux is async... It'll complete later console.log(this._ServiceOptions.name + ' installed'); process.exit(); } @@ -207,9 +207,9 @@ function serviceHost(serviceName) console.log(e); process.exit(); } - if (process.platform == 'win32') + if (process.platform == 'win32' || process.platform == 'darwin') { - // Only do this on Windows, becuase Linux is async... It'll complete later + // Only do this on Windows/MacOS, becuase Linux is async... It'll complete later console.log(this._ServiceOptions.name + ' uninstalled'); process.exit(); } @@ -251,138 +251,141 @@ function serviceHost(serviceName) }); return; } - - var moduleName = this._ServiceOptions ? this._ServiceOptions.name : process.execPath.substring(1 + process.execPath.lastIndexOf('/')); - - for (var i = 0; i < process.argv.length; ++i) + else if (process.platform == 'linux') { - switch(process.argv[i]) - { - case 'start': - case '-d': - var child = require('child_process').execFile(process.execPath, [moduleName], { type: require('child_process').SpawnTypes.DETACHED }); - var pstream = null; - try - { - pstream = require('fs').createWriteStream('/var/run/' + moduleName + '.pid', { flags: 'w' }); - } - catch(e) - { - } - if (pstream == null) - { - pstream = require('fs').createWriteStream('.' + moduleName + '.pid', { flags: 'w' }); - } - pstream.end(child.pid.toString()); + var moduleName = this._ServiceOptions ? this._ServiceOptions.name : process.execPath.substring(1 + process.execPath.lastIndexOf('/')); - console.log(moduleName + ' started!'); - process.exit(); - break; - case 'stop': - case '-s': - var pid = null; - try - { - pid = parseInt(require('fs').readFileSync('/var/run/' + moduleName + '.pid', { flags: 'r' })); - require('fs').unlinkSync('/var/run/' + moduleName + '.pid'); - } - catch(e) - { - } - if(pid == null) - { - try - { - pid = parseInt(require('fs').readFileSync('.' + moduleName + '.pid', { flags: 'r' })); - require('fs').unlinkSync('.' + moduleName + '.pid'); + for (var i = 0; i < process.argv.length; ++i) { + switch (process.argv[i]) { + case 'start': + case '-d': + var child = require('child_process').execFile(process.execPath, [moduleName], { type: require('child_process').SpawnTypes.DETACHED }); + var pstream = null; + try { + pstream = require('fs').createWriteStream('/var/run/' + moduleName + '.pid', { flags: 'w' }); } - catch(e) - { + catch (e) { } - } + if (pstream == null) { + pstream = require('fs').createWriteStream('.' + moduleName + '.pid', { flags: 'w' }); + } + pstream.end(child.pid.toString()); - if(pid) - { - process.kill(pid); - console.log(moduleName + ' stopped'); + console.log(moduleName + ' started!'); + process.exit(); + break; + case 'stop': + case '-s': + var pid = null; + try { + pid = parseInt(require('fs').readFileSync('/var/run/' + moduleName + '.pid', { flags: 'r' })); + require('fs').unlinkSync('/var/run/' + moduleName + '.pid'); + } + catch (e) { + } + if (pid == null) { + try { + pid = parseInt(require('fs').readFileSync('.' + moduleName + '.pid', { flags: 'r' })); + require('fs').unlinkSync('.' + moduleName + '.pid'); + } + catch (e) { + } + } + + if (pid) { + process.kill(pid); + console.log(moduleName + ' stopped'); + } + else { + console.log(moduleName + ' not running'); + } + process.exit(); + break; + } + } + + if (serviceOperation == 0) { + // This is non-windows, so we need to check how this binary was started to determine if this was a service start + + // Start by checking if we were started with start/stop + var pid = null; + try { + pid = parseInt(require('fs').readFileSync('/var/run/' + moduleName + '.pid', { flags: 'r' })); + } + catch (e) { + } + if (pid == null) { + try { + pid = parseInt(require('fs').readFileSync('.' + moduleName + '.pid', { flags: 'r' })); } - else - { - console.log(moduleName + ' not running'); + catch (e) { } - process.exit(); - break; + } + + if (pid != null && pid == process.pid) { + this.emit('serviceStart'); + } + else { + // Now we need to check if we were started with systemd + if (require('process-manager').getProcessInfo(1).Name == 'systemd') { + this._checkpid = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); + this._checkpid.result = ''; + this._checkpid.parent = this; + this._checkpid.on('exit', function onCheckPIDExit() { + var lines = this.result.split('\r\n'); + for (i in lines) { + if (lines[i].startsWith(' Main PID:')) { + var tokens = lines[i].split(' '); + if (parseInt(tokens[3]) == process.pid) { + this.parent.emit('serviceStart'); + } + else { + this.parent.emit('normalStart'); + } + delete this.parent._checkpid; + return; + } + } + this.parent.emit('normalStart'); + delete this.parent._checkpid; + }); + this._checkpid.stdout.on('data', function (chunk) { this.parent.result += chunk.toString(); }); + this._checkpid.stdin.write("systemctl status " + moduleName + " | grep 'Main PID:'\n"); + this._checkpid.stdin.write('exit\n'); + } + else { + // This isn't even a systemd platform, so this couldn't have been a service start + this.emit('normalStart'); + } + } } } - - if(serviceOperation == 0) + else if(process.platform == 'darwin') { - // This is non-windows, so we need to check how this binary was started to determine if this was a service start + // First let's fetch all the PIDs of running services + var child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.str = ''; + child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); + child.stdin.write('launchctl list\nexit\n'); + child.waitExit(); - // Start by checking if we were started with start/stop - var pid = null; - try + var lines = child.stdout.str.split('\n'); + var tokens, i; + var p = {}; + for (i = 1; i < lines.length; ++i) { - pid = parseInt(require('fs').readFileSync('/var/run/' + moduleName + '.pid', { flags: 'r' })); + tokens = lines[i].split('\t'); + if (tokens[0] && tokens[0] != '-') { p[tokens[0]] = tokens[0]; } } - catch (e) - { - } - if (pid == null) - { - try - { - pid = parseInt(require('fs').readFileSync('.' + moduleName + '.pid', { flags: 'r' })); - } - catch (e) - { - } - } - - if (pid != null && pid == process.pid) + + if(p[process.pid.toString()]) { + // We are a service! this.emit('serviceStart'); } else { - // Now we need to check if we were started with systemd - if (require('process-manager').getProcessInfo(1).Name == 'systemd') - { - this._checkpid = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); - this._checkpid.result = ''; - this._checkpid.parent = this; - this._checkpid.on('exit', function onCheckPIDExit() - { - var lines = this.result.split('\r\n'); - for (i in lines) - { - if(lines[i].startsWith(' Main PID:')) - { - var tokens = lines[i].split(' '); - if (parseInt(tokens[3]) == process.pid) - { - this.parent.emit('serviceStart'); - } - else - { - this.parent.emit('normalStart'); - } - delete this.parent._checkpid; - return; - } - } - this.parent.emit('normalStart'); - delete this.parent._checkpid; - }); - this._checkpid.stdout.on('data', function (chunk) { this.parent.result += chunk.toString(); }); - this._checkpid.stdin.write("systemctl status " + moduleName + " | grep 'Main PID:'\n"); - this._checkpid.stdin.write('exit\n'); - } - else - { - // This isn't even a systemd platform, so this couldn't have been a service start - this.emit('normalStart'); - } + this.emit('normalStart'); } } }; diff --git a/modules/service-manager.js b/modules/service-manager.js index ee6689e..7c3092b 100644 --- a/modules/service-manager.js +++ b/modules/service-manager.js @@ -214,6 +214,13 @@ function serviceManager() throw ('could not find service: ' + name); } } + else + { + this.isAdmin = function isAdmin() + { + return (require('user-sessions').isRoot()); + } + } this.installService = function installService(options) { if (process.platform == 'win32') @@ -273,6 +280,8 @@ function serviceManager() } if(process.platform == 'linux') { + if (!this.isAdmin()) { throw ('Installing as Service, requires root'); } + switch (this.getServiceType()) { case 'init': @@ -311,14 +320,70 @@ function serviceManager() break; } } + if(process.platform == 'darwin') + { + if (!this.isAdmin()) { throw ('Installing as Service, requires root'); } + + // Mac OS + var stdoutpath = (options.stdout ? ('StandardOutPath\n' + options.stdout + '') : ''); + var autoStart = (options.startType == 'AUTO_START' ? '' : ''); + var params = ' ProgramArguments\n'; + params += ' \n'; + params += (' /usr/local/mesh_services/' + options.name + '/' + options.name + '\n'); + if(options.parameters) + { + for(var itm in options.parameters) + { + params += (' ' + options.parameters[itm] + '\n'); + } + } + params += ' \n'; + + var plist = '\n'; + plist += '\n'; + plist += '\n'; + plist += ' \n'; + plist += ' Label\n'; + plist += (' ' + options.name + '\n'); + plist += (params + '\n'); + plist += ' WorkingDirectory\n'; + plist += (' /usr/local/mesh_services/' + options.name + '\n'); + plist += (stdoutpath + '\n'); + plist += ' RunAtLoad\n'; + plist += (autoStart + '\n'); + plist += ' \n'; + plist += ''; + + if (!require('fs').existsSync('/usr/local/mesh_services')) { require('fs').mkdirSync('/usr/local/mesh_services'); } + if (!require('fs').existsSync('/Library/LaunchDaemons/' + options.name + '.plist')) + { + if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); } + if (options.binary) + { + require('fs').writeFileSync('/usr/local/mesh_services/' + options.name + '/' + options.name, options.binary); + } + else + { + require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.name); + } + require('fs').writeFileSync('/Library/LaunchDaemons/' + options.name + '.plist', plist); + var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.name).mode; + m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP); + require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.name, m); + } + else + { + throw ('Service: ' + options.name + ' already exists'); + } + } } this.uninstallService = function uninstallService(name) { + if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); } + if (typeof (name) == 'object') { name = name.name; } if (process.platform == 'win32') { - if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); } - var service = this.getService(name); if (service.status.state == undefined || service.status.state == 'STOPPED') { @@ -388,6 +453,39 @@ function serviceManager() break; } } + else if(process.platform == 'darwin') + { + if (require('fs').existsSync('/Library/LaunchDaemons/' + name + '.plist')) + { + var child = require('child_process').execFile('/bin/sh', ['sh']); + child.stdout.on('data', function (chunk) { }); + child.stdin.write('launchctl stop ' + name + '\n'); + child.stdin.write('launchctl unload /Library/LaunchDaemons/' + name + '.plist\n'); + child.stdin.write('exit\n'); + child.waitExit(); + + try + { + require('fs').unlinkSync('/usr/local/mesh_services/' + name + '/' + name); + require('fs').unlinkSync('/Library/LaunchDaemons/' + name + '.plist'); + } + catch(e) + { + throw ('Error uninstalling service: ' + name + ' => ' + e); + } + + try + { + require('fs').rmdirSync('/usr/local/mesh_services/' + name); + } + catch(e) + {} + } + else + { + throw ('Service: ' + name + ' does not exist'); + } + } } if(process.platform == 'linux') {