Files
Joe Uhren d9e0e54dec Allow backup and restore of a single collection
-Both the backup and restore scripts now support a new optional parameter that allows backing up and restoring a single collection only
-Added new verbiage and examples to the backup and restore script sections of the README
-Removed extra "the"'s from some of the restore database examples in the README
2025-03-04 20:40:00 -07:00

166 lines
6.1 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const lib = require('../lib/explorer');
const archiveSuffix = '.bak';
const backupLockName = 'backup';
const settings = require('../lib/settings');
let backupPath = path.join(path.dirname(__dirname), 'backups');
let backupFilename;
let singleCollection = '';
let lockCreated = false;
// exit function used to cleanup lock before finishing script
function exit(exitCode) {
// only remove backup lock if it was created in this session
if (!lockCreated || lib.remove_lock(backupLockName) == true) {
// clean exit with previous exit code
process.exit(exitCode);
} else {
// error removing lock
process.exit(1);
}
}
// verify that the collection exists
function verify_collection_exists(cb) {
// check if the backup will be for a single collection
if (singleCollection != null && singleCollection != '') {
const mongoose = require('mongoose');
const dbString = `mongodb://${encodeURIComponent(settings.dbsettings.user)}:${encodeURIComponent(settings.dbsettings.password)}@${settings.dbsettings.address}:${settings.dbsettings.port}/${settings.dbsettings.database}`;
console.log('Connecting to database..');
mongoose.set('strictQuery', true);
// connect to mongo database
mongoose.connect(dbString).then(() => {
// lookup the collection in the list of collections
mongoose.connection.db.listCollections({ name: singleCollection }).toArray().then((collections) => {
// check if the collection exists
if (collections.length > 0) {
// collection exists
return cb(false);
} else {
// collection not found
return cb(true);
}
}).catch((err) => {
console.log(err);
return cb(true);
});
}).catch((err) => {
console.log('Error: Unable to connect to database: %s', err);
exit(999);
return cb(true);
});
} else
return cb(false);
}
// check if a backup filename was passed into the script
if (process.argv[2] != null && process.argv[2] != '') {
// use the backup filename passed into this script
backupFilename = process.argv[2];
} else {
const systemDate = new Date();
const monthName = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
// no backup filename passed in. use todays date as the backup filename
backupFilename = `${systemDate.getFullYear()}-${monthName[systemDate.getMonth()]}-${systemDate.getDate()}`;
}
// check if a collection name was passed into the script
if (process.argv[3] != null && process.argv[3] != '') {
// save the collection name to a variable for later use
singleCollection = process.argv[3];
}
// check if backup filename has the archive suffix already
if (backupFilename.endsWith(archiveSuffix)) {
// remove the archive suffix from the backup filename
backupFilename = backupFilename.substring(0, backupFilename.length - archiveSuffix.length);
}
// check if the backup filename already has a path
if (path.isAbsolute(backupFilename)) {
// the backup filename is a full path
// split out the path and filename
backupPath = path.dirname(backupFilename);
backupFilename = path.basename(backupFilename);
}
// check if the backup directory exists
if (!fs.existsSync(backupPath)) {
// attempt to create the directory
fs.mkdirSync(backupPath);
}
// check if backup file already exists
if (!fs.existsSync(path.join(backupPath, `${backupFilename}${archiveSuffix}`))) {
// check if the "create backup" process is already running
if (lib.is_locked([backupLockName]) == false) {
// create a new backup lock before checking the rest of the locks to minimize problems with running scripts at the same time
lib.create_lock(backupLockName);
// ensure the lock will be deleted on exit
lockCreated = true;
// check all other possible locks since backups should not run at the same time that data is being changed
if (lib.is_locked(['restore', 'delete', 'index', 'markets', 'peers', 'masternodes', 'plugin']) == false) {
// check if the collection name exists
verify_collection_exists(function(collection_error) {
// check if there was an error finding the collection by name
if (!collection_error) {
// all tests passed. OK to run backup
console.log(`${settings.localization.script_launched }: ${process.pid}`);
const { exec } = require('child_process');
// execute backup
const backupProcess = exec(`mongodump --host="${settings.dbsettings.address}" --port="${settings.dbsettings.port}" --username="${settings.dbsettings.user}" --password="${settings.dbsettings.password}" --db="${settings.dbsettings.database}" --archive="${path.join(backupPath, backupFilename + archiveSuffix)}" --gzip${singleCollection == null || singleCollection == '' ? '' : ` --collection ${singleCollection}`}`);
backupProcess.stdout.on('data', (data) => {
console.log(data);
});
backupProcess.stderr.on('data', (data) => {
console.log(Buffer.from(data).toString());
});
backupProcess.on('error', (error) => {
console.log(error);
});
backupProcess.on('exit', (code, signal) => {
if (code) {
console.log(`Process exit with code: ${code}`);
exit(code);
} else if (signal) {
console.log(`Process killed with signal: ${signal}`);
exit(1);
} else {
console.log(`Backup saved successfully to ${path.join(backupPath, backupFilename + archiveSuffix)}`);
exit(0);
}
});
} else {
// the collection does not exist
console.log(`Collection "${singleCollection}" does not exist`);
exit(2);
}
});
} else {
// another script process is currently running
console.log("Backup aborted");
exit(2);
}
} else {
// backup process is already running
console.log("Backup aborted");
exit(2);
}
} else {
// backup already exists
console.log(`A backup named ${backupFilename}${archiveSuffix} already exists`);
exit(2);
}