/* Copyright 2018 Intel Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ var promise = require('promise'); function failureActionToInteger(action) { var ret; switch(action) { default: case 'NONE': ret=0; break; case 'SERVICE_RESTART': ret=1; break; case 'REBOOT': ret=2; break; } return(ret); } function extractFileName(filePath) { if (typeof (filePath) == 'string') { var tokens = filePath.split('\\').join('/').split('/'); var name; while ((name = tokens.pop()) == ''); return (name); } else { return(filePath.newName) } } function extractFileSource(filePath) { return (typeof (filePath) == 'string' ? filePath : filePath.source); } function prepareFolders(folderPath) { var dlmtr = process.platform == 'win32' ? '\\' : '/'; var tokens = folderPath.split(dlmtr); var path = null; while (tokens.length>0) { path = (path == null ? tokens.shift() : (path + dlmtr + tokens.shift())); if (path.indexOf(process.platform == 'win32' ? '\\' : '/') < 0) { continue; } if (!require('fs').existsSync(path)) { require('fs').mkdirSync(path); } } } function parseServiceStatus(token) { var j = {}; var serviceType = token.Deref(0, 4).IntVal; j.isFileSystemDriver = ((serviceType & 0x00000002) == 0x00000002); j.isKernelDriver = ((serviceType & 0x00000001) == 0x00000001); j.isSharedProcess = ((serviceType & 0x00000020) == 0x00000020); j.isOwnProcess = ((serviceType & 0x00000010) == 0x00000010); j.isInteractive = ((serviceType & 0x00000100) == 0x00000100); j.waitHint = token.Deref((6 * 4), 4).toBuffer().readUInt32LE(); switch (token.Deref((1 * 4), 4).toBuffer().readUInt32LE()) { case 0x00000005: j.state = 'CONTINUE_PENDING'; break; case 0x00000006: j.state = 'PAUSE_PENDING'; break; case 0x00000007: j.state = 'PAUSED'; break; case 0x00000004: j.state = 'RUNNING'; break; case 0x00000002: j.state = 'START_PENDING'; break; case 0x00000003: j.state = 'STOP_PENDING'; break; case 0x00000001: j.state = 'STOPPED'; break; } var controlsAccepted = token.Deref((2 * 4), 4).toBuffer().readUInt32LE(); j.controlsAccepted = []; if ((controlsAccepted & 0x00000010) == 0x00000010) { j.controlsAccepted.push('SERVICE_CONTROL_NETBINDADD'); j.controlsAccepted.push('SERVICE_CONTROL_NETBINDREMOVE'); j.controlsAccepted.push('SERVICE_CONTROL_NETBINDENABLE'); j.controlsAccepted.push('SERVICE_CONTROL_NETBINDDISABLE'); } if ((controlsAccepted & 0x00000008) == 0x00000008) { j.controlsAccepted.push('SERVICE_CONTROL_PARAMCHANGE'); } if ((controlsAccepted & 0x00000002) == 0x00000002) { j.controlsAccepted.push('SERVICE_CONTROL_PAUSE'); j.controlsAccepted.push('SERVICE_CONTROL_CONTINUE'); } if ((controlsAccepted & 0x00000100) == 0x00000100) { j.controlsAccepted.push('SERVICE_CONTROL_PRESHUTDOWN'); } if ((controlsAccepted & 0x00000004) == 0x00000004) { j.controlsAccepted.push('SERVICE_CONTROL_SHUTDOWN'); } if ((controlsAccepted & 0x00000001) == 0x00000001) { j.controlsAccepted.push('SERVICE_CONTROL_STOP'); } if ((controlsAccepted & 0x00000020) == 0x00000020) { j.controlsAccepted.push('SERVICE_CONTROL_HARDWAREPROFILECHANGE'); } if ((controlsAccepted & 0x00000040) == 0x00000040) { j.controlsAccepted.push('SERVICE_CONTROL_POWEREVENT'); } if ((controlsAccepted & 0x00000080) == 0x00000080) { j.controlsAccepted.push('SERVICE_CONTROL_SESSIONCHANGE'); } j.pid = token.Deref((7 * 4), 4).toBuffer().readUInt32LE(); return (j); } if (process.platform == 'linux') { function _upstart_GetServiceTable() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("initctl list | tr '\n' '`' | awk -F'`' '"); child.stdin.write('{'); child.stdin.write(' printf "{"; '); child.stdin.write(' for(i=1;i 0 && raw.length > 0) { s = parseInt(self.shift()); r = parseInt(raw.shift()); if (s < r) { return (-1); } if (s > r) { return (1); } } if (self.length == raw.length) { return (0); } if (self.length < raw.length) { return (-1); } else { return (1); } } return (ret); }; function fetchPlist(folder, name, userid) { if (folder.endsWith('/')) { folder = folder.substring(0, folder.length - 1); } var ret = { name: name, close: function () { }, _uid: userid }; if (!require('fs').existsSync(folder + '/' + name + '.plist')) { // Before we throw in the towel, let's enumerate all the plist files, and see if one has a matching label var files = require('fs').readdirSync(folder); for (var file in files) { if (!files[file].endsWith('.plist')) { continue; } 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("cat " + folder + '/' + files[file] + " | tr '\n' '\.' | awk '{ split($0, a, \"Label\"); split(a[2], b, \"\"); split(b[1], c, \"\"); print c[2]; }'\nexit\n"); child.waitExit(); if (child.stdout.str.trim() == name) { ret.name = files[file].endsWith('.plist') ? files[file].substring(0, files[file].length - 6) : files[file]; Object.defineProperty(ret, 'alias', { value: name }); Object.defineProperty(ret, 'plist', { value: folder + '/' + files[file] }); break; } } if (ret.name == name) { throw (' ' + (folder.split('LaunchDaemon').length>1 ? 'LaunchDaemon' : 'LaunchAgent') + ' (' + name + ') NOT FOUND'); } } else { Object.defineProperty(ret, 'plist', { value: folder + '/' + name + '.plist' }); Object.defineProperty(ret, 'alias', { get: function () { 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("cat " + ret.plist + " | tr '\n' '\.' | awk '{ split($0, a, \"Label\"); split(a[2], b, \"\"); split(b[1], c, \"\"); print c[2]; }'\nexit\n"); child.waitExit(); return (child.stdout.str.trim()); } }); } Object.defineProperty(ret, 'daemon', { value: ret.plist.split('/LaunchDaemons/').length > 1 ? true : false }); ret.appWorkingDirectory = function appWorkingDirectory() { 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("cat " + this.plist + " | tr '\n' '\.' | awk '{ split($0, a, \"WorkingDirectory\"); split(a[2], b, \"\"); split(b[1], c, \"\"); print c[2]; }'\nexit\n"); child.waitExit(); child.stdout.str = child.stdout.str.trim(); return (child.stdout.str.endsWith('/') ? child.stdout.str.substring(0, child.stdout.str.length - 1) : child.stdout.str); }; ret.appLocation = function appLocation() { 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("cat " + this.plist + " | tr '\n' '\.' | awk '{ split($0, a, \"ProgramArguments\"); split(a[2], b, \"\"); split(b[1], c, \"\"); print c[2]; }'\nexit\n"); child.waitExit(); return (child.stdout.str.trim()); }; Object.defineProperty(ret, '_runAtLoad', { get: function () { // We need to see if this is an Auto-Starting service, in order to figure out how to implement 'start' 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("cat " + ret.plist + " | tr '\n' '\.' | awk '{ split($0, a, \"RunAtLoad\"); split(a[2], b, \"/>\"); split(b[1], c, \"<\"); print c[2]; }'\nexit\n"); child.waitExit(); return (child.stdout.str.trim().toUpperCase() == "TRUE"); } }); Object.defineProperty(ret, 'startType', { get: function() { if(this.daemon) { return (this._runAtLoad ? 'AUTO_START' : 'DEMAND_START'); } else { return ('AUTO_START'); } } }); Object.defineProperty(ret, "_keepAlive", { get: function () { 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("cat " + ret.plist + " | tr '\n' '\.' | awk '{split($0, a, \"KeepAlive\"); split(a[2], b, \"<\"); split(b[2], c, \">\"); "); child.stdin.write(" if(c[1]==\"dict\"){ split(a[2], d, \"\"); if(split(d[1], truval, \"\")>1) { split(truval[1], kn1, \"\"); split(kn1[2], kn2, \"\"); print kn2[1]; } }"); child.stdin.write(" else { split(c[1], ka, \"/\"); if(ka[1]==\"true\") {print \"ALWAYS\";} } }'\nexit\n"); child.waitExit(); return (child.stdout.str.trim()); } }); ret.getPID = function getPID(uid, asString) { var options = undefined; var command; if (this._uid != null) { uid = this._uid; } if (getOSVersion().compareTo('10.10') < 0) { command = "launchctl list | grep '" + this.alias + "' | awk '{ if($3==\"" + this.alias + "\"){print $1;}}'\nexit\n"; options = { uid: uid }; } else { if (uid == null) { command = 'launchctl print system | grep "' + this.alias + '" | awk \'{ if(split($0, tmp, " ")==3) { if($3=="' + this.alias + '") { print $1; } }}\'\nexit\n'; } else { command = 'launchctl print gui/' + uid + ' | grep "' + this.alias + '" | awk \'{ if(split($0, tmp, " ")==3) { if($3=="' + this.alias + '") { print $1; } }}\'\nexit\n'; } } var child = require('child_process').execFile('/bin/sh', ['sh'], options); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stdin.write(command); child.waitExit(); if (asString == null || asString != true) { return (parseInt(child.stdout.str.trim())); } else { return (child.stdout.str.trim()); } }; ret.isLoaded = function isLoaded(uid) { if (this._uid != null) { uid = this._uid; } return (this.getPID(uid, true) != ''); }; ret.isRunning = function isRunning(uid) { if (this._uid != null) { uid = this._uid; } return (this.getPID(uid) > 0); }; ret.isMe = function isMe(uid) { if (this._uid != null) { uid = this._uid; } return (this.getPID(uid) == process.pid); }; ret.load = function load(uid) { var self = require('user-sessions').Self(); var ver = getOSVersion(); var options = undefined; var command = 'load'; if (this._uid != null) { uid = this._uid; } if (this.daemon) { if(uid!=null && uid!=0) { throw ('LaunchDaemon must run as root'); } } else { if (uid == null) { uid = self; } if(ver.compareTo('10.10') < 0 && uid != self && self != 0) { throw ('On this version of MacOS, must be root to load this service into the specified user space'); } else if (ver.compareTo('10.10') < 0) { options = { uid: uid }; } else { command = 'bootstrap gui/' + uid; } } var child = require('child_process').execFile('/bin/sh', ['sh'], options); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); }); child.stdin.write('launchctl ' + command + ' ' + this.plist + '\n\exit\n'); child.waitExit(); }; ret.unload = function unload(uid) { var child = null; var v = getOSVersion(); var self = require('user-sessions').Self(); var options = undefined; var useBootout = false; if (this._uid != null) { uid = this._uid; } if(uid!=null) { if (v.compareTo('10.10') <= 0 && self == 0) { // We must switch to user context to unload the service options = { uid: uid }; } else { if(v.compareTo('10.10') > 0) { if(self == 0 || self == uid) { // use bootout useBootout = true; } else { // insufficient access throw ('Needs elevated privileges') } } else { if (self == uid) { // just unload, becuase we are already in the right context useBootout = false; } else { // insufficient access throw ('Needs elevated privileges') } } } } else { if(self == 0) { if(v.compareTo('10.10') > 0) { // use bootout useBootout = true; } else { // just unload useBootout = false; } } else { // Insufficient access throw ('Needs elevated privileges') } } child = require('child_process').execFile('/bin/sh', ['sh'], options); child.stdout.str = ''; child.stderr.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stderr.on('data', function (chunk) { this.str += chunk.toString(); }); if (useBootout) { if (uid == null) { child.stdin.write('launchctl bootout system ' + this.plist + '\nexit\n'); } else { child.stdin.write('launchctl bootout gui/' + uid + ' ' + this.plist + '\nexit\n'); } } else { child.stdin.write('launchctl unload ' + this.plist + '\nexit\n'); } child.waitExit(); }; ret.start = function start(uid) { var options = undefined; var self = require('user-sessions').Self(); if (this._uid != null) { uid = this._uid; } if (!this.daemon && uid == null) { uid = self; } if (!this.daemon && uid > 0 && self == 0) { options = { uid: uid }; } if (!this.daemon && uid > 0 && self != 0 && uid != self) { throw ('Cannot start LaunchAgent into another user domain while not root'); } if (this.daemon && self != 0) { throw ('Cannot start LaunchDaemon while not root'); } this.load(uid); var child = require('child_process').execFile('/bin/sh', ['sh'], options); child.stdout.on('data', function (chunk) { }); child.stdin.write('launchctl start ' + this.alias + '\n\exit\n'); child.waitExit(); }; ret.stop = function stop(uid) { var options = undefined; var self = require('user-sessions').Self(); if (this._uid != null) { uid = this._uid; } if (!this.daemon && uid == null) { uid = self; } if (!this.daemon && uid > 0 && self == 0) { options = { uid: uid }; } if (!this.daemon && uid > 0 && self != 0 && uid != self) { throw ('Cannot stop LaunchAgent in another user domain while not root'); } if (this.daemon && self != 0) { throw ('Cannot stop LaunchDaemon while not root'); } if (!(this._keepAlive == 'Crashed' || this._keepAlive == '')) { // We must unload the service, rather than stopping it, because otherwise it'll likely restart this.unload(uid); } else { var child = require('child_process').execFile('/bin/sh', ['sh'], options); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); }); child.stdin.write('launchctl stop ' + this.alias + '\nexit\n'); child.waitExit(); } }; ret.restart = function restart(uid) { if (this._uid != null) { uid = this._uid; } if (getOSVersion().compareTo('10.10') < 0) { if (!this.daemon && uid == null) { uid = require('user-sessions').Self(); } var command = 'launchctl unload ' + this.plist + '\nlaunchctl load ' + this.plist + '\nlaunchctl start ' + this.alias + '\nexit\n'; var child = require('child_process').execFile('/bin/sh', ['sh'], { detached: true, uid: uid }); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); }); child.stdin.write(command); child.waitExit(); } else { var command = this.daemon ? ('system/' + this.alias) : ('gui/' + (uid != null ? uid : require('user-sessions').Self()) + '/' + this.alias); var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); child.stderr.str = ''; child.stderr.on('data', function (chunk) { this.str += chunk.toString(); }); child.stdin.write('launchctl kickstart -k ' + command + '\nexit\n'); child.waitExit(); } }; return (ret); }; } function serviceManager() { this._ObjectID = 'service-manager'; if (process.platform == 'win32') { this.GM = require('_GenericMarshal'); this.proxy = this.GM.CreateNativeProxy('Advapi32.dll'); this.proxy.CreateMethod('OpenSCManagerA'); this.proxy.CreateMethod('EnumServicesStatusExW'); this.proxy.CreateMethod('OpenServiceW'); this.proxy.CreateMethod('QueryServiceStatusEx'); this.proxy.CreateMethod('QueryServiceConfigA'); this.proxy.CreateMethod('QueryServiceConfig2A'); this.proxy.CreateMethod('ControlService'); this.proxy.CreateMethod('StartServiceA'); this.proxy.CreateMethod('CloseServiceHandle'); this.proxy.CreateMethod('CreateServiceW'); this.proxy.CreateMethod('ChangeServiceConfig2W'); this.proxy.CreateMethod('DeleteService'); this.proxy.CreateMethod('AllocateAndInitializeSid'); this.proxy.CreateMethod('CheckTokenMembership'); this.proxy.CreateMethod('FreeSid'); this.proxy2 = this.GM.CreateNativeProxy('Kernel32.dll'); this.proxy2.CreateMethod('GetLastError'); this.isAdmin = function isAdmin() { var NTAuthority = this.GM.CreateVariable(6); NTAuthority.toBuffer().writeInt8(5, 5); var AdministratorsGroup = this.GM.CreatePointer(); var admin = false; if (this.proxy.AllocateAndInitializeSid(NTAuthority, 2, 32, 544, 0, 0, 0, 0, 0, 0, AdministratorsGroup).Val != 0) { var member = this.GM.CreateInteger(); if (this.proxy.CheckTokenMembership(0, AdministratorsGroup.Deref(), member).Val != 0) { if (member.toBuffer().readUInt32LE() != 0) { admin = true; } } this.proxy.FreeSid(AdministratorsGroup.Deref()); } return admin; }; this.getProgramFolder = function getProgramFolder() { if (require('os').arch() == 'x64') { // 64 bit Windows if (this.GM.PointerSize == 4) { return (process.env['ProgramFiles(x86)'] ? process.env['ProgramFiles(x86)'] : process.env['ProgramFiles']); } return process.env['ProgramFiles']; // 64 bit App } // 32 bit Windows return process.env['ProgramFiles']; }; this.enumerateService = function () { var machineName = this.GM.CreatePointer(); var dbName = this.GM.CreatePointer(); var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0001 | 0x0004); var bytesNeeded = this.GM.CreatePointer(); var servicesReturned = this.GM.CreatePointer(); var resumeHandle = this.GM.CreatePointer(); //var services = this.proxy.CreateVariable(262144); var success = this.proxy.EnumServicesStatusExW(handle, 0, 0x00000030, 0x00000003, 0x00, 0x00, bytesNeeded, servicesReturned, resumeHandle, 0x00); var ptrSize = dbName._size; var sz = bytesNeeded.Deref(0, dbName._size).toBuffer().readUInt32LE(); if (sz < 0) { throw ('error enumerating services'); } var services = this.GM.CreateVariable(sz); this.proxy.EnumServicesStatusExW(handle, 0, 0x00000030, 0x00000003, services, sz, bytesNeeded, servicesReturned, resumeHandle, 0x00); var blockSize = 36 + (2 * ptrSize); blockSize += ((ptrSize - (blockSize % ptrSize)) % ptrSize); var retVal = []; for (var i = 0; i < servicesReturned.Deref(0, dbName._size).toBuffer().readUInt32LE(); ++i) { var token = services.Deref(i * blockSize, blockSize); var j = {}; j.name = token.Deref(0, ptrSize).Deref().Wide2UTF8; j.displayName = token.Deref(ptrSize, ptrSize).Deref().Wide2UTF8; j.status = parseServiceStatus(token.Deref(2 * ptrSize, 36)); retVal.push(j); } this.proxy.CloseServiceHandle(handle); return (retVal); } this.getService = function getService(name) { var serviceName = this.GM.CreateVariable(name, { wide: true }); var ptr = this.GM.CreatePointer(); var bytesNeeded = this.GM.CreateVariable(ptr._size); var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0001 | 0x0004 | 0x0020 | 0x0010); if (handle.Val == 0) { throw ('could not open ServiceManager'); } var h = this.proxy.OpenServiceW(handle, serviceName, 0x0001 | 0x0002 | 0x0004 | 0x0020 | 0x0010 | 0x00010000); if (h.Val != 0) { var retVal = { _ObjectID: 'service-manager.service' } retVal._scm = handle; retVal._service = h; retVal._GM = this.GM; retVal._proxy = this.proxy; retVal._proxy2 = this.proxy2; retVal.name = name; Object.defineProperty(retVal, 'status', { get: function() { var bytesNeeded = this._GM.CreateVariable(this._GM.PointerSize); this._proxy.QueryServiceStatusEx(this._service, 0, 0, 0, bytesNeeded); var st = this._GM.CreateVariable(bytesNeeded.toBuffer().readUInt32LE()); if (this._proxy.QueryServiceStatusEx(this._service, 0, st, st._size, bytesNeeded).Val != 0) { return(parseServiceStatus(st)); } else { return ({ state: 'UNKNOWN' }); } } }); Object.defineProperty(retVal, 'installedBy', { get: function() { var reg = require('win-registry'); try { return(reg.QueryKey(reg.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\' + this.name, '_InstalledBy')); } catch(xx) { return (null); } } }); if (retVal.status.state != 'UNKNOWN') { require('events').EventEmitter.call(retVal); retVal.close = function () { if(this._service && this._scm) { this._proxy.CloseServiceHandle(this._service); this._proxy.CloseServiceHandle(this._scm); this._service = this._scm = null; } }; retVal.on('~', retVal.close); retVal.isMe = function isMe() { return (parseInt(this.status.pid) == process.pid); } retVal.update = function update() { if (this.failureActions) { var actions = this._GM.CreateVariable(this.failureActions.actions.length * 8); // len*sizeof(SC_ACTION) for (var i = 0; i < this.failureActions.actions.length && i < 3; ++i) { actions.Deref(i*8, 4).toBuffer().writeUInt32LE(failureActionToInteger(this.failureActions.actions[i].type)); // SC_ACTION[i].type actions.Deref(4+(i*8), 4).toBuffer().writeUInt32LE(this.failureActions.actions[i].delay); // SC_ACTION[i].delay } var updatedFailureActions = this._GM.CreateVariable(40); // sizeof(SERVICE_FAILURE_ACTIONS) updatedFailureActions.Deref(0, 4).toBuffer().writeUInt32LE(this.failureActions.resetPeriod); // dwResetPeriod updatedFailureActions.Deref(this._GM.PointerSize == 8 ? 24 : 12, 4).toBuffer().writeUInt32LE(this.failureActions.actions.length); // cActions actions.pointerBuffer().copy(updatedFailureActions.Deref(this._GM.PointerSize == 8 ? 32 : 16, this._GM.PointerSize).toBuffer()); if (this._proxy.ChangeServiceConfig2W(this._service, 2, updatedFailureActions).Val == 0) { throw('Unable to set FailureActions...'); } } }; retVal.appLocation = function () { var reg = require('win-registry'); var imagePath = reg.QueryKey(reg.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\' + this.name, 'ImagePath').toString(); var ret = imagePath.split('.exe')[0] + '.exe'; if (ret.startsWith('"')) { ret = ret.substring(1); } return (ret); }; retVal.appWorkingDirectory = function () { var tokens = this.appLocation().split('\\'); tokens.pop(); return (tokens.join('\\')); }; retVal.isRunning = function () { return (this.status.state == 'RUNNING'); }; retVal._stopEx = function(s, p) { var current = s.status.state; var pid = s.status.pid; switch (current) { case 'STOPPED': p._res('STOPPED'); break; case 'STOP_PENDING': p._elapsedTime = Date.now() - p._startTime; if (p._elapsedTime < 10000) { p.timer = setTimeout(s._stopEx, p._waitTime, s, p); } else { if (pid > 0) { process.kill(pid); p._res('STOPPED/KILLED'); } else { p._rej('timeout waiting for service to stop'); } } break; default: if (pid > 0) { } else { p._rej('Unexpected state: ' + current); } break; } } retVal.stop = function () { var ret = new promise(function (a, r) { this._res = a; this._rej = r; }); var status = this.status; var pid = this.status.pid; if(status.state == 'RUNNING') { // Stop Service var newstate = this._GM.CreateVariable(36); var reason; if(this._proxy.ControlService(this._service, 0x00000001, newstate).Val == 0 && (reason = this._proxy2.GetLastError().Val)!=0) { ret._rej(this.name + '.stop() failed with error: ' + reason); } else { // Now we need to setup a timed callback to check the status ret._startTime = Date.now(); ret._elapsedTime = 0; ret._waitTime = status.waitHint / 10; if (ret._waitTime < 500) { ret._waitTime = 500; } if (ret._waitTime > 5000) { ret._waitTime = 5000; } ret.timer = setTimeout(this._stopEx, ret._waitTime, this, ret); } } else if (status.state == 'STOP_PENDING' && pid > 0) { process.kill(pid); ret._res('STOPPED/KILLED'); } else { ret._rej('cannot call ' + this.name + '.stop(), when current state is: ' + this.status.state); } return (ret); } retVal.start = function () { if (this.status.state == 'STOPPED') { var success = this._proxy.StartServiceA(this._service, 0, 0); if (success == 0) { throw (this.name + '.start() failed'); } } else { throw ('cannot call ' + this.name + '.start(), when current state is: ' + this.status.state); } } retVal.restart = function () { if (this.isMe()) { // In order to restart ourselves on Windows, we must spawn a detached child process, becuase we need to call start, once we are stopped child = require('child_process').execFile(process.execPath, [process.execPath.split('\\').pop(), '-exec "' + "require('service-manager').manager.getService('" + this.name + "').restart().finally(function(){process.exit();});" + '"'], { type: 4, detached: true }); } else { var p = this.stop(); p.startp = new promise(function (a, r) { this._a = a; this._r = r; }); p.service = this; p.then(function () { try { this.service.start(); } catch (e) { this.startp._r(e); return; } this.startp._a(); }, function (e) { console.rawLog('stop() failed => ' + e.toString());}); return (p.startp); } } var query_service_configa_DWORD = this.GM.CreateVariable(4); this.proxy.QueryServiceConfigA(h, 0, 0, query_service_configa_DWORD); if (query_service_configa_DWORD.toBuffer().readUInt32LE() > 0) { var query_service_configa = this.GM.CreateVariable(query_service_configa_DWORD.toBuffer().readUInt32LE()); if(this.proxy.QueryServiceConfigA(h, query_service_configa, query_service_configa._size, query_service_configa_DWORD).Val != 0) { var val = query_service_configa.Deref(this.GM.PointerSize == 4 ? 28 : 48, this.GM.PointerSize).Deref().String; Object.defineProperty(retVal, 'user', { value: val }); switch(query_service_configa.Deref(4,4).toBuffer().readUInt32LE()) { case 0x00: case 0x01: case 0x02: retVal.startType = 'AUTO_START'; break; case 0x03: retVal.startType = 'DEMAND_START'; break; case 0x04: retVal.startType = 'DISABLED'; break; } } } var failureactions = this.GM.CreateVariable(8192); var bneeded = this.GM.CreateVariable(4); if (this.proxy.QueryServiceConfig2A(h, 2, failureactions, 8192, bneeded).Val != 0) { var cActions = failureactions.toBuffer().readUInt32LE(this.GM.PointerSize == 8 ? 24 : 12); retVal.failureActions = {}; retVal.failureActions.resetPeriod = failureactions.Deref(0, 4).toBuffer().readUInt32LE(0); retVal.failureActions.actions = []; for(var act = 0 ; act < cActions; ++act) { var action = failureactions.Deref(this.GM.PointerSize == 8 ? 32 : 16, this.GM.PointerSize).Deref().Deref(act*8,8).toBuffer(); switch(action.readUInt32LE()) { case 0: retVal.failureActions.actions.push({ type: 'NONE' }); break; case 1: retVal.failureActions.actions.push({ type: 'SERVICE_RESTART' }); break; case 2: retVal.failureActions.actions.push({ type: 'REBOOT' }); break; default: retVal.failureActions.actions.push({ type: 'OTHER' }); break; } retVal.failureActions.actions.peek().delay = action.readUInt32LE(4); } } return (retVal); } else { } } this.proxy.CloseServiceHandle(handle); throw ('could not find service: ' + name); } } else { // Linux, MacOS, FreeBSD this.isAdmin = function isAdmin() { return (require('user-sessions').isRoot()); } if (process.platform == 'freebsd') { this.getService = function getService(name) { var ret = { name: name, close: function () { } }; if(require('fs').existsSync('/etc/rc.d/' + name)) { Object.defineProperty(ret, 'rc', { value: '/etc/rc.d/' + name }); } else if(require('fs').existsSync('/usr/local/etc/rc.d/' + name)) { Object.defineProperty(ret, 'rc', { value: '/usr/local/etc/rc.d/' + name }); } else { throw ('Service: ' + name + ' not found'); } Object.defineProperty(ret, "startType", { get: function () { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stderr.on('data', function (c) { }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write('service ' + this.name + ' rcvar | grep _enable= | awk \'{ a=split($0, b, "\\""); if(b[2]=="YES") { print "YES"; } }\'\nexit\n'); child.waitExit(); return (child.stdout.str.trim() == '' ? 'DEMAND_START' : 'AUTO_START'); } }); ret.description = function description() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("cat " + this.rc + " | grep desc= | awk -F= '" + '{ if($1=="desc") { $1=""; a=split($0, res, "\\""); if(a>1) { print res[2]; } else { print $0; } } }\'\nexit\n'); child.waitExit(); return (child.stdout.str.trim()); }; ret.appWorkingDirectory = function appWorkingDirectory() { var ret; var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("cat " + this.rc + " | grep " + this.name + "_chdir= | awk -F= '{ print $2 }' | awk -F\\\" '{ print $2 }'\nexit\n"); child.waitExit(); ret = child.stdout.str.trim(); if(ret == '') { ret = this.rc.split('/'); ret.pop(); ret = ret.join('/'); } return (ret); }; ret.appLocation = function appLocation() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("cat " + this.rc + " | grep command= | awk -F= '{ print $2 }' | awk -F\\\" '{ print $2 }'\nexit\n"); child.waitExit(); var tmp = child.stdout.str.trim().split('${name}').join(this.name); if(tmp=='/usr/sbin/daemon') { child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write('cat ' + this.rc + ' | grep command_args= | awk -F"-f " \'{ $1=""; split($0, res, "\\""); split(res[1], t, " "); print t[1]; }\'\nexit\n'); child.waitExit(); return(child.stdout.str.trim()); } else { return(tmp); } }; ret.isRunning = function isRunning() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("service " + this.name + " onestatus | awk '{ print $3 }'\nexit\n"); child.waitExit(); return (child.stdout.str.trim() == 'running'); }; ret.isMe = function isMe() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("service " + this.name + " onestatus | awk '{ split($6, res, \".\"); print res[1]; }'\nexit\n"); child.waitExit(); return (parseInt(child.stdout.str.trim()) == process.pid); }; ret.stop = function stop() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("service " + this.name + " onestop\nexit\n"); child.waitExit(); }; ret.start = function start() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("service " + this.name + " onestart\nexit\n"); child.waitExit(); }; ret.restart = function restart() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("service " + this.name + " onerestart\nexit\n"); child.waitExit(); }; return (ret); }; } if (process.platform == 'darwin') { this.getService = function getService(name) { return (fetchPlist('/Library/LaunchDaemons', name)); }; this.getLaunchAgent = function getLaunchAgent(name, userid) { if (userid == null) { return (fetchPlist('/Library/LaunchAgents', name)); } else { return (fetchPlist(require('user-sessions').getHomeFolder(require('user-sessions').getUsername(userid)) + '/Library/LaunchAgents', name, userid)); } }; } if(process.platform == 'linux') { this.getService = function getService(name, platform) { if (!platform) { platform = this.getServiceType(); } var ret = { name: name, close: function () { }, serviceType: platform}; switch(platform) { case 'init': case 'upstart': if (require('fs').existsSync('/etc/init.d/' + name)) { platform = 'init'; } if (require('fs').existsSync('/etc/init/' + name + '.conf')) { platform = 'upstart'; } if ((platform == 'init' && require('fs').existsSync('/etc/init.d/' + name)) || (platform == 'upstart' && require('fs').existsSync('/etc/init/' + name + '.conf'))) { ret.conf = (platform == 'upstart' ? ('/etc/init' + name + '.conf') : ('/etc/init.d/' + name)); ret.serviceType = platform; Object.defineProperty(ret, "startType", { get: function () { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stderr.on('data', function (c) { }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); if (this.serviceType == 'upstart') { child.stdin.write('cat ' + this.conf + ' | grep "start on runlevel"\nexit\n'); } else { child.stdin.write('find /etc/rc* -maxdepth 2 -type l -ls | grep " ../init.d/' + this.name + '" | awk -F"-> " \'{ if($2=="../init.d/' + this.name + '") { print "true"; } }\'\nexit\n'); } child.waitExit(); return (child.stdout.str.trim() == '' ? 'DEMAND_START' : 'AUTO_START'); } }); ret.description = function description() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); if(description.platform == 'upstart') { child.stdin.write("cat /etc/init/" + this.name + ".conf | grep description | awk '" + '{ if($1=="description") { $1=""; a=split($0, res, "\\""); if(a>1) { print res[2]; } else { print $0; }}}\'\nexit\n'); } else { child.stdin.write("cat /etc/init.d/" + this.name + " | grep Short-Description: | awk '" + '{ if($2=="Short-Description:") { $1=""; $2=""; print $0; }}\'\nexit\n'); } child.waitExit(); return (child.stdout.str.trim()); } ret.description.platform = platform; ret.appWorkingDirectory = function appWorkingDirectory() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); if (appWorkingDirectory.platform == 'init') { child.stdin.write("cat /etc/init.d/" + this.name + " | grep 'SCRIPT=' | awk -F= '{ len=split($2, a, \"/\"); print substr($2,0,length($2)-length(a[len])); }'\nexit\n"); } else { child.stdin.write("cat /etc/init/" + this.name + ".conf | grep 'chdir ' | awk '{print $2}'\nexit\n"); } child.waitExit(); return (child.stdout.str.trim()); }; ret.appWorkingDirectory.platform = platform; ret.appLocation = function appLocation() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); if(appLocation.platform == 'init') { child.stdin.write("cat /etc/init.d/" + this.name + " | grep 'SCRIPT=' | awk -F= '{print $2}'\nexit\n"); } else { child.stdin.write("cat /etc/init/" + this.name + ".conf | grep 'exec ' | awk '{print $2}'\nexit\n"); } child.waitExit(); return (child.stdout.str.trim()); }; ret.appLocation.platform = platform; ret.isMe = function isMe() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); if (isMe.platform == 'upstart') { child.stdin.write("initctl status " + this.name + " | awk '{print $NF}'\nexit\n"); } else { child.stdin.write("service " + this.name + " status | awk '{print $NF}'\nexit\n"); } child.waitExit(); return (parseInt(child.stdout.str.trim()) == process.pid); }; ret.isMe.platform = platform; ret.isRunning = function isRunning() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); if (isRunning.platform == 'upstart') { child.stdin.write("initctl status " + this.name + " | awk '{print $2}' | awk -F, '{print $1}'\nexit\n"); } else { child.stdin.write("service " + this.name + " status | awk '{print $2}' | awk -F, '{print $1}'\nexit\n"); } child.waitExit(); return (child.stdout.str.trim() == 'start/running'); }; ret.isRunning.platform = platform; ret.start = function start() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.on('data', function (chunk) { }); if (start.platform == 'upstart') { child.stdin.write('initctl start ' + this.name + '\nexit\n'); } else { child.stdin.write('service ' + this.name + ' start\nexit\n'); } child.waitExit(); }; ret.start.platform = platform; ret.stop = function stop() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.on('data', function (chunk) { }); if (stop.platform == 'upstart') { child.stdin.write('initctl stop ' + this.name + '\nexit\n'); } else { child.stdin.write('service ' + this.name + ' stop\nexit\n'); } child.waitExit(); }; ret.stop.platform = platform; ret.restart = function restart() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.on('data', function (chunk) { }); if (restart.platform == 'upstart') { child.stdin.write('initctl restart ' + this.name + '\nexit\n'); } else { child.stdin.write('service ' + this.name + ' restart\nexit\n'); } child.waitExit(); }; ret.restart.platform = platform; ret.status = function status() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout._str = ''; child.stdout.on('data', function (chunk) { this._str += chunk.toString(); }); if (status.platform == 'upstart') { child.stdin.write('initctl status ' + this.name + '\nexit\n'); } else { child.stdin.write('service ' + this.name + ' status\nexit\n'); } child.waitExit(); return (child.stdout._str); }; ret.status.platform = platform; return (ret); } else { throw (platform + ' Service (' + name + ') NOT FOUND'); } break; case 'systemd': if (require('fs').existsSync('/lib/systemd/system/' + name + '.service')) { ret.conf = '/lib/systemd/system/' + name + '.service'; } else if (require('fs').existsSync('/usr/lib/systemd/system/' + name + '.service')) { ret.conf = '/usr/lib/systemd/system/' + name + '.service'; } if (ret.conf) { Object.defineProperty(ret, "startType", { get: function () { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stderr.on('data', function (c) { }); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write('systemctl status ' + this.name + ' | grep Loaded: | awk \'{ a=split($0, b, ";"); for(c=1;c<=a;++c) { if(b[c]=="enabled" || b[c]==" enabled") { print "true"; } } }\'\nexit\n'); child.waitExit(); return (child.stdout.str.trim() == '' ? 'DEMAND_START' : 'AUTO_START'); } }); ret.description = function description() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); if (require('fs').existsSync('/lib/systemd/system/' + name + '.service')) { child.stdin.write('cat /lib/systemd/system/' + name + '.service'); } else { child.stdin.write('cat /usr/lib/systemd/system/' + name + '.service'); } child.stdin.write(' | grep Description= | awk -F= \'{ if($1=="Description") { $1=""; print $0; }}\'\nexit\n'); child.waitExit(); return (child.stdout.str.trim()); } ret.appWorkingDirectory = function appWorkingDirectory() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); if (require('fs').existsSync('/lib/systemd/system/' + this.name + '.service')) { child.stdin.write("cat /lib/systemd/system/" + this.name + ".service | grep 'WorkingDirectory=' | awk -F= '{ print $2 }'\n\exit\n"); } else { child.stdin.write("cat /usr/lib/systemd/system/" + this.name + ".service | grep 'WorkingDirectory=' | awk -F= '{ print $2 }'\n\exit\n"); } child.waitExit(); return (child.stdout.str.trim()); }; ret.appLocation = function () { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (chunk) { this.str += chunk.toString(); }); if (require('fs').existsSync('/lib/systemd/system/' + this.name + '.service')) { child.stdin.write("cat /lib/systemd/system/" + this.name + ".service | grep 'ExecStart=' | awk -F= '{ split($2, a, \" \"); print a[1] }'\n\exit\n"); } else { child.stdin.write("cat /usr/lib/systemd/system/" + this.name + ".service | grep 'ExecStart=' | awk -F= '{ split($2, a, \" \"); print a[1] }'\n\exit\n"); } child.waitExit(); return (child.stdout.str.trim()); }; ret.isMe = function isMe() { 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("systemctl status " + this.name + " | grep 'Main PID:' | awk '{print $3}'\nexit\n"); child.waitExit(); return (parseInt(child.stdout.str.trim()) == process.pid); }; ret.isRunning = function isRunning() { 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("systemctl status " + this.name + " | grep 'Active:' | awk '{print $2}'\nexit\n"); child.waitExit(); return (child.stdout.str.trim() == 'active'); }; ret.start = function start() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.on('data', function (chunk) { }); child.stdin.write('systemctl start ' + this.name + '\nexit\n'); child.waitExit(); }; ret.stop = function stop() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.on('data', function (chunk) { }); child.stdin.write('systemctl stop ' + this.name + '\nexit\n'); child.waitExit(); }; ret.restart = function restart() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.on('data', function (chunk) { }); child.stdin.write('systemctl restart ' + this.name + '\nexit\n'); child.waitExit(); }; ret.status = function status() { 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('systemctl status ' + this.name + '\nexit\n'); child.waitExit(); return (child.stdout._str); }; return (ret); } else { throw (platform + ' Service (' + name + ') NOT FOUND'); } break; default: // Peudo Service (meshDaemon) if (require('fs').existsSync('/usr/local/mesh_daemons/' + name + '.service')) { ret.conf = '/usr/local/mesh_daemons/' + name + '.service'; ret.start = function start() { var child; child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function (c) { }); child.stdin.write('cat ' + this.conf + " | tr '\n' '~' | awk -F~ '{ wd=" + '""; parms=""; respawn="0"; for(i=1;i<=NF;++i) { split($i, tok1, "="); if(tok1[1]=="workingDirectory") { wd=tok1[2];} if(tok1[1]=="parameters") { parms=substr($i,12);} if(tok1[1]=="respawn") { respawn="1"; } } printf "{ \\\"wd\\\": \\\"%s\\\", \\\"parms\\\": %s, \\\"respawn\\\": %s }", wd, parms, respawn }\'\nexit\n'); child.waitExit(); var info = JSON.parse(child.stdout.str.trim()); info.exePath = info.wd + '/' + info.parms.shift(); var options = { pidPath: info.wd + '/pid', logOutputs: false, crashRestart: info.respawn ? true : false }; require('service-manager').manager.daemon(info.exePath, info.parms, options); }; ret.stop = function stop() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write('cat /usr/local/mesh_daemons/' + name + '/pid \nexit\n'); child.waitExit(); try { process.kill(parseInt(child.stdout.str.trim()), 'SIGTERM'); } catch(x) { } }; ret.isMe = function isMe() { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write('cat /usr/local/mesh_daemons/' + name + '/pid \nexit\n'); child.waitExit(); return (parseInt(child.stdout.str.trim()) == process.pid); }; ret.appWorkingDirectory = function appWorkingDirectory() { var child; child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function (c) { }); child.stdin.write('cat ' + this.conf + " | tr '\n' '~' | awk -F~ '{ wd=" + '""; parms=""; for(i=1;i<=NF;++i) { split($i, tok1, "="); if(tok1[1]=="workingDirectory") { wd=tok1[2];} if(tok1[1]=="parameters") { parms=tok1[2];} } printf "{ \\\"wd\\\": \\\"%s\\\", \\\"parms\\\": %s }", wd, parms }\'\nexit\n'); child.waitExit(); var info = JSON.parse(child.stdout.str.trim()); return (info.wd); }; ret.appLocation = function appLocation() { var child; child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stderr.on('data', function (c) { }); child.stdin.write('cat ' + this.conf + " | tr '\n' '~' | awk -F~ '{ wd=" + '""; parms=""; for(i=1;i<=NF;++i) { split($i, tok1, "="); if(tok1[1]=="workingDirectory") { wd=tok1[2];} if(tok1[1]=="parameters") { parms=substr($i,12);} } printf "{ \\\"wd\\\": \\\"%s\\\", \\\"parms\\\": %s }", wd, parms }\'\nexit\n'); child.waitExit(); var info = JSON.parse(child.stdout.str.trim()); return (info.wd + '/' + info.parms.shift()); }; ret.isRunning = function isRunning() { if(require('fs').existsSync('/usr/local/mesh_daemons/' + name + '/pid')) { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write('cat /usr/local/mesh_daemons/' + name + '/pid \nexit\n'); child.waitExit(); var pid = child.stdout.str.trim(); child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write('ps -p ' + pid + ' -o pid h\nexit\n'); child.waitExit(); if(child.stdout.str.trim() == pid) { return (true); } else { try { require('fs').unlinkSync('/usr/local/mesh_daemons/' + name + '/pid'); } catch(x) { } return (false); } } else { return (false); } }; return (ret); } else { throw ('MeshDaemon (' + name + ') NOT FOUND'); } break; } }; } this.enumerateService = function (options) { var results = []; var paths = []; var runtable = {}; switch(process.platform) { case 'linux': switch((options && options.platformType)?options.platformType : this.getServiceType()) { case 'init': paths.push('/etc/init.d'); break; case 'upstart': paths.push('/etc/init'); runtable = _upstart_GetServiceTable(); break; case 'systemd': paths.push('/lib/systemd/system'); paths.push('/usr/lib/systemd/system'); runtable = _systemd_GetServiceTable(); break; default: paths.push('/usr/local/mesh_daemons'); break; } break; case 'freebsd': paths.push('/etc/rc.d'); paths.push('/usr/local/etc/rc.d'); break; case 'darwin': paths.push('/Library/LaunchDaemons'); paths.push('/System/Library/LaunchDaemons'); break; } for(var i in paths) { var files = require('fs').readdirSync(paths[i]); for(var j in files) { switch(process.platform) { case 'linux': switch ((options && options.platformType) ? options.platformType : this.getServiceType()) { case 'init': try { results.push(this.getService(files[j], 'init')); } catch (e) { } break; case 'upstart': if (files[j].endsWith('.conf')) { try { results.push(this.getService(files[j].split('.conf')[0], 'upstart')); if(runtable[results.peek().name]) { results.peek().state = runtable[results.peek().name].state; if(runtable[results.peek().name].pid != '') { try { results.peek().pid = parseInt(runtable[results.peek().name].pid); } catch(px) { } } } } catch (e) { } } break; case 'systemd': if (files[j].endsWith('.service')) { try { results.push(this.getService(files[j].split('.service')[0], 'systemd')); if (runtable[results.peek().conf.split('/').pop()]) { results.peek().state = 'RUNNING'; } } catch(e) { } } break; default: if (files[j].endsWith('.service')) { try { results.push(this.getService(files[j].split('.service')[0], 'unknown')); } catch (e) { } } break; } break; case 'freebsd': try { results.push(this.getService(files[j])); } catch (e) { } break; case 'darwin': if (files[j].endsWith('.plist')) { try { results.push(fetchPlist(paths[i], files[j].split('.plist')[0])); } catch (e) { } } break; } } } for (var k in results) { if (results[k].description) { results[k].description = results[k].description(); } } return (results); }; } this.installService = function installService(options) { if (!options.target) { options.target = options.name; } if (!options.displayName) { options.displayName = options.name; } if (options.installPath) { if (!options.installPath.endsWith(process.platform == 'win32' ? '\\' : '/')) { options.installPath += (process.platform == 'win32' ? '\\' : '/'); } } if (options.installPath && options.installInPlace) { throw ('Cannot specify both installPath and installInPlace'); } if (process.platform != 'win32' && (options.installInPlace || options.installPath)) { throw ('Installation into non standard location is not supported on this platform'); } if (process.platform == 'win32') { var reg = require('win-registry'); if (!this.isAdmin()) { throw ('Installing as Service, requires admin'); } // Before we start, we need to copy the binary to the right place var folder; if(!options.installPath) { options.installPath = this.getProgramFolder(); switch(options.companyName) { case null: options.installPath += '\\mesh'; break; case '': break; default: options.installPath += ('\\' + options.companyName); break; } } folder = options.installPath; if (folder.endsWith('\\')) { folder = folder.substring(0, folder.length - 1); } if (!options.installInPlace) { prepareFolders(folder + '\\' + options.name); } if (options.servicePath == process.execPath) { options._isMeshAgent = true; } if (!options.installInPlace) { if (options.servicePath != folder + '\\' + options.name + '\\' + options.target + '.exe') { require('fs').copyFileSync(options.servicePath, folder + '\\' + options.name + '\\' + options.target + '.exe'); } options.servicePath = folder + '\\' + options.name + '\\' + options.target + '.exe'; if (!options.installPath) { options.installPath = folder + '\\' + options.name + '\\'; } } else { options.servicePath = process.execPath; options.installPath = process.execPath.split('\\'); options.installPath.pop(); options.installPath = options.installPath.join('\\') + '\\'; } var servicePath = this.GM.CreateVariable('"' + options.servicePath + '"', { wide: true }); var handle = this.proxy.OpenSCManagerA(0x00, 0x00, 0x0002); if (handle.Val == 0) { throw ('error opening SCManager'); } var serviceName = this.GM.CreateVariable(options.name, { wide: true }); var displayName = this.GM.CreateVariable(options.displayName, { wide: true}); var allAccess = 0x000F01FF; var serviceType; switch (options.startType) { case 'AUTO_START': serviceType = 0x02; // Automatic break; case 'DEMAND_START': default: serviceType = 0x03; // Manual break; case 'DISABLED': serviceType = 0x04; // Disabled break; } var h = this.proxy.CreateServiceW(handle, serviceName, displayName, allAccess, 0x10 | 0x100, serviceType, 0, servicePath, 0, 0, 0, 0, 0); if (h.Val == 0) { this.proxy.CloseServiceHandle(handle); throw ('Error Creating Service: ' + this.proxy2.GetLastError().Val); } if (options.description) { var dsc = this.GM.CreateVariable(options.description, { wide: true }); var serviceDescription = this.GM.CreateVariable(this.GM.PointerSize); dsc.pointerBuffer().copy(serviceDescription.Deref(0, this.GM.PointerSize).toBuffer()); if (this.proxy.ChangeServiceConfig2W(h, 1, serviceDescription).Val == 0) { console.log('unable to set description...'); } } if (options.failureRestart == null || options.failureRestart > 0) { var delay = options.failureRestart == null ? 5000 : options.failureRestart; // Delay in milliseconds var actions = this.GM.CreateVariable(3 * 8); // 3*sizeof(SC_ACTION) actions.Deref(0, 4).toBuffer().writeUInt32LE(1); // SC_ACTION[0].type actions.Deref(4, 4).toBuffer().writeUInt32LE(delay); // SC_ACTION[0].delay actions.Deref(8, 4).toBuffer().writeUInt32LE(1); // SC_ACTION[1].type actions.Deref(12, 4).toBuffer().writeUInt32LE(delay); // SC_ACTION[1].delay actions.Deref(16, 4).toBuffer().writeUInt32LE(1); // SC_ACTION[2].type actions.Deref(20, 4).toBuffer().writeUInt32LE(delay); // SC_ACTION[2].delay var failureActions = this.GM.CreateVariable(40); // sizeof(SERVICE_FAILURE_ACTIONS) failureActions.Deref(0, 4).toBuffer().writeUInt32LE(7200); // dwResetPeriod: 2 Hours failureActions.Deref(this.GM.PointerSize == 8 ? 24 : 12, 4).toBuffer().writeUInt32LE(3);// cActions: 3 actions.pointerBuffer().copy(failureActions.Deref(this.GM.PointerSize == 8 ? 32 : 16, this.GM.PointerSize).toBuffer()); if (this.proxy.ChangeServiceConfig2W(h, 2, failureActions).Val == 0) { console.log('Unable to set FailureActions...'); } } this.proxy.CloseServiceHandle(h); this.proxy.CloseServiceHandle(handle); if (options.files) { for(var i in options.files) { if (options.files[i]._buffer) { console.log('writing ' + extractFileName(options.files[i])); require('fs').writeFileSync(options.installPath + extractFileName(options.files[i]), options.files[i]._buffer); } else { console.log('copying ' + extractFileSource(options.files[i])); require('fs').copyFileSync(extractFileSource(options.files[i]), options.installPath + extractFileName(options.files[i])); } } } if (options.parameters) { var imagePath = reg.QueryKey(reg.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\' + options.name, 'ImagePath'); imagePath += (' ' + options.parameters.join(' ')); reg.WriteKey(reg.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\' + options.name, 'ImagePath', imagePath); } try { reg.WriteKey(reg.HKEY.LocalMachine, 'SYSTEM\\CurrentControlSet\\Services\\' + options.name, '_InstalledBy', reg.usernameToUserKey(require('user-sessions').getProcessOwnerName(process.pid).name)); } catch (xx) { } if (options._isMeshAgent) { // // For now, we'll only provide an uninstaller if the binary is the mesh agent binary, so we // won't need to copy the binary to run the uninstall script // var script = Buffer.from("try{require('service-manager').manager.uninstallService('" + options.name + "');}catch(x){}process.exit();").toString('base64'); try { reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'DisplayName', options.displayName); reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'DisplayIcon', options.servicePath); if (options.publisher) { reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'Publisher', options.publisher); } reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'InstallLocation', options.installPath); reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'EstimatedSize', Math.floor(require('fs').statSync(options.servicePath).size / 1024)); reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'NoModify', 0x1); reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'NoRepair', 0x1); if (options.name == 'Mesh Agent') { reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'UninstallString', options.servicePath + ' -funinstall'); } else { reg.WriteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + options.name, 'UninstallString', options.servicePath + ' -b64exec ' + script); } } catch (xx) { } } } if (process.platform == 'freebsd') { if (!this.isAdmin()) { console.log('Installing a Service requires root'); throw ('Installing as Service, requires root'); } var parameters = options.parameters ? options.parameters.join(' ') : ''; if (!require('fs').existsSync('/usr/local/mesh_services')) { require('fs').mkdirSync('/usr/local/mesh_services'); } if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); } if (options.servicePath != '/usr/local/mesh_services/' + options.name + '/' + options.target) { require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.target); } var bm = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.target).mode; bm |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.target, bm); var rc = require('fs').createWriteStream('/usr/local/etc/rc.d/' + options.name, { flags: 'wb' }); rc.write('#!/bin/sh\n'); rc.write('# PROVIDE: ' + options.name + '\n'); rc.write('# REQUIRE: FILESYSTEMS NETWORKING\n'); rc.write('# KEYWORD: shutdown\n'); rc.write('. /etc/rc.subr\n\n'); rc.write('name="' + options.name + '"\n'); rc.write('desc="' + (options.description ? options.description : 'MeshCentral Agent') + '"\n'); rc.write('rcvar=${name}_enable\n'); rc.write('pidfile="/var/run/' + options.name + '.pid"\n'); rc.write(options.name + '_chdir="/usr/local/mesh_services/' + options.name + '"\n'); rc.write('command="/usr/sbin/daemon"\n'); rc.write('command_args="-P ${pidfile} ' + ((options.failureRestart == null || options.failureRestart > 0)?'-r':'') + ' -f /usr/local/mesh_services/' + options.name + '/' + options.target + ' ' + parameters + '"\n'); rc.write('\n'); rc.write('load_rc_config $name\n'); rc.write(': ${' + options.name + '_enable="' + ((options.startType == 'AUTO_START' || options.startType == 'BOOT_START')?'YES':'NO') + '"}\n'); rc.write('run_rc_command "$1"\n'); rc.end(); var m = require('fs').statSync('/usr/local/etc/rc.d/' + options.name).mode; m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/usr/local/etc/rc.d/' + options.name, m); } if(process.platform == 'linux') { if (!this.isAdmin()) { console.log('Installing a Service requires root'); throw ('Installing as Service, requires root'); } var parameters = options.parameters ? options.parameters.join(' ') : ''; var conf; if (!options.servicePlatform) { options.servicePlatform = this.getServiceType(); } switch (options.servicePlatform) { case 'init': if (!require('fs').existsSync('/usr/local/mesh_services/')) { require('fs').mkdirSync('/usr/local/mesh_services'); } if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); } if (options.servicePath != '/usr/local/mesh_services/' + options.name + '/' + options.target) { require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.target); } console.log('copying ' + options.servicePath); var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.target).mode; m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.target, m); if (options.failureRestart == null || options.failureRestart > 0) { // Crash Restart is enabled, but it isn't inherently supported by INIT, so we must fake it with JS var tmp_parameters = parameters.split('"').join('\\"'); parameters = "-exec \\\"var child; process.on('SIGTERM', function () { child.removeAllListeners('exit'); child.kill(); process.exit(); }); function start() { child = require('child_process').execFile(process.execPath, [process.argv0, \\\"" + tmp_parameters + "\\\"]); child.stdout.on('data', function (c) { }); child.stderr.on('data', function (c) { }); child.on('exit', function (status) { start(); }); } start();\\\""; } // The following is the init.d script I wrote. Rather than having to deal with escaping the thing, I just Base64 encoded it to prevent issues. conf = require('fs').createWriteStream('/etc/init.d/' + options.name, { flags: 'wb' }); conf.write(Buffer.from('IyEvYmluL3NoCgoKU0NSSVBUPS91c3IvbG9jYWwvbWVzaF9zZXJ2aWNlcy9YWFhYWC9ZWVlZWQpSVU5BUz1yb290CgpQSURGSUxFPS92YXIvcnVuL1hYWFhYLnBpZApMT0dGSUxFPS92YXIvbG9nL1hYWFhYLmxvZwoKc3RhcnQoKSB7CiAgaWYgWyAtZiAiJFBJREZJTEUiIF0gJiYga2lsbCAtMCAkKGNhdCAiJFBJREZJTEUiKSAyPi9kZXYvbnVsbDsgdGhlbgogICAgZWNobyAnU2VydmljZSBhbHJlYWR5IHJ1bm5pbmcnID4mMgogICAgcmV0dXJuIDEKICBmaQogIGVjaG8gJ1N0YXJ0aW5nIHNlcnZpY2XigKYnID4mMgogIGxvY2FsIENNRD0iJFNDUklQVCB7e1BBUk1TfX0gJj4gXCIkTE9HRklMRVwiICYgZWNobyBcJCEiCiAgbG9jYWwgQ01EUEFUSD0kKGVjaG8gJFNDUklQVCB8IGF3ayAneyBsZW49c3BsaXQoJDAsIGEsICIvIik7IHByaW50IHN1YnN0cigkMCwgMCwgbGVuZ3RoKCQwKS1sZW5ndGgoYVtsZW5dKSk7IH0nKQogIGNkICRDTURQQVRICiAgc3UgLWMgIiRDTUQiICRSVU5BUyA+ICIkUElERklMRSIKICBlY2hvICdTZXJ2aWNlIHN0YXJ0ZWQnID4mMgp9CgpzdG9wKCkgewogIGlmIFsgISAtZiAiJFBJREZJTEUiIF07IHRoZW4KICAgIGVjaG8gJ1NlcnZpY2Ugbm90IHJ1bm5pbmcnID4mMgogICAgcmV0dXJuIDEKICBlbHNlCglwaWQ9JCggY2F0ICIkUElERklMRSIgKQoJaWYga2lsbCAtMCAkcGlkIDI+L2Rldi9udWxsOyB0aGVuCiAgICAgIGVjaG8gJ1N0b3BwaW5nIHNlcnZpY2XigKYnID4mMgogICAgICBraWxsIC0xNSAkcGlkCiAgICAgIGVjaG8gJ1NlcnZpY2Ugc3RvcHBlZCcgPiYyCgllbHNlCgkgIGVjaG8gJ1NlcnZpY2Ugbm90IHJ1bm5pbmcnCglmaQoJcm0gLWYgJCJQSURGSUxFIgogIGZpCn0KcmVzdGFydCgpewoJc3RvcAoJc3RhcnQKfQpzdGF0dXMoKXsKCWlmIFsgLWYgIiRQSURGSUxFIiBdCgl0aGVuCgkJcGlkPSQoIGNhdCAiJFBJREZJTEUiICkKCQlpZiBraWxsIC0wICRwaWQgMj4vZGV2L251bGw7IHRoZW4KCQkJZWNobyAiWFhYWFggc3RhcnQvcnVubmluZywgcHJvY2VzcyAkcGlkIgoJCWVsc2UKCQkJZWNobyAnWFhYWFggc3RvcC93YWl0aW5nJwoJCWZpCgllbHNlCgkJZWNobyAnWFhYWFggc3RvcC93YWl0aW5nJwoJZmkKCn0KCgpjYXNlICIkMSIgaW4KCXN0YXJ0KQoJCXN0YXJ0CgkJOzsKCXN0b3ApCgkJc3RvcAoJCTs7CglyZXN0YXJ0KQoJCXN0b3AKCQlzdGFydAoJCTs7CglzdGF0dXMpCgkJc3RhdHVzCgkJOzsKCSopCgkJZWNobyAiVXNhZ2U6IHNlcnZpY2UgWFhYWFgge3N0YXJ0fHN0b3B8cmVzdGFydHxzdGF0dXN9IgoJCTs7CmVzYWMKZXhpdCAwCgo=', 'base64').toString().split('XXXXX').join(options.name).split('YYYYY').join(options.target).replace('{{PARMS}}', parameters)); conf.end(); m = require('fs').statSync('/etc/init.d/' + options.name).mode; m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/etc/init.d/' + options.name, m); switch (options.startType) { case 'BOOT_START': case 'SYSTEM_START': case 'AUTO_START': var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.on('data', function (chunk) { }); child.stdin.write('update-rc.d ' + options.name + ' defaults\nexit\n'); child.waitExit(); break; default: break; } break; case 'upstart': if (!require('fs').existsSync('/usr/local/mesh_services/')) { require('fs').mkdirSync('/usr/local/mesh_services'); } if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); } if (options.servicePath != '/usr/local/mesh_services/' + options.name + '/' + options.target) { require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.target); } console.log('copying ' + options.servicePath); var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.target).mode; m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.target, m); conf = require('fs').createWriteStream('/etc/init/' + options.name + '.conf', { flags: 'wb' }); switch (options.startType) { case 'BOOT_START': case 'SYSTEM_START': case 'AUTO_START': conf.write('start on runlevel [2345]\n'); break; default: break; } conf.write('stop on runlevel [016]\n\n'); if (options.failureRestart == null || options.failureRestart > 0) { conf.write('respawn\n\n'); } conf.write('chdir /usr/local/mesh_services/' + options.name + '\n'); conf.write('exec /usr/local/mesh_services/' + options.name + '/' + options.target + ' ' + parameters + '\n\n'); conf.end(); break; case 'systemd': var serviceDescription = options.description ? options.description : 'MeshCentral Agent'; if (!require('fs').existsSync('/usr/local/mesh_services/')) { require('fs').mkdirSync('/usr/local/mesh_services'); } if (!require('fs').existsSync('/usr/local/mesh_services/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_services/' + options.name); } if (options.servicePath != '/usr/local/mesh_services/' + options.name + '/' + options.target) { console.log('copying ' + options.servicePath); require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.target); } var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.target).mode; m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.target, m); if (require('fs').existsSync('/lib/systemd/system')) { conf = require('fs').createWriteStream('/lib/systemd/system/' + options.name + '.service', { flags: 'wb' }); } else if (require('fs').existsSync('/usr/lib/systemd/system')) { conf = require('fs').createWriteStream('/usr/lib/systemd/system/' + options.name + '.service', { flags: 'wb' }); } else { throw ('unknown location for systemd configuration files'); } conf.write('[Unit]\nDescription=' + serviceDescription + '\n'); conf.write('[Service]\n'); conf.write('WorkingDirectory=/usr/local/mesh_services/' + options.name + '\n'); conf.write('ExecStart=/usr/local/mesh_services/' + options.name + '/' + options.target + ' ' + parameters + '\n'); conf.write('StandardOutput=null\n'); if (options.failureRestart == null || options.failureRestart > 0) { conf.write('Restart=on-failure\n'); if (options.failureRestart == null) { conf.write('RestartSec=3\n'); } else { conf.write('RestartSec=' + (options.failureRestart / 1000) + '\n'); } } switch (options.startType) { case 'BOOT_START': case 'SYSTEM_START': case 'AUTO_START': conf.write('[Install]\n'); conf.write('WantedBy=multi-user.target\n'); conf.write('Alias=' + options.name + '.service\n'); conf.end(); this._update = require('child_process').execFile('/bin/sh', ['sh']); this._update._moduleName = options.name; this._update.stdout.on('data', function (chunk) { }); this._update.stdin.write('systemctl enable ' + options.name + '.service\n'); this._update.stdin.write('exit\n'); this._update.waitExit(); default: conf.end(); break; } break; default: // Unknown Service Type, install as a Pseudo Service (MeshDaemon) if (!require('fs').existsSync('/usr/local/mesh_daemons/')) { require('fs').mkdirSync('/usr/local/mesh_daemons'); } if (!require('fs').existsSync('/usr/local/mesh_daemons/' + options.name)) { require('fs').mkdirSync('/usr/local/mesh_daemons/' + options.name); } if (!require('fs').existsSync('/usr/local/mesh_daemons/daemon')) { var exeGuid = 'B996015880544A19B7F7E9BE44914C18'; var daemonJS = Buffer.from('LyoKQ29weXJpZ2h0IDIwMTkgSW50ZWwgQ29ycG9yYXRpb24KCkxpY2Vuc2VkIHVuZGVyIHRoZSBBcGFjaGUgTGljZW5zZSwgVmVyc2lvbiAyLjAgKHRoZSAiTGljZW5zZSIpOwp5b3UgbWF5IG5vdCB1c2UgdGhpcyBmaWxlIGV4Y2VwdCBpbiBjb21wbGlhbmNlIHdpdGggdGhlIExpY2Vuc2UuCllvdSBtYXkgb2J0YWluIGEgY29weSBvZiB0aGUgTGljZW5zZSBhdAoKICAgIGh0dHA6Ly93d3cuYXBhY2hlLm9yZy9saWNlbnNlcy9MSUNFTlNFLTIuMAoKVW5sZXNzIHJlcXVpcmVkIGJ5IGFwcGxpY2FibGUgbGF3IG9yIGFncmVlZCB0byBpbiB3cml0aW5nLCBzb2Z0d2FyZQpkaXN0cmlidXRlZCB1bmRlciB0aGUgTGljZW5zZSBpcyBkaXN0cmlidXRlZCBvbiBhbiAiQVMgSVMiIEJBU0lTLApXSVRIT1VUIFdBUlJBTlRJRVMgT1IgQ09ORElUSU9OUyBPRiBBTlkgS0lORCwgZWl0aGVyIGV4cHJlc3Mgb3IgaW1wbGllZC4KU2VlIHRoZSBMaWNlbnNlIGZvciB0aGUgc3BlY2lmaWMgbGFuZ3VhZ2UgZ292ZXJuaW5nIHBlcm1pc3Npb25zIGFuZApsaW1pdGF0aW9ucyB1bmRlciB0aGUgTGljZW5zZS4KKi8KCgppZiAocHJvY2Vzcy5hcmd2Lmxlbmd0aCA8IDMpCnsKICAgIGNvbnNvbGUubG9nKCd1c2FnZTogZGFlbW9uIFsgc3RhcnQgfCBzdG9wIHwgc3RhdHVzIF0gW3NlcnZpY2VdJyk7CiAgICBwcm9jZXNzLmV4aXQoKTsKfQoKdmFyIHMgPSBudWxsOwp0cnkKewogICAgcyA9IHJlcXVpcmUoJ3NlcnZpY2UtbWFuYWdlcicpLm1hbmFnZXIuZ2V0U2VydmljZShwcm9jZXNzLmFyZ3ZbMl0pOwp9CmNhdGNoKHgpCnsKICAgIGNvbnNvbGUubG9nKHgpOwogICAgcHJvY2Vzcy5leGl0KCk7Cn0KCnN3aXRjaChwcm9jZXNzLmFyZ3ZbMV0pCnsKICAgIGNhc2UgJ3N0YXJ0JzoKICAgICAgICBzLnN0YXJ0KCk7CiAgICAgICAgY29uc29sZS5sb2coJ1N0YXJ0aW5nLi4uJyk7CiAgICAgICAgYnJlYWs7CiAgICBjYXNlICdzdG9wJzoKICAgICAgICBzLnN0b3AoKTsKICAgICAgICBjb25zb2xlLmxvZygnU3RvcHBpbmcuLi4nKTsKICAgICAgICBicmVhazsKICAgIGNhc2UgJ3N0YXR1cyc6CiAgICAgICAgaWYgKHMuaXNSdW5uaW5nKCkpCiAgICAgICAgewogICAgICAgICAgICBjb25zb2xlLmxvZygnUnVubmluZywgUElEID0gJyArIHJlcXVpcmUoJ2ZzJykucmVhZEZpbGVTeW5jKCcvdXNyL2xvY2FsL21lc2hfZGFlbW9ucy8nICsgcHJvY2Vzcy5hcmd2WzJdICsgJy9waWQnKS50b1N0cmluZygpKTsKICAgICAgICB9CiAgICAgICAgZWxzZQogICAgICAgIHsKICAgICAgICAgICAgY29uc29sZS5sb2coJ05vdCBydW5uaW5nJyk7CiAgICAgICAgfQogICAgICAgIGJyZWFrOwogICAgZGVmYXVsdDoKICAgICAgICBjb25zb2xlLmxvZygnVW5rbm93biBjb21tYW5kOiAnICsgcHJvY2Vzcy5hcmd2WzFdKTsKICAgICAgICBicmVhazsKfQoKcHJvY2Vzcy5leGl0KCk7Cg==', 'base64'); var exe = require('fs').readFileSync(process.execPath); var padding = Buffer.alloc(8 - ((exe.length + daemonJS.length + 16 + 4) % 8)); var w = require('fs').createWriteStream('/usr/local/mesh_daemons/daemon', { flags: "wb" }); var daemonJSLen = Buffer.alloc(4); daemonJSLen.writeUInt32BE(daemonJS.length); w.write(exe); if (padding.length > 0) { w.write(padding); } w.write(daemonJS); w.write(daemonJSLen); w.write(Buffer.from(exeGuid, 'hex')); w.end(); require('fs').chmodSync('/usr/local/mesh_daemons/daemon', require('fs').statSync('/usr/local/mesh_daemons/daemon').mode | require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP); } if (options.servicePath != '/usr/local/mesh_daemons/' + options.name + '/' + options.target) { require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_daemons/' + options.name + '/' + options.target); } var m = require('fs').statSync('/usr/local/mesh_daemons/' + options.name + '/' + options.target).mode; m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/usr/local/mesh_daemons/' + options.name + '/' + options.target, m); conf = require('fs').createWriteStream('/usr/local/mesh_daemons/' + options.name + '.service', { flags: 'wb' }); conf.write('workingDirectory=' + '/usr/local/mesh_daemons/' + options.name + '\n'); if(!options.parameters) {options.parameters = [];} options.parameters.unshift(options.name); conf.write('parameters=' + JSON.stringify(options.parameters) + '\n'); options.parameters.shift(); if (options.failureRestart == null || options.failureRestart > 0) { conf.write('respawn\n'); } conf.end(); 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.target + '\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 += ' KeepAlive\n'; if(options.failureRestart == null || options.failureRestart > 0) { plist += ' \n'; plist += ' Crashed\n'; plist += ' \n'; plist += ' \n'; } else { plist += ' \n'; } if(options.failureRestart != null) { plist += ' ThrottleInterval\n'; plist += ' ' + (options.failureRestart / 1000) + '\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.target, options.binary); } else { if (options.servicePath != '/usr/local/mesh_services/' + options.name + '/' + options.target) { require('fs').copyFileSync(options.servicePath, '/usr/local/mesh_services/' + options.name + '/' + options.target); } } require('fs').writeFileSync('/Library/LaunchDaemons/' + options.name + '.plist', plist); var m = require('fs').statSync('/usr/local/mesh_services/' + options.name + '/' + options.target).mode; m |= (require('fs').CHMOD_MODES.S_IXUSR | require('fs').CHMOD_MODES.S_IXGRP | require('fs').CHMOD_MODES.S_IXOTH); require('fs').chmodSync('/usr/local/mesh_services/' + options.name + '/' + options.target, m); } else { throw ('Service: ' + options.name + ' already exists'); } } if (options.files) { for (var i in options.files) { if (options.files[i]._buffer) { console.log('writing ' + extractFileName(options.files[i])); if (options.servicePlatform == 'unknown') { require('fs').writeFileSync('/usr/local/mesh_daemons/' + options.name + '/' + extractFileName(options.files[i]), options.files[i]._buffer); } else { require('fs').writeFileSync('/usr/local/mesh_services/' + options.name + '/' + extractFileName(options.files[i]), options.files[i]._buffer); } } else { console.log('copying ' + extractFileSource(options.files[i])); if (options.servicePlatform == 'unknown') { require('fs').copyFileSync(extractFileSource(options.files[i]), '/usr/local/mesh_daemons/' + options.name + '/' + extractFileName(options.files[i])); } else { require('fs').copyFileSync(extractFileSource(options.files[i]), '/usr/local/mesh_services/' + options.name + '/' + extractFileName(options.files[i])); } } } } } if (process.platform == 'darwin') { this.installLaunchAgent = function installLaunchAgent(options) { if (!(options.uid || options.user) && !this.isAdmin()) { throw ('Installing a Global Agent/Daemon, requires admin'); } var servicePathTokens = options.servicePath.split('/'); servicePathTokens.pop(); if (servicePathTokens.peek() == '.') { servicePathTokens.pop(); } options.workingDirectory = servicePathTokens.join('/'); var autoStart = (options.startType == 'AUTO_START' ? '' : ''); var stdoutpath = (options.stdout ? ('StandardOutPath\n' + options.stdout + '') : ''); var params = ' ProgramArguments\n'; params += ' \n'; params += (' ' + options.servicePath + '\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 += (' ' + options.workingDirectory + '\n'); plist += (stdoutpath + '\n'); plist += ' RunAtLoad\n'; plist += (autoStart + '\n'); if (options.sessionTypes && options.sessionTypes.length > 0) { plist += ' LimitLoadToSessionType\n'; plist += ' \n'; for (var stype in options.sessionTypes) { plist += (' ' + options.sessionTypes[stype] + '\n'); } plist += ' \n'; } plist += ' KeepAlive\n'; if (options.failureRestart == null || options.failureRestart > 0) { plist += ' \n'; plist += ' Crashed\n'; plist += ' \n'; plist += ' \n'; } else { plist += ' \n'; } if (options.failureRestart != null) { plist += ' ThrottleInterval\n'; plist += ' ' + (options.failureRestart / 1000) + '\n'; } plist += ' \n'; plist += ''; if (options.uid) { options.user = require('user-sessions').getUsername(options.uid); } var folder = options.user ? (require('user-sessions').getHomeFolder(options.user) + '/Library/LaunchAgents/') : '/Library/LaunchAgents/'; options.gid = require('user-sessions').getGroupID(options.uid); if (!require('fs').existsSync(folder)) { require('fs').mkdirSync(folder); require('fs').chownSync(folder, options.uid, options.gid); } require('fs').writeFileSync(folder + options.name + '.plist', plist); if(options.user) { require('fs').chownSync(folder + options.name + '.plist', options.uid, options.gid); } }; } this.uninstallService = function uninstallService(name, options) { if (!this.isAdmin()) { throw ('Uninstalling a service, requires admin'); } if (typeof (name) == 'object') { name = name.name; } var service = this.getService(name); var servicePath = service.appLocation(); if (process.platform == 'win32') { if (!options || !options.skipDeleteBinary) { try { require('fs').unlinkSync(servicePath); } catch (e) { var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/C CHOICE /C Y /N /D Y /T 10 & del "' + servicePath + '"'], { type: 4 }); } } if (this.proxy.DeleteService(service._service) == 0) { throw ('Uninstall Service for: ' + name + ', failed with error: ' + this.proxy2.GetLastError()); } service.close(); service = null; try { var reg = require('win-registry'); reg.DeleteKey(reg.HKEY.LocalMachine, 'SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\' + name); } catch(ee) { } } else if(process.platform == 'linux') { switch (this.getServiceType()) { case 'init': case 'upstart': if (require('fs').existsSync('/etc/init.d/' + name)) { // init.d service this._update = require('child_process').execFile('/bin/sh', ['sh']); this._update.stdout.on('data', function (chunk) { }); this._update.stdin.write('service ' + name + ' stop\n'); this._update.stdin.write('update-rc.d -f ' + name + ' remove\n'); this._update.stdin.write('exit\n'); this._update.waitExit(); try { require('fs').unlinkSync('/etc/init.d/' + name); if (!options || !options.skipDeleteBinary) { require('fs').unlinkSync(servicePath); } console.log(name + ' uninstalled'); } catch (e) { console.log(name + ' could not be uninstalled', e) } } if (require('fs').existsSync('/etc/init/' + name + '.conf')) { // upstart service this._update = require('child_process').execFile('/bin/sh', ['sh']); this._update.stdout.on('data', function (chunk) { }); this._update.stdin.write('service ' + name + ' stop\n'); this._update.stdin.write('exit\n'); this._update.waitExit(); try { require('fs').unlinkSync('/etc/init/' + name + '.conf'); if (!options || !options.skipDeleteBinary) { require('fs').unlinkSync(servicePath); } console.log(name + ' uninstalled'); } catch (e) { console.log(name + ' could not be uninstalled', e) } } break; case 'systemd': this._update = require('child_process').execFile('/bin/sh', ['sh'], { type: require('child_process').SpawnTypes.TERM }); this._update.stdout.on('data', function (chunk) { }); this._update.stdin.write('systemctl stop ' + name + '.service\n'); this._update.stdin.write('systemctl disable ' + name + '.service\n'); this._update.stdin.write('exit\n'); this._update.waitExit(); try { if (!options || !options.skipDeleteBinary) { require('fs').unlinkSync(servicePath); } if (require('fs').existsSync('/lib/systemd/system/' + name + '.service')) { require('fs').unlinkSync('/lib/systemd/system/' + name + '.service'); } if (require('fs').existsSync('/usr/lib/systemd/system/' + name + '.service')) { require('fs').unlinkSync('/usr/lib/systemd/system/' + name + '.service'); } console.log(name + ' uninstalled'); } catch (e) { console.log(name + ' could not be uninstalled', e) } break; default: // unknown platform service type if (service.isRunning()) { service.stop(); } if (!options || !options.skipDeleteBinary) { try { require('fs').unlinkSync(servicePath); } catch (x) { } } try { require('fs').unlinkSync(service.conf); } catch(x) { } console.log(name + ' uninstalled'); break; } } else if(process.platform == 'darwin') { service.unload(); try { require('fs').unlinkSync(service.plist); if (!options || !options.skipDeleteBinary) { require('fs').unlinkSync(servicePath); } } catch (e) { throw ('Error uninstalling service: ' + name + ' => ' + e); } try { require('fs').rmdirSync('/usr/local/mesh_services/' + name); } catch (e) { } } else if(process.platform == 'freebsd') { service.stop(); if (!options || !options.skipDeleteBinary) { require('fs').unlinkSync(service.appLocation()); } require('fs').unlinkSync(service.rc); try { require('fs').rmdirSync('/usr/local/mesh_services/' + name); } catch (e) { } } } this.getServiceType = function getServiceType() { var platform = 'unknown'; switch(process.platform) { case 'win32': platform = 'windows'; break; case 'freebsd': platform = 'freebsd'; break; case 'darwin': platform = 'launchd'; break; case 'linux': platform = require('process-manager').getProcessInfo(1).Name; if (platform == "busybox") { var child = require('child_process').execFile('/bin/sh', ['sh']); child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); }); child.stdin.write("ps -ax -o pid -o command | awk '{ if($1==\"1\") { $1=\"\"; split($0, res, \" \"); print res[2]; }}'\nexit\n"); child.waitExit(); platform = child.stdout.str.trim(); } if (platform == 'init') { if (require('fs').existsSync('/etc/init')) { platform = 'upstart'; } } switch (platform) { case 'init': case 'upstart': case 'systemd': break; default: platform = 'unknown'; break; } break; } return (platform); }; this.daemon = function daemon(path, parameters, options) { var tmp = JSON.stringify(parameters); tmp = tmp.substring(1, tmp.length - 1); if (!options) { options = {}; } var childParms = "\ var child = null; \ var options = " + JSON.stringify(options) + ";\ if(options.logOutput)\ { console.setDestination(console.Destinations.LOGFILE); console.log('Logging Outputs...'); }\ else\ {\ console.setDestination(console.Destinations.DISABLED);\ }\ function cleanupAndExit()\ {\ if(options.pidPath) { try{require('fs').unlinkSync(options.pidPath);} catch(x){} }\ }\ function spawnChild()\ {\ child = require('child_process').execFile('" + path + "', ['" + (process.platform == 'win32' ? path.split('\\').pop() : path.split('/').pop() + "'" + (tmp != '' ? (", " + tmp) : "")) + "]);\ if(child)\ {\ child.stdout.on('data', function(c) { console.log(c.toString()); });\ child.stderr.on('data', function(c) { console.log(c.toString()); });\ child.once('exit', function (code) \ {\ if(options.crashRestart) { spawnChild(); } else { cleanupAndExit(); }\ });\ }\ }\ if(options.pidPath) { require('fs').writeFileSync(options.pidPath, process.pid.toString()); }\ spawnChild();\ process.on('SIGTERM', function()\ {\ if(child) { child.kill(); }\ cleanupAndExit();\ process.exit();\ });"; var parms = [process.platform == 'win32' ? process.execPath.split('\\').pop() : process.execPath.split('/').pop()]; parms.push('-b64exec'); parms.push(Buffer.from(childParms).toString('base64')); options._parms = parms; options.detached = true; options.type = 4; var child = require('child_process').execFile(process.execPath, options._parms, options); if (!child) { throw ('Error spawning process'); } } } module.exports = serviceManager; module.exports.manager = new serviceManager(); if (process.platform == 'darwin') { module.exports.getOSVersion = getOSVersion; }