Added server self-update support along with many fixes.

Ylian Saint-Hilaire committed Sep 6, 2017 at 18:10 UTC ac53c7ae3cb67ba84c4551e3c36866166c83fc8d
7 files changed +234 -18
agents/MeshCommander-Small.gz
Binary files /dev/null and b/agents/MeshCommander-Small.gz differ
agents/webapppush.js new
+172
@@ -0,0 +1,172 @@
1 +/*
2 +Copyright 2017 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 +
18 +// Polyfill String.endsWith
19 +if (!String.prototype.endsWith) {
20 + String.prototype.endsWith = function (searchString, position) {
21 + var subjectString = this.toString();
22 + if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) { position = subjectString.length; }
23 + position -= searchString.length;
24 + var lastIndex = subjectString.lastIndexOf(searchString, position);
25 + return lastIndex !== -1 && lastIndex === position;
26 + };
27 +}
28 +
29 +// Replace a string with a number if the string is an exact number
30 +function toNumberIfNumber(x) { if ((typeof x == 'string') && (+parseInt(x) == x)) { x = parseInt(x); } return x; }
31 +
32 +// Convert decimal to hex
33 +function char2hex(i) { return (i + 0x100).toString(16).substr(-2).toUpperCase(); }
34 +
35 +// Convert a raw string to a hex string
36 +function rstr2hex(input) { var r = '', i; for (i = 0; i < input.length; i++) { r += char2hex(input.charCodeAt(i)); } return r; }
37 +
38 +// Convert a buffer into a string
39 +function buf2rstr(buf) { var r = ''; for (var i = 0; i < buf.length; i++) { r += String.fromCharCode(buf[i]); } return r; }
40 +
41 +// Convert a hex string to a raw string // TODO: Do this using Buffer(), will be MUCH faster
42 +function hex2rstr(d) {
43 + if (typeof d != "string" || d.length == 0) return '';
44 + var r = '', m = ('' + d).match(/../g), t;
45 + while (t = m.shift()) r += String.fromCharCode('0x' + t);
46 + return r
47 +}
48 +
49 +// Convert an object to string with all functions
50 +function objToString(x, p, ret) {
51 + if (ret == undefined) ret = '';
52 + if (p == undefined) p = 0;
53 + if (x == null) { return '[null]'; }
54 + if (p > 8) { return '[...]'; }
55 + if (x == undefined) { return '[undefined]'; }
56 + if (typeof x == 'string') { if (p == 0) return x; return '"' + x + '"'; }
57 + if (typeof x == 'buffer') { return '[buffer]'; }
58 + if (typeof x != 'object') { return x; }
59 + var r = '{' + (ret ? '\r\n' : ' ');
60 + for (var i in x) { r += (addPad(p + 2, ret) + i + ': ' + objToString(x[i], p + 2, ret) + (ret ? '\r\n' : ' ')); }
61 + return r + addPad(p, ret) + '}';
62 +}
63 +
64 +// Return p number of spaces
65 +function addPad(p, ret) { var r = ''; for (var i = 0; i < p; i++) { r += ret; } return r; }
66 +
67 +// Split a string taking into account the quoats. Used for command line parsing
68 +function splitArgs(str) {
69 + var myArray = [], myRegexp = /[^\s"]+|"([^"]*)"/gi;
70 + do { var match = myRegexp.exec(str); if (match != null) { myArray.push(match[1] ? match[1] : match[0]); } } while (match != null);
71 + return myArray;
72 +}
73 +
74 +// Parse arguments string array into an object
75 +function parseArgs(argv) {
76 + var results = { '_': [] }, current = null;
77 + for (var i = 1, len = argv.length; i < len; i++) {
78 + var x = argv[i];
79 + if (x.length > 2 && x[0] == '-' && x[1] == '-') {
80 + if (current != null) { results[current] = true; }
81 + current = x.substring(2);
82 + } else {
83 + if (current != null) { results[current] = toNumberIfNumber(x); current = null; } else { results['_'].push(toNumberIfNumber(x)); }
84 + }
85 + }
86 + if (current != null) { results[current] = true; }
87 + return results;
88 +}
89 +
90 +// Parge a URL string into an options object
91 +function parseUrl(url) {
92 + var x = url.split('/');
93 + if (x.length < 4) return null;
94 + var y = x[2].split(':');
95 + var options = {};
96 + var options = { protocol: x[0], hostname: y[0], path: '/' + x.splice(3).join('/') };
97 + if (y.length == 1) { options.port = ((x[0] == 'https:') || (x[0] == 'wss:')) ? 443 : 80; } else { options.port = parseInt(y[1]); }
98 + if (isNaN(options.port) == true) return null;
99 + return options;
100 +}
101 +
102 +// Read a entire file into a buffer
103 +function readFileToBuffer(filePath) {
104 + try {
105 + var fs = require('fs');
106 + var stats = fs.statSync(filePath);
107 + if (stats == null) { return null; }
108 + var fileData = new Buffer(stats.size);
109 + var fd = fs.openSync(filePath, 'r');
110 + fs.readSync(fd, fileData, 0, stats.size, 0);
111 + fs.closeSync(fd);
112 + return fileData;
113 + } catch (e) { return null; }
114 +}
115 +
116 +// Performs an HTTP get on a URL and return the data back
117 +function makeHttpGetRequest(url, func) {
118 + var http = require('http');
119 + var request = http.get(url, function (res) {
120 + var htmlData = '';
121 + res.on('data', function (d) { htmlData += d; });
122 + res.on('end', function (d) { func(res.statusCode, htmlData); });
123 + }).on('error', function (e) { func(0, null); });
124 +}
125 +
126 +// Performs an HTTP get on a URL and return the data back (Alternative implementation)
127 +function makeHttpGetRequest2(url, func) {
128 + var http = require('http');
129 + var options = http.parseUri(url);
130 + options.username = 'admin';
131 + options.password = 'P@ssw0rd';
132 + var request = http.request(options, function (res) {
133 + var htmlData = '';
134 + res.on('data', function (d) { htmlData += d; });
135 + res.on('end', function () { func(res.statusCode, htmlData); });
136 + });
137 + request.on('error', function (e) { func(0, null); });
138 + request.end();
139 +}
140 +
141 +// Performs an HTTP get on a URL and return the data back (Alternative implementation)
142 +function intelAmtSetStorage(url, buffer, func) {
143 + var http = require('http');
144 + var options = http.parseUri(url);
145 + options.user = 'admin'; // TODO: Does not support HTTP digest auth yet!!!!!!!!!!!!!!!!
146 + options.pass = 'P@ssw0rd';
147 + var request = http.request(options, function (res) {
148 + var htmlData = '';
149 + res.on('data', function (d) { htmlData += d; });
150 + res.on('end', function () { func(res.statusCode, htmlData); });
151 + });
152 + request.on('error', function (e) { func(0, null); });
153 + request.end();
154 +}
155 +
156 +//console.log(objToString(db2, 2, ' '));
157 +
158 +console.log('--- Start ---');
159 +
160 +var fileData = readFileToBuffer('MeshCommander-Small.gz');
161 +if (fileData != null) {
162 + makeHttpGetRequest2('http://192.168.2.105:16992/index.htm', function (status, htmlData) { console.log(status, htmlData); });
163 +
164 + /*
165 + intelAmtSetStorage('http://192.168.2.105:16992/amt-storage/index.htm', fileData, function (status, htmlData) {
166 + console.log('intelAmtSetStorage', status, htmlData);
167 + });
168 + */
169 +}
170 +
171 +console.log('--- End ---');
172 +//process.exit(2);
\ No newline at end of file
meshagent.js
+1 -1
@@ -300,7 +300,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) {
300 // We have a location in the database for this remote IP
301 var iploc = nodes[0], x = {};
302 x.publicip = iploc.ip;
303 - x.iploc = iploc.loc + ',' + (Math.floor((new Date(command.value.date)) / 1000));
303 + x.iploc = iploc.loc + ',' + (Math.floor((new Date(iploc.date)) / 1000));
304 ChangeAgentLocationInfo(x);
305 } else {
306 // Check if we need to ask for the IP location
meshcentral.js
+42 -11
@@ -34,6 +34,8 @@ function CreateMeshCentralServer() {
34 obj.meshAgentBinaries = {}; // Mesh Agent Binaries, Architecture type --> { hash:(sha256 hash), size:(binary size), path:(binary path) }
35 obj.meshAgentInstallScripts = {}; // Mesh Install Scripts, Script ID -- { hash:(sha256 hash), size:(binary size), path:(binary path) }
36 obj.multiServer = null;
37 + obj.currentVer = null;
38 + obj.maintenanceTimer = null;
39
40 // Create data and files folders if needed
41 try { obj.fs.mkdirSync(obj.datapath); } catch (e) { }
@@ -54,7 +56,7 @@ function CreateMeshCentralServer() {
56 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.
57
58 // Check for invalid arguments
57 - 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'];
59 + 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'];
60 for (var arg in obj.args) { if (validArguments.indexOf(arg.toLocaleLowerCase()) == -1) { console.log('Invalid argument "' + arg + '", use --help.'); return; } }
61 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; }
62
@@ -123,6 +125,14 @@ function CreateMeshCentralServer() {
125 } else if (xprocess.xrestart == 2) {
126 console.log('Expected exit...');
127 process.exit(); // User CTRL-C exit.
128 + } else if (xprocess.xrestart == 3) {
129 + // Server self-update exit
130 + var child_process = require('child_process');
131 + var xxprocess = child_process.exec('npm install meshcentral', { cwd: obj.path.join(__dirname, '../..') }, function (error, stdout, stderr) { });
132 + xxprocess.data = '';
133 + xxprocess.stdout.on('data', function (data) { xxprocess.data += data; });
134 + xxprocess.stderr.on('data', function (data) { xxprocess.data += data; });
135 + xxprocess.on('close', function (code) { console.log('Update completed...'); setTimeout(function () { obj.launchChildServer(startLine); }, 1000); });
136 } else {
137 if (error != null) {
138 // This is an un-expected restart
@@ -131,7 +141,7 @@ function CreateMeshCentralServer() {
141 }
142 }
143 });
134 - 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); });
144 + 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); });
145 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'); });
146 xprocess.on('close', function (code) { if ((code != 0) && (code != 123)) { /* console.log("Exited with code " + code); */ } });
147 }
@@ -140,25 +150,22 @@ function CreateMeshCentralServer() {
150 obj.getLatestServerVersion = function (callback) {
151 if (callback == undefined) return;
152 var child_process = require('child_process');
143 - var xprocess = child_process.exec('npm view meshcentral dist-tags.latest', function (error, stdout, stderr) {
144 - if (xprocess.xrestart == true) {
145 - setTimeout(function () { obj.launchChildServer(startLine); }, 500); // If exit with restart requested, restart the server.
146 - } else {
147 - if (error != null) { console.log('ERROR: Unable to start MeshCentral: ' + error); process.exit(); }
148 - }
149 - });
153 + var xprocess = child_process.exec('npm view meshcentral dist-tags.latest', function (error, stdout, stderr) { });
154 xprocess.data = '';
155 xprocess.stdout.on('data', function (data) { xprocess.data += data; });
156 xprocess.stderr.on('data', function (data) { });
157 xprocess.on('close', function (code) {
158 var currentVer = null;
155 - try { currentVer = JSON.parse(require('fs').readFileSync('package.json', 'utf8')).version; } catch (e) { }
159 + try { currentVer = JSON.parse(require('fs').readFileSync(obj.path.join(__dirname, 'package.json'), 'utf8')).version; } catch (e) { }
160 var latestVer = null;
161 if (code == 0) { try { latestVer = xprocess.data.split(' ').join('').split('\r').join('').split('\n').join(''); } catch (e) { } }
162 callback(currentVer, latestVer);
163 });
164 }
165
166 + // Initiate server self-update
167 + obj.performServerUpdate = function () { console.log('Starting self upgrade...'); process.exit(200); }
168 +
169 obj.StartEx = function () {
170 // Look to see if data and/or file path is specified
171 if (obj.args.datapath) { obj.datapath = obj.args.datapath; }
@@ -310,6 +317,9 @@ function CreateMeshCentralServer() {
317 obj.mpsserver = require('./mpsserver.js').CreateMpsServer(obj, obj.db, obj.args, obj.certificates);
318 }
319
320 + // Start periodic maintenance
321 + obj.maintenanceTimer = setInterval(obj.maintenanceActions, 1000 * 60 * 60); // Run this every hour
322 +
323 // Dispatch an event that the server is now running
324 obj.DispatchEvent(['*'], obj, { etype: 'server', action: 'started', msg: 'Server started' })
325
@@ -319,7 +329,28 @@ function CreateMeshCentralServer() {
329 });
330 });
331 }
322 -
332 +
333 + // Perform maintenance operations (called every hour)
334 + obj.maintenanceActions = function () {
335 + // Check if we need to perform server self-update
336 + if (obj.args.selfupdate == true) {
337 + obj.db.getValueOfTheDay('performSelfUpdate', 1, function (performSelfUpdate) {
338 + if (performSelfUpdate.value > 0) {
339 + performSelfUpdate.value--;
340 + obj.db.Set(performSelfUpdate);
341 + obj.getLatestServerVersion(function (currentVer, latestVer) { if (currentVer != latestVer) { obj.performServerUpdate(); return; } });
342 + }
343 + });
344 + }
345 +
346 + // Clear old event entries and power entires
347 + obj.db.clearOldEntries('event', 30); // Clear all event entires that are older than 30 days.
348 + 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.
349 +
350 + // Perform other database cleanup
351 + obj.db.cleanup();
352 + }
353 +
354 // Stop the Meshcentral server
355 obj.Stop = function (restoreFile) {
356 // If the database is not setup, exit now.
package.json
+1 -1
@@ -1,6 +1,6 @@
1 {
2 "name": "meshcentral",
3 - "version": "0.0.7-g",
3 + "version": "0.0.7-n",
4 "keywords": [
5 "Remote Management",
6 "Intel AMT",
views/default.handlebars
+11 -5
@@ -616,7 +616,6 @@
616 var features = {{{features}}};
617 var serverPublicNamePort = "{{{serverDnsName}}}:{{{serverPublicPort}}}";
618 var amtScanResults = null;
619 - //var xxmap = null;
619
620 function startup() {
621 // Guard against other site's top frames (web bugs).
@@ -852,14 +851,17 @@
851 }
852 case 'serverversion': {
853 if ((xxdialogMode == 2) && (xxdialogTag == 'MeshCentralServerUpdate')) {
855 - console.log(message);
854 var x = '<div style=width:100%;max-height:260px;overflow-x:hidden;overflow-y:auto;line-height:160%>';
855 if (!message.current) { message.current = 'Unknown'; }
856 if (!message.latest) { message.latest = 'Unknown'; }
857 x += addHtmlValue2('Current Version', '<b>' + EscapeHtml(message.current) + '</b>');
858 x += addHtmlValue2('Latest Version', '<b>' + EscapeHtml(message.latest) + '</b>');
859 x += '</div>';
862 - QH('d2verinfo', x);
860 + if (message.current == message.latest) {
861 + setDialogMode(2, "MeshCentral Version", 1, null, x);
862 + } else {
863 + setDialogMode(2, "MeshCentral Version", 3, server_showVersionDlgEx, x + '<br />Select OK to start server self-update.');
864 + }
865 }
866 break;
867 }
@@ -1187,7 +1189,7 @@
1189 QV('devMapToolbar', view == 3);
1190 QV('devListToolbarSort', view < 3);
1191 if (view == 3) {
1190 - setTimeout( function() { xxmap.map.updateSize();}, 200);
1192 + setTimeout( function() { if (xxmap.map != null) { xxmap.map.updateSize(); } }, 200);
1193 // TODO
1194 } else {
1195 // 3 wide or list view
@@ -3688,10 +3690,14 @@
3690
3691 function server_showVersionDlg() {
3692 if (xxdialogMode) return;
3691 - setDialogMode(2, "MeshCentral Version", 1, null, "<div id=d2verinfo>Loading...</div>", 'MeshCentralServerUpdate');
3693 + setDialogMode(2, "MeshCentral Version", 1, null, "Loading...", 'MeshCentralServerUpdate');
3694 meshserver.Send({ action: 'serverversion' });
3695 }
3696
3697 + function server_showVersionDlgEx() {
3698 + meshserver.Send({ action: 'serverupdate' });
3699 + }
3700 +
3701 //
3702 // MY MESHS
3703 //
webserver.js
+7
@@ -1165,6 +1165,13 @@ module.exports.CreateWebServer = function (parent, db, args, secret, certificate
1165 obj.parent.getLatestServerVersion(function (currentVersion, latestVersion) { ws.send(JSON.stringify({ action: 'serverversion', current: currentVersion, latest: latestVersion })); });
1166 break;
1167 }
1168 + case 'serverupdate':
1169 + {
1170 + // Perform server update
1171 + if ((user.siteadmin & 16) == 0) break;
1172 + obj.parent.performServerUpdate();
1173 + break;
1174 + }
1175 case 'createmesh':
1176 {
1177 // Create mesh