Fix archiver error, add backup options and SQLite maintenance (#6487)
PTR committed
Nov 3, 2024 at 19:44 UTC
e58d659fa94a74e6bbbef5198f85ec10e8a3993e
3 files changed
+425
-245
db.js
+380
-235
@@ -32,6 +32,20 @@ module.exports.CreateDB = function (parent, func) {
32
var expirePowerEventsSeconds = (60 * 60 * 24 * 10); // By default, expire power events after 10 days (864000). (Seconds * Minutes * Hours * Days)
33
var expireServerStatsSeconds = (60 * 60 * 24 * 30); // By default, expire power events after 30 days (2592000). (Seconds * Minutes * Hours * Days)
34
const common = require('./common.js');
35
+ const path = require('path');
36
+ const fs = require('fs');
37
+ const DB_NEDB = 1, DB_MONGOJS = 2, DB_MONGODB = 3,DB_MARIADB = 4, DB_MYSQL = 5, DB_POSTGRESQL = 6, DB_ACEBASE = 7, DB_SQLITE = 8;
38
+ const DB_LIST = ['None', 'NeDB', 'MongoJS', 'MongoDB', 'MariaDB', 'MySQL', 'PostgreSQL', 'AceBase', 'SQLite']; //for the info command
39
+ let databaseName = 'meshcentral';
40
+ let datapathParentPath = path.dirname(parent.datapath);
41
+ let datapathFoldername = path.basename(parent.datapath);
42
+ obj.performingBackup = false;
43
+ const BACKUPFAIL_ZIPCREATE = 0x0001;
44
+ const BACKUPFAIL_ZIPMODULE = 0x0010;
45
+ const BACKUPFAIL_DBDUMP = 0x0100;
46
+ let backupStatus = 0x0;
47
+ let newAutoBackupFile;
48
+ let newDBDumpFile;
49
obj.identifier = null;
50
obj.dbKey = null;
51
obj.dbRecordsEncryptKey = null;
@@ -105,16 +119,16 @@ module.exports.CreateDB = function (parent, func) {
119
120
// Perform database maintenance
121
obj.maintenance = function () {
108
- if (obj.databaseType == 1) { // NeDB will not remove expired records unless we try to access them. This will force the removal.
122
+ if (obj.databaseType == DB_NEDB) { // NeDB will not remove expired records unless we try to access them. This will force the removal.
123
obj.eventsfile.remove({ time: { '$lt': new Date(Date.now() - (expireEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
124
obj.powerfile.remove({ time: { '$lt': new Date(Date.now() - (expirePowerEventsSeconds * 1000)) } }, { multi: true }); // Force delete older events
125
obj.serverstatsfile.remove({ time: { '$lt': new Date(Date.now() - (expireServerStatsSeconds * 1000)) } }, { multi: true }); // Force delete older events
112
- } else if ((obj.databaseType == 4) || (obj.databaseType == 5)) { // MariaDB or MySQL
126
+ } else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) { // MariaDB or MySQL
127
sqlDbQuery('DELETE FROM events WHERE time < ?', [new Date(Date.now() - (expireEventsSeconds * 1000))], function (doc, err) { }); // Delete events older than expireEventsSeconds
128
sqlDbQuery('DELETE FROM power WHERE time < ?', [new Date(Date.now() - (expirePowerEventsSeconds * 1000))], function (doc, err) { }); // Delete events older than expirePowerSeconds
129
sqlDbQuery('DELETE FROM serverstats WHERE expire < ?', [new Date()], function (doc, err) { }); // Delete events where expiration date is in the past
130
sqlDbQuery('DELETE FROM smbios WHERE expire < ?', [new Date()], function (doc, err) { }); // Delete events where expiration date is in the past
117
- } else if (obj.databaseType == 7) { // AceBase
131
+ } else if (obj.databaseType == DB_ACEBASE) { // AceBase
132
//console.log('Performing AceBase maintenance');
133
obj.file.query('events').filter('time', '<', new Date(Date.now() - (expireEventsSeconds * 1000))).remove().then(function () {
134
obj.file.query('stats').filter('time', '<', new Date(Date.now() - (expireServerStatsSeconds * 1000))).remove().then(function () {
@@ -123,8 +137,13 @@ module.exports.CreateDB = function (parent, func) {
137
});
138
});
139
});
126
- } else if (obj.databaseType == 8) { // SQLite3
127
- // TODO
140
+ } else if (obj.databaseType == DB_SQLITE) { // SQLite3
141
+ // TODO: Combine with others?
142
+ sqlDbQuery('DELETE FROM events WHERE time < ?', [new Date(Date.now() - (expireEventsSeconds * 1000))], function (doc, err) { });
143
+ sqlDbQuery('DELETE FROM power WHERE time < ?', [new Date(Date.now() - (expirePowerEventsSeconds * 1000))], function (doc, err) { });
144
+ sqlDbQuery('DELETE FROM serverstats WHERE expire < ?', [new Date()], function (doc, err) { });
145
+ sqlDbQuery('DELETE FROM smbios WHERE expire < ?', [new Date()], function (doc, err) { });
146
+ obj.file.run( 'PRAGMA optimize;' ); //see https://sqlite.org/pragma.html#pragma_optimize
147
}
148
obj.removeInactiveDevices();
149
}
@@ -245,18 +264,18 @@ module.exports.CreateDB = function (parent, func) {
264
obj.removeDomain = function (domainName, func) {
265
var pendingCalls;
266
// Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
248
- if (obj.databaseType == 7) {
267
+ if (obj.databaseType == DB_ACEBASE) {
268
// AceBase
269
pendingCalls = 3;
270
obj.file.query('meshcentral').filter('domain', '==', domainName).remove().then(function () { if (--pendingCalls == 0) { func(); } });
271
obj.file.query('events').filter('domain', '==', domainName).remove().then(function () { if (--pendingCalls == 0) { func(); } });
272
obj.file.query('power').filter('domain', '==', domainName).remove().then(function () { if (--pendingCalls == 0) { func(); } });
254
- } else if ((obj.databaseType == 4) || (obj.databaseType == 5) || (obj.databaseType == 6)) {
273
+ } else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL) || (obj.databaseType == DB_POSTGRESQL)) {
274
// MariaDB, MySQL or PostgreSQL
275
pendingCalls = 2;
276
sqlDbQuery('DELETE FROM main WHERE domain = $1', [domainName], function () { if (--pendingCalls == 0) { func(); } });
277
sqlDbQuery('DELETE FROM events WHERE domain = $1', [domainName], function () { if (--pendingCalls == 0) { func(); } });
259
- } else if (obj.databaseType == 3) {
278
+ } else if (obj.databaseType == DB_MONGODB) {
279
// MongoDB
280
pendingCalls = 3;
281
obj.file.deleteMany({ domain: domainName }, { multi: true }, function () { if (--pendingCalls == 0) { func(); } });
@@ -276,17 +295,17 @@ module.exports.CreateDB = function (parent, func) {
295
// TODO: Remove all meshes that dont have any links
296
297
// Remove all events, power events and SMBIOS data from the main collection. They are all in seperate collections now.
279
- if ((obj.databaseType == 4) || (obj.databaseType == 5) || (obj.databaseType == 6)) {
298
+ if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL) || (obj.databaseType == DB_POSTGRESQL)) {
299
// MariaDB, MySQL or PostgreSQL
300
obj.RemoveAllOfType('event', function () { });
301
obj.RemoveAllOfType('power', function () { });
302
obj.RemoveAllOfType('smbios', function () { });
284
- } else if (obj.databaseType == 3) {
303
+ } else if (obj.databaseType == DB_MONGODB) {
304
// MongoDB
305
obj.file.deleteMany({ type: 'event' }, { multi: true });
306
obj.file.deleteMany({ type: 'power' }, { multi: true });
307
obj.file.deleteMany({ type: 'smbios' }, { multi: true });
289
- } else if ((obj.databaseType == 1) || (obj.databaseType == 2)) {
308
+ } else if ((obj.databaseType == DB_NEDB) || (obj.databaseType == DB_MONGOJS)) {
309
// NeDB or MongoJS
310
obj.file.remove({ type: 'event' }, { multi: true });
311
obj.file.remove({ type: 'power' }, { multi: true });
@@ -387,19 +406,19 @@ module.exports.CreateDB = function (parent, func) {
406
if (meshChange) { obj.Set(docs[i]); }
407
}
408
}
390
- if (obj.databaseType == 8) {
409
+ if (obj.databaseType == DB_SQLITE) {
410
// SQLite
411
393
- } else if (obj.databaseType == 7) {
412
+ } else if (obj.databaseType == DB_ACEBASE) {
413
// AceBase
414
396
- } else if (obj.databaseType == 6) {
415
+ } else if (obj.databaseType == DB_POSTGRESQL) {
416
// Postgres
417
sqlDbQuery('DELETE FROM Main WHERE ((extra != NULL) AND (extra LIKE (\'mesh/%\')) AND (extra != ANY ($1)))', [meshlist], function (err, response) { });
399
- } else if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
418
+ } else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) {
419
// MariaDB
420
sqlDbQuery('DELETE FROM Main WHERE (extra LIKE ("mesh/%") AND (extra NOT IN ?)', [meshlist], function (err, response) { });
402
- } else if (obj.databaseType == 3) {
421
+ } else if (obj.databaseType == DB_MONGODB) {
422
// MongoDB
423
obj.file.deleteMany({ meshid: { $exists: true, $nin: meshlist } }, { multi: true });
424
} else {
@@ -504,33 +523,33 @@ module.exports.CreateDB = function (parent, func) {
523
// Get the number of records in the database for various types, this is the slow NeDB way.
524
// WARNING: This is a terrible query for database performance. Only do this when needed. This query will look at almost every document in the database.
525
obj.getStats = function (func) {
507
- if (obj.databaseType == 7) {
526
+ if (obj.databaseType == DB_ACEBASE) {
527
// AceBase
528
// TODO
510
- } else if (obj.databaseType == 6) {
529
+ } else if (obj.databaseType == DB_POSTGRESQL) {
530
// PostgreSQL
531
// TODO
513
- } else if (obj.databaseType == 5) {
532
+ } else if (obj.databaseType == DB_MYSQL) {
533
// MySQL
534
// TODO
516
- } else if (obj.databaseType == 4) {
535
+ } else if (obj.databaseType == DB_MARIADB) {
536
// MariaDB
537
// TODO
519
- } else if (obj.databaseType == 3) {
538
+ } else if (obj.databaseType == DB_MONGODB) {
539
// MongoDB
540
obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }]).toArray(function (err, docs) {
541
var counters = {}, totalCount = 0;
542
if (err == null) { for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } } }
543
func(counters);
544
});
526
- } else if (obj.databaseType == 2) {
545
+ } else if (obj.databaseType == DB_MONGOJS) {
546
// MongoJS
547
obj.file.aggregate([{ "$group": { _id: "$type", count: { $sum: 1 } } }], function (err, docs) {
548
var counters = {}, totalCount = 0;
549
if (err == null) { for (var i in docs) { if (docs[i]._id != null) { counters[docs[i]._id] = docs[i].count; totalCount += docs[i].count; } } }
550
func(counters);
551
});
533
- } else if (obj.databaseType == 1) {
552
+ } else if (obj.databaseType == DB_NEDB) {
553
// NeDB version
554
obj.file.count({ type: 'node' }, function (err, nodeCount) {
555
obj.file.count({ type: 'mesh' }, function (err, meshCount) {
@@ -570,7 +589,7 @@ module.exports.CreateDB = function (parent, func) {
589
if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
590
obj.GetAllType('mesh', function (err, docs) {
591
if (err == null) { for (var i in docs) { count++; obj.Set(docs[i]); } }
573
- if (obj.databaseType == 1) { // If we are using NeDB, compact the database.
592
+ if (obj.databaseType == DB_NEDB) { // If we are using NeDB, compact the database.
593
obj.file.persistence.compactDatafile();
594
obj.file.on('compaction.done', function () { func(count); }); // It's important to wait for compaction to finish before exit, otherwise NeDB may corrupt.
595
} else {
@@ -721,13 +740,15 @@ module.exports.CreateDB = function (parent, func) {
740
741
if (parent.args.sqlite3) {
742
// SQLite3 database setup
724
- obj.databaseType = 8;
743
+ obj.databaseType = DB_SQLITE;
744
const sqlite3 = require('sqlite3');
726
- obj.file = new sqlite3.Database(parent.path.join(parent.datapath, 'meshcentral.sqlite'), sqlite3.OPEN_READWRITE, function (err) {
745
+ if (typeof parent.config.settings.sqlite3 == 'string') {databaseName = parent.config.settings.sqlite3};
746
+ //use sqlite3 cache mode https://github.com/TryGhost/node-sqlite3/wiki/Caching#caching
747
+ obj.file = new sqlite3.cached.Database(parent.path.join(parent.datapath, databaseName + '.sqlite'), sqlite3.OPEN_READWRITE, function (err) {
748
if (err && (err.code == 'SQLITE_CANTOPEN')) {
749
// Database needs to be created
729
- obj.file = new sqlite3.Database(parent.path.join(parent.datapath, 'meshcentral.sqlite'), function (err) {
730
- if (err) { console.log("SQLite Error: " + err); exit(1); return; }
750
+ obj.file = new sqlite3.Database(parent.path.join(parent.datapath, databaseName + '.sqlite'), function (err) {
751
+ if (err) { console.log("SQLite Error: " + err); process.exit(1);; return; }
752
obj.file.exec(`
753
CREATE TABLE main (id VARCHAR(256) PRIMARY KEY NOT NULL, type CHAR(32), domain CHAR(64), extra CHAR(255), extraex CHAR(255), doc JSON);
754
CREATE TABLE events(id INTEGER PRIMARY KEY, time TIMESTAMP, domain CHAR(64), action CHAR(255), nodeid CHAR(255), userid CHAR(255), doc JSON);
@@ -749,20 +770,29 @@ module.exports.CreateDB = function (parent, func) {
770
CREATE INDEX ndxsmbiostime ON smbios (time);
771
CREATE INDEX ndxsmbiosexpire ON smbios (expire);
772
`, function (err) {
752
- // Completed setup of SQLite3
773
+ // Completed DB creation of SQLite3
774
+ //WAL mode instead of roll-back/delete
775
+ obj.file.run( 'PRAGMA journal_mode=WAL;' );
776
+ //Together with the optimize in the maintenance run, see https://sqlite.org/pragma.html#pragma_optimize
777
+ obj.file.run( 'PRAGMA optimize=0x10002;' );
778
setupFunctions(func);
779
}
780
);
781
});
782
return;
758
- } else if (err) { console.log("SQLite Error: " + err); exit(1); return; }
783
+ } else if (err) { console.log("SQLite Error: " + err); process.exit(0); return; }
784
785
// Completed setup of SQLite3
786
+ //for existing db's
787
+ //WAL mode instead of roll-back/delete
788
+ obj.file.run( 'PRAGMA journal_mode=WAL;' );
789
+ //Together with the optimize in the maintenance run, see https://sqlite.org/pragma.html#pragma_optimize
790
+ obj.file.run( 'PRAGMA optimize=0x10002;' );
791
setupFunctions(func);
792
});
793
} else if (parent.args.acebase) {
794
// AceBase database setup
765
- obj.databaseType = 7;
795
+ obj.databaseType = DB_ACEBASE;
796
const { AceBase } = require('acebase');
797
// For information on AceBase sponsor: https://github.com/appy-one/acebase/discussions/100
798
obj.file = new AceBase('meshcentral', { sponsor: ((typeof parent.args.acebase == 'object') && (parent.args.acebase.sponsor)), logLevel: 'error', storage: { path: parent.datapath } });
@@ -818,7 +848,7 @@ module.exports.CreateDB = function (parent, func) {
848
849
if (parent.args.mariadb) {
850
// Use MariaDB
821
- obj.databaseType = 4;
851
+ obj.databaseType = DB_MARIADB;
852
var tempDatastore = require('mariadb').createPool(connectionObject);
853
tempDatastore.getConnection().then(function (conn) {
854
conn.query('CREATE DATABASE IF NOT EXISTS ' + dbname).then(function (result) {
@@ -832,7 +862,7 @@ module.exports.CreateDB = function (parent, func) {
862
createTablesIfNotExist(dbname);
863
} else if (parent.args.mysql) {
864
// Use MySQL
835
- obj.databaseType = 5;
865
+ obj.databaseType = DB_MYSQL;
866
var tempDatastore = require('mysql2').createPool(connectionObject);
867
tempDatastore.query('CREATE DATABASE IF NOT EXISTS ' + dbname, function (error) {
868
if (error != null) {
@@ -846,37 +876,50 @@ module.exports.CreateDB = function (parent, func) {
876
}
877
} else if (parent.args.postgres) {
878
// Postgres SQL
849
- var connectinArgs = parent.args.postgres;
850
- var dbname = (connectinArgs.database != null) ? connectinArgs.database : 'meshcentral';
851
- delete connectinArgs.database;
852
- obj.databaseType = 6;
853
- const { Pool, Client } = require('pg');
854
- connectinArgs.database = dbname;
879
+ let connectinArgs = parent.args.postgres;
880
+ connectinArgs.Database = (databaseName = (connectinArgs.database != null) ? connectinArgs.database : 'meshcentral');
881
+
882
+ let DatastoreTest;
883
+ obj.databaseType = DB_POSTGRESQL;
884
+ const { Client } = require('pg');
885
Datastore = new Client(connectinArgs);
856
- Datastore.connect();
857
- sqlDbQuery('SELECT 1 FROM pg_database WHERE datname = $1', [dbname], function (dberr, dbdocs) { // check database exists first before creating
858
- if (dberr == null) { // database exists now check tables exists
859
- sqlDbQuery('SELECT doc FROM main WHERE id = $1', ['DatabaseIdentifier'], function (err, docs) {
860
- if (err == null) { setupFunctions(func); } else { postgreSqlCreateTables(func); } // If not present, create the tables and indexes
886
+ //Connect to and check pg db first to check if own db exists. Otherwise errors out on 'database does not exist'
887
+ connectinArgs.database = 'postgres';
888
+ DatastoreTest = new Client(connectinArgs);
889
+ DatastoreTest.connect();
890
+
891
+ DatastoreTest.query('SELECT 1 FROM pg_catalog.pg_database WHERE datname = $1', [databaseName], function (err, res) { // check database exists first before creating
892
+ if (res.rowCount != 0) { // database exists now check tables exists
893
+ DatastoreTest.end();
894
+ Datastore.connect();
895
+ Datastore.query('SELECT doc FROM main WHERE id = $1', ['DatabaseIdentifier'], function (err, res) {
896
+ if (err == null) {
897
+ (res.rowCount ==0) ? postgreSqlCreateTables(func) : setupFunctions(func)
898
+ } else
899
+ if (err.code == '42P01') { //42P01 = undefined table, https://www.postgresql.org/docs/current/errcodes-appendix.html
900
+ postgreSqlCreateTables(func);
901
+ } else {
902
+ console.log('Postgresql database exists, other error: ', err.message); process.exit(0);
903
+ };
904
});
905
} else { // If not present, create the tables and indexes
863
- const pgtools = require('pgtools');
864
- pgtools.createdb(connectinArgs, dbname, function (err, res) {
906
+ //not needed, just use a create db statement: const pgtools = require('pgtools');
907
+ DatastoreTest.query('CREATE DATABASE '+ databaseName + ';', [], function (err, res) {
908
if (err == null) {
909
// Create the tables and indexes
910
+ DatastoreTest.end();
911
+ Datastore.connect();
912
postgreSqlCreateTables(func);
913
} else {
869
- // Database already existed, perform a test query to see if the main table is present
870
- sqlDbQuery('SELECT doc FROM main WHERE id = $1', ['DatabaseIdentifier'], function (err, docs) {
871
- if (err == null) { setupFunctions(func); } else { postgreSqlCreateTables(func); } // If not present, create the tables and indexes
872
- });
914
+ console.log('Postgresql database create error: ', err.message);
915
+ process.exit(0);
916
}
917
});
918
}
919
});
920
} else if (parent.args.mongodb) {
921
// Use MongoDB
879
- obj.databaseType = 3;
922
+ obj.databaseType = DB_MONGODB;
923
924
// If running an older NodeJS version, TextEncoder/TextDecoder is required
925
if (global.TextEncoder == null) { global.TextEncoder = require('util').TextEncoder; }
@@ -1059,7 +1102,7 @@ module.exports.CreateDB = function (parent, func) {
1102
});
1103
} else if (parent.args.xmongodb) {
1104
// Use MongoJS, this is the old system.
1062
- obj.databaseType = 2;
1105
+ obj.databaseType = DB_MONGOJS;
1106
Datastore = require('mongojs');
1107
var db = Datastore(parent.args.xmongodb);
1108
var dbcollection = 'meshcentral';
@@ -1163,7 +1206,7 @@ module.exports.CreateDB = function (parent, func) {
1206
setupFunctions(func); // Completed setup of MongoJS
1207
} else {
1208
// Use NeDB (The default)
1166
- obj.databaseType = 1;
1209
+ obj.databaseType = DB_NEDB;
1210
try { Datastore = require('@yetzt/nedb'); } catch (ex) { } // This is the NeDB with fixed security dependencies.
1211
if (Datastore == null) { Datastore = require('nedb'); } // So not to break any existing installations, if the old NeDB is present, use it.
1212
var datastoreOptions = { filename: parent.getConfigFilePath('meshcentral.db'), autoload: true };
@@ -1275,7 +1318,7 @@ module.exports.CreateDB = function (parent, func) {
1318
1319
// Query the database
1320
function sqlDbQuery(query, args, func, debug) {
1278
- if (obj.databaseType == 8) { // SQLite
1321
+ if (obj.databaseType == DB_SQLITE) { // SQLite
1322
if (args == null) { args = []; }
1323
obj.file.all(query, args, function (err, docs) {
1324
if (err != null) { console.log(query, args, err, docs); }
@@ -1290,7 +1333,7 @@ module.exports.CreateDB = function (parent, func) {
1333
}
1334
if (func) { func(err, docs); }
1335
});
1293
- } else if (obj.databaseType == 4) { // MariaDB
1336
+ } else if (obj.databaseType == DB_MARIADB) { // MariaDB
1337
Datastore.getConnection()
1338
.then(function (conn) {
1339
conn.query(query, args)
@@ -1309,7 +1352,7 @@ module.exports.CreateDB = function (parent, func) {
1352
})
1353
.catch(function (err) { conn.release(); if (func) try { func(err); } catch (ex) { console.log('SQLERR2', ex); } });
1354
}).catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log('SQLERR3', ex); } } });
1312
- } else if (obj.databaseType == 5) { // MySQL
1355
+ } else if (obj.databaseType == DB_MYSQL) { // MySQL
1356
Datastore.query(query, args, function (error, results, fields) {
1357
if (error != null) {
1358
if (func) try { func(error); } catch (ex) { console.log('SQLERR4', ex); }
@@ -1330,7 +1373,7 @@ module.exports.CreateDB = function (parent, func) {
1373
if (func) { try { func(null, docs); } catch (ex) { console.log('SQLERR5', ex); } }
1374
}
1375
});
1333
- } else if (obj.databaseType == 6) { // Postgres SQL
1376
+ } else if (obj.databaseType == DB_POSTGRESQL) { // Postgres SQL
1377
Datastore.query(query, args, function (error, results) {
1378
if (error != null) {
1379
if (func) try { func(error); } catch (ex) { console.log('SQLERR4', ex); }
@@ -1359,7 +1402,7 @@ module.exports.CreateDB = function (parent, func) {
1402
1403
// Exec on the database
1404
function sqlDbExec(query, args, func) {
1362
- if (obj.databaseType == 4) { // MariaDB
1405
+ if (obj.databaseType == DB_MARIADB) { // MariaDB
1406
Datastore.getConnection()
1407
.then(function (conn) {
1408
conn.query(query, args)
@@ -1369,7 +1412,7 @@ module.exports.CreateDB = function (parent, func) {
1412
})
1413
.catch(function (err) { conn.release(); if (func) try { func(err); } catch (ex) { console.log(ex); } });
1414
}).catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
1372
- } else if ((obj.databaseType == 5) || (obj.databaseType == 6)) { // MySQL or Postgres SQL
1415
+ } else if ((obj.databaseType == DB_MYSQL) || (obj.databaseType == DB_POSTGRESQL)) { // MySQL or Postgres SQL
1416
Datastore.query(query, args, function (error, results, fields) {
1417
if (func) try { func(error, results ? results[0] : null); } catch (ex) { console.log(ex); }
1418
});
@@ -1378,7 +1421,7 @@ module.exports.CreateDB = function (parent, func) {
1421
1422
// Execute a batch of commands on the database
1423
function sqlDbBatchExec(queries, func) {
1381
- if (obj.databaseType == 4) { // MariaDB
1424
+ if (obj.databaseType == DB_MARIADB) { // MariaDB
1425
Datastore.getConnection()
1426
.then(function (conn) {
1427
var Promises = [];
@@ -1388,7 +1431,7 @@ module.exports.CreateDB = function (parent, func) {
1431
.catch(function (err) { conn.release(); if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
1432
})
1433
.catch(function (err) { if (func) { try { func(err); } catch (ex) { console.log(ex); } } });
1391
- } else if (obj.databaseType == 5) { // MySQL
1434
+ } else if (obj.databaseType == DB_MYSQL) { // MySQL
1435
Datastore.getConnection(function(err, connection) {
1436
if (err) { if (func) { try { func(err); } catch (ex) { console.log(ex); } } return; }
1437
var Promises = [];
@@ -1397,7 +1440,7 @@ module.exports.CreateDB = function (parent, func) {
1440
.then(function (error, results, fields) { connection.release(); if (func) { try { func(error, results); } catch (ex) { console.log(ex); } } })
1441
.catch(function (error, results, fields) { connection.release(); if (func) { try { func(error); } catch (ex) { console.log(ex); } } });
1442
});
1400
- } else if (obj.databaseType == 6) { // Postgres
1443
+ } else if (obj.databaseType == DB_POSTGRESQL) { // Postgres
1444
var Promises = [];
1445
for (var i in queries) { if (typeof queries[i] == 'string') { Promises.push(Datastore.query(queries[i])); } else { Promises.push(Datastore.query(queries[i][0], queries[i][1])); } }
1446
Promise.all(Promises)
@@ -1407,7 +1450,7 @@ module.exports.CreateDB = function (parent, func) {
1450
}
1451
1452
function setupFunctions(func) {
1410
- if (obj.databaseType == 8) {
1453
+ if (obj.databaseType == DB_SQLITE) {
1454
// Database actions on the main collection. SQLite3: https://www.linode.com/docs/guides/getting-started-with-nodejs-sqlite/
1455
obj.Set = function (value, func) {
1456
obj.dbCounters.fileSet++;
@@ -1733,7 +1776,7 @@ module.exports.CreateDB = function (parent, func) {
1776
obj.setPluginStatus = function (id, status, func) { sqlDbQuery('UPDATE plugin SET doc=JSON_SET(doc,"$.status",$1) WHERE id=$2', [status,id], func); };
1777
obj.updatePlugin = function (id, args, func) { delete args._id; sqlDbQuery('UPDATE plugin SET doc=json_patch(doc,$1) WHERE id=$2', [JSON.stringify(args),id], func); };
1778
}
1736
- } else if (obj.databaseType == 7) {
1779
+ } else if (obj.databaseType == DB_ACEBASE) {
1780
// Database actions on the main collection. AceBase: https://github.com/appy-one/acebase
1781
obj.Set = function (data, func) {
1782
data = common.escapeLinksFieldNameEx(data);
@@ -2025,7 +2068,7 @@ module.exports.CreateDB = function (parent, func) {
2068
obj.setPluginStatus = function (id, status, func) { obj.file.ref('plugin').child(encodeURIComponent(id)).update({ status: status }).then(function (ref) { if (func) { func(); } }) };
2069
obj.updatePlugin = function (id, args, func) { delete args._id; obj.file.ref('plugin').child(encodeURIComponent(id)).set(args).then(function (ref) { if (func) { func(); } }) };
2070
}
2028
- } else if (obj.databaseType == 6) {
2071
+ } else if (obj.databaseType == DB_POSTGRESQL) {
2072
// Database actions on the main collection (Postgres)
2073
obj.Set = function (value, func) {
2074
obj.dbCounters.fileSet++;
@@ -2285,7 +2328,7 @@ module.exports.CreateDB = function (parent, func) {
2328
obj.setPluginStatus = function (id, status, func) { sqlDbQuery("UPDATE plugin SET doc= jsonb_set(doc::jsonb,'{status}',$1) WHERE id=$2", [status,id], func); };
2329
obj.updatePlugin = function (id, args, func) { delete args._id; sqlDbQuery('UPDATE plugin SET doc= doc::jsonb || ($1) WHERE id=$2', [args,id], func); };
2330
}
2288
- } else if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
2331
+ } else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) {
2332
// Database actions on the main collection (MariaDB or MySQL)
2333
obj.Set = function (value, func) {
2334
obj.dbCounters.fileSet++;
@@ -2535,7 +2578,7 @@ module.exports.CreateDB = function (parent, func) {
2578
obj.setPluginStatus = function (id, status, func) { sqlDbQuery('UPDATE meshcentral.plugin SET doc=JSON_SET(doc,"$.status",?) WHERE id=?', [status,id], func); };
2579
obj.updatePlugin = function (id, args, func) { delete args._id; sqlDbQuery('UPDATE meshcentral.plugin SET doc=JSON_MERGE_PATCH(doc,?) WHERE id=?', [JSON.stringify(args),id], func); };
2580
}
2538
- } else if (obj.databaseType == 3) {
2581
+ } else if (obj.databaseType == DB_MONGODB) {
2582
// Database actions on the main collection (MongoDB)
2583
2584
// Bulk operations
@@ -2919,7 +2962,7 @@ module.exports.CreateDB = function (parent, func) {
2962
obj.GetEvents = function (ids, domain, filter, func) {
2963
var finddata = { domain: domain, ids: { $in: ids } };
2964
if (filter != null) finddata.action = filter;
2922
- if (obj.databaseType == 1) {
2965
+ if (obj.databaseType == DB_NEDB) {
2966
obj.eventsfile.find(finddata, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func);
2967
} else {
2968
obj.eventsfile.find(finddata, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func);
@@ -2928,7 +2971,7 @@ module.exports.CreateDB = function (parent, func) {
2971
obj.GetEventsWithLimit = function (ids, domain, limit, filter, func) {
2972
var finddata = { domain: domain, ids: { $in: ids } };
2973
if (filter != null) finddata.action = filter;
2931
- if (obj.databaseType == 1) {
2974
+ if (obj.databaseType == DB_NEDB) {
2975
obj.eventsfile.find(finddata, { _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func);
2976
} else {
2977
obj.eventsfile.find(finddata, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func);
@@ -2937,7 +2980,7 @@ module.exports.CreateDB = function (parent, func) {
2980
obj.GetUserEvents = function (ids, domain, userid, filter, func) {
2981
var finddata = { domain: domain, $or: [{ ids: { $in: ids } }, { userid: userid }] };
2982
if (filter != null) finddata.action = filter;
2940
- if (obj.databaseType == 1) {
2983
+ if (obj.databaseType == DB_NEDB) {
2984
obj.eventsfile.find(finddata, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).exec(func);
2985
} else {
2986
obj.eventsfile.find(finddata, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }, func);
@@ -2946,21 +2989,21 @@ module.exports.CreateDB = function (parent, func) {
2989
obj.GetUserEventsWithLimit = function (ids, domain, userid, limit, filter, func) {
2990
var finddata = { domain: domain, $or: [{ ids: { $in: ids } }, { userid: userid }] };
2991
if (filter != null) finddata.action = filter;
2949
- if (obj.databaseType == 1) {
2992
+ if (obj.databaseType == DB_NEDB) {
2993
obj.eventsfile.find(finddata, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit).exec(func);
2994
} else {
2995
obj.eventsfile.find(finddata, { type: 0, _id: 0, domain: 0, ids: 0, node: 0 }).sort({ time: -1 }).limit(limit, func);
2996
}
2997
};
2998
obj.GetEventsTimeRange = function (ids, domain, msgids, start, end, func) {
2956
- if (obj.databaseType == 1) {
2999
+ if (obj.databaseType == DB_NEDB) {
3000
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }, { type: 0, _id: 0, domain: 0, node: 0 }).sort({ time: 1 }).exec(func);
3001
} else {
3002
obj.eventsfile.find({ domain: domain, $or: [{ ids: { $in: ids } }], msgid: { $in: msgids }, time: { $gte: start, $lte: end } }, { type: 0, _id: 0, domain: 0, node: 0 }).sort({ time: 1 }, func);
3003
}
3004
};
3005
obj.GetUserLoginEvents = function (domain, userid, func) {
2963
- if (obj.databaseType == 1) {
3006
+ if (obj.databaseType == DB_NEDB) {
3007
obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }, { action: 1, time: 1, msgid: 1, msgArgs: 1, tokenName: 1 }).sort({ time: -1 }).exec(func);
3008
} else {
3009
obj.eventsfile.find({ domain: domain, action: { $in: ['authfail', 'login'] }, userid: userid, msgArgs: { $exists: true } }, { action: 1, time: 1, msgid: 1, msgArgs: 1, tokenName: 1 }).sort({ time: -1 }, func);
@@ -2969,7 +3012,7 @@ module.exports.CreateDB = function (parent, func) {
3012
obj.GetNodeEventsWithLimit = function (nodeid, domain, limit, filter, func) {
3013
var finddata = { domain: domain, nodeid: nodeid };
3014
if (filter != null) finddata.action = filter;
2972
- if (obj.databaseType == 1) {
3015
+ if (obj.databaseType == DB_NEDB) {
3016
obj.eventsfile.find(finddata, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func);
3017
} else {
3018
obj.eventsfile.find(finddata, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func);
@@ -2978,7 +3021,7 @@ module.exports.CreateDB = function (parent, func) {
3021
obj.GetNodeEventsSelfWithLimit = function (nodeid, domain, userid, limit, filter, func) {
3022
var finddata = { domain: domain, nodeid: nodeid, userid: { $in: [userid, null] } };
3023
if (filter != null) finddata.action = filter;
2981
- if (obj.databaseType == 1) {
3024
+ if (obj.databaseType == DB_NEDB) {
3025
obj.eventsfile.find(finddata, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit).exec(func);
3026
} else {
3027
obj.eventsfile.find(finddata, { type: 0, etype: 0, _id: 0, domain: 0, ids: 0, node: 0, nodeid: 0 }).sort({ time: -1 }).limit(limit, func);
@@ -2992,7 +3035,7 @@ module.exports.CreateDB = function (parent, func) {
3035
// Database actions on the power collection
3036
obj.getAllPower = function (func) { obj.powerfile.find({}, func); };
3037
obj.storePowerEvent = function (event, multiServer, func) { if (multiServer != null) { event.server = multiServer.serverid; } obj.powerfile.insert(event, func); };
2995
- obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == 1) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } };
3038
+ obj.getPowerTimeline = function (nodeid, func) { if (obj.databaseType == DB_NEDB) { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }).exec(func); } else { obj.powerfile.find({ nodeid: { $in: ['*', nodeid] } }, { _id: 0, nodeid: 0, s: 0 }).sort({ time: 1 }, func); } };
3039
obj.removeAllPowerEvents = function () { obj.powerfile.remove({}, { multi: true }); };
3040
obj.removeAllPowerEventsForNode = function (nodeid) { if (nodeid == null) return; obj.powerfile.remove({ nodeid: nodeid }, { multi: true }); };
3041
@@ -3072,21 +3115,20 @@ module.exports.CreateDB = function (parent, func) {
3115
var r = '', backupPath = parent.backuppath;
3116
if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
3117
3075
- var dbname = 'meshcentral';
3118
+ let dbname = 'meshcentral';
3119
if (parent.args.mongodbname) { dbname = parent.args.mongodbname; }
3120
else if ((typeof parent.args.mariadb == 'object') && (typeof parent.args.mariadb.database == 'string')) { dbname = parent.args.mariadb.database; }
3121
else if ((typeof parent.args.mysql == 'object') && (typeof parent.args.mysql.database == 'string')) { dbname = parent.args.mysql.database; }
3122
+ else if (typeof parent.config.settings.sqlite3 == 'string') {dbname = parent.config.settings.sqlite3 + '.sqlite'};
3123
3124
const currentDate = new Date();
3125
const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
3082
- const newAutoBackupFile = 'meshcentral-autobackup-' + fileSuffix;
3083
- const newAutoBackupPath = parent.path.join(backupPath, newAutoBackupFile);
3126
+ const newAutoBackupFile = ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix;
3127
3128
r += 'DB Name: ' + dbname + '\r\n';
3086
- r += 'DB Type: ' + ['None', 'NeDB', 'MongoJS', 'MongoDB', 'MariaDB', 'MySQL', 'AceBase'][obj.databaseType] + '\r\n';
3129
+ r += 'DB Type: ' + DB_LIST[obj.databaseType] + '\r\n';
3130
r += 'BackupPath: ' + backupPath + '\r\n';
3088
- r += 'newAutoBackupFile: ' + newAutoBackupFile + '\r\n';
3089
- r += 'newAutoBackupPath: ' + newAutoBackupPath + '\r\n';
3131
+ r += 'BackupFile: ' + newAutoBackupFile + '.zip\r\n';
3132
3133
if (parent.config.settings.autobackup == null) {
3134
r += 'No Settings/AutoBackup\r\n';
@@ -3117,11 +3159,33 @@ module.exports.CreateDB = function (parent, func) {
3159
if (typeof parent.config.settings.autobackup.mysqldumppath != 'string') { r += 'Bad mysqldump type\r\n'; }
3160
else { r += parent.config.settings.autobackup.mysqldumppath + '\r\n'; }
3161
}
3162
+ if (parent.config.settings.autobackup.backupotherfolders) {
3163
+ r += 'Backup other folders: ';
3164
+ r += parent.filespath + ', ' + parent.recordpath + '\r\n';
3165
+ }
3166
+ if (parent.config.settings.autobackup.backupwebfolders) {
3167
+ r += 'Backup webfolders: ';
3168
+ if (parent.webViewsOverridePath) {r += parent.webViewsOverridePath };
3169
+ if (parent.webPublicOverridePath) {r += ', '+ parent.webPublicOverridePath};
3170
+ if (parent.webEmailsOverridePath) {r += ',' + parent.webEmailsOverridePath};
3171
+ r+= '\r\n';
3172
+ }
3173
+ if (parent.config.settings.autobackup.backupignorefilesglob != []) {
3174
+ r += 'Backup IgnoreFilesGlob: ';
3175
+ { r += parent.config.settings.autobackup.backupignorefilesglob + '\r\n'; }
3176
+ }
3177
+ if (parent.config.settings.autobackup.backupskipfoldersglob != []) {
3178
+ r += 'Backup SkipFoldersGlob: ';
3179
+ { r += parent.config.settings.autobackup.backupskipfoldersglob + '\r\n'; }
3180
+ }
3181
+
3182
if (typeof parent.config.settings.autobackup.s3 == 'object') {
3183
r += 'S3 Backups: Enabled\r\n';
3184
}
3185
if (typeof parent.config.settings.autobackup.webdav == 'object') {
3186
r += 'WebDAV Backups: Enabled\r\n';
3187
+ r += 'WebDAV backup path: ' + ((typeof parent.config.settings.autobackup.webdav.foldername == 'string') ? parent.config.settings.autobackup.webdav.foldername : 'MeshCentral-Backups') + '\r\n';
3188
+ r += 'WebDAV maximum files: '+ ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') ? parent.config.settings.autobackup.webdav.maxfiles : 'no limit') + '\r\n';
3189
}
3190
if (typeof parent.config.settings.autobackup.googledrive == 'object') {
3191
r += 'Google Drive Backups: Enabled\r\n';
@@ -3134,7 +3198,7 @@ module.exports.CreateDB = function (parent, func) {
3198
}
3199
3200
function buildSqlDumpCommand() {
3137
- var props = (obj.databaseType == 4) ? parent.args.mariadb : parent.args.mysql;
3201
+ var props = (obj.databaseType == DB_MARIADB) ? parent.args.mariadb : parent.args.mysql;
3202
3203
var mysqldumpPath = 'mysqldump';
3204
if (parent.config.settings.autobackup && parent.config.settings.autobackup.mysqldumppath) {
@@ -3151,7 +3215,7 @@ module.exports.CreateDB = function (parent, func) {
3215
3216
// SSL options different on mariadb/mysql
3217
var sslOptions = '';
3154
- if (obj.databaseType == 4) {
3218
+ if (obj.databaseType == DB_MARIADB) {
3219
if (props.ssl) {
3220
sslOptions = ' --ssl';
3221
if (props.ssl.cacertpath) sslOptions = ' --ssl-ca=' + props.ssl.cacertpath;
@@ -3193,14 +3257,14 @@ module.exports.CreateDB = function (parent, func) {
3257
3258
// Check that the server is capable of performing a backup
3259
obj.checkBackupCapability = function (func) {
3196
- if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { func(); }
3197
- if ((obj.databaseType == 2) || (obj.databaseType == 3)) {
3198
- // Check that we have access to MongoDump
3199
- var backupPath = parent.backuppath;
3200
- if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
3201
- try { parent.fs.mkdirSync(backupPath); } catch (ex) { }
3202
- if (parent.fs.existsSync(backupPath) == false) { func(1, "Backup folder \"" + backupPath + "\" does not exist, database auto-backup will not be performed."); return; }
3260
+ if ((parent.config.settings.autobackup == null) || (parent.config.settings.autobackup == false)) { func(); return; };
3261
+ let backupPath = parent.backuppath;
3262
+ if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
3263
+ try { parent.fs.mkdirSync(backupPath); } catch (e) { }
3264
+ if (parent.fs.existsSync(backupPath) == false) { func(1, "Backup folder \"" + backupPath + "\" does not exist, auto-backup will not be performed."); return; }
3265
3266
+ if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) {
3267
+ // Check that we have access to MongoDump
3268
var cmd = buildMongoDumpCommand();
3269
cmd += (parent.platform == 'win32') ? ' --archive=\"nul\"' : ' --archive=\"/dev/null\"';
3270
const child_process = require('child_process');
@@ -3217,13 +3281,8 @@ module.exports.CreateDB = function (parent, func) {
3281
}
3282
} catch (ex) { console.log(ex); }
3283
});
3220
- } else if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
3284
+ } else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) {
3285
// Check that we have access to mysqldump
3222
- var backupPath = parent.backuppath;
3223
- if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
3224
- try { parent.fs.mkdirSync(backupPath); } catch (e) { }
3225
- if (parent.fs.existsSync(backupPath) == false) { func(1, "Backup folder \"" + backupPath + "\" does not exist, database auto-backup will not be performed."); return; }
3226
-
3286
var cmd = buildSqlDumpCommand();
3287
cmd += ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"');
3288
const child_process = require('child_process');
@@ -3240,6 +3299,23 @@ module.exports.CreateDB = function (parent, func) {
3299
}
3300
} catch (ex) { console.log(ex); }
3301
});
3302
+ } else if (obj.databaseType == DB_POSTGRESQL) {
3303
+ // Check that we have access to pg_dump
3304
+ parent.config.settings.autobackup.pgdumppath = path.normalize(parent.config.settings.autobackup.pgdumppath ? parent.config.settings.autobackup.pgdumppath : 'pg_dump');
3305
+ let cmd = '"' + parent.config.settings.autobackup.pgdumppath + '"'
3306
+ + ' --dbname=postgresql://' + parent.config.settings.postgres.user + ":" +parent.config.settings.postgres.password
3307
+ + "@" + parent.config.settings.postgres.host + ":" + parent.config.settings.postgres.port + "/" + databaseName
3308
+ + ' > ' + ((parent.platform == 'win32') ? '\"nul\"' : '\"/dev/null\"');
3309
+ const child_process = require('child_process');
3310
+ child_process.exec(cmd, { cwd: backupPath }, function(error, stdout, stdin) {
3311
+ try {
3312
+ if ((error != null) && (error != '')) {
3313
+ func(1, "Unable to find pg_dump, PostgreSQL database auto-backup will not be performed.");
3314
+ } else {
3315
+ func();
3316
+ }
3317
+ } catch (ex) { console.log(ex); }
3318
+ });
3319
} else {
3320
func();
3321
}
@@ -3355,167 +3431,234 @@ module.exports.CreateDB = function (parent, func) {
3431
}
3432
3433
// Perform a server backup
3358
- obj.performingBackup = false;
3434
obj.performBackup = function (func) {
3435
+ parent.debug('db','Entering performBackup');
3436
try {
3437
if (obj.performingBackup) return 1;
3438
obj.performingBackup = true;
3363
- //console.log('Performing backup...');
3439
+ let backupPath = parent.backuppath;
3440
+ let dataPath = parent.datapath;
3441
3365
- var backupPath = parent.backuppath;
3442
if (parent.config.settings.autobackup && parent.config.settings.autobackup.backuppath) { backupPath = parent.config.settings.autobackup.backuppath; }
3443
try { parent.fs.mkdirSync(backupPath); } catch (e) { }
3368
- const dbname = (parent.args.mongodbname) ? (parent.args.mongodbname) : 'meshcentral';
3369
- const dburl = parent.args.mongodb;
3444
const currentDate = new Date();
3445
const fileSuffix = currentDate.getFullYear() + '-' + padNumber(currentDate.getMonth() + 1, 2) + '-' + padNumber(currentDate.getDate(), 2) + '-' + padNumber(currentDate.getHours(), 2) + '-' + padNumber(currentDate.getMinutes(), 2);
3372
- const newAutoBackupFile = 'meshcentral-autobackup-' + fileSuffix;
3373
- const newAutoBackupPath = parent.path.join(backupPath, newAutoBackupFile);
3446
+ newAutoBackupFile = path.join(backupPath, ((typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-') + fileSuffix + '.zip');
3447
3375
- if ((obj.databaseType == 2) || (obj.databaseType == 3)) {
3376
- // Perform a MongoDump backup
3377
- const newBackupFile = 'mongodump-' + fileSuffix;
3378
- var newBackupPath = parent.path.join(backupPath, newBackupFile);
3448
+ if ((obj.databaseType == DB_MONGOJS) || (obj.databaseType == DB_MONGODB)) {
3449
+ // Perform a MongoDump in the datadir
3450
+ const dbname = (parent.args.mongodbname) ? (parent.args.mongodbname) : 'meshcentral';
3451
+ const dburl = parent.args.mongodb;
3452
+
3453
+ //const newDBDumpFile = 'mongodump-' + fileSuffix;
3454
+ newDBDumpFile = path.join(dataPath, (dbname + '-mongodump-' + fileSuffix + '.archive'));
3455
3456
var cmd = buildMongoDumpCommand();
3381
- cmd += (dburl) ? ' --archive=\"' + newBackupPath + '.archive\"' :
3382
- ' --db=\"' + dbname + '\" --archive=\"' + newBackupPath + '.archive\"';
3457
+ cmd += (dburl) ? ' --archive=\"' + newDBDumpFile + '\"' :
3458
+ ' --db=\"' + dbname + '\" --archive=\"' + newDBDumpFile + '\"';
3459
3460
const child_process = require('child_process');
3385
- var backupProcess = child_process.exec(cmd, { cwd: backupPath }, function (error, stdout, stderr) {
3386
- try {
3387
- var mongoDumpSuccess = true;
3388
- backupProcess = null;
3389
- if ((error != null) && (error != '')) { mongoDumpSuccess = false; console.log('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); }
3390
-
3391
- // Perform archive compression
3392
- var archiver = require('archiver');
3393
- var output = parent.fs.createWriteStream(newAutoBackupPath + '.zip');
3394
- var archive = null;
3395
- if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
3396
- try {
3397
- archiver.registerFormat('zip-encrypted', require('archiver-zip-encrypted'));
3398
- archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
3399
- if (func) { func('Creating encrypted ZIP'); }
3400
- } catch (ex) { // registering encryption failed, so create without encryption
3401
- archive = archiver('zip', { zlib: { level: 9 } });
3402
- if (func) { func('Creating encrypted ZIP failed, so falling back to normal ZIP'); }
3403
- }
3404
- } else {
3405
- archive = archiver('zip', { zlib: { level: 9 } });
3406
- }
3407
- output.on('close', function () {
3408
- obj.performingBackup = false;
3409
- if (func) { if (mongoDumpSuccess) { func('Auto-backup completed.'); } else { func('Auto-backup completed without mongodb database: ' + error); } }
3410
- obj.performCloudBackup(newAutoBackupPath + '.zip', func);
3411
- setTimeout(function () { try { parent.fs.unlink(newBackupPath + '.archive', function () { }); } catch (ex) { console.log(ex); } }, 5000);
3412
- });
3413
- output.on('end', function () { });
3414
- output.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
3415
- archive.on('warning', function (err) { console.log('Backup warning: ' + err); if (func) { func('Backup warning: ' + err); } });
3416
- archive.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
3417
- archive.pipe(output);
3418
- if (mongoDumpSuccess == true) { archive.file(newBackupPath + '.archive', { name: newBackupFile + '.archive' }); }
3419
- archive.directory(parent.datapath, 'meshcentral-data');
3420
- archive.finalize();
3421
- } catch (ex) { console.log(ex); }
3422
- });
3423
- } else if ((obj.databaseType == 4) || (obj.databaseType == 5)) {
3461
+ const dumpProcess = child_process.exec(
3462
+ cmd,
3463
+ { cwd: parent.parentpath },
3464
+ (error)=> {if (error) {backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform MongoDB backup: ' + error + '\r\n'); obj.createBackupfile(func);}}
3465
+ );
3466
+ dumpProcess.on('exit', (code) => {
3467
+ if (code != 0) {console.log(`Mongodump child process exited with code ${code}`); backupStatus |= BACKUPFAIL_DBDUMP;}
3468
+ obj.createBackupfile(func);
3469
+ });
3470
+
3471
+ } else if ((obj.databaseType == DB_MARIADB) || (obj.databaseType == DB_MYSQL)) {
3472
// Perform a MySqlDump backup
3473
const newBackupFile = 'mysqldump-' + fileSuffix;
3426
- var newBackupPath = parent.path.join(backupPath, newBackupFile);
3474
+ newDBDumpFile = path.join(dataPath, newBackupFile + '.sql');
3475
3476
var cmd = buildSqlDumpCommand();
3429
- cmd += ' --result-file=\"' + newBackupPath + '.sql\"';
3477
+ cmd += ' --result-file=\"' + newDBDumpFile + '\"';
3478
+
3479
const child_process = require('child_process');
3431
- var backupProcess = child_process.exec(cmd, { cwd: backupPath }, function (error, stdout, stderr) {
3432
- try {
3433
- var sqlDumpSuccess = true;
3434
- backupProcess = null;
3435
- if ((error != null) && (error != '')) { sqlDumpSuccess = false; console.log('ERROR: Unable to perform MySQL/MariaDB backup: ' + error + '\r\n'); }
3436
-
3437
- var archiver = require('archiver');
3438
- var output = parent.fs.createWriteStream(newAutoBackupPath + '.zip');
3439
- var archive = null;
3440
- if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
3441
- try {
3442
- archiver.registerFormat('zip-encrypted', require('archiver-zip-encrypted'));
3443
- archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
3444
- if (func) { func('Creating encrypted ZIP'); }
3445
- } catch (ex) { // registering encryption failed, so create without encryption
3446
- archive = archiver('zip', { zlib: { level: 9 } });
3447
- if (func) { func('Creating encrypted ZIP failed, so falling back to normal ZIP'); }
3448
- }
3449
- } else {
3450
- archive = archiver('zip', { zlib: { level: 9 } });
3451
- }
3452
- output.on('close', function () {
3453
- obj.performingBackup = false;
3454
- if (func) { if (sqlDumpSuccess) { func('Auto-backup completed.'); } else { func('Auto-backup completed without MySQL/MariaDB database: ' + error); } }
3455
- obj.performCloudBackup(newAutoBackupPath + '.zip', func);
3456
- setTimeout(function () { try { parent.fs.unlink(newBackupPath + '.sql', function () { }); } catch (ex) { console.log(ex); } }, 5000);
3457
- });
3458
- output.on('end', function () { });
3459
- output.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
3460
- archive.on('warning', function (err) { console.log('Backup warning: ' + err); if (func) { func('Backup warning: ' + err); } });
3461
- archive.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
3462
- archive.pipe(output);
3463
- if (sqlDumpSuccess == true) { archive.file(newBackupPath + '.sql', { name: newBackupFile + '.sql' }); }
3464
- archive.directory(parent.datapath, 'meshcentral-data');
3465
- archive.finalize();
3466
- } catch (ex) { console.log(ex); }
3480
+ const dumpProcess = child_process.exec(
3481
+ cmd,
3482
+ { cwd: parent.parentpath },
3483
+ (error)=> {if (error) {backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform MySQL backup: ' + error + '\r\n'); obj.createBackupfile(func);}}
3484
+ );
3485
+ dumpProcess.on('exit', (code) => {
3486
+ if (code != 0) {console.log(`MySQLdump child process exited with code ${code}`); backupStatus |= BACKUPFAIL_DBDUMP;}
3487
+ obj.createBackupfile(func);
3488
+ });
3489
+
3490
+ } else if (obj.databaseType == DB_SQLITE) {
3491
+ //.db3 suffix to escape escape backupfile glob to exclude the sqlite db files
3492
+ newDBDumpFile = path.join(dataPath, databaseName + '-sqlitedump-' + fileSuffix + '.db3');
3493
+ /*undocumented in node-sqlite3 API, check https://github.com/TryGhost/node-sqlite3/blob/593c9d498be2510d286349134537e3bf89401c4a/test/backup.test.js
3494
+ var backup = obj.file.backup(newDBDumpFile);
3495
+ backup.step(-1, function (err) {
3496
+ if (err) { console.log('SQLite start-backup error: ' + err); backupStatus |=BACKUPFAIL_DBDUMP; obj.createBackupfile(func); };
3497
+ backup.finish(function (err) {
3498
+ if (err) { console.log('SQLite backup error: ' + err); backupStatus |=BACKUPFAIL_DBDUMP;};
3499
+ obj.createBackupfile(func);
3500
+ });
3501
+ });
3502
+ */
3503
+ // do a VACUUM INTO in favor of the backup API to compress the export, see https://www.sqlite.org/backup.html
3504
+ obj.file.exec('VACUUM INTO \'' + newDBDumpFile + '\'', function (err) {
3505
+ if (err) { console.log('SQLite start-backup error: ' + err); backupStatus |=BACKUPFAIL_DBDUMP;};
3506
+ //always finish/clean up
3507
+ obj.createBackupfile(func);
3508
+ });
3509
+ } else if (obj.databaseType == DB_POSTGRESQL) {
3510
+ // Perform a PostgresDump backup
3511
+ const newBackupFile = databaseName + '-pgdump-' + fileSuffix + '.sql';
3512
+ newDBDumpFile = path.join(dataPath, newBackupFile);
3513
+ let cmd = '"' + parent.config.settings.autobackup.pgdumppath + '"'
3514
+ + ' --dbname=postgresql://' + parent.config.settings.postgres.user + ":" +parent.config.settings.postgres.password
3515
+ + "@" + parent.config.settings.postgres.host + ":" + parent.config.settings.postgres.port + "/" + databaseName
3516
+ + " --file=" + newDBDumpFile;
3517
+ const child_process = require('child_process');
3518
+ const dumpProcess = child_process.exec(
3519
+ cmd,
3520
+ { cwd: dataPath },
3521
+ (error)=> {if (error) {backupStatus |= BACKUPFAIL_DBDUMP; console.log('ERROR: Unable to perform PostgreSQL dump: ' + error.message + '\r\n'); obj.createBackupfile(func);}}
3522
+ );
3523
+ dumpProcess.on('exit', (code) => {
3524
+ if (code != 0) {console.log(`PostgreSQLdump child process exited with code: ` + code); backupStatus |= BACKUPFAIL_DBDUMP;}
3525
+ obj.createBackupfile(func);
3526
});
3527
} else {
3469
- // Perform a NeDB backup
3470
- var archiver = require('archiver');
3471
- var output = parent.fs.createWriteStream(newAutoBackupPath + '.zip');
3472
- var archive = null;
3473
- if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
3474
- try {
3475
- archiver.registerFormat('zip-encrypted', require('archiver-zip-encrypted'));
3476
- archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
3477
- if (func) { func('Creating encrypted ZIP'); }
3478
- } catch (ex) { // registering encryption failed, so create without encryption
3479
- archive = archiver('zip', { zlib: { level: 9 } });
3480
- if (func) { func('Creating encrypted ZIP failed, so falling back to normal ZIP'); }
3481
- }
3482
- } else {
3483
- archive = archiver('zip', { zlib: { level: 9 } });
3484
- }
3485
- output.on('close', function () { obj.performingBackup = false; if (func) { func('Auto-backup completed.'); } obj.performCloudBackup(newAutoBackupPath + '.zip', func); });
3486
- output.on('end', function () { });
3487
- output.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
3488
- archive.on('warning', function (err) { console.log('Backup warning: ' + err); if (func) { func('Backup warning: ' + err); } });
3489
- archive.on('error', function (err) { console.log('Backup error: ' + err); if (func) { func('Backup error: ' + err); } });
3490
- archive.pipe(output);
3491
- archive.directory(parent.datapath, 'meshcentral-data');
3492
- archive.finalize();
3493
- }
3494
-
3495
- // Remove old backups
3496
- if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) {
3497
- var cutoffDate = new Date();
3498
- cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup);
3499
- parent.fs.readdir(parent.backuppath, function (err, dir) {
3500
- try {
3501
- if ((err == null) && (dir.length > 0)) {
3502
- for (var i in dir) {
3503
- var name = dir[i];
3504
- if (name.startsWith('meshcentral-autobackup-') && name.endsWith('.zip')) {
3505
- var timex = name.substring(23, name.length - 4).split('-');
3506
- if (timex.length == 5) {
3507
- var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4]));
3508
- if (fileDate && (cutoffDate > fileDate)) { try { parent.fs.unlink(parent.path.join(parent.backuppath, name), function () { }); } catch (ex) { } }
3528
+ //NeDB backup, no db dump needed, just make a file backup
3529
+ obj.createBackupfile(func);
3530
+ }
3531
+ } catch (ex) { console.log(ex); };
3532
+ return(0);
3533
+ };
3534
+
3535
+ obj.createBackupfile = function(func) {
3536
+ parent.debug('db', 'Entering createFileBackup');
3537
+ let archiver = require('archiver');
3538
+ let archive = null;
3539
+ //if password defined, create encrypted zip
3540
+ if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.zippassword == 'string')) {
3541
+ try {
3542
+ //Only register format once, otherwise it triggers an error
3543
+ if (archiver.isRegisteredFormat('zip-encrypted') == false) { archiver.registerFormat('zip-encrypted', require('archiver-zip-encrypted')); }
3544
+ archive = archiver.create('zip-encrypted', { zlib: { level: 9 }, encryptionMethod: 'aes256', password: parent.config.settings.autobackup.zippassword });
3545
+ if (func) { func('Creating encrypted ZIP'); }
3546
+ } catch (ex) { // registering encryption failed, do not fall back to non-encrypted, fail backup and skip old backup removal as a precaution to not lose any backups
3547
+ backupStatus |= BACKUPFAIL_ZIPMODULE;
3548
+ if (func) { func('Zipencryptionmodule failed, aborting'); }
3549
+ console.log('Zipencryptionmodule failed, aborting');
3550
+ }
3551
+ } else {
3552
+ if (func) { func('Creating a NON-ENCRYPTED ZIP'); }
3553
+ archive = archiver('zip', { zlib: { level: 9 } });
3554
+ }
3555
+
3556
+ //original behavior, just a filebackup if dbdump fails : (backupStatus == 0 || backupStatus == BACKUPFAIL_DBDUMP)
3557
+ if (backupStatus == 0) {
3558
+ // Zip the data directory with the dbdump|NeDB files
3559
+ let output = parent.fs.createWriteStream(newAutoBackupFile);
3560
+ output.on('close', function () {
3561
+ if (backupStatus == 0) {
3562
+ //remove dump archive file, because zipped and otherwise fills up
3563
+ if (obj.databaseType != DB_NEDB) {
3564
+ try { parent.fs.unlink(newDBDumpFile, function () { }); } catch (ex) {console.log('Failed to clean up dbdump file')};
3565
+ };
3566
+ obj.performCloudBackup(newAutoBackupFile, func);
3567
+ // Remove old backups
3568
+ if (parent.config.settings.autobackup && (typeof parent.config.settings.autobackup.keeplastdaysbackup == 'number')) {
3569
+ let cutoffDate = new Date();
3570
+ cutoffDate.setDate(cutoffDate.getDate() - parent.config.settings.autobackup.keeplastdaysbackup);
3571
+ parent.fs.readdir(parent.backuppath, function (err, dir) {
3572
+ try {
3573
+ if ((err == null) && (dir.length > 0)) {
3574
+ let fileName = (typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-';
3575
+ for (var i in dir) {
3576
+ var name = dir[i];
3577
+ if (name.startsWith(fileName) && name.endsWith('.zip')) {
3578
+ var timex = name.substring(23, name.length - 4).split('-');
3579
+ if (timex.length == 5) {
3580
+ var fileDate = new Date(parseInt(timex[0]), parseInt(timex[1]) - 1, parseInt(timex[2]), parseInt(timex[3]), parseInt(timex[4]));
3581
+ if (fileDate && (cutoffDate > fileDate)) { try { parent.fs.unlink(parent.path.join(parent.backuppath, name), function () { }); } catch (ex) { } }
3582
+ }
3583
+ }
3584
}
3585
}
3511
- }
3512
- }
3513
- } catch (ex) { console.log(ex); }
3586
+ } catch (ex) { console.log(ex); }
3587
+ });
3588
+ }
3589
+ console.log('Auto-backup completed.');
3590
+ if (func) { func('Auto-backup completed.'); };
3591
+ } else {
3592
+ console.log('Zipbackup failed ('+ (+backupStatus).toString(16).slice(-4) + '), deleting incomplete backup: ' + newAutoBackupFile );
3593
+ if (func) { func('Zipbackup failed ('+ (+backupStatus).toString(16).slice(-4) + '), deleting incomplete backup: ' + newAutoBackupFile) };
3594
+ try { parent.fs.unlink(newAutoBackupFile, function () { }); parent.fs.unlink(newDBDumpFile, function () { }); } catch (ex) {console.log('Failed to delete incomplete backup files')};
3595
+ };
3596
+ obj.performingBackup = false;
3597
+ backupStatus = 0x0;
3598
+ });
3599
+ output.on('end', function () { });
3600
+ output.on('error', function (err) {
3601
+ if ((backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
3602
+ console.log('Output error: ' + err);
3603
+ if (func) { func('Output error: ' + err); };
3604
+ backupStatus |= BACKUPFAIL_ZIPCREATE;
3605
+ archive.abort();
3606
+ };
3607
+ });
3608
+ archive.on('warning', function (err) {
3609
+ //if files added to the archiver object aren't reachable anymore (e.g. sqlite-journal files)
3610
+ //an ENOENT warning is given, but the archiver module has no option to/does not skip/resume
3611
+ //so the backup needs te be aborted as it otherwise leaves an incomplete zip and never 'ends'
3612
+ if ((backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
3613
+ console.log('Zip warning: ' + err);
3614
+ if (func) { func('Zip warning: ' + err); };
3615
+ backupStatus |= BACKUPFAIL_ZIPCREATE;
3616
+ archive.abort();
3617
+ };
3618
+ });
3619
+ archive.on('error', function (err) {
3620
+ if ((backupStatus & BACKUPFAIL_ZIPCREATE) == 0) {
3621
+ console.log('Zip error: ' + err);
3622
+ if (func) { func('Zip error: ' + err); };
3623
+ backupStatus |= BACKUPFAIL_ZIPCREATE;
3624
+ archive.abort();
3625
+ }
3626
});
3515
- }
3516
- } catch (ex) { console.log(ex); }
3517
- return 0;
3518
- }
3627
+ archive.pipe(output);
3628
+
3629
+ let globIgnoreFiles;
3630
+ //slice in case exclusion gets pushed
3631
+ globIgnoreFiles = parent.config.settings.autobackup.backupignorefilesglob.slice();
3632
+ if (parent.config.settings.sqlite3) { globIgnoreFiles.push (datapathFoldername + '/' + databaseName + '.sqlite*'); }; //skip sqlite database file, and temp files with ext -journal, -wal & -shm
3633
+ //archiver.glob doesn't seem to use the third param, archivesubdir. Bug?
3634
+ //workaround: go up a dir and add data dir explicitly to keep the zip tidy
3635
+ archive.glob((datapathFoldername + '/**'), {
3636
+ cwd: datapathParentPath,
3637
+ ignore: globIgnoreFiles,
3638
+ skip: parent.config.settings.autobackup.backupskipfoldersglob
3639
+ });
3640
+
3641
+ if (parent.config.settings.autobackup.backupwebfolders) {
3642
+ if (parent.webViewsOverridePath) { archive.directory(parent.webViewsOverridePath, 'meshcentral-views'); }
3643
+ if (parent.webPublicOverridePath) { archive.directory(parent.webPublicOverridePath, 'meshcentral-public'); }
3644
+ if (parent.webEmailsOverridePath) { archive.directory(parent.webEmailsOverridePath, 'meshcentral-emails'); }
3645
+ };
3646
+ if (parent.config.settings.autobackup.backupotherfolders) {
3647
+ archive.directory(parent.filespath, 'meshcentral-files');
3648
+ archive.directory(parent.recordpath, 'meshcentral-recordings');
3649
+ };
3650
+
3651
+ archive.finalize();
3652
+ } else {
3653
+ //failed somewhere before zipping
3654
+ console.log('Backup failed ('+ (+backupStatus).toString(16).slice(-4) + ')');
3655
+ if (func) { func('Backup failed ('+ (+backupStatus).toString(16).slice(-4) + ')') };
3656
+ //Just in case something's there
3657
+ try { parent.fs.unlink(newDBDumpFile, function () { }); } catch (ex) { };
3658
+ backupStatus = 0x0;
3659
+ obj.performingBackup = false;
3660
+ };
3661
+ };
3662
3663
// Perform cloud backup
3664
obj.performCloudBackup = function (filename, func) {
@@ -3531,7 +3674,9 @@ module.exports.CreateDB = function (parent, func) {
3674
// Clean up our WebDAV folder
3675
function performWebDavCleanup(client) {
3676
if ((typeof parent.config.settings.autobackup.webdav.maxfiles == 'number') && (parent.config.settings.autobackup.webdav.maxfiles > 1)) {
3534
- var directoryItems = client.getDirectoryContents(webdavfolderName);
3677
+ let fileName = (typeof parent.config.settings.autobackup.backupname == 'string') ? parent.config.settings.autobackup.backupname : 'meshcentral-autobackup-';
3678
+ //only files matching our backupfilename
3679
+ let directoryItems = client.getDirectoryContents(webdavfolderName, { deep: false, glob: "/**/" + fileName + "*.zip" });
3680
directoryItems.then(
3681
function (files) {
3682
for (var i in files) { files[i].xdate = new Date(files[i].lastmod); }
meshcentral-config-schema.json
+32
-2
@@ -94,9 +94,9 @@
94
}
95
},
96
"sqlite3": {
97
- "type": "boolean",
97
+ "type": [ "boolean", "string" ],
98
"default": false,
99
- "description": "Set true to use SQLite3 as a local MeshCentral database."
99
+ "description": "Set boolean true to use SQLite3 as a local MeshCentral database with default db filename 'meshcentral' or enter a string for a different db filename. Extension .sqlite is appended"
100
},
101
"mySQL": {
102
"type": "object",
@@ -836,6 +836,11 @@
836
"default": "mysqldump",
837
"description": "The file path of where \"mysqldump\" is located. Default is \"mysqldump\""
838
},
839
+ "pgDumpPath": {
840
+ "type": "string",
841
+ "default": "pg_dump",
842
+ "description": "The file path of where \"pg_dump\" is located. Default is \"pg_dump\""
843
+ },
844
"backupIntervalHours": {
845
"type": "integer",
846
"default": 24,
@@ -857,6 +862,31 @@
862
"default": "meshcentral-backups",
863
"description": "The file path where backup files are kept. The default is \"meshcentral-backups\" which sits next to \"meshcentral-data\"."
864
},
865
+ "backupName": {
866
+ "type": "string",
867
+ "default": "meshcentral-autobackup-",
868
+ "description": "The filename of the backupfile. The default is \"meshcentral-autobackup-\", the filename is appended with the time of backup."
869
+ },
870
+ "backupWebFolders": {
871
+ "type": "boolean",
872
+ "default": false,
873
+ "description": "Add views, public and emails directories if overridden"
874
+ },
875
+ "backupOtherFolders": {
876
+ "type": "boolean",
877
+ "default": false,
878
+ "description": "Also add files and recordings folder to the backup"
879
+ },
880
+ "backupIgnoreFilesGlob": {
881
+ "type": "array",
882
+ "default": [],
883
+ "description": "Glob for ignoring files in the data directory. For example [\"**/*.log\"] !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !! Don't do string..."
884
+ },
885
+ "backupSkipFoldersGlob":{
886
+ "type": "array",
887
+ "default": [],
888
+ "description": "Glob for ignoring directories in the data directory. For example [\"**/signedagents\"]"
889
+ },
890
"googleDrive": {
891
"type": "object",
892
"description": "Enabled automated upload of the server backups to a Google Drive account, once enabled you need to go in \"My Server\" tab as administrator to associate the account.",
meshcentral.js
+13
-8
@@ -2089,11 +2089,16 @@ function CreateMeshCentralServer(config, args) {
2089
obj.updateServerState('state', "running");
2090
2091
// Setup auto-backup defaults
2092
- if (obj.config.settings.autobackup == null || obj.config.settings.autobackup === true) { obj.config.settings.autobackup = { backupintervalhours: 24, keeplastdaysbackup: 10 }; }
2093
- else if (obj.config.settings.autobackup === false) { delete obj.config.settings.autobackup; }
2094
- else if (typeof obj.config.settings.autobackup == 'object'){
2095
- if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; }
2096
- if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; }
2092
+ if (obj.config.settings.autobackup == null || obj.config.settings.autobackup == false || obj.config.settings.autobackup == 'false') { delete obj.config.settings.autobackup; }
2093
+ else {
2094
+ if (obj.config.settings.autobackup === true) {obj.config.settings.autobackup = {backupintervalhours: 24, keeplastdaysbackup: 10}; };
2095
+ if (typeof obj.config.settings.autobackup.backupintervalhours != 'number') { obj.config.settings.autobackup.backupintervalhours = 24; };
2096
+ if (typeof obj.config.settings.autobackup.keeplastdaysbackup != 'number') { obj.config.settings.autobackup.keeplastdaysbackup = 10; };
2097
+ //arrayfi in case of string and remove possible ', ' space. !! If a string instead of an array is passed, it will be split by ',' so *{.txt,.log} won't work in that case !!
2098
+ if (!obj.config.settings.autobackup.backupignorefilesglob) {obj.config.settings.autobackup.backupignorefilesglob = []}
2099
+ else if (typeof obj.config.settings.autobackup.backupignorefilesglob == 'string') { obj.config.settings.autobackup.backupignorefilesglob = obj.config.settings.autobackup.backupignorefilesglob.replaceAll(', ', ',').split(','); };
2100
+ if (!obj.config.settings.autobackup.backupskipfoldersglob) {obj.config.settings.autobackup.backupskipfoldersglob = []}
2101
+ else if (typeof obj.config.settings.autobackup.backupskipfoldersglob == 'string') { obj.config.settings.autobackup.backupskipfoldersglob = obj.config.settings.autobackup.backupskipfoldersglob.replaceAll(', ', ',').split(','); };
2102
}
2103
2104
// Check that autobackup path is not within the "meshcentral-data" folder.
@@ -4220,10 +4225,10 @@ function mainStart() {
4225
if (config.settings.mysql != null) { modules.push('mysql2@3.6.2'); } // Add MySQL.
4226
//if (config.settings.mysql != null) { modules.push('@mysql/xdevapi@8.0.33'); } // Add MySQL, official driver (https://dev.mysql.com/doc/dev/connector-nodejs/8.0/)
4227
if (config.settings.mongodb != null) { modules.push('mongodb@4.13.0'); modules.push('saslprep@1.0.3'); } // Add MongoDB, official driver.
4223
- if (config.settings.postgres != null) { modules.push('pg@8.7.1'); modules.push('pgtools@0.3.2'); } // Add Postgres, Postgres driver.
4228
+ if (config.settings.postgres != null) { modules.push('pg@8.13.1') } // Add Postgres, official driver.
4229
if (config.settings.mariadb != null) { modules.push('mariadb@3.2.2'); } // Add MariaDB, official driver.
4230
if (config.settings.acebase != null) { modules.push('acebase@1.29.5'); } // Add AceBase, official driver.
4226
- if (config.settings.sqlite3 != null) { modules.push('sqlite3@5.1.6'); } // Add sqlite3, official driver.
4231
+ if (config.settings.sqlite3 != null) { modules.push('sqlite3@5.1.7'); } // Add sqlite3, official driver.
4232
if (config.settings.vault != null) { modules.push('node-vault@0.10.2'); } // Add official HashiCorp's Vault module.
4233
if (config.settings.plugins != null) { modules.push('semver@7.5.4'); } // Required for version compat testing and update checks
4234
if ((config.settings.plugins != null) && (config.settings.plugins.proxy != null)) { modules.push('https-proxy-agent@7.0.2'); } // Required for HTTP/HTTPS proxy support
@@ -4240,7 +4245,7 @@ function mainStart() {
4245
if (typeof config.settings.autobackup.googledrive == 'object') { modules.push('googleapis@128.0.0'); }
4246
// Enable WebDAV Support
4247
if (typeof config.settings.autobackup.webdav == 'object') {
4243
- if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.3'); }
4248
+ if ((typeof config.settings.autobackup.webdav.url != 'string') || (typeof config.settings.autobackup.webdav.username != 'string') || (typeof config.settings.autobackup.webdav.password != 'string')) { addServerWarning("Missing WebDAV parameters.", 2, null, !args.launch); } else { modules.push('webdav@4.11.4'); }
4249
}
4250
// Enable S3 Support
4251
if (typeof config.settings.autobackup.s3 == 'object') { modules.push('minio@8.0.1'); }