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