Added server self-update support along with many fixes.

This commit is contained in:
Ylian Saint-Hilaire 2017-09-06 18:10:24 -07:00
parent de2e5402f6
commit ac53c7ae3c
7 changed files with 234 additions and 18 deletions

Binary file not shown.

172
agents/webapppush.js Normal file
View File

@ -0,0 +1,172 @@
/*
Copyright 2017 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.
*/
// Polyfill String.endsWith
if (!String.prototype.endsWith) {
String.prototype.endsWith = function (searchString, position) {
var subjectString = this.toString();
if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
position -= searchString.length;
var lastIndex = subjectString.lastIndexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
};
}
// Replace a string with a number if the string is an exact number
function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) == x)) { x = parseInt(x); } return x; }
// Convert decimal to hex
function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
// Convert a raw string to a hex string
function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
// Convert a buffer into a string
function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
// Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
function hex2rstr(d) {
if (typeof d != "string" || d.length == 0) return '';
var r = '', m = ('' + d).match(/../g), t;
while (t = m.shift()) r += String.fromCharCode('0x' + t);
return r
}
// Convert an object to string with all functions
function objToString(x, p, ret) {
if (ret == undefined) ret = '';
if (p == undefined) p = 0;
if (x == null) { return '[null]'; }
if (p > 8) { return '[...]'; }
if (x == undefined) { return '[undefined]'; }
if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
if (typeof x == 'buffer') { return '[buffer]'; }
if (typeof x != 'object') { return x; }
var r = '{' + (ret ? '\r\n' : ' ');
for (var i in x) { r += (addPad(p + 2, ret) + i + ': ' + objToString(x[i], p + 2, ret) + (ret ? '\r\n' : ' ')); }
return r + addPad(p, ret) + '}';
}
// Return p number of spaces
function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
// Split a string taking into account the quoats. Used for command line parsing
function splitArgs(str) {
var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
return myArray;
}
// Parse arguments string array into an object
function parseArgs(argv) {
var results = { '_': [] }, current = null;
for (var i = 1, len = argv.length; i < len; i++) {
var x = argv[i];
if (x.length > 2 && x[0] == '-' && x[1] == '-') {
if (current != null) { results[current] = true; }
current = x.substring(2);
} else {
if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
}
}
if (current != null) { results[current] = true; }
return results;
}
// Parge a URL string into an options object
function parseUrl(url) {
var x = url.split('/');
if (x.length < 4) return null;
var y = x[2].split(':');
var options = {};
var options = { protocol: x[0], hostname: y[0], path: '/' + x.splice(3).join('/') };
if (y.length == 1) { options.port = ((x[0] == 'https:') || (x[0] == 'wss:')) ? 443 : 80; } else { options.port = parseInt(y[1]); }
if (isNaN(options.port) == true) return null;
return options;
}
// Read a entire file into a buffer
function readFileToBuffer(filePath) {
try {
var fs = require('fs');
var stats = fs.statSync(filePath);
if (stats == null) { return null; }
var fileData = new Buffer(stats.size);
var fd = fs.openSync(filePath, 'r');
fs.readSync(fd, fileData, 0, stats.size, 0);
fs.closeSync(fd);
return fileData;
} catch (e) { return null; }
}
// Performs an HTTP get on a URL and return the data back
function makeHttpGetRequest(url, func) {
var http = require('http');
var request = http.get(url, function (res) {
var htmlData = '';
res.on('data', function (d) { htmlData += d; });
res.on('end', function (d) { func(res.statusCode, htmlData); });
}).on('error', function (e) { func(0, null); });
}
// Performs an HTTP get on a URL and return the data back (Alternative implementation)
function makeHttpGetRequest2(url, func) {
var http = require('http');
var options = http.parseUri(url);
options.username = 'admin';
options.password = 'P@ssw0rd';
var request = http.request(options, function (res) {
var htmlData = '';
res.on('data', function (d) { htmlData += d; });
res.on('end', function () { func(res.statusCode, htmlData); });
});
request.on('error', function (e) { func(0, null); });
request.end();
}
// Performs an HTTP get on a URL and return the data back (Alternative implementation)
function intelAmtSetStorage(url, buffer, func) {
var http = require('http');
var options = http.parseUri(url);
options.user = 'admin'; // TODO: Does not support HTTP digest auth yet!!!!!!!!!!!!!!!!
options.pass = 'P@ssw0rd';
var request = http.request(options, function (res) {
var htmlData = '';
res.on('data', function (d) { htmlData += d; });
res.on('end', function () { func(res.statusCode, htmlData); });
});
request.on('error', function (e) { func(0, null); });
request.end();
}
//console.log(objToString(db2, 2, ' '));
console.log('--- Start ---');
var fileData = readFileToBuffer('MeshCommander-Small.gz');
if (fileData != null) {
makeHttpGetRequest2('http://192.168.2.105:16992/index.htm', function (status, htmlData) { console.log(status, htmlData); });
/*
intelAmtSetStorage('http://192.168.2.105:16992/amt-storage/index.htm', fileData, function (status, htmlData) {
console.log('intelAmtSetStorage', status, htmlData);
});
*/
}
console.log('--- End ---');
//process.exit(2);

View File

@ -300,7 +300,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
// We have a location in the database for this remote IP
var iploc = nodes[0], x = {};
x.publicip = iploc.ip;
x.iploc = iploc.loc + ',' + (Math.floor((new Date(command.value.date)) / 1000));
x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000));
ChangeAgentLocationInfo(x);
} else {
// Check if we need to ask for the IP location

View File

@ -34,6 +34,8 @@ function CreateMeshCentralServer() {
obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha256 hash), size:(binary size), path:(binary path) }
obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha256 hash), size:(binary size), path:(binary path) }
obj.multiServer = null;
obj.currentVer = null;
obj.maintenanceTimer = null;
// Create data and files folders if needed
try { obj.fs.mkdirSync(obj.datapath); } catch (e) { }
@ -54,7 +56,7 @@ function CreateMeshCentralServer() {
try { require('./pass').hash('test', function () { }); } catch (e) { console.log('Old version of node, must upgrade.'); return; } // TODO: Not sure if this test works or not.
// Check for invalid arguments
var validArguments = ['_', 'notls', 'user', 'port', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport'];
var validArguments = ['_', 'notls', 'user', 'port', 'mpsport', 'redirport', 'cert', 'deletedomain', 'deletedefaultdomain', 'showusers', 'shownodes', 'showmeshes', 'showevents', 'showpower', 'showiplocations', 'help', 'exactports', 'install', 'uninstall', 'start', 'stop', 'restart', 'debug', 'filespath', 'datapath', 'noagentupdate', 'launch', 'noserverbackup', 'mongodb', 'mongodbcol', 'wanonly', 'lanonly', 'nousers', 'mpsdebug', 'mpspass', 'ciralocalfqdn', 'dbexport', 'dbimport', 'selfupdate'];
for (var arg in obj.args) { if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
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; }
@ -123,6 +125,14 @@ function CreateMeshCentralServer() {
} else if (xprocess.xrestart == 2) {
console.log('Expected exit...');
process.exit(); // User CTRL-C exit.
} else if (xprocess.xrestart == 3) {
// Server self-update exit
var child_process = require('child_process');
var xxprocess = child_process.exec('npm install meshcentral', { cwd: obj.path.join(__dirname, '../..') }, function (error, stdout, stderr) { });
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) { console.log('Update completed...'); setTimeout(function () { obj.launchChildServer(startLine); }, 1000); });
} else {
if (error != null) {
// This is an un-expected restart
@ -131,7 +141,7 @@ function CreateMeshCentralServer() {
}
}
});
xprocess.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) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } console.log(data); });
xprocess.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) { xprocess.xrestart = 1; } else if (data.indexOf('Server Ctrl-C exit...') >= 0) { xprocess.xrestart = 2; } else if (data.indexOf('Starting self upgrade...') >= 0) { xprocess.xrestart = 3; } console.log(data); });
xprocess.stderr.on('data', function (data) { if (data[data.length - 1] == '\n') { data = data.substring(0, data.length - 1); } obj.fs.appendFileSync('mesherrors.txt', '-------- ' + new Date().toLocaleString() + ' --------\r\n\r\n' + data + '\r\n\r\n\r\n'); });
xprocess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } });
}
@ -140,25 +150,22 @@ function CreateMeshCentralServer() {
obj.getLatestServerVersion = function (callback) {
if (callback == undefined) return;
var child_process = require('child_process');
var xprocess = child_process.exec('npm view meshcentral dist-tags.latest', function (error, stdout, stderr) {
if (xprocess.xrestart == true) {
setTimeout(function () { obj.launchChildServer(startLine); }, 500); // If exit with restart requested, restart the server.
} else {
if (error != null) { console.log('ERROR: Unable to start MeshCentral: ' + error); process.exit(); }
}
});
var xprocess = child_process.exec('npm view meshcentral dist-tags.latest', function (error, stdout, stderr) { });
xprocess.data = '';
xprocess.stdout.on('data', function (data) { xprocess.data += data; });
xprocess.stderr.on('data', function (data) { });
xprocess.on('close', function (code) {
var currentVer = null;
try { currentVer = JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version; } catch (e) { }
try { currentVer = JSON.parse(require('fs').readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { }
var latestVer = null;
if (code == 0) { try { latestVer = xprocess.data.split(' ').join('').split('\r').join('').split('\n').join(''); } catch (e) { } }
callback(currentVer, latestVer);
});
}
// Initiate server self-update
obj.performServerUpdate = function () { console.log('Starting self upgrade...'); process.exit(200); }
obj.StartEx = function () {
// Look to see if data and/or file path is specified
if (obj.args.datapath) { obj.datapath = obj.args.datapath; }
@ -310,6 +317,9 @@ function CreateMeshCentralServer() {
obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates);
}
// Start periodic maintenance
obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
// Dispatch an event that the server is now running
obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' })
@ -320,6 +330,27 @@ function CreateMeshCentralServer() {
});
}
// Perform maintenance operations (called every hour)
obj.maintenanceActions = function () {
// Check if we need to perform server self-update
if (obj.args.selfupdate == true) {
obj.db.getValueOfTheDay('performSelfUpdate', 1, function (performSelfUpdate) {
if (performSelfUpdate.value > 0) {
performSelfUpdate.value--;
obj.db.Set(performSelfUpdate);
obj.getLatestServerVersion(function (currentVer, latestVer) { if (currentVer != latestVer) { obj.performServerUpdate(); return; } });
}
});
}
// Clear old event entries and power entires
obj.db.clearOldEntries('event', 30); // Clear all event entires that are older than 30 days.
obj.db.clearOldEntries('power', 10); // Clear all event entires that are older than 10 days. If a node is connected longer than 10 days, current power state will be used for everything.
// Perform other database cleanup
obj.db.cleanup();
}
// Stop the Meshcentral server
obj.Stop = function (restoreFile) {
// If the database is not setup, exit now.

View File

@ -1,6 +1,6 @@
{
"name": "meshcentral",
"version": "0.0.7-g",
"version": "0.0.7-n",
"keywords": [
"Remote Management",
"Intel AMT",

View File

@ -616,7 +616,6 @@
var features = {{{features}}};
var serverPublicNamePort = "{{{serverDnsName}}}:{{{serverPublicPort}}}";
var amtScanResults = null;
//var xxmap = null;
function startup() {
// Guard against other site's top frames (web bugs).
@ -852,14 +851,17 @@
}
case 'serverversion': {
if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerUpdate')) {
console.log(message);
var x = '<div style=width:100%;max-height:260px;overflow-x:hidden;overflow-y:auto;line-height:160%>';
if (!message.current) { message.current = 'Unknown'; }
if (!message.latest) { message.latest = 'Unknown'; }
x += addHtmlValue2('Current Version', '<b>' + EscapeHtml(message.current) + '</b>');
x += addHtmlValue2('Latest Version', '<b>' + EscapeHtml(message.latest) + '</b>');
x += '</div>';
QH('d2verinfo', x);
if (message.current == message.latest) {
setDialogMode(2, "MeshCentral Version", 1, null, x);
} else {
setDialogMode(2, "MeshCentral Version", 3, server_showVersionDlgEx, x + '<br />Select OK to start server self-update.');
}
}
break;
}
@ -1187,7 +1189,7 @@
QV('devMapToolbar', view == 3);
QV('devListToolbarSort', view < 3);
if (view == 3) {
setTimeout( function() { xxmap.map.updateSize();}, 200);
setTimeout( function() { if (xxmap.map != null) { xxmap.map.updateSize(); } }, 200);
// TODO
} else {
// 3 wide or list view
@ -3688,10 +3690,14 @@
function server_showVersionDlg() {
if (xxdialogMode) return;
setDialogMode(2, "MeshCentral Version", 1, null, "<div id=d2verinfo>Loading...</div>", 'MeshCentralServerUpdate');
setDialogMode(2, "MeshCentral Version", 1, null, "Loading...", 'MeshCentralServerUpdate');
meshserver.Send({ action: 'serverversion' });
}
function server_showVersionDlgEx() {
meshserver.Send({ action: 'serverupdate' });
}
//
// MY MESHS
//

View File

@ -1165,6 +1165,13 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
obj.parent.getLatestServerVersion(function (currentVersion, latestVersion) { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); });
break;
}
case 'serverupdate':
{
// Perform server update
if ((user.siteadmin & 16) == 0) break;
obj.parent.performServerUpdate();
break;
}
case 'createmesh':
{
// Create mesh