diff --git a/src/apps.js b/src/apps.js index 59738d4ff..c8135a362 100644 --- a/src/apps.js +++ b/src/apps.js @@ -1113,10 +1113,12 @@ async function onTaskFinished(error, appId, installationState, taskId, auditSour await eventlog.add(eventlog.ACTION_APP_UPDATE_FINISH, auditSource, { app, toManifest, fromManifest, success, errorMessage }); break; } - case exports.ISTATE_PENDING_BACKUP: - await eventlog.add(eventlog.ACTION_APP_BACKUP_FINISH, auditSource, { app, success, errorMessage, backupId: task.result }); + case exports.ISTATE_PENDING_BACKUP: { + const backup = await backups.get(task.result); + await eventlog.add(eventlog.ACTION_APP_BACKUP_FINISH, auditSource, { app, success, errorMessage, remotePath: backup?.remotePath, backupId: task.result }); break; } + } } async function scheduleTask(appId, installationState, taskId, auditSource) { @@ -2023,7 +2025,7 @@ async function restore(app, backupId, auditSource) { values.mailboxDomain = app.domain; } - const restoreConfig = { backupId, backupFormat: backupInfo.format }; + const restoreConfig = { remotePath: backupInfo.remotePath, backupFormat: backupInfo.format }; const task = { args: { @@ -2037,7 +2039,7 @@ async function restore(app, backupId, auditSource) { const taskId = await addTask(appId, exports.ISTATE_PENDING_RESTORE, task, auditSource); - await eventlog.add(eventlog.ACTION_APP_RESTORE, auditSource, { app: app, backupId: backupInfo.id, fromManifest: app.manifest, toManifest: backupInfo.manifest, taskId }); + await eventlog.add(eventlog.ACTION_APP_RESTORE, auditSource, { app, backupId: backupInfo.id, remotePath: backupInfo.remotePath, fromManifest: app.manifest, toManifest: backupInfo.manifest, taskId }); return { taskId }; } @@ -2050,10 +2052,10 @@ async function importApp(app, data, auditSource) { const appId = app.id; // all fields are optional - data.backupId = data.backupId || null; + data.remotePath = data.remotePath || null; data.backupFormat = data.backupFormat || null; data.backupConfig = data.backupConfig || null; - const { backupId, backupFormat, backupConfig } = data; + const { remotePath, backupFormat, backupConfig } = data; let error = backupFormat ? validateBackupFormat(backupFormat) : null; if (error) throw error; @@ -2089,7 +2091,7 @@ async function importApp(app, data, auditSource) { } } - const restoreConfig = { backupId, backupFormat, backupConfig }; + const restoreConfig = { remotePath, backupFormat, backupConfig }; const task = { args: { @@ -2102,7 +2104,7 @@ async function importApp(app, data, auditSource) { }; const taskId = await addTask(appId, exports.ISTATE_PENDING_IMPORT, task, auditSource); - await eventlog.add(eventlog.ACTION_APP_IMPORT, auditSource, { app: app, backupId, fromManifest: app.manifest, toManifest: app.manifest, taskId }); + await eventlog.add(eventlog.ACTION_APP_IMPORT, auditSource, { app: app, remotePath, fromManifest: app.manifest, toManifest: app.manifest, taskId }); return { taskId }; } @@ -2158,6 +2160,7 @@ async function clone(app, data, user, auditSource) { const backupInfo = await backups.get(backupId); + if (!backupInfo) throw new BoxError(BoxError.NOT_FOUND, 'Backup not found'); if (!backupInfo.manifest) throw new BoxError(BoxError.EXTERNAL_ERROR, 'Could not get restore config'); if (backupInfo.encryptionVersion === 1) throw new BoxError(BoxError.BAD_FIELD, 'This encrypted backup was created with an older Cloudron version and cannot be cloned'); @@ -2216,7 +2219,7 @@ async function clone(app, data, user, auditSource) { await purchaseApp({ appId: newAppId, appstoreId: app.appStoreId, manifestId: manifest.id || 'customapp' }); - const restoreConfig = { backupId, backupFormat: backupInfo.format }; + const restoreConfig = { remotePath: backup.remotePath, backupFormat: backupInfo.format }; const task = { args: { restoreConfig, overwriteDns, skipDnsSetup, oldManifest: null }, values: {}, @@ -2230,7 +2233,7 @@ async function clone(app, data, user, auditSource) { newApp.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); newApp.aliasDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, domainObjectMap[ad.domain]); }); - await eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId, oldApp: app, newApp, taskId }); + await eventlog.add(eventlog.ACTION_APP_CLONE, auditSource, { appId: newAppId, oldAppId: appId, backupId, remotePath: backupInfo.remotePath, oldApp: app, newApp, taskId }); return { id: newAppId, taskId }; } @@ -2474,11 +2477,11 @@ async function restoreInstalledApps(options, auditSource) { apps = apps.filter(app => app.installationState !== exports.ISTATE_PENDING_RESTORE); // safeguard against tasks being created non-stop if we crash on startup for (const app of apps) { - const [error, results] = await safe(backups.getByIdentifierAndStatePaged(app.id, backups.BACKUP_STATE_NORMAL, 1, 1)); + const [error, backups] = await safe(backups.getByIdentifierAndStatePaged(app.id, backups.BACKUP_STATE_NORMAL, 1, 1)); let installationState, restoreConfig, oldManifest; - if (!error && results.length) { + if (!error && backups.length) { installationState = exports.ISTATE_PENDING_RESTORE; - restoreConfig = { backupId: results[0].id, backupFormat: results[0].format }; + restoreConfig = { remotePath: backups[0].remotePath, backupFormat: backups[0].format }; oldManifest = app.manifest; } else { installationState = exports.ISTATE_PENDING_INSTALL; diff --git a/src/apptask.js b/src/apptask.js index c2f1ed6ee..d40209c82 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -328,7 +328,7 @@ async function install(app, args, progressCallback) { } await services.teardownAddons(app, addonsToRemove); - if (!restoreConfig || restoreConfig.backupId) { // in-place import should not delete data dir + if (!restoreConfig || restoreConfig.remotePath) { // in-place import should not delete data dir await deleteAppDir(app, { removeDirectory: false }); // do not remove any symlinked appdata dir } @@ -357,7 +357,7 @@ async function install(app, args, progressCallback) { if (!restoreConfig) { // install await progressCallback({ percent: 60, message: 'Setting up addons' }); await services.setupAddons(app, app.manifest.addons); - } else if (app.installationState === apps.ISTATE_PENDING_IMPORT && !restoreConfig.backupId) { // in-place import + } else if (app.installationState === apps.ISTATE_PENDING_IMPORT && !restoreConfig.remotePath) { // in-place import await progressCallback({ percent: 60, message: 'Importing addons in-place' }); await services.setupAddons(app, app.manifest.addons); await services.clearAddons(app, _.omit(app.manifest.addons, 'localstorage')); diff --git a/src/backups.js b/src/backups.js index 1b2fb6225..4e119c601 100644 --- a/src/backups.js +++ b/src/backups.js @@ -248,7 +248,8 @@ async function startBackupTask(auditSource) { const errorMessage = error ? error.message : ''; const timedOut = error ? error.code === tasks.ETIMEOUT : false; - await safe(eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, errorMessage, timedOut, backupId }), { debug }); + const backup = await get(backupId); + await safe(eventlog.add(eventlog.ACTION_BACKUP_FINISH, auditSource, { taskId, errorMessage, timedOut, backupId, remotePath: backup?.remotePath }), { debug }); }); return taskId; diff --git a/src/backuptask.js b/src/backuptask.js index 160aea183..0cdff58d5 100644 --- a/src/backuptask.js +++ b/src/backuptask.js @@ -602,21 +602,21 @@ function downloadDir(backupConfig, backupFilePath, dataLayout, progressCallback, }, callback); } -function download(backupConfig, backupId, format, dataLayout, progressCallback, callback) { +function download(backupConfig, remotePath, format, dataLayout, progressCallback, callback) { assert.strictEqual(typeof backupConfig, 'object'); - assert.strictEqual(typeof backupId, 'string'); + assert.strictEqual(typeof remotePath, 'string'); assert.strictEqual(typeof format, 'string'); assert(dataLayout instanceof DataLayout, 'dataLayout must be a DataLayout'); assert.strictEqual(typeof progressCallback, 'function'); assert.strictEqual(typeof callback, 'function'); - debug(`download: Downloading ${backupId} of format ${format} to ${dataLayout.toString()}`); + debug(`download: Downloading ${remotePath} of format ${format} to ${dataLayout.toString()}`); - const backupFilePath = storage.getBackupFilePath(backupConfig, backupId, format); + const backupFilePath = storage.getBackupFilePath(backupConfig, remotePath, format); if (format === 'tgz') { async.retry({ times: 5, interval: 20000 }, function (retryCallback) { - progressCallback({ message: `Downloading backup ${backupId}` }); + progressCallback({ message: `Downloading backup ${remotePath}` }); storage.api(backupConfig.provider).download(backupConfig, backupFilePath, function (error, sourceStream) { if (error) return retryCallback(error); @@ -644,16 +644,16 @@ function download(backupConfig, backupId, format, dataLayout, progressCallback, } } -async function restore(backupConfig, backupId, progressCallback) { +async function restore(backupConfig, remotePath, progressCallback) { assert.strictEqual(typeof backupConfig, 'object'); - assert.strictEqual(typeof backupId, 'string'); + assert.strictEqual(typeof remotePath, 'string'); assert.strictEqual(typeof progressCallback, 'function'); const boxDataDir = safe.fs.realpathSync(paths.BOX_DATA_DIR); if (!boxDataDir) throw new BoxError(BoxError.FS_ERROR, `Error resolving boxdata: ${safe.error.message}`); const dataLayout = new DataLayout(boxDataDir, []); - await util.promisify(download)(backupConfig, backupId, backupConfig.format, dataLayout, progressCallback); + await util.promisify(download)(backupConfig, remotePath, backupConfig.format, dataLayout, progressCallback); debug('restore: download completed, importing database'); @@ -677,7 +677,7 @@ async function downloadApp(app, restoreConfig, progressCallback) { const backupConfig = restoreConfig.backupConfig || await settings.getBackupConfig(); const downloadAsync = util.promisify(download); - await downloadAsync(backupConfig, restoreConfig.backupId, restoreConfig.backupFormat, dataLayout, progressCallback); + await downloadAsync(backupConfig, restoreConfig.remotePath, restoreConfig.backupFormat, dataLayout, progressCallback); debug('downloadApp: time: %s', (new Date() - startTime)/1000); } @@ -1026,7 +1026,7 @@ async function downloadMail(restoreConfig, progressCallback) { const startTime = new Date(); const downloadAsync = util.promisify(download); - await downloadAsync(restoreConfig.backupConfig, restoreConfig.backupId, restoreConfig.backupFormat, dataLayout, progressCallback); + await downloadAsync(restoreConfig.backupConfig, restoreConfig.remotePath, restoreConfig.backupFormat, dataLayout, progressCallback); debug('downloadMail: time: %s', (new Date() - startTime)/1000); } diff --git a/src/provision.js b/src/provision.js index 2f86787dc..6185d01cb 100644 --- a/src/provision.js +++ b/src/provision.js @@ -153,16 +153,16 @@ async function activate(username, password, email, displayName, ip, auditSource) }; } -async function restoreTask(backupConfig, backupId, sysinfoConfig, options, auditSource) { +async function restoreTask(backupConfig, remotePath, sysinfoConfig, options, auditSource) { assert.strictEqual(typeof backupConfig, 'object'); - assert.strictEqual(typeof backupId, 'string'); + assert.strictEqual(typeof remotePath, 'string'); assert.strictEqual(typeof sysinfoConfig, 'object'); assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof auditSource, 'object'); try { setProgress('restore', 'Downloading box backup'); - await backuptask.restore(backupConfig, backupId, (progress) => setProgress('restore', progress.message)); + await backuptask.restore(backupConfig, remotePath, (progress) => setProgress('restore', progress.message)); setProgress('restore', 'Downloading mail backup'); const mailBackups = await backups.getByIdentifierAndStatePaged(backups.BACKUP_IDENTIFIER_MAIL, backups.BACKUP_STATE_NORMAL, 1, 1); @@ -178,7 +178,7 @@ async function restoreTask(backupConfig, backupId, sysinfoConfig, options, audit if (!options.skipDnsSetup) await cloudron.setupDnsAndCert(constants.DASHBOARD_LOCATION, dashboardDomain, auditSource, (progress) => setProgress('restore', progress.message)); await cloudron.setDashboardDomain(dashboardDomain, auditSource); await settings.setBackupCredentials(backupConfig); // update just the credentials and not the policy and flags - await eventlog.add(eventlog.ACTION_RESTORE, auditSource, { backupId }); + await eventlog.add(eventlog.ACTION_RESTORE, auditSource, { remotePath }); setImmediate(() => safe(cloudron.onActivated(options), { debug })); } catch (error) { diff --git a/src/routes/apps.js b/src/routes/apps.js index 1784064d2..9367411ba 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -481,8 +481,8 @@ async function importApp(req, res, next) { const data = req.body; - if ('backupId' in data) { // if not provided, we import in-place - if (typeof data.backupId !== 'string') return next(new HttpError(400, 'backupId must be string')); + if ('remotePath' in data) { // if not provided, we import in-place + if (typeof data.remotePath !== 'string') return next(new HttpError(400, 'remotePath must be string')); if (typeof data.backupFormat !== 'string') return next(new HttpError(400, 'backupFormat must be string')); if ('backupConfig' in data && typeof data.backupConfig !== 'object') return next(new HttpError(400, 'backupConfig must be an object')); diff --git a/src/routes/provision.js b/src/routes/provision.js index d536fbfc4..dee869d08 100644 --- a/src/routes/provision.js +++ b/src/routes/provision.js @@ -106,7 +106,7 @@ async function restore(req, res, next) { if (typeof backupConfig.format !== 'string') return next(new HttpError(400, 'format must be a string')); if ('acceptSelfSignedCerts' in backupConfig && typeof backupConfig.acceptSelfSignedCerts !== 'boolean') return next(new HttpError(400, 'format must be a boolean')); - if (typeof req.body.backupId !== 'string') return next(new HttpError(400, 'backupId must be a string')); + if (typeof req.body.remotePath !== 'string') return next(new HttpError(400, 'remotePath must be a string')); if (typeof req.body.version !== 'string') return next(new HttpError(400, 'version must be a string')); if ('sysinfoConfig' in req.body && typeof req.body.sysinfoConfig !== 'object') return next(new HttpError(400, 'sysinfoConfig must be an object')); @@ -116,7 +116,7 @@ async function restore(req, res, next) { skipDnsSetup: req.body.skipDnsSetup || false }; - const [error] = await safe(provision.restore(backupConfig, req.body.backupId, req.body.version, req.body.sysinfoConfig || { provider: 'generic' }, options, AuditSource.fromRequest(req))); + const [error] = await safe(provision.restore(backupConfig, req.body.remotePath, req.body.version, req.body.sysinfoConfig || { provider: 'generic' }, options, AuditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, {}));