var SERVICE_WIN32 = 0x00000010 | 0x00000020; var SERVICE_STATE = { STOPPED: 0x00000001, SERVICE_START_PENDING: 0x00000002, SERVICE_STOP_PENDING: 0x00000003, RUNNING: 0x00000004 }; var SERVICE_ACCEPT = { SERVICE_ACCEPT_STOP: 0x00000001, SERVICE_ACCEPT_SHUTDOWN: 0x00000004 }; var SERVICE_CONTROL = { SERVICE_CONTROL_SHUTDOWN: 0x00000005, SERVICE_CONTROL_STOP: 0x00000001 }; var NO_ERROR = 0; var serviceManager = require('serviceManager'); function serviceHost(serviceName) { this._ObjectID = 'serviceHost'; var emitterUtils = require('events').inherits(this); emitterUtils.createEvent('serviceStart'); emitterUtils.createEvent('serviceStop'); emitterUtils.createEvent('normalStart'); if (process.platform == 'win32') { this.GM = require('_GenericMarshal'); this.Advapi = this.GM.CreateNativeProxy('Advapi32.dll'); this.Advapi.CreateMethod({ method: 'StartServiceCtrlDispatcherA', threadDispatch: 1 }); this.Advapi.CreateMethod('RegisterServiceCtrlHandlerA'); this.Advapi.CreateMethod('SetServiceStatus'); this.Kernel32 = this.GM.CreateNativeProxy('Kernel32.dll'); this.Kernel32.CreateMethod('GetLastError'); this.Ole32 = this.GM.CreateNativeProxy('Ole32.dll'); this.Ole32.CreateMethod('CoInitializeEx'); this.Ole32.CreateMethod('CoUninitialize'); this._ServiceName = this.GM.CreateVariable(typeof (serviceName) == 'string' ? serviceName : serviceName.name); this._ServiceMain = this.GM.GetGenericGlobalCallback(2); this._ServiceMain.Parent = this; this._ServiceMain.GM = this.GM; this._ServiceMain.on('GlobalCallback', function onGlobalCallback(argc, argv) { //ToDo: Check to make sure this is for us this.Parent._ServiceStatus = this.GM.CreateVariable(28); //typedef struct _SERVICE_STATUS { // DWORD dwServiceType; // DWORD dwCurrentState; // DWORD dwControlsAccepted; // DWORD dwWin32ExitCode; // DWORD dwServiceSpecificExitCode; // DWORD dwCheckPoint; // DWORD dwWaitHint; //} SERVICE_STATUS, *LPSERVICE_STATUS; // Initialise service status this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_WIN32); this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_STATE.SERVICE_STOPPED, 4); this.Parent._ServiceStatusHandle = this.Parent.Advapi.RegisterServiceCtrlHandlerA(this.Parent._ServiceName, this.Parent._ServiceControlHandler); if (this.Parent._ServiceStatusHandle.Val == 0) { process.exit(1); } // Service is starting this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_STATE.SERVICE_START_PENDING, 4); this.Parent.Advapi.SetServiceStatus(this.Parent._ServiceStatusHandle, this.Parent._ServiceStatus); // Service running this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_STATE.RUNNING, 4); this.Parent._ServiceStatus.toBuffer().writeUInt32LE(SERVICE_ACCEPT.SERVICE_ACCEPT_STOP, 8); this.Parent.Advapi.SetServiceStatus(this.Parent._ServiceStatusHandle, this.Parent._ServiceStatus); this.Parent.Ole32.CoInitializeEx(0, 2); this.Parent.on('~', function OnServiceHostFinalizer() { var GM = require('_GenericMarshal'); var Advapi = GM.CreateNativeProxy('Advapi32.dll'); Advapi.CreateMethod('SetServiceStatus'); Kernel32 = this.GM.CreateNativeProxy('Kernel32.dll'); Kernel32.CreateMethod('GetLastError'); var status = GM.CreateVariable(28); // Service was stopped status.toBuffer().writeUInt32LE(SERVICE_WIN32); status.toBuffer().writeUInt32LE(0x00000001, 4); status.toBuffer().writeUInt32LE(0, 8); Advapi.SetServiceStatus(this._ServiceStatusHandle, status); this.Ole32.CoUninitialize(); }); this.Parent.emit('serviceStart'); }); this._ServiceControlHandler = this.GM.GetGenericGlobalCallback(1); this._ServiceControlHandler.Parent = this; this._ServiceControlHandler.GM = this.GM; this._ServiceControlHandler.on('GlobalCallback', function onServiceControlHandler(code) { switch (code.Val) { case SERVICE_CONTROL.SERVICE_CONTROL_SHUTDOWN: case SERVICE_CONTROL.SERVICE_CONTROL_STOP: this.Parent.emit('serviceStop'); return; default: break; } this.Parent.Advapi.SetServiceStatus(this.Parent._ServiceStatusHandle, this.Parent._ServiceStatus); }); } if (serviceName) { this._ServiceOptions = typeof (serviceName) == 'object' ? serviceName : { name: serviceName }; } else { throw ('Must specify either ServiceName or Options'); } if (!this._ServiceOptions.servicePath) { this._ServiceOptions.servicePath = process.execPath; } this.run = function run() { var serviceOperation = 0; for (var i = 0; i < process.argv.length; ++i) { switch (process.argv[i].toLowerCase()) { case 'install': if (!this._svcManager) { this._svcManager = new serviceManager(); } try { this._svcManager.installService(this._ServiceOptions); } catch (e) { console.log(e); process.exit(); } if (process.platform == 'win32') { // Only do this on Windows, becuase Linux is async... It'll complete later console.log(this._ServiceOptions.name + ' installed.'); process.exit(); } i = process.argv.length; serviceOperation = 1; break; case 'uninstall': if (!this._svcManager) { this._svcManager = new serviceManager(); } try { this._svcManager.uninstallService(this._ServiceOptions); } catch (e) { console.log(e); process.exit(); } if (process.platform == 'win32') { // Only do this on Windows, becuase Linux is async... It'll complete later console.log(this._ServiceOptions.name + ' uninstalled.'); process.exit(); } i = process.argv.length; serviceOperation = 1; break; case 'start': case '-d': if (!this._svcManager) { this._svcManager = new serviceManager(); } this._svcManager.getService(this._ServiceOptions.name).start(); console.log(this._ServiceOptions.name + ' starting.'); process.exit(); break; case 'stop': case '-s': if (!this._svcManager) { this._svcManager = new serviceManager(); } this._svcManager.getService(this._ServiceOptions.name).stop(); console.log(this._ServiceOptions.name + ' stopping.'); process.exit(); break; } } if (process.platform == 'win32') { var serviceTable = this.GM.CreateVariable(4 * this.GM.PointerSize); this._ServiceName.pointerBuffer().copy(serviceTable.toBuffer()); this._ServiceMain.pointerBuffer().copy(serviceTable.toBuffer(), this.GM.PointerSize); this._sscd = this.Advapi.StartServiceCtrlDispatcherA(serviceTable); this._sscd.parent = this; this._sscd.on('done', function OnStartServiceCtrlDispatcherA(retVal) { if (retVal.Val == 0) { this.parent.emit('normalStart'); } }); return; } var moduleName = this._ServiceOptions ? this._ServiceOptions.name : process.execPath.substring(1 + process.execPath.lastIndexOf('/')); for (var i = 0; i < process.argv.length; ++i) { switch (process.argv[i].toLowerCase()) { 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()); 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' })); } catch (e) { } } if (pid != null && pid == process.pid) { this.emit('serviceStart'); } else { // Now we need to check if we were started with systemd if (require('processManager').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'); } } } }; } module.exports = serviceHost;