diff --git a/dashboard/src/components/app/Backups.vue b/dashboard/src/components/app/Backups.vue index b725b5a3b..f0849e14b 100644 --- a/dashboard/src/components/app/Backups.vue +++ b/dashboard/src/components/app/Backups.vue @@ -229,6 +229,8 @@ async function onDownloadConfig(backup) { tmp[k] = backupSite[k]; } + tmp.siteId = backupSite.id; + const filename = `${props.app.fqdn}-backup-config-${(new Date(backup.creationTime)).toISOString().split('T')[0]}.json`; download(filename, JSON.stringify(tmp, null, 4)); } diff --git a/dashboard/src/views/BackupListView.vue b/dashboard/src/views/BackupListView.vue index 64f148179..7f2c43e7d 100644 --- a/dashboard/src/views/BackupListView.vue +++ b/dashboard/src/views/BackupListView.vue @@ -207,6 +207,8 @@ async function onDownloadConfig(backup) { tmp[k] = backupSite[k]; } + tmp.siteId = backupSite.id; + const filename = `${dashboardConfig.adminFqdn}-backup-config-${(new Date(backup.creationTime)).toISOString().split('T')[0]}.json`; download(filename, JSON.stringify(tmp, null, 4)); } diff --git a/src/provision.js b/src/provision.js index be7cba96d..f10a3098a 100644 --- a/src/provision.js +++ b/src/provision.js @@ -31,6 +31,7 @@ const appstore = require('./appstore.js'), semver = require('semver'), paths = require('./paths.js'), system = require('./system.js'), + tasks = require('./tasks.js'), users = require('./users.js'), tld = require('tldjs'), tokens = require('./tokens.js'); @@ -199,6 +200,14 @@ async function restoreTask(backupSite, remotePath, ipv4Config, ipv6Config, optio await backupSites.reinitAll(); + // when creating a sql dump during a full backup, the task has not completed yet. we mark it as completed here for the Sites UI to not indicate a crash + // siteId can be missing when restoring manually instead of via the backup config + const backupTasks = await tasks.list(1, 1, { + type: options.siteId ? tasks.TASK_FULL_BACKUP_PREFIX + options.siteId : null, + prefix: !options.siteId ? tasks.TASK_FULL_BACKUP_PREFIX : null // guess that the latest full backup was the site that was used + }); + await tasks.setCompleted(backupTasks[0].id, { error: null }); + const location = await dashboard.getLocation(); // load this fresh from after the backup.restore if (!options.skipDnsSetup) { await dns.registerLocations([location], { overwriteDns: true }, (progress) => setProgress('restore', progress.message)); @@ -225,7 +234,7 @@ async function restore(backupConfig, remotePath, version, ipv4Config, ipv6Config assert.strictEqual(typeof version, 'string'); assert.strictEqual(typeof ipv4Config, 'object'); assert.strictEqual(typeof ipv6Config, 'object'); - assert.strictEqual(typeof options, 'object'); // { skipDnsSetup } + assert.strictEqual(typeof options, 'object'); // { skipDnsSetup, siteId } assert.strictEqual(typeof auditSource, 'object'); if (!semver.valid(version)) throw new BoxError(BoxError.BAD_FIELD, 'version is not a valid semver'); diff --git a/src/routes/provision.js b/src/routes/provision.js index 43992c98e..b3e78d8fa 100644 --- a/src/routes/provision.js +++ b/src/routes/provision.js @@ -123,9 +123,11 @@ async function restore(req, res, next) { if ('ipv6Config' in req.body && typeof req.body.ipv6Config !== 'object') return next(new HttpError(400, 'ipv6Config must be an object')); if ('skipDnsSetup' in req.body && typeof req.body.skipDnsSetup !== 'boolean') return next(new HttpError(400, 'skipDnsSetup must be a boolean')); + if ('siteId' in req.body && typeof req.body.siteId !== 'string') return next(new HttpError(400, 'siteId must be a string')); const options = { - skipDnsSetup: req.body.skipDnsSetup || false + skipDnsSetup: req.body.skipDnsSetup || false, + siteId: req.body.siteId || null }; const [error] = await safe(provision.restore(backupConfig, req.body.remotePath, req.body.version, req.body.ipv4Config || { provider: 'generic' }, req.body.ipv6Config || { provider: 'generic' }, options, AuditSource.fromRequest(req)));