meshcentral
Version:
Web based remote computer management server
666 lines (627 loc) • 333 kB
JavaScript
/**
* @description MeshCentral main module
* @author Ylian Saint-Hilaire
* @copyright Intel Corporation 2018-2022
* @license Apache-2.0
* @version v0.0.1
*/
/*xjslint node: true */
/*xjslint plusplus: true */
/*xjslint maxlen: 256 */
/*jshint node: true */
/*jshint strict: false */
/*jshint esversion: 6 */
"use strict";
const common = require('./common.js');
// If app metrics is available
if (process.argv[2] == '--launch') { try { require('appmetrics-dash').monitor({ url: '/', title: 'MeshCentral', port: 88, host: '127.0.0.1' }); } catch (ex) { } }
function CreateMeshCentralServer(config, args) {
const obj = {};
obj.db = null;
obj.webserver = null; // HTTPS main web server, typically on port 443
obj.redirserver = null; // HTTP relay web server, typically on port 80
obj.mpsserver = null; // Intel AMT CIRA server, typically on port 4433
obj.mqttbroker = null; // MQTT server, not is not often used
obj.swarmserver = null; // Swarm server, this is used only to update older MeshCentral v1 agents
obj.smsserver = null; // SMS server, used to send user SMS messages
obj.msgserver = null; // Messaging server, used to sent used messages
obj.amtEventHandler = null;
obj.pluginHandler = null;
obj.amtScanner = null;
obj.amtManager = null; // Intel AMT manager, used to oversee all Intel AMT devices, activate them and sync policies
obj.meshScanner = null;
obj.taskManager = null;
obj.letsencrypt = null; // Let's encrypt server, used to get and renew TLS certificates
obj.eventsDispatch = {};
obj.fs = require('fs');
obj.path = require('path');
obj.crypto = require('crypto');
obj.exeHandler = require('./exeHandler.js');
obj.platform = require('os').platform();
obj.args = args;
obj.common = common;
obj.configurationFiles = null;
obj.certificates = null;
obj.connectivityByNode = {}; // This object keeps a list of all connected CIRA and agents, by nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect)
obj.peerConnectivityByNode = {}; // This object keeps a list of all connected CIRA and agents of peers, by serverid->nodeid->value (value: 1 = Agent, 2 = CIRA, 4 = AmtDirect)
obj.debugSources = [];
obj.debugRemoteSources = null;
obj.config = config; // Configuration file
obj.dbconfig = {}; // Persistance values, loaded from database
obj.certificateOperations = null;
obj.defaultMeshCmd = null;
obj.defaultMeshCores = {};
obj.defaultMeshCoresDeflate = {};
obj.defaultMeshCoresHash = {};
obj.meshToolsBinaries = {}; // Mesh Tools Binaries, ToolName --> { hash:(sha384 hash), size:(binary size), path:(binary path) }
obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha384 hash), size:(binary size), path:(binary path) }
obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha384 hash), size:(binary size), path:(binary path) }
obj.multiServer = null;
obj.ipKvmManager = null;
obj.maintenanceTimer = null;
obj.serverId = null;
obj.serverKey = Buffer.from(obj.crypto.randomBytes(48), 'binary');
obj.loginCookieEncryptionKey = null;
obj.invitationLinkEncryptionKey = null;
obj.serverSelfWriteAllowed = true;
obj.serverStatsCounter = Math.floor(Math.random() * 1000);
obj.taskLimiter = obj.common.createTaskLimiterQueue(50, 20, 60); // (maxTasks, maxTaskTime, cleaningInterval) This is a task limiter queue to smooth out server work.
obj.agentUpdateBlockSize = 65531; // MeshAgent update block size
obj.serverWarnings = []; // List of warnings that should be shown to administrators
obj.cookieUseOnceTable = {}; // List of cookies that are already expired
obj.cookieUseOnceTableCleanCounter = 0; // Clean the cookieUseOnceTable each 20 additions
obj.firstStats = true; // True until this server saves it's not stats to the database
// Server version
obj.currentVer = null;
function getCurrentVersion() { try { obj.currentVer = JSON.parse(obj.fs.readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (ex) { } return obj.currentVer; } // Fetch server version
getCurrentVersion();
// Setup the default configuration and files paths
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) {
obj.parentpath = obj.path.join(__dirname, '../..');
obj.datapath = obj.path.join(__dirname, '../../meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../../meshcentral-files');
obj.backuppath = obj.path.join(__dirname, '../../meshcentral-backups');
obj.recordpath = obj.path.join(__dirname, '../../meshcentral-recordings');
obj.webViewsPath = obj.path.join(__dirname, 'views');
obj.webPublicPath = obj.path.join(__dirname, 'public');
obj.webEmailsPath = obj.path.join(__dirname, 'emails');
if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../../meshcentral-web/views'); }
if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../../meshcentral-web/public'); }
if (obj.fs.existsSync(obj.path.join(__dirname, '../../meshcentral-web/emails'))) { obj.webEmailsOverridePath = obj.path.join(__dirname, '../../meshcentral-web/emails'); }
} else {
obj.parentpath = __dirname;
obj.datapath = obj.path.join(__dirname, '../meshcentral-data');
obj.filespath = obj.path.join(__dirname, '../meshcentral-files');
obj.backuppath = obj.path.join(__dirname, '../meshcentral-backups');
obj.recordpath = obj.path.join(__dirname, '../meshcentral-recordings');
obj.webViewsPath = obj.path.join(__dirname, 'views');
obj.webPublicPath = obj.path.join(__dirname, 'public');
obj.webEmailsPath = obj.path.join(__dirname, 'emails');
if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/views'))) { obj.webViewsOverridePath = obj.path.join(__dirname, '../meshcentral-web/views'); }
if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/public'))) { obj.webPublicOverridePath = obj.path.join(__dirname, '../meshcentral-web/public'); }
if (obj.fs.existsSync(obj.path.join(__dirname, '../meshcentral-web/emails'))) { obj.webEmailsOverridePath = obj.path.join(__dirname, '../meshcentral-web/emails'); }
}
// Clean up any temporary files
const removeTime = new Date(Date.now()).getTime() - (30 * 60 * 1000); // 30 minutes
const dir = obj.fs.readdir(obj.path.join(obj.filespath, 'tmp'), function (err, files) {
if (err != null) return;
for (var i in files) { try { const filepath = obj.path.join(obj.filespath, 'tmp', files[i]); if (obj.fs.statSync(filepath).mtime.getTime() < removeTime) { obj.fs.unlink(filepath, function () { }); } } catch (ex) { } }
});
// Look to see if data and/or file path is specified
if (obj.config.settings && (typeof obj.config.settings.datapath == 'string')) { obj.datapath = obj.config.settings.datapath; }
if (obj.config.settings && (typeof obj.config.settings.filespath == 'string')) { obj.filespath = obj.config.settings.filespath; }
// Create data and files folders if needed
try { obj.fs.mkdirSync(obj.datapath); } catch (ex) { }
try { obj.fs.mkdirSync(obj.filespath); } catch (ex) { }
// Windows Specific Code, setup service and event log
obj.service = null;
obj.servicelog = null;
if (obj.platform == 'win32') {
const nodewindows = require('node-windows');
obj.service = nodewindows.Service;
const eventlogger = nodewindows.EventLogger;
obj.servicelog = new eventlogger('MeshCentral');
}
// Start the Meshcentral server
obj.Start = function () {
var i;
try { require('./pass').hash('test', function () { }, 0); } catch (ex) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
// Check for invalid arguments
const validArguments = ['_', 'user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'rediraliasport', 'cert', 'mpscert', 'deletedomain', 'deletedefaultdomain', 'showall', 'showusers', 'showitem', 'listuserids', 'showusergroups', 'shownodes', 'showallmeshes', 'showmeshes', 'showevents', 'showsmbios', 'showpower', 'clearpower', 'showiplocations', 'help', 'exactports', 'xinstall', 'xuninstall', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbexportmin', 'dbimport', 'dbmerge', 'dbfix', 'dbencryptkey', 'selfupdate', 'tlsoffload', 'usenodedefaulttlsciphers', 'tlsciphers', 'userallowedip', 'userblockedip', 'swarmallowedip', 'agentallowedip', 'agentblockedip', 'fastcert', 'swarmport', 'logintoken', 'logintokenkey', 'logintokengen', 'mailtokengen', 'admin', 'unadmin', 'sessionkey', 'sessiontime', 'minify', 'minifycore', 'dblistconfigfiles', 'dbshowconfigfile', 'dbpushconfigfiles', 'oldencrypt', 'dbpullconfigfiles', 'dbdeleteconfigfiles', 'vaultpushconfigfiles', 'vaultpullconfigfiles', 'vaultdeleteconfigfiles', 'configkey', 'loadconfigfromdb', 'npmpath', 'serverid', 'recordencryptionrecode', 'vault', 'token', 'unsealkey', 'name', 'log', 'dbstats', 'translate', 'createaccount', 'setuptelegram', 'resetaccount', 'pass', 'removesubdomain', 'adminaccount', 'domain', 'email', 'configfile', 'maintenancemode', 'nedbtodb', 'removetestagents', 'agentupdatetest', 'hashpassword', 'hashpass', 'indexmcrec', 'mpsdebug', 'dumpcores', 'dev', 'mysql', 'mariadb', 'trustedproxy'];
for (var arg in obj.args) { obj.args[arg.toLocaleLowerCase()] = obj.args[arg]; if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
const ENVVAR_PREFIX = "meshcentral_"
let envArgs = []
for (let [envvar, envval] of Object.entries(process.env)) {
if (envvar.toLocaleLowerCase().startsWith(ENVVAR_PREFIX)) {
let argname = envvar.slice(ENVVAR_PREFIX.length).toLocaleLowerCase()
if (!!argname && !(validArguments.indexOf(argname) == -1)) {
envArgs = envArgs.concat([`--${argname}`, envval])
}
}
}
envArgs = require('minimist')(envArgs)
obj.args = Object.assign(envArgs, obj.args)
if (obj.args.mongodb == true) { console.log('Must specify: --mongodb [connectionstring] \r\nSee https://docs.mongodb.com/manual/reference/connection-string/ for MongoDB connection string.'); return; }
if (obj.args.mysql == true) { console.log('Must specify: --mysql [connectionstring] \r\nExample mysql://user:password@127.0.0.1:3306/database'); return; }
if (obj.args.mariadb == true) { console.log('Must specify: --mariadb [connectionstring] \r\nExample mariadb://user:password@127.0.0.1:3306/database'); return; }
for (i in obj.config.settings) { obj.args[i] = obj.config.settings[i]; } // Place all settings into arguments, arguments have already been placed into settings so arguments take precedence.
if ((obj.args.help == true) || (obj.args['?'] == true)) {
console.log('MeshCentral v' + getCurrentVersion() + ', remote computer management web portal.');
console.log('This software is open source under Apache 2.0 license.');
console.log('Details at: https://www.meshcentral.com\r\n');
if ((obj.platform == 'win32') || (obj.platform == 'linux')) {
console.log('Run as a background service');
console.log(' --install/uninstall Install MeshCentral as a background service.');
console.log(' --start/stop/restart Control MeshCentral background service.');
console.log('');
console.log('Run standalone, console application');
}
console.log(' --user [username] Always login as [username] if account exists.');
console.log(' --port [number] Web server port number.');
console.log(' --redirport [number] Creates an additional HTTP server to redirect users to the HTTPS server.');
console.log(' --exactports Server must run with correct ports or exit.');
console.log(' --noagentupdate Server will not update mesh agent native binaries.');
console.log(' --nedbtodb Transfer all NeDB records into current database.');
console.log(' --listuserids Show a list of a user identifiers in the database.');
console.log(' --cert [name], (country), (org) Create a web server certificate with [name] server name.');
console.log(' country and organization can optionally be set.');
console.log('');
console.log('Server recovery commands, use only when MeshCentral is offline.');
console.log(' --createaccount [userid] Create a new user account.');
console.log(' --resetaccount [userid] Unlock an account, disable 2FA and set a new account password.');
console.log(' --adminaccount [userid] Promote account to site administrator.');
return;
}
// Fix a NeDB database
if (obj.args.dbfix) {
var lines = null, badJsonCount = 0, fieldNames = [], fixedDb = [];
try { lines = obj.fs.readFileSync(obj.getConfigFilePath(obj.args.dbfix), { encoding: 'utf8' }).split('\n'); } catch (ex) { console.log('Invalid file: ' + obj.args.dbfix + ': ' + ex); process.exit(); }
for (var i = 0; i < lines.length; i++) {
var x = null;
try { x = JSON.parse(lines[i]); } catch (ex) { badJsonCount++; }
if (x != null) { fixedDb.push(lines[i]); for (var j in x) { if (fieldNames.indexOf(j) == -1) { fieldNames.push(j); } } }
}
console.log('Lines: ' + lines.length + ', badJSON: ' + badJsonCount + ', Feilds: ' + fieldNames);
obj.fs.writeFileSync(obj.getConfigFilePath(obj.args.dbfix) + '-fixed', fixedDb.join('\n'), { encoding: 'utf8' });
return;
}
// Check for invalid cert name
if ((obj.args.cert != null) && ((typeof obj.args.cert != "string") || (obj.args.cert.indexOf('@') >= 0) || (obj.args.cert.indexOf('/') >= 0) || (obj.args.cert.indexOf(':') >= 0))) { console.log("Invalid certificate name"); process.exit(); return; }
// Perform a password hash
if (obj.args.hashpassword) { require('./pass').hash(obj.args.hashpassword, function (err, salt, hash, tag) { console.log(salt + ',' + hash); process.exit(); }); return; }
// Dump to mesh cores
if (obj.args.dumpcores) { obj.updateMeshCore(function () { console.log('Done.'); }, true); return; }
// Setup Telegram
if (obj.args.setuptelegram) { require('./meshmessaging.js').SetupTelegram(obj); return; }
// Perform web site translations into different languages
if (obj.args.translate) {
// Check NodeJS version
const NodeJSVer = Number(process.version.match(/^v(\d+\.\d+)/)[1]);
if (NodeJSVer < 8) { console.log("Translation feature requires Node v8 or above, current version is " + process.version + "."); process.exit(); return; }
// Check if translate.json is in the "meshcentral-data" folder, if so use that and translate default pages.
var translationFile = null, customTranslation = false;
if (require('fs').existsSync(obj.path.join(obj.datapath, 'translate.json'))) { translationFile = obj.path.join(obj.datapath, 'translate.json'); console.log("Using translate.json in meshcentral-data."); customTranslation = true; }
if (translationFile == null) { if (require('fs').existsSync(obj.path.join(__dirname, 'translate', 'translate.json'))) { translationFile = obj.path.join(__dirname, 'translate', 'translate.json'); console.log("Using default translate.json."); } }
if (translationFile == null) { console.log("Unable to find translate.json."); process.exit(); return; }
// Perform translation operations
var didSomething = false;
process.chdir(obj.path.join(__dirname, 'translate'));
const translateEngine = require('./translate/translate.js')
if (customTranslation == true) {
// Translate all of the default files using custom translation file
translateEngine.startEx(['', '', 'minifyall']);
translateEngine.startEx(['', '', 'translateall', translationFile]);
translateEngine.startEx(['', '', 'extractall', translationFile]);
didSomething = true;
} else {
// Translate all of the default files
translateEngine.startEx(['', '', 'minifyall']);
translateEngine.startEx(['', '', 'translateall']);
translateEngine.startEx(['', '', 'extractall']);
didSomething = true;
}
// Check if "meshcentral-web" exists, if so, translate all pages in that folder.
if (obj.webViewsOverridePath != null) {
didSomething = true;
var files = obj.fs.readdirSync(obj.webViewsOverridePath);
for (var i in files) {
var file = obj.path.join(obj.webViewsOverridePath, files[i]);
if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
translateEngine.startEx(['', '', 'minify', file]);
}
}
files = obj.fs.readdirSync(obj.webViewsOverridePath);
for (var i in files) {
var file = obj.path.join(obj.webViewsOverridePath, files[i]);
if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
}
}
}
// Check domains and see if "meshcentral-web-DOMAIN" exists, if so, translate all pages in that folder
for (i in obj.config.domains) {
if (i == "") continue;
var path = obj.path.join(obj.datapath, '..', 'meshcentral-web-' + i, 'views');
if (require('fs').existsSync(path)) {
didSomething = true;
var files = obj.fs.readdirSync(path);
for (var a in files) {
var file = obj.path.join(path, files[a]);
if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
translateEngine.startEx(['', '', 'minify', file]);
}
}
files = obj.fs.readdirSync(path);
for (var a in files) {
var file = obj.path.join(path, files[a]);
if (file.endsWith('.handlebars') && !file.endsWith('-min.handlebars')) {
translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
}
}
}
}
/*
if (obj.webPublicOverridePath != null) {
didSomething = true;
var files = obj.fs.readdirSync(obj.webPublicOverridePath);
for (var i in files) {
var file = obj.path.join(obj.webPublicOverridePath, files[i]);
if (file.endsWith('.htm') && !file.endsWith('-min.htm')) {
translateEngine.startEx(['', '', 'translate', '*', translationFile, file, '--subdir:translations']);
}
}
}
*/
if (didSomething == false) { console.log("Nothing to do."); }
console.log('Finished Translating.')
process.exit();
return;
}
// Setup the Node+NPM path if possible, this makes it possible to update the server even if NodeJS and NPM are not in default paths.
if (obj.args.npmpath == null) {
try {
var nodepath = process.argv[0];
var npmpath = obj.path.join(obj.path.dirname(process.argv[0]), 'npm');
if (obj.fs.existsSync(nodepath) && obj.fs.existsSync(npmpath)) {
if (nodepath.indexOf(' ') >= 0) { nodepath = '"' + nodepath + '"'; }
if (npmpath.indexOf(' ') >= 0) { npmpath = '"' + npmpath + '"'; }
if (obj.platform == 'win32') { obj.args.npmpath = npmpath; } else { obj.args.npmpath = (nodepath + ' ' + npmpath); }
}
} catch (ex) { }
}
// Linux background service systemd handling
if (obj.platform == 'linux') {
if (obj.args.install == true) {
// Install MeshCentral in Systemd
console.log('Installing MeshCentral as background Service...');
var systemdConf = null;
const userinfo = require('os').userInfo();
if (require('fs').existsSync('/etc/systemd/system')) { systemdConf = '/etc/systemd/system/meshcentral.service'; }
else if (require('fs').existsSync('/lib/systemd/system')) { systemdConf = '/lib/systemd/system/meshcentral.service'; }
else if (require('fs').existsSync('/usr/lib/systemd/system')) { systemdConf = '/usr/lib/systemd/system/meshcentral.service'; }
else { console.log('Unable to find systemd configuration folder.'); process.exit(); return; }
console.log('Writing config file...');
require('child_process').exec('which node', {}, function (error, stdout, stderr) {
if ((error != null) || (stdout.indexOf('\n') == -1)) { console.log('ERROR: Unable to get node location: ' + error); process.exit(); return; }
const nodePath = stdout.substring(0, stdout.indexOf('\n'));
const config = '[Unit]\nDescription=MeshCentral Server\nWants=network-online.target\nAfter=network-online.target\n\n[Service]\nType=simple\nLimitNOFILE=1000000\nExecStart=' + nodePath + ' ' + __dirname + '/meshcentral\nWorkingDirectory=' + userinfo.homedir + '\nEnvironment=NODE_ENV=production\nUser=' + userinfo.username + '\nGroup=' + userinfo.username + '\nRestart=always\n# Restart service after 10 seconds if node service crashes\nRestartSec=10\n# Set port permissions capability\nAmbientCapabilities=cap_net_bind_service\n\n[Install]\nWantedBy=multi-user.target\n';
require('child_process').exec('echo \"' + config + '\" | sudo tee ' + systemdConf, {}, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to write config file: ' + error); process.exit(); return; }
console.log('Enabling service...');
require('child_process').exec('sudo systemctl enable meshcentral.service', {}, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to enable MeshCentral as a service: ' + error); process.exit(); return; }
if (stdout.length > 0) { console.log(stdout); }
console.log('Starting service...');
require('child_process').exec('sudo systemctl start meshcentral.service', {}, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to start MeshCentral as a service: ' + error); process.exit(); return; }
if (stdout.length > 0) { console.log(stdout); }
console.log('Done.');
});
});
});
});
return;
} else if (obj.args.uninstall == true) {
// Uninstall MeshCentral in Systemd
console.log('Uninstalling MeshCentral background service...');
var systemdConf = null;
if (require('fs').existsSync('/etc/systemd/system')) { systemdConf = '/etc/systemd/system/meshcentral.service'; }
else if (require('fs').existsSync('/lib/systemd/system')) { systemdConf = '/lib/systemd/system/meshcentral.service'; }
else if (require('fs').existsSync('/usr/lib/systemd/system')) { systemdConf = '/usr/lib/systemd/system/meshcentral.service'; }
else { console.log('Unable to find systemd configuration folder.'); process.exit(); return; }
console.log('Stopping service...');
require('child_process').exec('sudo systemctl stop meshcentral.service', {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to stop MeshCentral as a service: ' + err); }
if (stdout.length > 0) { console.log(stdout); }
console.log('Disabling service...');
require('child_process').exec('sudo systemctl disable meshcentral.service', {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to disable MeshCentral as a service: ' + err); }
if (stdout.length > 0) { console.log(stdout); }
console.log('Removing config file...');
require('child_process').exec('sudo rm ' + systemdConf, {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to delete MeshCentral config file: ' + err); }
console.log('Done.');
});
});
});
return;
} else if (obj.args.start == true) {
// Start MeshCentral in Systemd
require('child_process').exec('sudo systemctl start meshcentral.service', {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to start MeshCentral: ' + err); process.exit(); return; }
console.log('Done.');
});
return;
} else if (obj.args.stop == true) {
// Stop MeshCentral in Systemd
require('child_process').exec('sudo systemctl stop meshcentral.service', {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to stop MeshCentral: ' + err); process.exit(); return; }
console.log('Done.');
});
return;
} else if (obj.args.restart == true) {
// Restart MeshCentral in Systemd
require('child_process').exec('sudo systemctl restart meshcentral.service', {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to restart MeshCentral: ' + err); process.exit(); return; }
console.log('Done.');
});
return;
}
}
// FreeBSD background service handling, MUST USE SPAWN FOR SERVICE COMMANDS!
if (obj.platform == 'freebsd') {
if (obj.args.install == true) {
// Install MeshCentral in rc.d
console.log('Installing MeshCentral as background Service...');
var systemdConf = "/usr/local/etc/rc.d/meshcentral";
const userinfo = require('os').userInfo();
console.log('Writing config file...');
require('child_process').exec('which node', {}, function (error, stdout, stderr) {
if ((error != null) || (stdout.indexOf('\n') == -1)) { console.log('ERROR: Unable to get node location: ' + error); process.exit(); return; }
const nodePath = stdout.substring(0, stdout.indexOf('\n'));
const config = '#!/bin/sh\n# MeshCentral FreeBSD Service Script\n# PROVIDE: meshcentral\n# REQUIRE: NETWORKING\n# KEYWORD: shutdown\n. /etc/rc.subr\nname=meshcentral\nuser=' + userinfo.username + '\nrcvar=meshcentral_enable\n: \\${meshcentral_enable:=\\"NO\\"}\n: \\${meshcentral_args:=\\"\\"}\npidfile=/var/run/meshcentral/meshcentral.pid\ncommand=\\"/usr/sbin/daemon\\"\nmeshcentral_chdir=\\"' + obj.parentpath + '\\"\ncommand_args=\\"-r -u \\${user} -P \\${pidfile} -S -T meshcentral -m 3 ' + nodePath + ' ' + __dirname + ' \\${meshcentral_args}\\"\nload_rc_config \\$name\nrun_rc_command \\"\\$1\\"\n';
require('child_process').exec('echo \"' + config + '\" | tee ' + systemdConf + ' && chmod +x ' + systemdConf, {}, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to write config file: ' + error); process.exit(); return; }
console.log('Enabling service...');
require('child_process').exec('sysrc meshcentral_enable="YES"', {}, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to enable MeshCentral as a service: ' + error); process.exit(); return; }
if (stdout.length > 0) { console.log(stdout); }
console.log('Starting service...');
const service = require('child_process').spawn('service', ['meshcentral', 'start']);
service.stdout.on('data', function (data) { console.log(data.toString()); });
service.stderr.on('data', function (data) { console.log(data.toString()); });
service.on('exit', function (code) {
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to start MeshCentral as a service');
process.exit(); // Must exit otherwise we just hang
});
});
});
});
return;
} else if (obj.args.uninstall == true) {
// Uninstall MeshCentral in rc.d
console.log('Uninstalling MeshCentral background service...');
var systemdConf = "/usr/local/etc/rc.d/meshcentral";
console.log('Stopping service...');
const service = require('child_process').spawn('service', ['meshcentral', 'stop']);
service.stdout.on('data', function (data) { console.log(data.toString()); });
service.stderr.on('data', function (data) { console.log(data.toString()); });
service.on('exit', function (code) {
if (code !== 0) { console.log('ERROR: Unable to stop MeshCentral as a service'); }
console.log('Disabling service...');
require('child_process').exec('sysrc -x meshcentral_enable', {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to disable MeshCentral as a service: ' + err); }
if (stdout.length > 0) { console.log(stdout); }
console.log('Removing config file...');
require('child_process').exec('rm ' + systemdConf, {}, function (err, stdout, stderr) {
if ((err != null) && (err != '')) { console.log('ERROR: Unable to delete MeshCentral config file: ' + err); }
console.log('Done.');
process.exit(); // Must exit otherwise we just hang
});
});
});
return;
} else if (obj.args.start == true) {
// Start MeshCentral in rc.d
const service = require('child_process').spawn('service', ['meshcentral', 'start']);
service.stdout.on('data', function (data) { console.log(data.toString()); });
service.stderr.on('data', function (data) { console.log(data.toString()); });
service.on('exit', function (code) {
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to start MeshCentral as a service: ' + error);
process.exit(); // Must exit otherwise we just hang
});
return;
} else if (obj.args.stop == true) {
// Stop MeshCentral in rc.d
const service = require('child_process').spawn('service', ['meshcentral', 'stop']);
service.stdout.on('data', function (data) { console.log(data.toString()); });
service.stderr.on('data', function (data) { console.log(data.toString()); });
service.on('exit', function (code) {
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to stop MeshCentral as a service: ' + error);
process.exit(); // Must exit otherwise we just hang
});
return;
} else if (obj.args.restart == true) {
// Restart MeshCentral in rc.d
const service = require('child_process').spawn('service', ['meshcentral', 'restart']);
service.stdout.on('data', function (data) { console.log(data.toString()); });
service.stderr.on('data', function (data) { console.log(data.toString()); });
service.on('exit', function (code) {
console.log((code === 0) ? 'Done.' : 'ERROR: Unable to restart MeshCentral as a service: ' + error);
process.exit(); // Must exit otherwise we just hang
});
return;
}
}
// Index a recorded file
if (obj.args.indexmcrec != null) {
if (typeof obj.args.indexmcrec != 'string') {
console.log('Usage: --indexmrec [filename.mcrec]');
} else if (obj.fs.existsSync(obj.args.indexmcrec)) {
console.log('Indexing file: ' + obj.args.indexmcrec);
require(require('path').join(__dirname, 'mcrec.js')).indexFile(obj.args.indexmcrec);
} else {
console.log('Unable to find file: ' + obj.args.indexmcrec);
}
return;
}
// Windows background service handling
if ((obj.platform == 'win32') && (obj.service != null)) {
// Build MeshCentral parent path and Windows Service path
var mcpath = __dirname;
if (mcpath.endsWith('\\node_modules\\meshcentral') || mcpath.endsWith('/node_modules/meshcentral')) { mcpath = require('path').join(mcpath, '..', '..'); }
const servicepath = obj.path.join(mcpath, 'WinService');
// Check if we need to install, start, stop, remove ourself as a background service
if (((obj.args.xinstall == true) || (obj.args.xuninstall == true) || (obj.args.start == true) || (obj.args.stop == true) || (obj.args.restart == true))) {
var env = [], xenv = ['user', 'port', 'aliasport', 'mpsport', 'mpsaliasport', 'redirport', 'exactport', 'rediraliasport', 'debug'];
for (i in xenv) { if (obj.args[xenv[i]] != null) { env.push({ name: 'mesh' + xenv[i], value: obj.args[xenv[i]] }); } } // Set some args as service environment variables.
var serviceFilePath = null;
if (obj.fs.existsSync(obj.path.join(servicepath, 'winservice.js'))) { serviceFilePath = obj.path.join(servicepath, 'winservice.js'); }
else if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService/winservice.js'))) { serviceFilePath = obj.path.join(__dirname, '../WinService/winservice.js'); }
else if (obj.fs.existsSync(obj.path.join(__dirname, 'winservice.js'))) { serviceFilePath = obj.path.join(__dirname, 'winservice.js'); }
if (serviceFilePath == null) { console.log('Unable to find winservice.js'); return; }
const svc = new obj.service({ name: 'MeshCentral', description: 'MeshCentral Remote Management Server', script: servicepath, env: env, wait: 2, grow: 0.5 });
svc.on('install', function () { console.log('MeshCentral service installed.'); svc.start(); });
svc.on('uninstall', function () { console.log('MeshCentral service uninstalled.'); process.exit(); });
svc.on('start', function () { console.log('MeshCentral service started.'); process.exit(); });
svc.on('stop', function () { console.log('MeshCentral service stopped.'); if (obj.args.stop) { process.exit(); } if (obj.args.restart) { console.log('Holding 5 seconds...'); setTimeout(function () { svc.start(); }, 5000); } });
svc.on('alreadyinstalled', function () { console.log('MeshCentral service already installed.'); process.exit(); });
svc.on('invalidinstallation', function () { console.log('Invalid MeshCentral service installation.'); process.exit(); });
if (obj.args.xinstall == true) { try { svc.install(); } catch (ex) { logException(ex); } }
if (obj.args.stop == true || obj.args.restart == true) { try { svc.stop(); } catch (ex) { logException(ex); } }
if (obj.args.start == true) { try { svc.start(); } catch (ex) { logException(ex); } }
if (obj.args.xuninstall == true) { try { svc.uninstall(); } catch (ex) { logException(ex); } }
return;
}
// Windows service install using the external winservice.js
if (obj.args.install == true) {
console.log('Installing MeshCentral as Windows Service...');
if (obj.fs.existsSync(servicepath) == false) { try { obj.fs.mkdirSync(servicepath); } catch (ex) { console.log('ERROR: Unable to create WinService folder: ' + ex); process.exit(); return; } }
try { obj.fs.createReadStream(obj.path.join(__dirname, 'winservice.js')).pipe(obj.fs.createWriteStream(obj.path.join(servicepath, 'winservice.js'))); } catch (ex) { console.log('ERROR: Unable to copy winservice.js: ' + ex); process.exit(); return; }
require('child_process').exec('node winservice.js --install', { maxBuffer: 512000, timeout: 120000, cwd: servicepath }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to install MeshCentral as a service: ' + error); process.exit(); return; }
console.log(stdout);
});
return;
} else if (obj.args.uninstall == true) {
console.log('Uninstalling MeshCentral Windows Service...');
if (obj.fs.existsSync(servicepath) == true) {
require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: servicepath }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
console.log(stdout);
try { obj.fs.unlinkSync(obj.path.join(servicepath, 'winservice.js')); } catch (ex) { }
try { obj.fs.rmdirSync(servicepath); } catch (ex) { }
});
} else if (obj.fs.existsSync(obj.path.join(__dirname, '../WinService')) == true) {
require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: obj.path.join(__dirname, '../WinService') }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
console.log(stdout);
try { obj.fs.unlinkSync(obj.path.join(__dirname, '../WinService/winservice.js')); } catch (ex) { }
try { obj.fs.rmdirSync(obj.path.join(__dirname, '../WinService')); } catch (ex) { }
});
} else {
require('child_process').exec('node winservice.js --uninstall', { maxBuffer: 512000, timeout: 120000, cwd: __dirname }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('ERROR: Unable to uninstall MeshCentral service: ' + error); process.exit(); return; }
console.log(stdout);
});
}
return;
}
}
// If "--launch" is in the arguments, launch now
if (obj.args.launch) {
if (obj.args.vault) { obj.StartVault(); } else { obj.StartEx(); }
} else {
// if "--launch" is not specified, launch the server as a child process.
const startArgs = [];
for (i in process.argv) {
if (i > 0) {
const arg = process.argv[i];
if ((arg.length > 0) && ((arg.indexOf(' ') >= 0) || (arg.indexOf('&') >= 0))) { startArgs.push(arg); } else { startArgs.push(arg); }
}
}
startArgs.push('--launch', process.pid);
obj.launchChildServer(startArgs);
}
};
// Launch MeshCentral as a child server and monitor it.
obj.launchChildServer = function (startArgs) {
const child_process = require('child_process');
const isInspectorAttached = (()=> { try { return require('node:inspector').url() !== undefined; } catch (_) { return false; } }).call();
const logFromChildProcess = isInspectorAttached ? () => {} : console.log.bind(console);
try { if (process.traceDeprecation === true) { startArgs.unshift('--trace-deprecation'); } } catch (ex) { }
try { if (process.traceProcessWarnings === true) { startArgs.unshift('--trace-warnings'); } } catch (ex) { }
if (startArgs[0] != "--disable-proto=delete") startArgs.unshift("--disable-proto=delete")
childProcess = child_process.execFile(process.argv[0], startArgs, { maxBuffer: Infinity, cwd: obj.parentpath }, function (error, stdout, stderr) {
if (childProcess.xrestart == 1) {
setTimeout(function () { obj.launchChildServer(startArgs); }, 500); // This is an expected restart.
} else if (childProcess.xrestart == 2) {
console.log('Expected exit...');
process.exit(); // User CTRL-C exit.
} else if (childProcess.xrestart == 3) {
// Server self-update exit
var version = '';
if (typeof obj.args.selfupdate == 'string') { version = '@' + obj.args.selfupdate; }
else if (typeof obj.args.specificupdate == 'string') { version = '@' + obj.args.specificupdate; delete obj.args.specificupdate; }
const child_process = require('child_process');
const npmpath = ((typeof obj.args.npmpath == 'string') ? obj.args.npmpath : 'npm');
const npmproxy = ((typeof obj.args.npmproxy == 'string') ? (' --proxy ' + obj.args.npmproxy) : '');
const env = Object.assign({}, process.env); // Shallow clone
if (typeof obj.args.npmproxy == 'string') { env['HTTP_PROXY'] = env['HTTPS_PROXY'] = env['http_proxy'] = env['https_proxy'] = obj.args.npmproxy; }
// always use --save-exact - https://stackoverflow.com/a/64507176/1210734
const xxprocess = child_process.exec(npmpath + ' install --save-exact --no-audit meshcentral' + version + npmproxy, { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) {
if ((error != null) && (error != '')) { console.log('Update failed: ' + error); }
});
xxprocess.data = '';
xxprocess.stdout.on('data', function (data) { xxprocess.data += data; });
xxprocess.stderr.on('data', function (data) { xxprocess.data += data; });
xxprocess.on('close', function (code) {
if (code == 0) { console.log('Update completed...'); }
// Run the server updated script if present
if (typeof obj.config.settings.runonserverupdated == 'string') {
const child_process = require('child_process');
var parentpath = __dirname;
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
child_process.exec(obj.config.settings.runonserverupdated + ' ' + getCurrentVersion(), { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) { });
}
if (obj.args.cleannpmcacheonupdate === true) {
// Perform NPM cache clean
console.log('Cleaning NPM cache...');
const xxxprocess = child_process.exec(npmpath + ' cache clean --force', { maxBuffer: Infinity, cwd: obj.parentpath, env: env }, function (error, stdout, stderr) { });
xxxprocess.on('close', function (code) { setTimeout(function () { obj.launchChildServer(startArgs); }, 1000); });
} else {
// Run the updated server
setTimeout(function () { obj.launchChildServer(startArgs); }, 1000);
}
});
} else {
if (error != null) {
// This is an un-expected restart
console.log(error);
console.log('ERROR: MeshCentral failed with critical error, check mesherrors.txt. Restarting in 5 seconds...');
setTimeout(function () { obj.launchChildServer(startArgs); }, 5000);
// Run the server error script if present
if (typeof obj.config.settings.runonservererror == 'string') {
const child_process = require('child_process');
var parentpath = __dirname;
if ((__dirname.endsWith('/node_modules/meshcentral')) || (__dirname.endsWith('\\node_modules\\meshcentral')) || (__dirname.endsWith('/node_modules/meshcentral/')) || (__dirname.endsWith('\\node_modules\\meshcentral\\'))) { parentpath = require('path').join(__dirname, '../..'); }
child_process.exec(obj.config.settings.runonservererror + ' ' + getCurrentVersion(), { maxBuffer: 512000, timeout: 120000, cwd: parentpath }, function (error, stdout, stderr) { });
}
}
}
});
childProcess.stdout.on('data', function (data) {
if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); }
if (data.indexOf('Updating settings folder...') >= 0) { childProcess.xrestart = 1; }
else if (data.indexOf('Updating server certificates...') >= 0) { childProcess.xrestart = 1; }
else if (data.indexOf('Server Ctrl-C exit...') >= 0) { childProcess.xrestart = 2; }
else if (data.indexOf('Starting self upgrade...') >= 0) { childProcess.xrestart = 3; }
else if (data.indexOf('Server restart...') >= 0) { childProcess.xrestart = 1; }
else if (data.indexOf('Starting self upgrade to: ') >= 0) { obj.args.specificupdate = data.substring(26).split('\r')[0].split('\n')[0]; childProcess.xrestart = 3; }
var datastr = data;
while (datastr.endsWith('\r') || datastr.endsWith('\n')) { datastr = datastr.substring(0, datastr.length - 1); }
logFromChildProcess(datastr);
});
childProcess.stderr.on('data', function (data) {
var datastr = data;
wh