2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
2025-07-24 19:02:02 +02:00
get ,
list ,
add ,
del ,
2016-04-10 19:17:44 -07:00
2025-07-24 19:02:02 +02:00
setConfig ,
setLimits ,
setSchedule ,
setRetention ,
2017-09-20 09:57:16 -07:00
2021-01-21 11:31:35 -08:00
removePrivateFields ,
2019-02-09 18:08:10 -08:00
2025-07-24 19:02:02 +02:00
startBackupTask ,
startCleanupTask ,
cleanupCacheFilesSync ,
2021-07-14 11:07:19 -07:00
getSnapshotInfo ,
setSnapshotInfo ,
2023-08-15 20:24:54 +05:30
validateFormat ,
2020-05-12 14:00:05 -07:00
2023-08-15 20:24:54 +05:30
getRootPath ,
2023-08-15 20:24:54 +05:30
2021-10-11 17:45:35 +02:00
remount ,
2023-04-30 17:21:18 +02:00
getMountStatus ,
2024-09-09 17:39:17 +02:00
ensureMounted ,
2021-10-11 17:45:35 +02:00
2025-07-24 19:02:02 +02:00
_addDefault : addDefault ,
_getDefault : getDefault ,
2015-07-20 00:09:47 -07:00
} ;
2021-07-14 11:07:19 -07:00
const assert = require ( 'assert' ) ,
2025-07-24 18:09:33 +02:00
backupListing = require ( './backuplisting.js' ) ,
2019-10-22 20:36:20 -07:00
BoxError = require ( './boxerror.js' ) ,
2019-07-25 14:40:52 -07:00
constants = require ( './constants.js' ) ,
2023-08-04 11:24:28 +05:30
cron = require ( './cron.js' ) ,
2024-04-19 18:19:41 +02:00
{ CronTime } = require ( 'cron' ) ,
2017-11-22 10:58:07 -08:00
database = require ( './database.js' ) ,
2021-09-10 12:10:10 -07:00
debug = require ( 'debug' ) ( 'box:backups' ) ,
2018-12-09 03:20:00 -08:00
eventlog = require ( './eventlog.js' ) ,
2025-07-24 19:02:02 +02:00
hush = require ( './hush.js' ) ,
2024-12-07 14:35:45 +01:00
locks = require ( './locks.js' ) ,
2023-04-30 17:21:18 +02:00
mounts = require ( './mounts.js' ) ,
2016-04-10 19:17:44 -07:00
path = require ( 'path' ) ,
paths = require ( './paths.js' ) ,
2017-04-21 14:07:10 -07:00
safe = require ( 'safetydance' ) ,
2021-07-14 11:07:19 -07:00
storage = require ( './storage.js' ) ,
2023-08-04 11:24:28 +05:30
tasks = require ( './tasks.js' ) ,
2025-07-24 19:02:02 +02:00
uuid = require ( 'uuid' ) ;
const BACKUP _TARGET _FIELDS = [ 'id' , 'label' , 'provider' , 'configJson' , 'limitsJson' , 'retentionJson' , 'schedule' , 'encryptionJson' , 'format' , 'priority' , 'creationTime' , 'ts' ] . join ( ',' ) ;
function getRootPath ( provider , config , mountPath ) {
assert . strictEqual ( typeof config , 'object' ) ;
assert . strictEqual ( typeof mountPath , 'string' ) ;
2021-07-14 11:07:19 -07:00
2025-07-24 19:02:02 +02:00
if ( mounts . isManagedProvider ( provider ) ) {
return path . join ( mountPath , config . prefix ) ;
} else if ( provider === 'mountpoint' ) {
return path . join ( config . mountPoint , config . prefix ) ;
} else if ( provider === 'filesystem' ) {
return config . backupFolder ;
} else {
return config . prefix ;
}
}
2016-04-10 19:17:44 -07:00
2021-07-14 11:07:19 -07:00
function postProcess ( result ) {
assert . strictEqual ( typeof result , 'object' ) ;
2016-04-10 19:17:44 -07:00
2025-07-24 13:19:27 +02:00
result . config = result . configJson ? safe . JSON . parse ( result . configJson ) : { } ;
delete result . configJson ;
2025-07-24 19:02:02 +02:00
// note: rootPath will be dynamic for managed mount providers during app import . since it's used in api backends it has to be inside config
result . config . rootPath = getRootPath ( result . provider , result . config , paths . MANAGED _BACKUP _MOUNT _DIR ) ;
result . config . provider = result . provider ; // this allows api backends to identify the real provider
2025-07-24 13:19:27 +02:00
result . limits = result . limitsJson ? safe . JSON . parse ( result . limitsJson ) : { } ;
delete result . limitsJson ;
result . retention = result . retentionJson ? safe . JSON . parse ( result . retentionJson ) : { } ;
delete result . retentionJson ;
result . encryption = result . encryptionJson ? safe . JSON . parse ( result . encryptionJson ) : null ;
delete result . encryptionJson ;
result . priority = ! ! result . priority ;
return result ;
}
2025-07-24 19:02:02 +02:00
function removePrivateFields ( target ) {
assert . strictEqual ( typeof target , 'object' ) ;
if ( target . encryption ) {
delete target . encryption ;
target . password = constants . SECRET _PLACEHOLDER ;
2020-05-12 14:00:05 -07:00
}
2025-07-24 19:02:02 +02:00
delete target . rootPath ;
return storage . api ( target . provider ) . removePrivateFields ( target . config ) ;
2020-05-12 15:49:43 -07:00
}
2025-07-24 19:02:02 +02:00
function validateFormat ( format ) {
assert . strictEqual ( typeof format , 'string' ) ;
2020-05-12 14:00:05 -07:00
2025-07-24 19:02:02 +02:00
if ( format === 'tgz' || format == 'rsync' ) return null ;
return new BoxError ( BoxError . BAD _FIELD , 'Invalid backup format' ) ;
2020-05-12 14:00:05 -07:00
}
2019-12-05 11:55:51 -08:00
2025-07-24 19:02:02 +02:00
function validateLabel ( label ) {
assert . strictEqual ( typeof label , 'string' ) ;
2023-07-12 10:01:53 +05:30
2025-07-24 19:02:02 +02:00
if ( label . length > 48 ) return new BoxError ( BoxError . BAD _FIELD , 'Label too long' ) ;
}
function validateSchedule ( schedule ) {
assert . strictEqual ( typeof schedule , 'string' ) ;
if ( schedule === constants . CRON _PATTERN _NEVER ) return null ;
const job = safe . safeCall ( function ( ) { return new CronTime ( schedule ) ; } ) ;
2023-07-12 10:01:53 +05:30
if ( ! job ) return new BoxError ( BoxError . BAD _FIELD , 'Invalid schedule pattern' ) ;
2025-07-24 19:02:02 +02:00
return null ;
}
function validateRetention ( retention ) {
assert . strictEqual ( typeof retention , 'object' ) ;
2023-07-12 10:01:53 +05:30
if ( ! retention ) return new BoxError ( BoxError . BAD _FIELD , 'retention is required' ) ;
if ( ! [ 'keepWithinSecs' , 'keepDaily' , 'keepWeekly' , 'keepMonthly' , 'keepYearly' ] . find ( k => ! ! retention [ k ] ) ) return new BoxError ( BoxError . BAD _FIELD , 'retention properties missing' ) ;
if ( 'keepWithinSecs' in retention && typeof retention . keepWithinSecs !== 'number' ) return new BoxError ( BoxError . BAD _FIELD , 'retention.keepWithinSecs must be a number' ) ;
if ( 'keepDaily' in retention && typeof retention . keepDaily !== 'number' ) return new BoxError ( BoxError . BAD _FIELD , 'retention.keepDaily must be a number' ) ;
if ( 'keepWeekly' in retention && typeof retention . keepWeekly !== 'number' ) return new BoxError ( BoxError . BAD _FIELD , 'retention.keepWeekly must be a number' ) ;
if ( 'keepMonthly' in retention && typeof retention . keepMonthly !== 'number' ) return new BoxError ( BoxError . BAD _FIELD , 'retention.keepMonthly must be a number' ) ;
if ( 'keepYearly' in retention && typeof retention . keepYearly !== 'number' ) return new BoxError ( BoxError . BAD _FIELD , 'retention.keepYearly must be a number' ) ;
2025-07-24 19:02:02 +02:00
return null ;
}
function validateEncryptionPassword ( password ) {
assert . strictEqual ( typeof password , 'string' ) ;
if ( password . length < 8 ) return new BoxError ( BoxError . BAD _FIELD , 'password must be atleast 8 characters' ) ;
2023-07-12 10:01:53 +05:30
}
2025-07-24 19:02:02 +02:00
async function list ( page , perPage ) {
assert ( typeof page === 'number' && page > 0 ) ;
assert ( typeof perPage === 'number' && perPage > 0 ) ;
const results = await database . query ( ` SELECT ${ BACKUP _TARGET _FIELDS } FROM backupTargets ORDER BY creationTime DESC LIMIT ?,? ` , [ ( page - 1 ) * perPage , perPage ] ) ;
results . forEach ( function ( result ) { postProcess ( result ) ; } ) ;
return results ;
}
async function getDefault ( ) {
const results = await database . query ( ` SELECT ${ BACKUP _TARGET _FIELDS } FROM backupTargets WHERE priority=? LIMIT 1 ` , [ true ] ) ;
return postProcess ( results [ 0 ] ) ;
}
async function get ( id ) {
const results = await database . query ( ` SELECT ${ BACKUP _TARGET _FIELDS } FROM backupTargets WHERE id=? ` , [ id ] ) ;
if ( results . length === 0 ) return null ;
return postProcess ( results [ 0 ] ) ;
}
async function addDefault ( ) {
const label = '' , priority = true ;
const limits = null , encryption = null ;
const retention = { keepWithinSecs : 2 * 24 * 60 * 60 } ;
const schedule = '00 00 23 * * *' ; ;
const config = { backupFolder : paths . DEFAULT _BACKUP _DIR } ;
const provider = 'filesystem' ;
const format = 'tgz' ;
const id = ` bc- ${ uuid . v4 ( ) } ` ;
await database . query ( 'INSERT INTO backupTargets (id, label, provider, configJson, limitsJson, retentionJson, schedule, encryptionJson, format, priority) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' ,
[ id , label , provider , JSON . stringify ( config ) , JSON . stringify ( limits ) , JSON . stringify ( retention ) , schedule , JSON . stringify ( encryption ) , format , priority ] ) ;
return id ;
}
async function update ( target , data ) {
assert . strictEqual ( typeof target , 'object' ) ;
assert ( data && typeof data === 'object' ) ;
const args = [ ] ;
const fields = [ ] ;
for ( const k in data ) {
if ( k === 'label' || k === 'schedule' || k === 'priority' ) { // format, provider cannot be updated
fields . push ( k + ' = ?' ) ;
args . push ( data [ k ] ) ;
} else if ( k === 'config' || k === 'limits' || k === 'retention' ) { // encryption cannot be updated
fields . push ( ` ${ k } JSON = ? ` ) ;
args . push ( JSON . stringify ( data [ k ] ) ) ;
}
}
args . push ( target . id ) ;
const [ updateError , result ] = await safe ( database . query ( 'UPDATE backupTargets SET ' + fields . join ( ', ' ) + ' WHERE id = ?' , args ) ) ;
if ( updateError ) throw updateError ;
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Target not found' ) ;
}
async function setSchedule ( target , schedule ) {
assert . strictEqual ( typeof target , 'object' ) ;
assert . strictEqual ( typeof schedule , 'string' ) ;
const error = await validateSchedule ( schedule ) ;
if ( error ) throw error ;
await update ( target , { schedule } ) ;
await cron . handleBackupScheduleChanged ( target ) ;
}
async function setLimits ( target , limits ) {
assert . strictEqual ( typeof target , 'object' ) ;
assert . strictEqual ( typeof limits , 'object' ) ;
await update ( target , { limits } ) ;
}
async function setRetention ( target , retention ) {
assert . strictEqual ( typeof target , 'object' ) ;
assert . strictEqual ( typeof retention , 'object' ) ;
const error = await validateRetention ( retention ) ;
if ( error ) throw error ;
await update ( target , { retention } ) ;
}
async function del ( target , auditSource ) {
assert . strictEqual ( typeof target , 'object' ) ;
assert ( auditSource && typeof auditSource === 'object' ) ;
if ( target . priority ) throw new BoxError ( BoxError . CONFLICT , 'Cannot delete the primary backup target' ) ;
const queries = [ ] ;
queries . push ( { query : 'DELETE FROM backups WHERE targetId = ?' , args : [ target . id ] } ) ;
queries . push ( { query : 'DELETE FROM backupTargets WHERE id=? AND priority=?' , args : [ target . id , false ] } ) ;
const [ error , result ] = await safe ( database . transaction ( queries ) ) ;
if ( error && error . code === 'ER_NO_REFERENCED_ROW_2' ) throw new BoxError ( BoxError . NOT _FOUND , error ) ;
if ( error ) throw error ;
if ( result [ 1 ] . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Target not found' ) ;
// await eventlog.add(eventlog.ACTION_ARCHIVES_DEL, auditSource, { id: archive.id, backupId: archive.backupId });
debug ( 'del: clearing backup cache' ) ;
cleanupCacheFilesSync ( ) ;
}
async function startBackupTask ( target , auditSource ) {
assert . strictEqual ( typeof target , 'object' ) ;
2025-07-18 13:22:33 +02:00
const [ error ] = await safe ( locks . acquire ( locks . TYPE _FULL _BACKUP _TASK ) ) ;
2024-12-07 14:35:45 +01:00
if ( error ) throw new BoxError ( BoxError . BAD _STATE , ` Another backup task is in progress: ${ error . message } ` ) ;
2020-05-10 21:40:25 -07:00
2025-07-24 19:02:02 +02:00
const memoryLimit = target . limits ? . memoryLimit ? Math . max ( target . limits . memoryLimit / 1024 / 1024 , 1024 ) : 1024 ;
2018-07-27 06:55:54 -07:00
2025-07-24 19:02:02 +02:00
const taskId = await tasks . add ( ` ${ tasks . TASK _FULL _BACKUP _PREFIX } ${ target . id } ` , [ target . id , { /* options */ } ] ) ;
2018-07-27 06:55:54 -07:00
2021-09-10 12:10:10 -07:00
await eventlog . add ( eventlog . ACTION _BACKUP _START , auditSource , { taskId } ) ;
2020-09-30 20:03:46 -07:00
2025-06-17 18:54:12 +02:00
// background
tasks . startTask ( taskId , { timeout : 24 * 60 * 60 * 1000 /* 24 hours */ , nice : 15 , memoryLimit , oomScoreAdjust : - 999 } )
. then ( async ( backupId ) => {
2025-07-24 18:09:33 +02:00
const backup = await backupListing . get ( backupId ) ;
2025-06-17 18:54:12 +02:00
await eventlog . add ( eventlog . ACTION _BACKUP _FINISH , auditSource , { taskId , backupId , remotePath : backup . remotePath } ) ;
} )
. catch ( async ( error ) => {
const timedOut = error . code === tasks . ETIMEOUT ;
await safe ( eventlog . add ( eventlog . ACTION _BACKUP _FINISH , auditSource , { taskId , errorMessage : error . message , timedOut } ) ) ;
} )
. finally ( async ( ) => {
2025-07-18 13:22:33 +02:00
await locks . release ( locks . TYPE _FULL _BACKUP _TASK ) ;
2025-06-17 18:54:12 +02:00
await locks . releaseByTaskId ( taskId ) ;
} ) ;
2021-09-10 12:10:10 -07:00
return taskId ;
2021-07-14 11:07:19 -07:00
}
2018-07-27 11:46:42 -07:00
2021-10-06 13:09:04 -07:00
// this function is used in migrations - 20200512172301-settings-backup-encryption.js
2025-07-24 19:02:02 +02:00
function cleanupCacheFilesSync ( target ) {
const files = safe . fs . readdirSync ( path . join ( paths . BACKUP _INFO _DIR , target . id ) ) ;
2021-07-14 11:07:19 -07:00
if ( ! files ) return ;
2017-09-20 09:57:16 -07:00
2021-09-10 12:10:10 -07:00
files
. filter ( function ( f ) { return f . endsWith ( '.sync.cache' ) ; } )
. forEach ( function ( f ) {
safe . fs . unlinkSync ( path . join ( paths . BACKUP _INFO _DIR , f ) ) ;
} ) ;
2020-05-10 21:40:25 -07:00
}
2021-07-14 11:07:19 -07:00
function getSnapshotInfo ( id ) {
assert . strictEqual ( typeof id , 'string' ) ;
2018-02-22 10:58:56 -08:00
2021-07-14 11:07:19 -07:00
const contents = safe . fs . readFileSync ( paths . SNAPSHOT _INFO _FILE , 'utf8' ) ;
const info = safe . JSON . parse ( contents ) ;
if ( ! info ) return { } ;
return info [ id ] || { } ;
2017-09-22 14:40:37 -07:00
}
2021-09-26 18:37:04 -07:00
// keeps track of contents of the snapshot directory. this provides a way to clean up backups of uninstalled apps
2021-09-16 13:59:03 -07:00
async function setSnapshotInfo ( id , info ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof info , 'object' ) ;
2017-09-22 14:40:37 -07:00
2021-07-14 11:07:19 -07:00
const contents = safe . fs . readFileSync ( paths . SNAPSHOT _INFO _FILE , 'utf8' ) ;
const data = safe . JSON . parse ( contents ) || { } ;
if ( info ) data [ id ] = info ; else delete data [ id ] ;
if ( ! safe . fs . writeFileSync ( paths . SNAPSHOT _INFO _FILE , JSON . stringify ( data , null , 4 ) , 'utf8' ) ) {
2021-09-16 13:59:03 -07:00
throw new BoxError ( BoxError . FS _ERROR , safe . error . message ) ;
2019-01-17 09:53:51 -08:00
}
2017-09-22 14:40:37 -07:00
}
2025-07-24 19:02:02 +02:00
async function startCleanupTask ( target , auditSource ) {
assert . strictEqual ( typeof target , 'object' ) ;
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof auditSource , 'object' ) ;
2017-09-19 20:27:36 -07:00
2025-07-24 19:02:02 +02:00
const taskId = await tasks . add ( ` ${ tasks . TASK _CLEAN _BACKUPS _PREFIX } ${ target . id } ` , [ target . id ] ) ;
2017-09-19 20:27:36 -07:00
2025-06-17 18:54:12 +02:00
// background
tasks . startTask ( taskId , { } )
. then ( async ( result ) => { // { removedBoxBackupPaths, removedAppBackupPaths, removedMailBackupPaths, missingBackupPaths }
await eventlog . add ( eventlog . ACTION _BACKUP _CLEANUP _FINISH , auditSource , { taskId , errorMessage : null , ... result } ) ;
} )
. catch ( async ( error ) => {
await eventlog . add ( eventlog . ACTION _BACKUP _CLEANUP _FINISH , auditSource , { taskId , errorMessage : error . message } ) ;
} ) ;
2018-12-20 11:41:38 -08:00
2021-07-14 11:07:19 -07:00
return taskId ;
2017-09-23 14:27:35 -07:00
}
2025-07-24 19:02:02 +02:00
function managedBackupMountObject ( config ) {
assert ( mounts . isManagedProvider ( config . provider ) ) ;
2023-08-15 08:53:02 +05:30
return {
name : 'backup' ,
hostPath : paths . MANAGED _BACKUP _MOUNT _DIR ,
2025-07-24 19:02:02 +02:00
mountType : config . provider ,
mountOptions : config . mountOptions
2023-08-15 08:53:02 +05:30
} ;
}
2025-07-24 19:02:02 +02:00
async function remount ( target ) {
assert . strictEqual ( typeof target , 'object' ) ;
2021-10-11 17:45:35 +02:00
2025-07-24 19:02:02 +02:00
if ( mounts . isManagedProvider ( target . provider ) ) {
await mounts . remount ( managedBackupMountObject ( target . config ) ) ;
2023-08-15 08:51:25 +05:30
}
2021-10-11 17:45:35 +02:00
}
2023-04-30 17:21:18 +02:00
2025-07-24 19:02:02 +02:00
async function getMountStatus ( target ) {
assert . strictEqual ( typeof target , 'object' ) ;
2023-04-30 17:21:18 +02:00
let hostPath ;
2025-07-24 19:02:02 +02:00
if ( mounts . isManagedProvider ( target . provider ) ) {
2023-04-30 17:21:18 +02:00
hostPath = paths . MANAGED _BACKUP _MOUNT _DIR ;
2025-07-24 19:02:02 +02:00
} else if ( target . provider === 'mountpoint' ) {
hostPath = target . config . mountPoint ;
} else if ( target . provider === 'filesystem' ) {
hostPath = target . config . backupFolder ;
2023-05-15 10:32:39 +02:00
} else {
2024-09-09 16:44:19 +02:00
return { state : 'active' } ;
2023-04-30 17:21:18 +02:00
}
2025-07-24 19:02:02 +02:00
return await mounts . getStatus ( target . provider , hostPath ) ; // { state, message }
2023-04-30 17:21:18 +02:00
}
2023-08-04 11:24:28 +05:30
2025-07-24 19:02:02 +02:00
async function ensureMounted ( target ) {
assert . strictEqual ( typeof target , 'object' ) ;
const status = await getMountStatus ( target ) ;
2024-09-09 17:39:17 +02:00
if ( status . state === 'active' ) return status ;
await remount ( ) ;
2025-07-24 19:02:02 +02:00
return await getMountStatus ( target ) ;
2023-08-04 11:24:28 +05:30
}
2025-07-24 19:02:02 +02:00
async function setConfig ( target , newConfig ) {
assert . strictEqual ( typeof target , 'object' ) ;
assert . strictEqual ( typeof newConfig , 'object' ) ;
2023-08-04 11:24:28 +05:30
2025-07-24 19:02:02 +02:00
if ( constants . DEMO ) throw new BoxError ( BoxError . BAD _STATE , 'Not allowed in demo mode' ) ;
2023-09-29 06:49:55 +05:30
2025-07-24 19:02:02 +02:00
const oldConfig = target . config ;
2023-09-29 06:49:55 +05:30
2025-07-24 19:02:02 +02:00
storage . api ( target . provider ) . injectPrivateFields ( newConfig , oldConfig ) ;
2024-06-11 14:32:31 +02:00
2025-07-24 19:02:02 +02:00
debug ( 'setConfig: validating new storage configuration' ) ;
await storage . testMount ( target . provider , newConfig , '/mnt/backup-storage-validation' ) ;
2023-09-29 06:49:55 +05:30
2025-07-24 19:02:02 +02:00
debug ( 'setConfig: removing old storage configuration' ) ;
if ( mounts . isManagedProvider ( target . provider ) ) await safe ( mounts . removeMount ( managedBackupMountObject ( oldConfig ) ) ) ;
2023-09-29 06:49:55 +05:30
2025-07-24 19:02:02 +02:00
debug ( 'setConfig: setting up new storage configuration' ) ;
await storage . setupManagedMount ( target . provider , newConfig , paths . MANAGED _BACKUP _MOUNT _DIR ) ;
2023-09-29 06:49:55 +05:30
2025-07-24 19:02:02 +02:00
debug ( 'setConfig: clearing backup cache' ) ;
cleanupCacheFilesSync ( target ) ;
2023-09-29 06:49:55 +05:30
2025-07-24 19:02:02 +02:00
await update ( target , { config : newConfig } ) ;
2023-09-29 06:49:55 +05:30
}
2025-07-24 19:02:02 +02:00
async function add ( data ) {
assert . strictEqual ( typeof data , 'object' ) ;
2023-08-15 08:14:35 +05:30
2024-06-06 11:32:15 +02:00
if ( constants . DEMO ) throw new BoxError ( BoxError . BAD _STATE , 'Not allowed in demo mode' ) ;
2025-07-24 19:02:02 +02:00
const { provider , label , config , format , retention , schedule } = data ; // required
const limits = data . limits || null ,
encryptionPassword = data . encryptionPassword || null ,
encryptedFilenames = data . encryptedFilenames || false ;
2023-08-04 11:24:28 +05:30
2025-07-24 19:02:02 +02:00
const formatError = validateFormat ( format ) ;
2024-04-09 12:23:43 +02:00
if ( formatError ) throw formatError ;
2025-07-24 19:02:02 +02:00
const labelError = validateLabel ( label ) ;
if ( labelError ) throw labelError ;
2024-04-09 12:23:43 +02:00
2025-07-24 19:02:02 +02:00
let encryption = null ;
if ( encryptionPassword ) {
const encryptionPasswordError = validateEncryptionPassword ( encryptionPassword ) ;
if ( encryptionPasswordError ) throw encryptionPasswordError ;
encryption = hush . generateEncryptionKeysSync ( encryptionPassword ) ;
encryption . encryptedFilenames = ! ! encryptedFilenames ;
2024-04-09 12:23:43 +02:00
}
2023-08-15 18:37:21 +05:30
2025-07-24 19:02:02 +02:00
debug ( 'add: validating new storage configuration' ) ;
await storage . testMount ( provider , config , '/mnt/backup-storage-validation' ) ;
2023-08-15 20:24:54 +05:30
debug ( 'setStorage: setting up new storage configuration' ) ;
2025-07-24 19:02:02 +02:00
await storage . setupManagedMount ( provider , config , paths . MANAGED _BACKUP _MOUNT _DIR ) ;
2023-08-04 11:24:28 +05:30
2025-07-24 19:02:02 +02:00
const id = ` bc- ${ uuid . v4 ( ) } ` ;
await database . query ( 'INSERT INTO backupTargets (id, label, provider, configJson, limitsJson, retentionJson, schedule, encryptionJson, format, priority) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' ,
[ id , label , provider , JSON . stringify ( config ) , JSON . stringify ( limits ) , JSON . stringify ( retention ) , schedule , JSON . stringify ( encryption ) , format , false ] ) ;
return id ;
2023-08-15 20:24:54 +05:30
}