backup site: add contents
it is a json that can be one of the three: * null - include everything * include - only include these ids * exclude - everything except these ids
This commit is contained in:
@@ -12,9 +12,10 @@ exports = module.exports = {
|
||||
setLimits,
|
||||
setSchedule,
|
||||
setRetention,
|
||||
setPrimary,
|
||||
setEncryption,
|
||||
setPrimary,
|
||||
setName,
|
||||
setContents,
|
||||
|
||||
removePrivateFields,
|
||||
|
||||
@@ -36,7 +37,6 @@ exports = module.exports = {
|
||||
|
||||
const assert = require('node:assert'),
|
||||
backupFormats = require('./backupformats.js'),
|
||||
backups = require('./backups.js'),
|
||||
BoxError = require('./boxerror.js'),
|
||||
constants = require('./constants.js'),
|
||||
cron = require('./cron.js'),
|
||||
@@ -62,7 +62,7 @@ const assert = require('node:assert'),
|
||||
// filesystem - backupDir, noHardlinks
|
||||
// mountpoint - mountPoint, prefix, noHardlinks
|
||||
// encryption: 'encryptionPassword' and 'encryptedFilenames' is converted into an 'encryption' object using hush.js. Password is lost forever after conversion.
|
||||
const BACKUP_TARGET_FIELDS = [ 'id', 'name', 'provider', 'configJson', 'limitsJson', 'retentionJson', 'schedule', 'encryptionJson', 'format', 'main', 'creationTime', 'ts', 'integrityKeyPairJson' ].join(',');
|
||||
const BACKUP_TARGET_FIELDS = [ 'id', 'name', 'provider', 'configJson', 'limitsJson', 'retentionJson', 'schedule', 'encryptionJson', 'format', 'main', 'contentsJson', 'creationTime', 'ts', 'integrityKeyPairJson' ].join(',');
|
||||
|
||||
function storageApi(backupSite) {
|
||||
assert.strictEqual(typeof backupSite, 'object');
|
||||
@@ -119,6 +119,9 @@ function postProcess(result) {
|
||||
result.primary = !!result.main; // primary is a reserved keyword in mysql
|
||||
delete result.main;
|
||||
|
||||
result.contents = safe.JSON.parse(result.contentsJson) || null;
|
||||
delete result.contentsJson;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -143,6 +146,22 @@ function validateName(name) {
|
||||
if (name.length > 100) return new BoxError(BoxError.BAD_FIELD, 'name too long');
|
||||
}
|
||||
|
||||
function validateContents(contents) {
|
||||
assert.strictEqual(typeof contents, 'object');
|
||||
|
||||
if (contents === null) return null;
|
||||
|
||||
if ('exclude' in contents) {
|
||||
if (!Array.isArray(contents.exclude)) return new BoxError(BoxError.BAD_FIELD, 'exclude should be an array of strings');
|
||||
if (!contents.exclude.every(item => typeof item === 'string')) return new BoxError(BoxError.BAD_FIELD, 'exclude should be an array of strings');
|
||||
} else if ('include' in contents) {
|
||||
if (!Array.isArray(contents.include)) return new BoxError(BoxError.BAD_FIELD, 'include should be an array of strings');
|
||||
if (!contents.include.every(item => typeof item === 'string')) return new BoxError(BoxError.BAD_FIELD, 'include should be an array of strings');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateSchedule(schedule) {
|
||||
assert.strictEqual(typeof schedule, 'string');
|
||||
|
||||
@@ -207,7 +226,7 @@ async function update(site, data) {
|
||||
if (k === 'name' || k === 'schedule' || k === 'main') { // format, provider cannot be updated
|
||||
fields.push(k + ' = ?');
|
||||
args.push(data[k]);
|
||||
} else if (k === 'config' || k === 'limits' || k === 'retention') { // encryption cannot be updated
|
||||
} else if (k === 'config' || k === 'limits' || k === 'retention' || k === 'contents') { // encryption cannot be updated
|
||||
fields.push(`${k}JSON = ?`);
|
||||
args.push(JSON.stringify(data[k]));
|
||||
}
|
||||
@@ -308,6 +327,18 @@ async function setName(backupSite, name, auditSource) {
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_TARGET_UPDATE, auditSource, { backupSite, name });
|
||||
}
|
||||
|
||||
async function setContents(backupSite, contents, auditSource) {
|
||||
assert.strictEqual(typeof backupSite, 'object');
|
||||
assert.strictEqual(typeof contents, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
|
||||
const contentsError = validateContents(contents);
|
||||
if (contentsError) throw contentsError;
|
||||
|
||||
await update(backupSite, { contents });
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_TARGET_UPDATE, auditSource, { backupSite, contents });
|
||||
}
|
||||
|
||||
async function del(backupSite, auditSource) {
|
||||
assert.strictEqual(typeof backupSite, 'object');
|
||||
assert.strictEqual(typeof auditSource, 'object');
|
||||
@@ -326,7 +357,7 @@ async function del(backupSite, auditSource) {
|
||||
if (error && error.code === 'ER_NO_REFERENCED_ROW_2') throw new BoxError(BoxError.NOT_FOUND, error);
|
||||
if (error) throw error;
|
||||
if (result[2].affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Target not found');
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_TARGET_REMOVE, auditSource, { backupSite: backupSite });
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_TARGET_REMOVE, auditSource, { backupSite });
|
||||
|
||||
backupSite.schedule = constants.CRON_PATTERN_NEVER;
|
||||
await cron.handleBackupScheduleChanged(backupSite);
|
||||
@@ -345,13 +376,12 @@ async function startBackupTask(site, auditSource) {
|
||||
|
||||
const taskId = await tasks.add(`${tasks.TASK_FULL_BACKUP_PREFIX}${site.id}`, [ site.id, { /* options */ } ]);
|
||||
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_START, auditSource, { taskId });
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_START, auditSource, { taskId, siteId: site });
|
||||
|
||||
// background
|
||||
tasks.startTask(taskId, { timeout: 24 * 60 * 60 * 1000 /* 24 hours */, nice: 15, memoryLimit, oomScoreAdjust: -999 })
|
||||
.then(async (backupId) => {
|
||||
const backup = await backups.get(backupId);
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, backupId, remotePath: backup.remotePath });
|
||||
.then(async (result) => { // this can be the an array or string depending on site.contents
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, result, site });
|
||||
})
|
||||
.catch(async (error) => {
|
||||
const timedOut = error.code === tasks.ETIMEOUT;
|
||||
@@ -481,7 +511,7 @@ async function add(data, auditSource) {
|
||||
|
||||
if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode');
|
||||
|
||||
const { provider, name, config, format, retention, schedule } = data; // required
|
||||
const { provider, name, config, format, contents, retention, schedule } = data; // required
|
||||
const limits = data.limits || null,
|
||||
encryptionPassword = data.encryptionPassword || null,
|
||||
encryptedFilenames = data.encryptedFilenames || false,
|
||||
@@ -493,6 +523,9 @@ async function add(data, auditSource) {
|
||||
const nameError = validateName(name);
|
||||
if (nameError) throw nameError;
|
||||
|
||||
const contentsError = validateContents(contents);
|
||||
if (contentsError) throw contentsError;
|
||||
|
||||
let encryption = null;
|
||||
if (encryptionPassword) {
|
||||
const encryptionPasswordError = validateEncryptionPassword(encryptionPassword);
|
||||
@@ -513,13 +546,13 @@ async function add(data, auditSource) {
|
||||
debug('add: validating new storage configuration');
|
||||
const sanitizedConfig = await storageApi({ provider }).verifyConfig({id, provider, config });
|
||||
|
||||
await database.query('INSERT INTO backupSites (id, name, provider, configJson, limitsJson, integrityKeyPairJson, retentionJson, schedule, encryptionJson, format, main) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ id, name, provider, JSON.stringify(sanitizedConfig), JSON.stringify(limits), JSON.stringify(integrityKeyPair), JSON.stringify(retention), schedule, JSON.stringify(encryption), format, false ]);
|
||||
await database.query('INSERT INTO backupSites (id, name, provider, configJson, contentsJson, limitsJson, integrityKeyPairJson, retentionJson, schedule, encryptionJson, format) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[ id, name, provider, JSON.stringify(sanitizedConfig), JSON.stringify(contents), JSON.stringify(limits), JSON.stringify(integrityKeyPair), JSON.stringify(retention), schedule, JSON.stringify(encryption), format ]);
|
||||
|
||||
debug('add: setting up new storage configuration');
|
||||
await storageApi({ provider }).setup(sanitizedConfig);
|
||||
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_TARGET_ADD, auditSource, { id, name, provider, config, schedule, format });
|
||||
await eventlog.add(eventlog.ACTION_BACKUP_TARGET_ADD, auditSource, { id, name, provider, config, contents, schedule, format });
|
||||
|
||||
return id;
|
||||
}
|
||||
@@ -534,10 +567,10 @@ async function addDefault(auditSource) {
|
||||
config: { backupDir: paths.DEFAULT_BACKUP_DIR },
|
||||
retention: { keepWithinSecs: 2 * 24 * 60 * 60 },
|
||||
schedule: '00 00 23 * * *',
|
||||
format: 'tgz'
|
||||
format: 'tgz',
|
||||
contents: null
|
||||
};
|
||||
defaultBackupSite.id = await add(defaultBackupSite, auditSource);
|
||||
await setPrimary(defaultBackupSite, auditSource);
|
||||
return await add(defaultBackupSite, auditSource);
|
||||
}
|
||||
|
||||
// creates a backup site object that is not in the database
|
||||
|
||||
Reference in New Issue
Block a user