mirror of
https://github.com/Ylianst/MeshAgent
synced 2025-12-06 00:13:33 +00:00
1016 lines
35 KiB
JavaScript
1016 lines
35 KiB
JavaScript
/*
|
|
Copyright 2020 Intel Corporation
|
|
@author Bryan Roe
|
|
|
|
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.
|
|
*/
|
|
|
|
|
|
//
|
|
// This is a helper utility that is used by the Mesh Agent to install itself
|
|
// as a background service, on all platforms that the agent supports.
|
|
//
|
|
|
|
try
|
|
{
|
|
// This peroperty is a polyfill for an Array, to fetch the specified element if it exists, removing the surrounding quotes if they are there
|
|
Object.defineProperty(Array.prototype, 'getParameterEx',
|
|
{
|
|
value: function (name, defaultValue)
|
|
{
|
|
var i, ret;
|
|
for (i = 0; i < this.length; ++i)
|
|
{
|
|
if (this[i].startsWith(name + '='))
|
|
{
|
|
ret = this[i].substring(name.length + 1);
|
|
if (ret.startsWith('"')) { ret = ret.substring(1, ret.length - 1); }
|
|
return (ret);
|
|
}
|
|
}
|
|
return (defaultValue);
|
|
}
|
|
});
|
|
|
|
// This property is a polyfill for an Array, to fetch the specified element if it exists
|
|
Object.defineProperty(Array.prototype, 'getParameter',
|
|
{
|
|
value: function (name, defaultValue)
|
|
{
|
|
return (this.getParameterEx('--' + name, defaultValue));
|
|
}
|
|
});
|
|
}
|
|
catch(x)
|
|
{ }
|
|
try
|
|
{
|
|
// This property is a polyfill for an Array, to fetch the index of the specified element, if it exists
|
|
Object.defineProperty(Array.prototype, 'getParameterIndex',
|
|
{
|
|
value: function (name)
|
|
{
|
|
var i;
|
|
for (i = 0; i < this.length; ++i)
|
|
{
|
|
if (this[i].startsWith('--' + name + '='))
|
|
{
|
|
return (i);
|
|
}
|
|
}
|
|
return (-1);
|
|
}
|
|
});
|
|
}
|
|
catch(x)
|
|
{ }
|
|
try
|
|
{
|
|
// This property is a polyfill for an Array, to remove the specified element, if it exists
|
|
Object.defineProperty(Array.prototype, 'deleteParameter',
|
|
{
|
|
value: function (name)
|
|
{
|
|
var i = this.getParameterIndex(name);
|
|
if(i>=0)
|
|
{
|
|
this.splice(i, 1);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch(x)
|
|
{ }
|
|
try
|
|
{
|
|
// This property is a polyfill for an Array, to to fetch the value YY of an element XX in the format --XX=YY, if it exists
|
|
Object.defineProperty(Array.prototype, 'getParameterValue',
|
|
{
|
|
value: function (i)
|
|
{
|
|
var ret = this[i].substring(this[i].indexOf('=')+1);
|
|
if (ret.startsWith('"')) { ret = ret.substring(1, ret.length - 1); }
|
|
return (ret);
|
|
}
|
|
});
|
|
}
|
|
catch(x)
|
|
{ }
|
|
|
|
// This function performs some checks on the parameter structure, to make sure the minimum set of requried elements are present
|
|
function checkParameters(parms)
|
|
{
|
|
var msh = _MSH();
|
|
if (parms.getParameter('description', null) == null && msh.description != null) { parms.push('--description="' + msh.description + '"'); }
|
|
if (parms.getParameter('displayName', null) == null && msh.displayName != null) { parms.push('--displayName="' + msh.displayName + '"'); }
|
|
if (parms.getParameter('companyName', null) == null && msh.companyName != null) { parms.push('--companyName="' + msh.companyName + '"'); }
|
|
|
|
if (msh.fileName != null)
|
|
{
|
|
// This converts the --fileName parameter of the installer, to the --target=XXX format required by service-manager.js
|
|
var i = parms.getParameterIndex('fileName');
|
|
if(i>=0)
|
|
{
|
|
parms.splice(i, 1);
|
|
}
|
|
parms.push('--target="' + msh.fileName + '"');
|
|
}
|
|
|
|
if (parms.getParameter('meshServiceName', null) == null)
|
|
{
|
|
if(msh.meshServiceName != null)
|
|
{
|
|
// This adds the specified service name, to be consumed by service-manager.js
|
|
parms.push('--meshServiceName="' + msh.meshServiceName + '"');
|
|
}
|
|
else
|
|
{
|
|
// Still no meshServiceName specified... Let's also check installed services...
|
|
var tmp = process.platform == 'win32' ? 'Mesh Agent' : 'meshagent';
|
|
try
|
|
{
|
|
tmp = require('_agentNodeId').serviceName();
|
|
}
|
|
catch(xx)
|
|
{
|
|
}
|
|
|
|
// The default is 'Mesh Agent' for Windows, and 'meshagent' for everything else...
|
|
if(tmp != (process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'))
|
|
{
|
|
parms.push('--meshServiceName="' + tmp + '"');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// This is the entry point for installing the service
|
|
function installService(params)
|
|
{
|
|
process.stdout.write('...Installing service');
|
|
console.info1('');
|
|
|
|
var target = null;
|
|
var targetx = params.getParameterIndex('target');
|
|
if (targetx >= 0)
|
|
{
|
|
// Let's remove any embedded spaces in 'target' as that can mess up some OSes
|
|
target = params.getParameterValue(targetx);
|
|
params.splice(targetx, 1);
|
|
target = target.split(' ').join('');
|
|
if (target.length == 0) { target = null; }
|
|
}
|
|
|
|
var proxyFile = process.execPath;
|
|
if (process.platform == 'win32')
|
|
{
|
|
proxyFile = proxyFile.split('.exe').join('.proxy');
|
|
try
|
|
{
|
|
// Add this parameter, so the agent instance will be embedded with the Windows User that installed the service
|
|
params.push('--installedByUser="' + require('win-registry').usernameToUserKey(require('user-sessions').getProcessOwnerName(process.pid).name) + '"');
|
|
}
|
|
catch(exc)
|
|
{
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// On Linux, the --installedByUser property is populated with the UID of the user that is installing the service
|
|
var u = require('user-sessions').tty();
|
|
var uid = 0;
|
|
try
|
|
{
|
|
uid = require('user-sessions').getUid(u);
|
|
}
|
|
catch(e)
|
|
{
|
|
}
|
|
params.push('--installedByUser=' + uid);
|
|
proxyFile += '.proxy';
|
|
}
|
|
|
|
|
|
// We're going to create the OPTIONS object to hand to service-manager.js. We're going to populate all the properties we can, using
|
|
// values that were passed into the installer, using default values for the ones that aren't specified.
|
|
var options =
|
|
{
|
|
name: params.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'),
|
|
target: target==null?(process.platform == 'win32' ? 'MeshAgent' : 'meshagent'):target,
|
|
servicePath: process.execPath,
|
|
startType: 'AUTO_START',
|
|
parameters: params,
|
|
_installer: true
|
|
};
|
|
options.displayName = params.getParameter('displayName', options.name); params.deleteParameter('displayName');
|
|
options.description = params.getParameter('description', options.name + ' background service'); params.deleteParameter('description');
|
|
|
|
if (process.platform == 'win32') { options.companyName = ''; }
|
|
if (global.gOptions != null)
|
|
{
|
|
if(Array.isArray(global.gOptions.files))
|
|
{
|
|
options.files = global.gOptions.files;
|
|
}
|
|
if(global.gOptions.binary != null)
|
|
{
|
|
options.servicePath = global.gOptions.binary;
|
|
}
|
|
}
|
|
|
|
// If a .proxy file was found, we'll include it in the list of files to be copied when installing the agent
|
|
if (require('fs').existsSync(proxyFile))
|
|
{
|
|
if (options.files == null) { options.files = []; }
|
|
options.files.push({ source: proxyFile, newName: options.target + '.proxy' });
|
|
}
|
|
|
|
// if '--copy-msh' is specified, we will try to copy the .msh configuration file found in the current working directory
|
|
var i;
|
|
if ((i = params.indexOf('--copy-msh="1"')) >= 0)
|
|
{
|
|
var mshFile = process.platform == 'win32' ? (process.execPath.split('.exe').join('.msh')) : (process.execPath + '.msh');
|
|
if (options.files == null) { options.files = []; }
|
|
var newtarget = (process.platform == 'linux' && require('service-manager').manager.getServiceType() == 'systemd') ? options.target.split("'").join('-') : options.target;
|
|
options.files.push({ source: mshFile, newName: newtarget + '.msh' });
|
|
options.parameters.splice(i, 1);
|
|
}
|
|
if ((i=params.indexOf('--_localService="1"'))>=0)
|
|
{
|
|
// install in place
|
|
options.parameters.splice(i, 1);
|
|
options.installInPlace = true;
|
|
}
|
|
|
|
// We're going to specify what folder the agent should be installed into
|
|
if (global._workingpath != null && global._workingpath != '' && global._workingpath != '/')
|
|
{
|
|
for (i = 0; i < options.parameters.length; ++i)
|
|
{
|
|
if (options.parameters[i].startsWith('--installPath='))
|
|
{
|
|
global._workingpath = null;
|
|
break;
|
|
}
|
|
}
|
|
if(global._workingpath != null)
|
|
{
|
|
options.parameters.push('--installPath="' + global._workingpath + '"');
|
|
}
|
|
}
|
|
if ((i = options.parameters.getParameterIndex('installPath')) >= 0)
|
|
{
|
|
options.installPath = options.parameters.getParameterValue(i);
|
|
options.installInPlace = false;
|
|
options.parameters.splice(i, 1);
|
|
}
|
|
|
|
// If companyName was specified, we're going to move it into the structure
|
|
if ((i = options.parameters.getParameterIndex('companyName')) >= 0)
|
|
{
|
|
options.companyName = options.parameters.getParameterValue(i);
|
|
options.parameters.splice(i, 1);
|
|
}
|
|
|
|
if (global.gOptions != null && global.gOptions.noParams === true) { options.parameters = []; }
|
|
|
|
try
|
|
{
|
|
// Let's actually install the service
|
|
require('service-manager').manager.installService(options);
|
|
process.stdout.write(' [DONE]\n');
|
|
if(process.platform == 'win32')
|
|
{
|
|
// On Windows, we're going to enable this service to be runnable from SafeModeWithNetworking
|
|
require('win-bcd').enableSafeModeService(options.name);
|
|
}
|
|
}
|
|
catch(sie)
|
|
{
|
|
process.stdout.write(' [ERROR] ' + sie);
|
|
process.exit();
|
|
}
|
|
var svc = require('service-manager').manager.getService(options.name);
|
|
|
|
// macOS needs a LaunchAgent to help with some usages that need to run from within the user session,
|
|
// so we can setup ourselves to accomplish that.
|
|
if (process.platform == 'darwin')
|
|
{
|
|
svc.load();
|
|
process.stdout.write(' -> setting up launch agent...');
|
|
try
|
|
{
|
|
require('service-manager').manager.installLaunchAgent(
|
|
{
|
|
name: options.name,
|
|
servicePath: svc.appLocation(),
|
|
startType: 'AUTO_START',
|
|
sessionTypes: ['LoginWindow'],
|
|
parameters: ['-kvm1']
|
|
});
|
|
process.stdout.write(' [DONE]\n');
|
|
}
|
|
catch (sie)
|
|
{
|
|
process.stdout.write(' [ERROR] ' + sie);
|
|
}
|
|
}
|
|
|
|
// For Windows, we're going to add an INBOUND UDP rule for WebRTC Data
|
|
if(process.platform == 'win32')
|
|
{
|
|
var loc = svc.appLocation();
|
|
process.stdout.write(' -> Writing firewall rules for ' + options.name + ' Service...');
|
|
|
|
var rule =
|
|
{
|
|
DisplayName: options.name + ' WebRTC Traffic',
|
|
direction: 'inbound',
|
|
Program: loc,
|
|
Protocol: 'UDP',
|
|
Profile: 'Public, Private, Domain',
|
|
Description: 'Mesh Central Agent WebRTC P2P Traffic',
|
|
EdgeTraversalPolicy: 'allow',
|
|
Enabled: true
|
|
};
|
|
require('win-firewall').addFirewallRule(rule);
|
|
process.stdout.write(' [DONE]\n');
|
|
}
|
|
|
|
// Let's try to start the service that we just installed
|
|
process.stdout.write(' -> Starting service...');
|
|
try
|
|
{
|
|
svc.start();
|
|
process.stdout.write(' [OK]\n');
|
|
}
|
|
catch(ee)
|
|
{
|
|
process.stdout.write(' [ERROR]\n');
|
|
}
|
|
|
|
// On Windows we should explicitly close the service manager when we are done, instead of relying on the Garbage Collection, so the service object isn't unnecessarily locked
|
|
if (process.platform == 'win32') { svc.close(); }
|
|
if (parseInt(params.getParameter('__skipExit', 0)) == 0)
|
|
{
|
|
process.exit();
|
|
}
|
|
}
|
|
|
|
// The last step in uninstalling a service
|
|
function uninstallService3(params)
|
|
{
|
|
// macOS has a LaunchAgent, that we need to uninstall
|
|
if (process.platform == 'darwin')
|
|
{
|
|
process.stdout.write(' -> Uninstalling launch agent...');
|
|
try
|
|
{
|
|
var launchagent = require('service-manager').manager.getLaunchAgent(params.getParameter('meshServiceName', 'meshagent'));
|
|
launchagent.unload();
|
|
require('fs').unlinkSync(launchagent.plist);
|
|
process.stdout.write(' [DONE]\n');
|
|
}
|
|
catch (e)
|
|
{
|
|
process.stdout.write(' [ERROR]\n');
|
|
}
|
|
}
|
|
|
|
if (params != null && !params.includes('_stop'))
|
|
{
|
|
// Since we are done uninstalling a previously installed service, we can continue with installation
|
|
installService(params);
|
|
}
|
|
else
|
|
{
|
|
// We are going to stop here, if we are only intending to uninstall the service
|
|
process.exit();
|
|
}
|
|
}
|
|
|
|
// Step 2 in service uninstallation
|
|
function uninstallService2(params, msh)
|
|
{
|
|
var secondaryagent = false;
|
|
var i;
|
|
var dataFolder = null;
|
|
var appPrefix = null;
|
|
var uninstallOptions = null;
|
|
var serviceName = params.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); // get the service name, using the provided defaults if not specified
|
|
|
|
// Remove the .msh file if present
|
|
try { require('fs').unlinkSync(msh); } catch (mshe) { }
|
|
if ((i = params.indexOf('__skipBinaryDelete')) >= 0)
|
|
{
|
|
// We will skip deleting of the actual binary, if this option was provided.
|
|
// This will happen if we try to install the service to a location where we are running the installer from.
|
|
params.splice(i, 1);
|
|
uninstallOptions = { skipDeleteBinary: true };
|
|
}
|
|
if (params && params.includes('--_deleteData="1"'))
|
|
{
|
|
// This will facilitate cleanup of the files associated with the agent
|
|
dataFolder = params.getParameterEx('_workingDir', null);
|
|
appPrefix = params.getParameterEx('_appPrefix', null);
|
|
}
|
|
|
|
process.stdout.write(' -> Uninstalling previous installation...');
|
|
try
|
|
{
|
|
// Let's actually try to uninstall the service
|
|
require('service-manager').manager.uninstallService(serviceName, uninstallOptions);
|
|
process.stdout.write(' [DONE]\n');
|
|
if (process.platform == 'win32')
|
|
{
|
|
// For Windows, we can remove the entry to enable this service to be runnable from SafeModeWithNetworking
|
|
require('win-bcd').disableSafeModeService(serviceName);
|
|
}
|
|
|
|
// Lets try to cleanup the uninstalled service
|
|
if (dataFolder && appPrefix)
|
|
{
|
|
process.stdout.write(' -> Deleting agent data...');
|
|
if (process.platform != 'win32')
|
|
{
|
|
// On Non-Windows platforms, we're going to cleanup using the shell
|
|
var levelUp = dataFolder.split('/');
|
|
levelUp.pop();
|
|
levelUp = levelUp.join('/');
|
|
|
|
console.info1(' Cleaning operation =>');
|
|
console.info1(' cd "' + dataFolder + '"');
|
|
console.info1(' rm "' + appPrefix + '.*"');
|
|
console.info1(' rm DAIPC');
|
|
console.info1(' cd /');
|
|
console.info1(' rmdir "' + dataFolder + '"');
|
|
console.info1(' rmdir "' + levelUp + '"');
|
|
|
|
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
|
child.stdout.on('data', function (c) { console.info1(c.toString()); });
|
|
child.stderr.on('data', function (c) { console.info1(c.toString()); });
|
|
child.stdin.write('cd "' + dataFolder + '"\n');
|
|
child.stdin.write('rm DAIPC\n');
|
|
|
|
child.stdin.write("ls | awk '");
|
|
child.stdin.write('{');
|
|
child.stdin.write(' if($0 ~ /^' + appPrefix + '\\./)');
|
|
child.stdin.write(' {');
|
|
child.stdin.write(' sh=sprintf("rm \\"%s\\"", $0);');
|
|
child.stdin.write(' system(sh);');
|
|
child.stdin.write(' }');
|
|
child.stdin.write("}'\n");
|
|
|
|
child.stdin.write('cd /\n');
|
|
child.stdin.write('rmdir "' + dataFolder + '"\n');
|
|
child.stdin.write('rmdir "' + levelUp + '"\n');
|
|
child.stdin.write('exit\n');
|
|
child.waitExit();
|
|
}
|
|
else
|
|
{
|
|
// On Windows, we're going to spawn a command shell to cleanup
|
|
var levelUp = dataFolder.split('\\');
|
|
levelUp.pop();
|
|
levelUp = levelUp.join('\\');
|
|
var child = require('child_process').execFile(process.env['windir'] + '\\system32\\cmd.exe', ['/C del "' + dataFolder + '\\' + appPrefix + '.*" && rmdir "' + dataFolder + '" && rmdir "' + levelUp + '"']);
|
|
child.stdout.on('data', function (c) { });
|
|
child.stderr.on('data', function (c) { });
|
|
child.waitExit();
|
|
}
|
|
|
|
process.stdout.write(' [DONE]\n');
|
|
}
|
|
}
|
|
catch (e)
|
|
{
|
|
process.stdout.write(' [ERROR]\n');
|
|
}
|
|
|
|
// Check for secondary agent
|
|
try
|
|
{
|
|
process.stdout.write(' -> Checking for secondary agent...');
|
|
var s = require('service-manager').manager.getService(serviceName + 'Diagnostic');
|
|
var loc = s.appLocation();
|
|
s.close();
|
|
process.stdout.write(' [FOUND]\n');
|
|
process.stdout.write(' -> Uninstalling secondary agent...');
|
|
secondaryagent = true;
|
|
try
|
|
{
|
|
require('service-manager').manager.uninstallService(serviceName + 'Diagnostic');
|
|
process.stdout.write(' [DONE]\n');
|
|
}
|
|
catch (e)
|
|
{
|
|
process.stdout.write(' [ERROR]\n');
|
|
}
|
|
}
|
|
catch (e)
|
|
{
|
|
process.stdout.write(' [NONE]\n');
|
|
}
|
|
|
|
if(secondaryagent)
|
|
{
|
|
// If a secondary agent was found, remove the CRON job for it
|
|
process.stdout.write(' -> removing secondary agent from task scheduler...');
|
|
var p = require('task-scheduler').delete(serviceName + 'Diagnostic/periodicStart');
|
|
p._params = params;
|
|
p.then(function ()
|
|
{
|
|
process.stdout.write(' [DONE]\n');
|
|
uninstallService3(this._params);
|
|
}, function ()
|
|
{
|
|
process.stdout.write(' [ERROR]\n');
|
|
uninstallService3(this._params);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
uninstallService3(params);
|
|
}
|
|
}
|
|
|
|
// First step in service uninstall
|
|
function uninstallService(params)
|
|
{
|
|
// Before we uninstall, we need to fetch the service from service-manager.js
|
|
var svc = require('service-manager').manager.getService(params.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'));
|
|
|
|
// We can calculate what the .msh file location is, based on the appLocation of the service
|
|
var msh = svc.appLocation();
|
|
if (process.platform == 'win32')
|
|
{
|
|
msh = msh.substring(0, msh.length - 4) + '.msh';
|
|
}
|
|
else
|
|
{
|
|
msh = msh + '.msh';
|
|
}
|
|
|
|
// Let's try to stop the service if we think it might be running
|
|
if (svc.isRunning == null || svc.isRunning())
|
|
{
|
|
process.stdout.write(' -> Stopping Service...');
|
|
if(process.platform=='win32')
|
|
{
|
|
svc.stop().then(function ()
|
|
{
|
|
process.stdout.write(' [STOPPED]\n');
|
|
svc.close();
|
|
uninstallService2(this._params, msh);
|
|
}, function ()
|
|
{
|
|
process.stdout.write(' [ERROR]\n');
|
|
svc.close();
|
|
uninstallService2(this._params, ms);
|
|
}).parentPromise._params = params;
|
|
}
|
|
else
|
|
{
|
|
if (process.platform == 'darwin')
|
|
{
|
|
// macOS requries us to unload the service
|
|
svc.unload();
|
|
}
|
|
else
|
|
{
|
|
svc.stop();
|
|
}
|
|
process.stdout.write(' [STOPPED]\n');
|
|
uninstallService2(params, msh);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (process.platform == 'win32') { svc.close(); }
|
|
uninstallService2(params, msh);
|
|
}
|
|
}
|
|
|
|
// A previous service installation was found, so lets do some extra processing
|
|
function serviceExists(loc, params)
|
|
{
|
|
process.stdout.write(' [FOUND: ' + loc + ']\n');
|
|
if(process.platform == 'win32')
|
|
{
|
|
// On Windows, we need to cleanup the firewall rules associated with our install path
|
|
process.stdout.write(' -> Checking firewall rules for previous installation... [0%]');
|
|
var p = require('win-firewall').getFirewallRulesAsync({ program: loc, noResult: true, minimal: true, timeout: 15000 });
|
|
p.on('progress', function (c)
|
|
{
|
|
process.stdout.write('\r -> Checking firewall rules for previous installation... [' + c + ']');
|
|
});
|
|
p.on('rule', function (r)
|
|
{
|
|
// Remove firewall entries for our install path
|
|
require('win-firewall').removeFirewallRule(r.DisplayName);
|
|
});
|
|
p.finally(function ()
|
|
{
|
|
process.stdout.write('\r -> Checking firewall rules for previous installation... [DONE]\n');
|
|
uninstallService(params);
|
|
});
|
|
}
|
|
else
|
|
{
|
|
uninstallService(params);
|
|
}
|
|
}
|
|
|
|
// Entry point for -fulluninstall
|
|
function fullUninstall(jsonString)
|
|
{
|
|
var parms = JSON.parse(jsonString);
|
|
if (parseInt(parms.getParameter('verbose', 0)) == 0)
|
|
{
|
|
console.setDestination(console.Destinations.DISABLED); // IF verbose is disabled(default), we will no-op console.log
|
|
}
|
|
else
|
|
{
|
|
console.setInfoLevel(1); // IF verbose is specified, we will show info level 1 messages
|
|
}
|
|
parms.push('_stop'); // Since we are intending to halt after uninstalling the service, we specify this, since we are re-using the uninstall code with the installer.
|
|
|
|
checkParameters(parms); // Perform some checks on the passed in parameters
|
|
|
|
var name = parms.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); // Set the service name, using the defaults if not specified
|
|
|
|
|
|
// Check for a previous installation of the service
|
|
try
|
|
{
|
|
process.stdout.write('...Checking for previous installation of "' + name + '"');
|
|
var s = require('service-manager').manager.getService(name);
|
|
var loc = s.appLocation();
|
|
var appPrefix = loc.split(process.platform == 'win32' ? '\\' : '/').pop();
|
|
if (process.platform == 'win32') { appPrefix = appPrefix.substring(0, appPrefix.length - 4); }
|
|
|
|
parms.push('_workingDir=' + s.appWorkingDirectory());
|
|
parms.push('_appPrefix=' + appPrefix);
|
|
|
|
s.close();
|
|
}
|
|
catch (e)
|
|
{
|
|
// No previous installation was found, so we can just exit
|
|
process.stdout.write(' [NONE]\n');
|
|
process.exit();
|
|
}
|
|
serviceExists(loc, parms);
|
|
}
|
|
|
|
// Entry point for -fullinstall, using JSON string
|
|
function fullInstall(jsonString, gOptions)
|
|
{
|
|
var parms = JSON.parse(jsonString);
|
|
fullInstallEx(parms, gOptions);
|
|
}
|
|
|
|
// Entry point for -fullinstall, using JSON object
|
|
function fullInstallEx(parms, gOptions)
|
|
{
|
|
if (gOptions != null) { global.gOptions = gOptions; }
|
|
|
|
// Perform some checks on the specified parameters
|
|
checkParameters(parms);
|
|
|
|
var loc = null;
|
|
var i;
|
|
var name = parms.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent'); // Set the service name, using defaults if not specified
|
|
if (process.platform != 'win32') { name = name.split(' ').join('_'); }
|
|
|
|
// No-op console.log() if verbose is not specified, otherwise set the verbosity level to level 1
|
|
if (parseInt(parms.getParameter('verbose', 0)) == 0)
|
|
{
|
|
console.setDestination(console.Destinations.DISABLED);
|
|
}
|
|
else
|
|
{
|
|
console.setInfoLevel(1);
|
|
}
|
|
|
|
// Check for a previous installation of the service
|
|
try
|
|
{
|
|
process.stdout.write('...Checking for previous installation of "' + name + '"');
|
|
var s = require('service-manager').manager.getService(name);
|
|
loc = s.appLocation();
|
|
|
|
global._workingpath = s.appWorkingDirectory();
|
|
console.info1('');
|
|
console.info1('Previous Working Path: ' + global._workingpath);
|
|
s.close();
|
|
}
|
|
catch (e)
|
|
{
|
|
// No previous installation was found, so we can continue with installation
|
|
process.stdout.write(' [NONE]\n');
|
|
installService(parms);
|
|
return;
|
|
}
|
|
if (process.execPath == loc)
|
|
{
|
|
parms.push('__skipBinaryDelete'); // If the installer is running from the installed service path, skip deleting the binary
|
|
}
|
|
serviceExists(loc, parms); // Previous installation was found, so we need to do some extra processing before we continue with installation
|
|
}
|
|
|
|
|
|
module.exports =
|
|
{
|
|
fullInstallEx: fullInstallEx,
|
|
fullInstall: fullInstall,
|
|
fullUninstall: fullUninstall
|
|
};
|
|
|
|
|
|
// Legacy Windows Helper function, to perform a self-update
|
|
function sys_update(isservice, b64)
|
|
{
|
|
// This is run on the 'updated' agent.
|
|
|
|
var service = null;
|
|
var serviceLocation = "";
|
|
var px;
|
|
|
|
if (isservice)
|
|
{
|
|
var parm = b64 != null ? JSON.parse(Buffer.from(b64, 'base64').toString()) : null;
|
|
if (parm != null)
|
|
{
|
|
console.info1('sys_update(' + isservice + ', ' + JSON.stringify(parm) + ')');
|
|
if ((px = parm.getParameterIndex('fakeUpdate')) >= 0)
|
|
{
|
|
console.info1('Removing "fakeUpdate" parameter');
|
|
parm.splice(px, 1);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Service Mode
|
|
//
|
|
|
|
// Check if we have sufficient permission
|
|
if (!require('user-sessions').isRoot())
|
|
{
|
|
// We don't have enough permissions, so copying the binary will likely fail, and we can't start...
|
|
// This is just to prevent looping, because agentcore.c should not call us in this scenario
|
|
console.log('* insufficient permission to continue with update');
|
|
process._exit();
|
|
return;
|
|
}
|
|
var servicename = parm != null ? (parm.getParameter('meshServiceName', process.platform == 'win32' ? 'Mesh Agent' : 'meshagent')) : (process.platform == 'win32' ? 'Mesh Agent' : 'meshagent');
|
|
try
|
|
{
|
|
if (b64 == null) { throw ('legacy'); }
|
|
service = require('service-manager').manager.getService(servicename)
|
|
serviceLocation = service.appLocation();
|
|
console.log(' Updating service: ' + servicename);
|
|
}
|
|
catch (f)
|
|
{
|
|
// Check to see if we can figure out the service name before we fail
|
|
var old = process.execPath.split('.update.exe').join('.exe');
|
|
var child = require('child_process').execFile(old, [old.split('\\').pop(), '-name']);
|
|
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
|
child.waitExit();
|
|
|
|
if (child.stdout.str.trim() == '' && b64 == null) { child.stdout.str = 'Mesh Agent'; }
|
|
if (child.stdout.str.trim() != '')
|
|
{
|
|
if (child.stdout.str.trim().split('\n').length > 1) { child.stdout.str = 'Mesh Agent'; }
|
|
try
|
|
{
|
|
service = require('service-manager').manager.getService(child.stdout.str.trim())
|
|
serviceLocation = service.appLocation();
|
|
console.log(' Updating service: ' + child.stdout.str.trim());
|
|
}
|
|
catch (ff)
|
|
{
|
|
console.log(' * ' + servicename + ' SERVICE NOT FOUND *');
|
|
console.log(' * ' + child.stdout.str.trim() + ' SERVICE NOT FOUND *');
|
|
process._exit();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.log(' * ' + servicename + ' SERVICE NOT FOUND *');
|
|
process._exit();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!global._interval)
|
|
{
|
|
global._interval = setInterval(sys_update, 60000, isservice, b64);
|
|
}
|
|
|
|
if (isservice === false)
|
|
{
|
|
//
|
|
// Console Mode (LEGACY)
|
|
//
|
|
if (process.platform == 'win32')
|
|
{
|
|
serviceLocation = process.execPath.split('.update.exe').join('.exe');
|
|
}
|
|
else
|
|
{
|
|
serviceLocation = process.execPath.substring(0, process.execPath.length - 7);
|
|
}
|
|
|
|
if (serviceLocation != process.execPath)
|
|
{
|
|
try
|
|
{
|
|
require('fs').copyFileSync(process.execPath, serviceLocation);
|
|
}
|
|
catch (ce)
|
|
{
|
|
console.log('\nAn error occured while updating agent.');
|
|
process.exit();
|
|
}
|
|
}
|
|
|
|
// Copied agent binary... Need to start agent in console mode
|
|
console.log('\nAgent update complete... Please re-start agent.');
|
|
process.exit();
|
|
}
|
|
|
|
|
|
service.stop().finally(function ()
|
|
{
|
|
require('process-manager').enumerateProcesses().then(function (proc)
|
|
{
|
|
for (var p in proc)
|
|
{
|
|
if (proc[p].path == serviceLocation)
|
|
{
|
|
process.kill(proc[p].pid);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
require('fs').copyFileSync(process.execPath, serviceLocation);
|
|
}
|
|
catch (ce)
|
|
{
|
|
console.log('Could not copy file.. Trying again in 60 seconds');
|
|
service.close();
|
|
return;
|
|
}
|
|
|
|
console.log('Agent update complete. Starting service...');
|
|
service.start();
|
|
process._exit();
|
|
});
|
|
});
|
|
}
|
|
|
|
// Another Windows Legacy Helper for Self-Update, that shows the updater version
|
|
function agent_updaterVersion(updatePath)
|
|
{
|
|
var ret = 0;
|
|
if (updatePath == null) { updatePath = process.execPath; }
|
|
var child;
|
|
|
|
try
|
|
{
|
|
child = require('child_process').execFile(updatePath, [updatePath.split(process.platform == 'win32' ? '\\' : '/').pop(), '-updaterversion']);
|
|
}
|
|
catch(x)
|
|
{
|
|
return (0);
|
|
}
|
|
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
|
child.waitExit();
|
|
|
|
if(child.stdout.str.trim() == '')
|
|
{
|
|
ret = 0;
|
|
}
|
|
else
|
|
{
|
|
ret = parseInt(child.stdout.str);
|
|
if (isNaN(ret)) { ret = 0; }
|
|
}
|
|
return (ret);
|
|
}
|
|
|
|
|
|
// Windows Helper to clear firewall entries
|
|
function win_clearfirewall(passthru)
|
|
{
|
|
process.stdout.write('Clearing firewall rules... [0%]');
|
|
var p = require('win-firewall').getFirewallRulesAsync({ program: process.execPath, noResult: true, minimal: true, timeout: 15000 });
|
|
p.on('progress', function (c)
|
|
{
|
|
process.stdout.write('\rClearing firewall rules... [' + c + ']');
|
|
});
|
|
p.on('rule', function (r)
|
|
{
|
|
require('win-firewall').removeFirewallRule(r.DisplayName);
|
|
});
|
|
p.finally(function ()
|
|
{
|
|
process.stdout.write('\rClearing firewall rules... [DONE]\n');
|
|
if (passthru == null) { process.exit(); }
|
|
});
|
|
if(passthru!=null)
|
|
{
|
|
return (p);
|
|
}
|
|
}
|
|
|
|
// Windows Helper for enumerating Firewall Rules associated with our binary
|
|
function win_checkfirewall()
|
|
{
|
|
process.stdout.write('Checking firewall rules... [0%]');
|
|
var p = require('win-firewall').getFirewallRulesAsync({ program: process.execPath, noResult: true, minimal: true, timeout: 15000 });
|
|
p.foundItems = 0;
|
|
p.on('progress', function (c)
|
|
{
|
|
process.stdout.write('\rChecking firewall rules... [' + c + ']');
|
|
});
|
|
p.on('rule', function (r)
|
|
{
|
|
this.foundItems++;
|
|
});
|
|
p.finally(function ()
|
|
{
|
|
process.stdout.write('\rChecking firewall rules... [DONE]\n');
|
|
process.stdout.write('Rules found: ' + this.foundItems + '\n');
|
|
|
|
process.exit();
|
|
});
|
|
}
|
|
|
|
// Windows Helper for setting a firewall rule entry
|
|
function win_setfirewall()
|
|
{
|
|
var p = win_clearfirewall(true);
|
|
p.finally(function ()
|
|
{
|
|
var rule =
|
|
{
|
|
DisplayName: 'MeshCentral WebRTC Traffic',
|
|
direction: 'inbound',
|
|
Program: process.execPath,
|
|
Protocol: 'UDP',
|
|
Profile: 'Public, Private, Domain',
|
|
Description: 'Mesh Central Agent WebRTC P2P Traffic',
|
|
EdgeTraversalPolicy: 'allow',
|
|
Enabled: true
|
|
};
|
|
require('win-firewall').addFirewallRule(rule);
|
|
process.stdout.write('Adding firewall rules..... [DONE]\n');
|
|
process.exit();
|
|
});
|
|
|
|
}
|
|
|
|
// Windows Helper, for performing SelfUpdate on Console Mode Agent
|
|
function win_consoleUpdate()
|
|
{
|
|
// This is run from the 'old' agent, to copy the 'updated' agent.
|
|
var copy = [];
|
|
copy.push("try { require('fs').copyFileSync(process.execPath, process.execPath.split('.update.exe').join('.exe')); }");
|
|
copy.push("catch (x) { console.log('\\nError updating Mesh Agent.'); process.exit(); }");
|
|
copy.push("if(require('child_process')._execve==null) { console.log('\\nMesh Agent was updated... Please re-run from the command line.'); process.exit(); }");
|
|
copy.push("require('child_process')._execve(process.execPath.split('.update.exe').join('.exe'), [process.execPath.split('.update.exe').join('.exe'), 'run']);");
|
|
var args = [];
|
|
args.push(process.execPath.split('.exe').join('.update.exe'));
|
|
args.push('-b64exec');
|
|
args.push(Buffer.from(copy.join('\r\n')).toString('base64'));
|
|
console.info1('_execve("' + process.execPath.split('.exe').join('.update.exe') + '", ' + JSON.stringify(args) + ');');
|
|
require('child_process')._execve(process.execPath.split('.exe').join('.update.exe'), args);
|
|
}
|
|
|
|
|
|
// Legacy Helper for Windows Self-Update. Shouldn't really be used anymore, but is still here for Legacy Support
|
|
module.exports.update = sys_update;
|
|
module.exports.updaterVersion = agent_updaterVersion;
|
|
|
|
if (process.platform == 'win32')
|
|
{
|
|
module.exports.consoleUpdate = win_consoleUpdate; // Windows Helper, for performing SelfUpdate on Console Mode Agent
|
|
module.exports.clearfirewall = win_clearfirewall; // Windows Helper, to clear firewall entries
|
|
module.exports.setfirewall = win_setfirewall; // Windows Helper, to set firewall entries
|
|
module.exports.checkfirewall = win_checkfirewall; // Windows Helper, to check firewall rules
|
|
}
|