apps: backup is not a state anymore

this is launched as a separate task
This commit is contained in:
Girish Ramakrishnan
2025-07-18 10:56:52 +02:00
parent 0aca6c2588
commit 0fa281083e
6 changed files with 74 additions and 81 deletions
+29 -32
View File
@@ -2,10 +2,10 @@
exports = module.exports = {
fullBackup,
appBackup,
restore,
backupApp,
downloadApp,
backupMail,
@@ -34,20 +34,6 @@ const apps = require('./apps.js'),
const BACKUP_UPLOAD_CMD = path.join(__dirname, 'scripts/backupupload.js');
function canBackupApp(app) {
// only backup apps that are installed or specific pending states
// stopped apps cannot be backed up because addons might be down (redis)
if (app.runState === apps.RSTATE_STOPPED) return false;
// we used to check the health here but that doesn't work for stopped apps. it's better to just fail
// and inform the user if the backup fails and the app addons have not been setup yet.
return app.installationState === apps.ISTATE_INSTALLED ||
app.installationState === apps.ISTATE_PENDING_CONFIGURE ||
app.installationState === apps.ISTATE_PENDING_BACKUP || // called from apptask
app.installationState === apps.ISTATE_PENDING_UPDATE; // called from apptask
}
async function checkPreconditions(backupConfig, dataLayout) {
assert.strictEqual(typeof backupConfig, 'object');
assert(dataLayout instanceof DataLayout, 'dataLayout must be a DataLayout');
@@ -316,20 +302,6 @@ async function rotateAppBackup(backupConfig, app, tag, options, progressCallback
return id;
}
async function backupApp(app, options, progressCallback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof progressCallback, 'function');
if (options.snapshotOnly) return await snapshotApp(app, progressCallback);
const tag = (new Date()).toISOString().replace(/[T.]/g, '-').replace(/[:Z]/g,'');
debug(`backupApp: backing up ${app.fqdn} with tag ${tag}`);
return await backupAppWithTag(app, tag, options, progressCallback);
}
async function snapshotApp(app, progressCallback) {
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof progressCallback, 'function');
@@ -380,7 +352,7 @@ async function backupAppWithTag(app, tag, options, progressCallback) {
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof progressCallback, 'function');
if (!canBackupApp(app)) { // if we cannot backup, reuse it's most recent backup
if (!apps.canBackupApp(app)) { // if we cannot backup, reuse it's most recent backup
const results = await backups.getByIdentifierAndStatePaged(app.id, backups.BACKUP_STATE_NORMAL, 1, 1);
if (results.length === 0) return null; // no backup to re-use
@@ -510,11 +482,11 @@ async function fullBackup(options, progressCallback) {
}
progressCallback({ percent, message: `Backing up ${app.fqdn} (${i+1}/${allApps.length}). Waiting for lock` });
await locks.wait(`${locks.TYPE_APP_TASK_PREFIX}${app.id}`);
await locks.wait(`${locks.TYPE_APP_BACKUP_PREFIX}${app.id}`);
const startTime = new Date();
const [appBackupError, appBackupId] = await safe(backupAppWithTag(app, tag, options, (progress) => progressCallback({ percent, message: progress.message })));
debug(`fullBackup: app ${app.fqdn} backup finished. Took ${(new Date() - startTime)/1000} seconds`);
await locks.release(`${locks.TYPE_APP_TASK_PREFIX}${app.id}`);
await locks.release(`${locks.TYPE_APP_BACKUP_PREFIX}${app.id}`);
if (appBackupError) throw appBackupError;
if (appBackupId) appBackupIds.push(appBackupId); // backupId can be null if in BAD_STATE and never backed up
}
@@ -530,3 +502,28 @@ async function fullBackup(options, progressCallback) {
const backupId = await backupBox(dependsOn, tag, options, (progress) => progressCallback({ percent, message: progress.message }));
return backupId;
}
// this function is called from external process
async function appBackup(appId, options, progressCallback) {
assert.strictEqual(typeof appId, 'string');
assert.strictEqual(typeof options, 'object');
assert.strictEqual(typeof progressCallback, 'function');
const app = await apps.get(appId);
if (!app) throw new BoxError(BoxError.BAD_FIELD, 'App not found');
let backupId = null;
await progressCallback({ percent: 1, message: `Backing up ${app.fqdn}. Waiting for lock` });
await locks.wait(`${locks.TYPE_APP_BACKUP_PREFIX}${app.id}`);
const startTime = new Date();
if (options.snapshotOnly) {
await snapshotApp(app, progressCallback);
} else {
const tag = (new Date()).toISOString().replace(/[T.]/g, '-').replace(/[:Z]/g,'');
backupId = await backupAppWithTag(app, tag, options, progressCallback);
}
await locks.release(`${locks.TYPE_APP_BACKUP_PREFIX}${app.id}`);
await progressCallback({ percent: 100, message: `app ${app.fqdn} backup finished. Took ${(new Date() - startTime)/1000} seconds` });
return backupId;
}