2021-07-14 11:07:19 -07:00
'use strict' ;
exports = module . exports = {
2021-09-26 18:37:04 -07:00
fullBackup ,
2021-07-14 11:07:19 -07:00
restore ,
backupApp ,
downloadApp ,
2021-09-26 18:37:04 -07:00
backupMail ,
downloadMail ,
2021-07-14 11:07:19 -07:00
upload ,
} ;
const apps = require ( './apps.js' ) ,
assert = require ( 'assert' ) ,
2022-04-28 18:43:14 -07:00
backupFormat = require ( './backupformat.js' ) ,
2021-07-14 11:07:19 -07:00
backups = require ( './backups.js' ) ,
BoxError = require ( './boxerror.js' ) ,
constants = require ( './constants.js' ) ,
DataLayout = require ( './datalayout.js' ) ,
database = require ( './database.js' ) ,
debug = require ( 'debug' ) ( 'box:backuptask' ) ,
2024-07-08 10:46:20 +02:00
df = require ( './df.js' ) ,
2021-07-14 11:07:19 -07:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
safe = require ( 'safetydance' ) ,
services = require ( './services.js' ) ,
shell = require ( './shell.js' ) ,
2022-04-30 16:42:14 -07:00
storage = require ( './storage.js' ) ;
2021-07-14 11:07:19 -07:00
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
}
2022-10-02 17:22:44 +02:00
async function checkPreconditions ( backupConfig , dataLayout ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert ( dataLayout instanceof DataLayout , 'dataLayout must be a DataLayout' ) ;
// check mount status before uploading
2024-09-09 17:39:17 +02:00
const status = await backups . ensureMounted ( ) ;
2022-10-02 17:22:44 +02:00
debug ( ` upload: mount point status is ${ JSON . stringify ( status ) } ` ) ;
2022-11-05 08:43:02 +01:00
if ( status . state !== 'active' ) throw new BoxError ( BoxError . MOUNT _ERROR , ` Backup endpoint is not active: ${ status . message } ` ) ;
2022-10-02 17:22:44 +02:00
// check availabe size. this requires root for df to work
2024-07-08 10:46:20 +02:00
const available = await storage . api ( backupConfig . provider ) . getAvailableSize ( backupConfig ) ;
2022-10-02 17:22:44 +02:00
let used = 0 ;
for ( const localPath of dataLayout . localPaths ( ) ) {
debug ( ` checkPreconditions: getting disk usage of ${ localPath } ` ) ;
2024-02-21 19:40:27 +01:00
const result = await shell . execArgs ( 'checkPreconditions' , 'du' , [ '-Dsb' , '--exclude=*.lock' , '--exclude=dovecot.list.index.log.*' , localPath ] , { } ) ;
2022-10-02 17:22:44 +02:00
used += parseInt ( result , 10 ) ;
}
2024-07-08 10:46:20 +02:00
debug ( ` checkPreconditions: total required= ${ used } available= ${ available } ` ) ;
2022-10-02 17:22:44 +02:00
const needed = 0.6 * used + ( 1024 * 1024 * 1024 ) ; // check if there is atleast 1GB left afterwards. aim for 60% because rsync/tgz won't need full 100%
2024-07-08 10:46:20 +02:00
if ( available <= needed ) throw new BoxError ( BoxError . FS _ERROR , ` Not enough disk space for backup. Needed: ${ df . prettyBytes ( needed ) } Available: ${ df . prettyBytes ( available ) } ` ) ;
2022-10-02 17:22:44 +02:00
}
2021-07-14 11:07:19 -07:00
// this function is called via backupupload (since it needs root to traverse app's directory)
2022-04-28 21:29:11 -07:00
async function upload ( remotePath , format , dataLayoutString , progressCallback ) {
2022-04-04 14:13:27 -07:00
assert . strictEqual ( typeof remotePath , 'string' ) ;
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof format , 'string' ) ;
assert . strictEqual ( typeof dataLayoutString , 'string' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-04-04 14:13:27 -07:00
debug ( ` upload: path ${ remotePath } format ${ format } dataLayout ${ dataLayoutString } ` ) ;
2021-07-14 11:07:19 -07:00
const dataLayout = DataLayout . fromString ( dataLayoutString ) ;
2023-08-04 11:24:28 +05:30
const backupConfig = await backups . getConfig ( ) ;
2022-10-02 17:22:44 +02:00
await checkPreconditions ( backupConfig , dataLayout ) ;
2021-07-14 11:07:19 -07:00
2022-04-30 16:42:14 -07:00
await backupFormat . api ( format ) . upload ( backupConfig , remotePath , dataLayout , progressCallback ) ;
2021-07-14 11:07:19 -07:00
}
2022-04-28 21:29:11 -07:00
async function download ( backupConfig , remotePath , format , dataLayout , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2022-04-05 09:28:30 -07:00
assert . strictEqual ( typeof remotePath , 'string' ) ;
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof format , 'string' ) ;
assert ( dataLayout instanceof DataLayout , 'dataLayout must be a DataLayout' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-04-05 09:28:30 -07:00
debug ( ` download: Downloading ${ remotePath } of format ${ format } to ${ dataLayout . toString ( ) } ` ) ;
2021-07-14 11:07:19 -07:00
2022-04-30 16:42:14 -07:00
await backupFormat . api ( format ) . download ( backupConfig , remotePath , dataLayout , progressCallback ) ;
2021-07-14 11:07:19 -07:00
}
2022-04-05 09:28:30 -07:00
async function restore ( backupConfig , remotePath , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2022-04-05 09:28:30 -07:00
assert . strictEqual ( typeof remotePath , 'string' ) ;
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
const boxDataDir = safe . fs . realpathSync ( paths . BOX _DATA _DIR ) ;
2021-09-16 13:59:03 -07:00
if ( ! boxDataDir ) throw new BoxError ( BoxError . FS _ERROR , ` Error resolving boxdata: ${ safe . error . message } ` ) ;
2021-07-14 11:07:19 -07:00
const dataLayout = new DataLayout ( boxDataDir , [ ] ) ;
2022-04-28 21:29:11 -07:00
await download ( backupConfig , remotePath , backupConfig . format , dataLayout , progressCallback ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
debug ( 'restore: download completed, importing database' ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
await database . importFromFile ( ` ${ dataLayout . localRoot ( ) } /box.mysqldump ` ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
debug ( 'restore: database imported' ) ;
2021-07-14 11:07:19 -07:00
}
2021-09-16 13:59:03 -07:00
async function downloadApp ( app , restoreConfig , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof restoreConfig , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
const appDataDir = safe . fs . realpathSync ( path . join ( paths . APPS _DATA _DIR , app . id ) ) ;
2021-09-16 13:59:03 -07:00
if ( ! appDataDir ) throw new BoxError ( BoxError . FS _ERROR , safe . error . message ) ;
2022-06-01 22:44:52 -07:00
const dataLayout = new DataLayout ( appDataDir , app . storageVolumeId ? [ { localDir : await apps . getStorageDir ( app ) , remoteDir : 'data' } ] : [ ] ) ;
2021-07-14 11:07:19 -07:00
const startTime = new Date ( ) ;
2023-08-04 11:24:28 +05:30
const backupConfig = restoreConfig . backupConfig || await backups . getConfig ( ) ;
2021-07-14 11:07:19 -07:00
2022-04-28 21:29:11 -07:00
await download ( backupConfig , restoreConfig . remotePath , restoreConfig . backupFormat , dataLayout , progressCallback ) ;
2021-09-16 13:59:03 -07:00
debug ( 'downloadApp: time: %s' , ( new Date ( ) - startTime ) / 1000 ) ;
2021-07-14 11:07:19 -07:00
}
2022-04-28 21:29:11 -07:00
async function runBackupUpload ( uploadConfig , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof uploadConfig , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-04-04 14:13:27 -07:00
const { remotePath , backupConfig , dataLayout , progressTag } = uploadConfig ;
assert . strictEqual ( typeof remotePath , 'string' ) ;
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof progressTag , 'string' ) ;
assert ( dataLayout instanceof DataLayout , 'dataLayout must be a DataLayout' ) ;
// https://stackoverflow.com/questions/48387040/node-js-recommended-max-old-space-size
const envCopy = Object . assign ( { } , process . env ) ;
2023-07-13 11:50:57 +05:30
if ( backupConfig . limits ? . memoryLimit >= 2 * 1024 * 1024 * 1024 ) {
const heapSize = Math . min ( ( backupConfig . limits . memoryLimit / 1024 / 1024 ) - 256 , 8192 ) ;
2021-07-14 11:07:19 -07:00
debug ( ` runBackupUpload: adjusting heap size to ${ heapSize } M ` ) ;
envCopy . NODE _OPTIONS = ` --max-old-space-size= ${ heapSize } ` ;
}
2022-04-28 21:29:11 -07:00
let result = '' ; // the script communicates error result as a string
function onMessage ( progress ) { // this is { message } or { result }
2021-07-14 11:07:19 -07:00
if ( 'message' in progress ) return progressCallback ( { message : ` ${ progress . message } ( ${ progressTag } ) ` } ) ;
debug ( ` runBackupUpload: result - ${ JSON . stringify ( progress ) } ` ) ;
result = progress . result ;
2022-04-28 21:29:11 -07:00
}
const [ error ] = await safe ( shell . promises . sudo ( ` backup- ${ remotePath } ` , [ BACKUP _UPLOAD _CMD , remotePath , backupConfig . format , dataLayout . toString ( ) ] , { env : envCopy , preserveEnv : true , ipc : true , onMessage } ) ) ;
if ( error && ( error . code === null /* signal */ || ( error . code !== 0 && error . code !== 50 ) ) ) { // backuptask crashed
throw new BoxError ( BoxError . INTERNAL _ERROR , 'Backuptask crashed' ) ;
} else if ( error && error . code === 50 ) { // exited with error
throw new BoxError ( BoxError . EXTERNAL _ERROR , result ) ;
}
2021-07-14 11:07:19 -07:00
}
2021-09-16 13:59:03 -07:00
async function snapshotBox ( progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
progressCallback ( { message : 'Snapshotting box' } ) ;
const startTime = new Date ( ) ;
2021-09-16 13:59:03 -07:00
await database . exportToFile ( ` ${ paths . BOX _DATA _DIR } /box.mysqldump ` ) ;
debug ( ` snapshotBox: took ${ ( new Date ( ) - startTime ) / 1000 } seconds ` ) ;
2021-07-14 11:07:19 -07:00
}
2021-09-16 13:59:03 -07:00
async function uploadBoxSnapshot ( backupConfig , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2021-09-16 13:59:03 -07:00
await snapshotBox ( progressCallback ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const boxDataDir = safe . fs . realpathSync ( paths . BOX _DATA _DIR ) ;
if ( ! boxDataDir ) throw new BoxError ( BoxError . FS _ERROR , ` Error resolving boxdata: ${ safe . error . message } ` ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const uploadConfig = {
2022-04-04 14:13:27 -07:00
remotePath : 'snapshot/box' ,
2021-09-16 13:59:03 -07:00
backupConfig ,
dataLayout : new DataLayout ( boxDataDir , [ ] ) ,
progressTag : 'box'
} ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
progressCallback ( { message : 'Uploading box snapshot' } ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const startTime = new Date ( ) ;
2021-07-14 11:07:19 -07:00
2022-04-28 21:29:11 -07:00
await runBackupUpload ( uploadConfig , progressCallback ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
debug ( ` uploadBoxSnapshot: took ${ ( new Date ( ) - startTime ) / 1000 } seconds ` ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
await backups . setSnapshotInfo ( 'box' , { timestamp : new Date ( ) . toISOString ( ) , format : backupConfig . format } ) ;
2021-07-14 11:07:19 -07:00
}
2022-04-04 14:13:27 -07:00
async function copy ( backupConfig , srcRemotePath , destRemotePath , progressCallback ) {
2021-09-26 18:37:04 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2022-04-04 14:13:27 -07:00
assert . strictEqual ( typeof srcRemotePath , 'string' ) ;
assert . strictEqual ( typeof destRemotePath , 'string' ) ;
2021-09-26 18:37:04 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-04-04 14:13:27 -07:00
const { provider , format } = backupConfig ;
2022-04-28 18:43:14 -07:00
const oldFilePath = backupFormat . api ( format ) . getBackupFilePath ( backupConfig , srcRemotePath ) ;
const newFilePath = backupFormat . api ( format ) . getBackupFilePath ( backupConfig , destRemotePath ) ;
2021-09-26 18:37:04 -07:00
2022-04-30 16:01:42 -07:00
const startTime = new Date ( ) ;
2023-01-17 10:43:17 +01:00
const [ copyError ] = await safe ( storage . api ( provider ) . copy ( backupConfig , oldFilePath , newFilePath , progressCallback ) ) ;
if ( copyError ) {
debug ( ` copy: copied to ${ destRemotePath } errored. error: ${ copyError . message } ` ) ;
throw copyError ;
}
2022-04-30 16:01:42 -07:00
debug ( ` copy: copied successfully to ${ destRemotePath } . Took ${ ( new Date ( ) - startTime ) / 1000 } seconds ` ) ;
2021-09-26 18:37:04 -07:00
}
async function rotateBoxBackup ( backupConfig , tag , options , dependsOn , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof tag , 'string' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
2021-09-26 18:37:04 -07:00
assert ( Array . isArray ( dependsOn ) ) ;
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-04-04 14:13:27 -07:00
const remotePath = ` ${ tag } /box_v ${ constants . VERSION } ` ;
2021-07-14 11:07:19 -07:00
const format = backupConfig . format ;
2022-04-04 14:13:27 -07:00
debug ( ` rotateBoxBackup: rotating to id ${ remotePath } ` ) ;
2021-07-14 11:07:19 -07:00
const data = {
2022-04-04 14:13:27 -07:00
remotePath ,
2021-07-14 11:07:19 -07:00
encryptionVersion : backupConfig . encryption ? 2 : null ,
packageVersion : constants . VERSION ,
type : backups . BACKUP _TYPE _BOX ,
state : backups . BACKUP _STATE _CREATING ,
2021-09-26 18:37:04 -07:00
identifier : backups . BACKUP _IDENTIFIER _BOX ,
dependsOn ,
2021-07-14 11:07:19 -07:00
manifest : null ,
2022-04-04 21:23:59 -07:00
format ,
preserveSecs : options . preserveSecs || 0
2021-07-14 11:07:19 -07:00
} ;
2022-04-04 14:13:27 -07:00
const id = await backups . add ( data ) ;
2022-04-05 13:11:30 +02:00
const [ error ] = await safe ( copy ( backupConfig , 'snapshot/box' , remotePath , progressCallback ) ) ;
const state = error ? backups . BACKUP _STATE _ERROR : backups . BACKUP _STATE _NORMAL ;
2022-04-04 21:23:59 -07:00
await backups . setState ( id , state ) ;
2022-04-05 13:11:30 +02:00
if ( error ) throw error ;
2022-04-04 14:13:27 -07:00
return id ;
2021-07-14 11:07:19 -07:00
}
2021-09-26 18:37:04 -07:00
async function backupBox ( dependsOn , tag , options , progressCallback ) {
assert ( Array . isArray ( dependsOn ) ) ;
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof tag , 'string' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2023-08-04 11:24:28 +05:30
const backupConfig = await backups . getConfig ( ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
await uploadBoxSnapshot ( backupConfig , progressCallback ) ;
2022-04-04 14:13:27 -07:00
return await rotateBoxBackup ( backupConfig , tag , options , dependsOn , progressCallback ) ;
2021-07-14 11:07:19 -07:00
}
async function rotateAppBackup ( backupConfig , app , tag , options , progressCallback ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof tag , 'string' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
const snapshotInfo = backups . getSnapshotInfo ( app . id ) ;
const manifest = snapshotInfo . restoreConfig ? snapshotInfo . restoreConfig . manifest : snapshotInfo . manifest ; // compat
2022-04-04 14:13:27 -07:00
const remotePath = ` ${ tag } /app_ ${ app . fqdn } _v ${ manifest . version } ` ;
2021-07-14 11:07:19 -07:00
const format = backupConfig . format ;
2022-04-04 14:13:27 -07:00
debug ( ` rotateAppBackup: rotating ${ app . fqdn } to path ${ remotePath } ` ) ;
2021-07-14 11:07:19 -07:00
const data = {
2022-04-04 14:13:27 -07:00
remotePath ,
2021-07-14 11:07:19 -07:00
encryptionVersion : backupConfig . encryption ? 2 : null ,
packageVersion : manifest . version ,
type : backups . BACKUP _TYPE _APP ,
state : backups . BACKUP _STATE _CREATING ,
identifier : app . id ,
2022-04-04 21:23:59 -07:00
dependsOn : [ ] ,
2021-07-14 11:07:19 -07:00
manifest ,
2022-04-04 21:23:59 -07:00
format ,
preserveSecs : options . preserveSecs || 0
2021-07-14 11:07:19 -07:00
} ;
2022-04-04 14:13:27 -07:00
const id = await backups . add ( data ) ;
2022-04-05 13:11:30 +02:00
const [ error ] = await safe ( copy ( backupConfig , ` snapshot/app_ ${ app . id } ` , remotePath , progressCallback ) ) ;
const state = error ? backups . BACKUP _STATE _ERROR : backups . BACKUP _STATE _NORMAL ;
2022-04-04 21:23:59 -07:00
await backups . setState ( id , state ) ;
2022-04-05 13:11:30 +02:00
if ( error ) throw error ;
2022-04-04 14:13:27 -07:00
return id ;
2021-07-14 11:07:19 -07:00
}
2021-09-16 13:59:03 -07:00
async function backupApp ( app , options , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2021-09-16 13:59:03 -07:00
if ( options . snapshotOnly ) return await snapshotApp ( app , progressCallback ) ;
2021-07-14 11:07:19 -07:00
const tag = ( new Date ( ) ) . toISOString ( ) . replace ( /[T.]/g , '-' ) . replace ( /[:Z]/g , '' ) ;
2021-09-26 18:45:23 -07:00
debug ( ` backupApp: backing up ${ app . fqdn } with tag ${ tag } ` ) ;
2021-07-14 11:07:19 -07:00
2021-09-30 10:45:25 -07:00
return await backupAppWithTag ( app , tag , options , progressCallback ) ;
2021-07-14 11:07:19 -07:00
}
2021-09-16 13:59:03 -07:00
async function snapshotApp ( app , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
const startTime = new Date ( ) ;
progressCallback ( { message : ` Snapshotting app ${ app . fqdn } ` } ) ;
2024-02-10 11:53:25 +01:00
await apps . writeConfig ( app ) ;
2021-09-16 13:59:03 -07:00
await services . backupAddons ( app , app . manifest . addons ) ;
2021-07-14 11:07:19 -07:00
2021-09-26 18:45:23 -07:00
debug ( ` snapshotApp: ${ app . fqdn } took ${ ( new Date ( ) - startTime ) / 1000 } seconds ` ) ;
2021-07-14 11:07:19 -07:00
}
2021-09-16 13:59:03 -07:00
async function uploadAppSnapshot ( backupConfig , app , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2021-09-16 13:59:03 -07:00
await snapshotApp ( app , progressCallback ) ;
2021-07-14 11:07:19 -07:00
2022-04-26 18:53:07 -07:00
const remotePath = ` snapshot/app_ ${ app . id } ` ;
2021-09-16 13:59:03 -07:00
const appDataDir = safe . fs . realpathSync ( path . join ( paths . APPS _DATA _DIR , app . id ) ) ;
if ( ! appDataDir ) throw new BoxError ( BoxError . FS _ERROR , ` Error resolving appsdata: ${ safe . error . message } ` ) ;
2021-07-14 11:07:19 -07:00
2022-06-01 22:44:52 -07:00
const dataLayout = new DataLayout ( appDataDir , app . storageVolumeId ? [ { localDir : await apps . getStorageDir ( app ) , remoteDir : 'data' } ] : [ ] ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
progressCallback ( { message : ` Uploading app snapshot ${ app . fqdn } ` } ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const uploadConfig = {
2022-04-04 14:13:27 -07:00
remotePath ,
2021-09-16 13:59:03 -07:00
backupConfig ,
dataLayout ,
progressTag : app . fqdn
} ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const startTime = new Date ( ) ;
2021-07-14 11:07:19 -07:00
2022-04-28 21:29:11 -07:00
await runBackupUpload ( uploadConfig , progressCallback ) ;
2021-07-14 11:07:19 -07:00
2022-04-26 18:53:07 -07:00
debug ( ` uploadAppSnapshot: ${ app . fqdn } uploaded to ${ remotePath } . ${ ( new Date ( ) - startTime ) / 1000 } seconds ` ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
await backups . setSnapshotInfo ( app . id , { timestamp : new Date ( ) . toISOString ( ) , manifest : app . manifest , format : backupConfig . format } ) ;
2021-07-14 11:07:19 -07:00
}
2021-09-16 13:59:03 -07:00
async function backupAppWithTag ( app , tag , options , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof app , 'object' ) ;
assert . strictEqual ( typeof tag , 'string' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
if ( ! canBackupApp ( app ) ) { // if we cannot backup, reuse it's most recent backup
2021-09-16 13:59:03 -07:00
const results = await backups . getByIdentifierAndStatePaged ( app . id , backups . BACKUP _STATE _NORMAL , 1 , 1 ) ;
if ( results . length === 0 ) return null ; // no backup to re-use
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
return results [ 0 ] . id ;
2021-07-14 11:07:19 -07:00
}
2023-08-04 11:24:28 +05:30
const backupConfig = await backups . getConfig ( ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
await uploadAppSnapshot ( backupConfig , app , progressCallback ) ;
2022-04-04 14:13:27 -07:00
return await rotateAppBackup ( backupConfig , app , tag , options , progressCallback ) ;
2021-07-14 11:07:19 -07:00
}
2021-09-26 18:37:04 -07:00
async function uploadMailSnapshot ( backupConfig , progressCallback ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
const mailDataDir = safe . fs . realpathSync ( paths . MAIL _DATA _DIR ) ;
if ( ! mailDataDir ) throw new BoxError ( BoxError . FS _ERROR , ` Error resolving maildata: ${ safe . error . message } ` ) ;
const uploadConfig = {
2022-04-04 14:13:27 -07:00
remotePath : 'snapshot/mail' ,
2021-09-26 18:37:04 -07:00
backupConfig ,
dataLayout : new DataLayout ( mailDataDir , [ ] ) ,
progressTag : 'mail'
} ;
progressCallback ( { message : 'Uploading mail snapshot' } ) ;
const startTime = new Date ( ) ;
2022-04-28 21:29:11 -07:00
await runBackupUpload ( uploadConfig , progressCallback ) ;
2021-09-26 18:37:04 -07:00
debug ( ` uploadMailSnapshot: took ${ ( new Date ( ) - startTime ) / 1000 } seconds ` ) ;
await backups . setSnapshotInfo ( 'mail' , { timestamp : new Date ( ) . toISOString ( ) , format : backupConfig . format } ) ;
}
async function rotateMailBackup ( backupConfig , tag , options , progressCallback ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
assert . strictEqual ( typeof tag , 'string' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-04-04 14:13:27 -07:00
const remotePath = ` ${ tag } /mail_v ${ constants . VERSION } ` ;
2021-09-26 18:37:04 -07:00
const format = backupConfig . format ;
2022-04-04 14:13:27 -07:00
debug ( ` rotateMailBackup: rotating to ${ remotePath } ` ) ;
2021-09-26 18:37:04 -07:00
const data = {
2022-04-04 14:13:27 -07:00
remotePath ,
2021-09-26 18:37:04 -07:00
encryptionVersion : backupConfig . encryption ? 2 : null ,
packageVersion : constants . VERSION ,
type : backups . BACKUP _TYPE _MAIL ,
state : backups . BACKUP _STATE _CREATING ,
identifier : backups . BACKUP _IDENTIFIER _MAIL ,
dependsOn : [ ] ,
manifest : null ,
2022-04-04 21:23:59 -07:00
format ,
preserveSecs : options . preserveSecs || 0
2021-09-26 18:37:04 -07:00
} ;
2022-04-04 14:13:27 -07:00
const id = await backups . add ( data ) ;
2022-04-05 13:11:30 +02:00
const [ error ] = await safe ( copy ( backupConfig , 'snapshot/mail' , remotePath , progressCallback ) ) ;
const state = error ? backups . BACKUP _STATE _ERROR : backups . BACKUP _STATE _NORMAL ;
2022-04-04 21:23:59 -07:00
await backups . setState ( id , state ) ;
2022-04-05 13:11:30 +02:00
if ( error ) throw error ;
2022-04-04 14:13:27 -07:00
return id ;
2021-09-26 18:37:04 -07:00
}
async function backupMailWithTag ( tag , options , progressCallback ) {
assert . strictEqual ( typeof tag , 'string' ) ;
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
debug ( ` backupMailWithTag: backing up mail with tag ${ tag } ` ) ;
2023-08-04 11:24:28 +05:30
const backupConfig = await backups . getConfig ( ) ;
2021-09-26 18:37:04 -07:00
await uploadMailSnapshot ( backupConfig , progressCallback ) ;
2022-04-04 14:13:27 -07:00
return await rotateMailBackup ( backupConfig , tag , options , progressCallback ) ;
2021-09-26 18:37:04 -07:00
}
async function backupMail ( options , progressCallback ) {
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
const tag = ( new Date ( ) ) . toISOString ( ) . replace ( /[T.]/g , '-' ) . replace ( /[:Z]/g , '' ) ;
debug ( ` backupMail: backing up mail with tag ${ tag } ` ) ;
return await backupMailWithTag ( tag , options , progressCallback ) ;
}
async function downloadMail ( restoreConfig , progressCallback ) {
assert . strictEqual ( typeof restoreConfig , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
const mailDataDir = safe . fs . realpathSync ( paths . MAIL _DATA _DIR ) ;
if ( ! mailDataDir ) throw new BoxError ( BoxError . FS _ERROR , ` Error resolving maildata: ${ safe . error . message } ` ) ;
const dataLayout = new DataLayout ( mailDataDir , [ ] ) ;
const startTime = new Date ( ) ;
2022-04-28 21:29:11 -07:00
await download ( restoreConfig . backupConfig , restoreConfig . remotePath , restoreConfig . backupFormat , dataLayout , progressCallback ) ;
2021-09-26 18:37:04 -07:00
debug ( 'downloadMail: time: %s' , ( new Date ( ) - startTime ) / 1000 ) ;
}
// this function is called from external process. calling process is expected to have a lock
async function fullBackup ( options , progressCallback ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof options , 'object' ) ;
assert . strictEqual ( typeof progressCallback , 'function' ) ;
2022-04-04 14:13:27 -07:00
const tag = ( new Date ( ) ) . toISOString ( ) . replace ( /[T.]/g , '-' ) . replace ( /[:Z]/g , '' ) ; // unique tag under which all apps/mail/box backs up
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const allApps = await apps . list ( ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
let percent = 1 ;
2024-07-08 10:47:00 +02:00
const step = 100 / ( allApps . length + 3 ) ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const appBackupIds = [ ] ;
2021-11-02 18:07:19 -07:00
for ( let i = 0 ; i < allApps . length ; i ++ ) {
const app = allApps [ i ] ;
progressCallback ( { percent : percent , message : ` Backing up ${ app . fqdn } ( ${ i + 1 } / ${ allApps . length } ) ` } ) ;
2021-09-16 13:59:03 -07:00
percent += step ;
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
if ( ! app . enableBackup ) {
2022-02-28 11:04:44 -08:00
debug ( ` fullBackup: skipped backup ${ app . fqdn } ( ${ i + 1 } / ${ allApps . length } ) since automatic backup disabled ` ) ;
2021-11-02 17:59:08 -07:00
continue ; // nothing to backup
2021-09-16 13:59:03 -07:00
}
2021-07-14 11:07:19 -07:00
2021-09-16 13:59:03 -07:00
const startTime = new Date ( ) ;
2022-04-04 14:13:27 -07:00
const appBackupId = await backupAppWithTag ( app , tag , options , ( progress ) => progressCallback ( { percent , message : progress . message } ) ) ;
2021-09-26 18:37:04 -07:00
debug ( ` fullBackup: app ${ app . fqdn } backup finished. Took ${ ( new Date ( ) - startTime ) / 1000 } seconds ` ) ;
if ( appBackupId ) appBackupIds . push ( appBackupId ) ; // backupId can be null if in BAD_STATE and never backed up
2021-09-16 13:59:03 -07:00
}
2021-07-14 11:07:19 -07:00
2022-04-04 14:13:27 -07:00
progressCallback ( { percent , message : 'Backing up mail' } ) ;
2021-09-26 18:37:04 -07:00
percent += step ;
2022-04-04 14:13:27 -07:00
const mailBackupId = await backupMailWithTag ( tag , options , ( progress ) => progressCallback ( { percent , message : progress . message } ) ) ;
2021-09-26 18:37:04 -07:00
2022-04-04 14:13:27 -07:00
progressCallback ( { percent , message : 'Backing up system data' } ) ;
2021-09-16 13:59:03 -07:00
percent += step ;
2021-07-14 11:07:19 -07:00
2021-09-26 18:37:04 -07:00
const dependsOn = appBackupIds . concat ( mailBackupId ) ;
2022-04-04 14:13:27 -07:00
const backupId = await backupBox ( dependsOn , tag , options , ( progress ) => progressCallback ( { percent , message : progress . message } ) ) ;
2021-09-16 13:59:03 -07:00
return backupId ;
2021-07-14 11:07:19 -07:00
}