diff --git a/migrations/20200514231149-backups-retentionSecs-to-retentionPolicy.js b/migrations/20200514231149-backups-retentionSecs-to-retentionPolicy.js new file mode 100644 index 000000000..7694a5c03 --- /dev/null +++ b/migrations/20200514231149-backups-retentionSecs-to-retentionPolicy.js @@ -0,0 +1,18 @@ +'use strict'; + +exports.up = function(db, callback) { + db.all('SELECT value FROM settings WHERE name="backup_config"', function (error, results) { + if (error || results.length === 0) return callback(error); + + var backupConfig = JSON.parse(results[0].value); + backupConfig.retentionPolicy = { keepWithin: backupConfig.retentionSecs }; + delete backupConfig.retentionSecs; + + // mark old encrypted backups as v1 + db.runSql('UPDATE settings SET value=? WHERE name="backup_config"', [ JSON.stringify(backupConfig) ], callback); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/src/backups.js b/src/backups.js index f682d5073..67b76a9b6 100644 --- a/src/backups.js +++ b/src/backups.js @@ -1272,7 +1272,7 @@ function cleanupAppBackups(backupConfig, referencedAppBackups, callback) { async.eachSeries(appBackups, function iterator(appBackup, iteratorDone) { if (referencedAppBackups.indexOf(appBackup.id) !== -1) return iteratorDone(); if ((now - appBackup.creationTime) < (appBackup.preserveSecs * 1000)) return iteratorDone(); - if ((now - appBackup.creationTime) < (backupConfig.retentionSecs * 1000)) return iteratorDone(); + if ((now - appBackup.creationTime) < (backupConfig.retentionPolicy.keepWithin * 1000)) return iteratorDone(); debug('cleanupAppBackups: removing %s', appBackup.id); @@ -1317,7 +1317,7 @@ function cleanupBoxBackups(backupConfig, auditSource, callback) { async.eachSeries(boxBackups, function iterator(boxBackup, iteratorNext) { // TODO: errored backups should probably be cleaned up before retention time, but we will // have to be careful not to remove any backup currently being created - if ((now - boxBackup.creationTime) < (backupConfig.retentionSecs * 1000)) { + if ((now - boxBackup.creationTime) < (backupConfig.retentionPolicy.keepWithin * 1000)) { referencedAppBackups = referencedAppBackups.concat(boxBackup.dependsOn); return iteratorNext(); } @@ -1391,7 +1391,7 @@ function cleanup(auditSource, progressCallback, callback) { settings.getBackupConfig(function (error, backupConfig) { if (error) return callback(error); - if (backupConfig.retentionSecs < 0) { + if (backupConfig.retentionPolicy.keepWithin < 0) { debug('cleanup: keeping all backups'); return callback(null, {}); } diff --git a/src/routes/settings.js b/src/routes/settings.js index 984abeba1..30ddbd1b1 100644 --- a/src/routes/settings.js +++ b/src/routes/settings.js @@ -97,7 +97,6 @@ function setBackupConfig(req, res, next) { assert.strictEqual(typeof req.body, 'object'); if (typeof req.body.provider !== 'string') return next(new HttpError(400, 'provider is required')); - if (typeof req.body.retentionSecs !== 'number') return next(new HttpError(400, 'retentionSecs is required')); if (typeof req.body.intervalSecs !== 'number') return next(new HttpError(400, 'intervalSecs is required')); if ('password' in req.body && typeof req.body.password !== 'string') return next(new HttpError(400, 'password must be a string')); if ('syncConcurrency' in req.body) { @@ -107,6 +106,9 @@ function setBackupConfig(req, res, next) { if (typeof req.body.format !== 'string') return next(new HttpError(400, 'format must be a string')); if ('acceptSelfSignedCerts' in req.body && typeof req.body.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'format must be a boolean')); + if (!req.body.retentionPolicy || typeof req.body.retentionPolicy !== 'object') return next(new HttpError(400, 'retentionPolicy is required')); + if (typeof req.body.retentionPolicy.keepWithin !== 'number') return next(400, 'keepWithin is required'); + // testing the backup using put/del takes a bit of time at times req.clearTimeout(); diff --git a/src/settings.js b/src/settings.js index b22bdfdd4..1b034a613 100644 --- a/src/settings.js +++ b/src/settings.js @@ -145,7 +145,7 @@ let gDefaults = (function () { backupFolder: '/var/backups', format: 'tgz', encryption: null, - retentionSecs: 2 * 24 * 60 * 60, // 2 days + retentionPolicy: { keepWithin: 2 * 24 * 60 * 60 }, // 2 days intervalSecs: 24 * 60 * 60 // ~1 day }; result[exports.PLATFORM_CONFIG_KEY] = {}; @@ -428,7 +428,7 @@ function setBackupCredentials(credentials, callback) { if (error) return callback(error); // preserve these fields - const extra = _.pick(currentConfig, 'retentionSecs', 'intervalSecs', 'copyConcurrency', 'syncConcurrency'); + const extra = _.pick(currentConfig, 'retentionPolicy', 'intervalSecs', 'copyConcurrency', 'syncConcurrency'); const backupConfig = _.extend({}, credentials, extra); diff --git a/src/test/backups-test.js b/src/test/backups-test.js index 107d98e75..e11566e87 100644 --- a/src/test/backups-test.js +++ b/src/test/backups-test.js @@ -80,7 +80,7 @@ describe('backups', function () { provider: 'filesystem', password: 'supersecret', backupFolder: BACKUP_DIR, - retentionSecs: 1, + retentionPolicy: { keepWithin: 1 }, format: 'tgz' }) ], done);