2015-07-20 00:09:47 -07:00
'use strict' ;
exports = module . exports = {
2021-01-21 11:31:35 -08:00
get ,
2021-07-14 11:07:19 -07:00
getByIdentifierAndStatePaged ,
getByTypePaged ,
add ,
update ,
2022-04-04 21:23:59 -07:00
setState ,
2021-07-14 11:07:19 -07:00
list ,
del ,
2015-09-21 14:14:21 -07:00
2021-01-21 11:31:35 -08:00
startBackupTask ,
2016-04-10 19:17:44 -07:00
2021-01-21 11:31:35 -08:00
startCleanupTask ,
cleanupCacheFilesSync ,
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
2021-01-21 11:31:35 -08:00
generateEncryptionKeysSync ,
2021-07-14 11:07:19 -07:00
getSnapshotInfo ,
setSnapshotInfo ,
2023-07-12 10:01:53 +05:30
validatePolicy ,
2023-07-13 12:42:38 +05:30
validateEncryptionPassword ,
2023-08-15 19:59:00 +05:30
testStorage ,
2023-08-15 20:24:54 +05:30
validateFormat ,
2020-05-12 14:00:05 -07:00
2023-08-04 11:24:28 +05:30
getPolicy ,
setPolicy ,
getConfig ,
setConfig ,
2023-08-15 08:14:35 +05:30
setStorage ,
setLimits ,
2023-08-04 11:24:28 +05:30
2023-08-15 20:24:54 +05:30
getRootPath ,
2023-08-15 20:24:54 +05:30
setupStorage ,
2021-10-11 17:45:35 +02:00
remount ,
2023-04-30 17:21:18 +02:00
getMountStatus ,
2021-10-11 17:45:35 +02:00
2020-06-14 12:19:51 -07:00
BACKUP _IDENTIFIER _BOX : 'box' ,
2021-09-26 18:37:04 -07:00
BACKUP _IDENTIFIER _MAIL : 'mail' ,
2020-06-14 12:19:51 -07:00
BACKUP _TYPE _APP : 'app' ,
BACKUP _TYPE _BOX : 'box' ,
2021-09-26 18:37:04 -07:00
BACKUP _TYPE _MAIL : 'mail' ,
2020-06-14 12:19:51 -07:00
BACKUP _STATE _NORMAL : 'normal' , // should rename to created to avoid listing in UI?
BACKUP _STATE _CREATING : 'creating' ,
BACKUP _STATE _ERROR : 'error' ,
2015-07-20 00:09:47 -07:00
} ;
2021-07-14 11:07:19 -07:00
const assert = require ( 'assert' ) ,
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' ) ,
2020-07-28 16:19:44 -07:00
CronJob = require ( 'cron' ) . CronJob ,
2017-09-20 09:57:16 -07:00
crypto = require ( 'crypto' ) ,
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' ) ,
2022-04-08 16:23:27 -07:00
hat = require ( './hat.js' ) ,
2018-12-09 03:20:00 -08:00
locker = require ( './locker.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' ) ,
2015-11-07 22:06:09 -08:00
settings = require ( './settings.js' ) ,
2021-07-14 11:07:19 -07:00
storage = require ( './storage.js' ) ,
2023-08-04 11:24:28 +05:30
tasks = require ( './tasks.js' ) ,
_ = require ( 'underscore' ) ;
2021-07-14 11:07:19 -07:00
2022-04-04 14:13:27 -07:00
const BACKUPS _FIELDS = [ 'id' , 'remotePath' , 'label' , 'identifier' , 'creationTime' , 'packageVersion' , 'type' , 'dependsOnJson' , 'state' , 'manifestJson' , 'format' , 'preserveSecs' , 'encryptionVersion' ] ;
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
2022-04-04 14:13:27 -07:00
result . dependsOn = result . dependsOnJson ? safe . JSON . parse ( result . dependsOnJson ) : [ ] ;
delete result . dependsOnJson ;
2021-07-14 11:07:19 -07:00
result . manifest = result . manifestJson ? safe . JSON . parse ( result . manifestJson ) : null ;
delete result . manifestJson ;
2015-11-06 18:14:59 -08:00
2021-07-14 11:07:19 -07:00
return result ;
2021-05-26 23:01:05 -07:00
}
2019-02-09 18:08:10 -08:00
function removePrivateFields ( backupConfig ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2020-05-12 14:00:05 -07:00
if ( backupConfig . encryption ) {
delete backupConfig . encryption ;
2020-05-14 23:01:44 +02:00
backupConfig . password = constants . SECRET _PLACEHOLDER ;
2020-05-12 14:00:05 -07:00
}
2023-08-15 20:24:54 +05:30
delete backupConfig . rootPath ;
2021-07-14 11:07:19 -07:00
return storage . api ( backupConfig . provider ) . removePrivateFields ( backupConfig ) ;
2020-05-12 15:49:43 -07:00
}
2021-10-06 13:09:04 -07:00
// this function is used in migrations - 20200512172301-settings-backup-encryption.js
2020-05-12 14:00:05 -07:00
function generateEncryptionKeysSync ( password ) {
assert . strictEqual ( typeof password , 'string' ) ;
const aesKeys = crypto . scryptSync ( password , Buffer . from ( 'CLOUDRONSCRYPTSALT' , 'utf8' ) , 128 ) ;
return {
dataKey : aesKeys . slice ( 0 , 32 ) . toString ( 'hex' ) ,
dataHmacKey : aesKeys . slice ( 32 , 64 ) . toString ( 'hex' ) ,
filenameKey : aesKeys . slice ( 64 , 96 ) . toString ( 'hex' ) ,
filenameHmacKey : aesKeys . slice ( 96 ) . toString ( 'hex' )
} ;
}
2019-12-05 11:55:51 -08:00
2022-04-04 14:13:27 -07:00
async function add ( data ) {
2021-07-14 11:07:19 -07:00
assert ( data && typeof data === 'object' ) ;
2022-04-04 14:13:27 -07:00
assert . strictEqual ( typeof data . remotePath , 'string' ) ;
2021-07-14 11:07:19 -07:00
assert ( data . encryptionVersion === null || typeof data . encryptionVersion === 'number' ) ;
assert . strictEqual ( typeof data . packageVersion , 'string' ) ;
assert . strictEqual ( typeof data . type , 'string' ) ;
assert . strictEqual ( typeof data . identifier , 'string' ) ;
assert . strictEqual ( typeof data . state , 'string' ) ;
assert ( Array . isArray ( data . dependsOn ) ) ;
assert . strictEqual ( typeof data . manifest , 'object' ) ;
assert . strictEqual ( typeof data . format , 'string' ) ;
2022-04-04 21:23:59 -07:00
assert . strictEqual ( typeof data . preserveSecs , 'number' ) ;
2021-07-14 11:07:19 -07:00
const creationTime = data . creationTime || new Date ( ) ; // allow tests to set the time
const manifestJson = JSON . stringify ( data . manifest ) ;
2022-09-29 18:01:16 +02:00
const prefixId = data . type === exports . BACKUP _TYPE _APP ? ` ${ data . type } _ ${ data . identifier } ` : data . type ; // type and identifier are same for other types
2023-09-05 09:15:12 +05:30
const id = ` ${ prefixId } _v ${ data . packageVersion } _ ${ hat ( 32 ) } ` ; // id is used by the UI to derive dependent packages. making this a UUID will require a lot of db querying
2021-07-14 11:07:19 -07:00
2022-04-04 21:23:59 -07:00
const [ error ] = await safe ( database . query ( 'INSERT INTO backups (id, remotePath, identifier, encryptionVersion, packageVersion, type, creationTime, state, dependsOnJson, manifestJson, format, preserveSecs) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' ,
[ id , data . remotePath , data . identifier , data . encryptionVersion , data . packageVersion , data . type , creationTime , data . state , JSON . stringify ( data . dependsOn ) , manifestJson , data . format , data . preserveSecs ] ) ) ;
2021-07-14 11:07:19 -07:00
if ( error && error . code === 'ER_DUP_ENTRY' ) throw new BoxError ( BoxError . ALREADY _EXISTS , 'Backup already exists' ) ;
if ( error ) throw error ;
2022-04-04 14:13:27 -07:00
return id ;
2021-07-14 11:07:19 -07:00
}
async function getByIdentifierAndStatePaged ( identifier , state , page , perPage ) {
2020-06-14 12:19:51 -07:00
assert . strictEqual ( typeof identifier , 'string' ) ;
2017-05-30 14:09:55 -07:00
assert . strictEqual ( typeof state , 'string' ) ;
2016-03-08 08:52:20 -08:00
assert ( typeof page === 'number' && page > 0 ) ;
assert ( typeof perPage === 'number' && perPage > 0 ) ;
2015-07-20 00:09:47 -07:00
2021-07-14 11:07:19 -07:00
const results = await database . query ( ` SELECT ${ BACKUPS _FIELDS } FROM backups WHERE identifier = ? AND state = ? ORDER BY creationTime DESC LIMIT ?,? ` , [ identifier , state , ( page - 1 ) * perPage , perPage ] ) ;
2016-06-13 13:42:25 -07:00
2021-07-14 11:07:19 -07:00
results . forEach ( function ( result ) { postProcess ( result ) ; } ) ;
2016-06-13 13:42:25 -07:00
2021-07-14 11:07:19 -07:00
return results ;
2016-06-13 13:42:25 -07:00
}
2021-07-14 11:07:19 -07:00
async function get ( id ) {
assert . strictEqual ( typeof id , 'string' ) ;
2017-09-19 20:40:38 -07:00
2021-07-14 11:07:19 -07:00
const result = await database . query ( 'SELECT ' + BACKUPS _FIELDS + ' FROM backups WHERE id = ? ORDER BY creationTime DESC' , [ id ] ) ;
if ( result . length === 0 ) return null ;
2020-06-05 13:27:18 +02:00
2021-07-14 11:07:19 -07:00
return postProcess ( result [ 0 ] ) ;
2017-09-19 20:40:38 -07:00
}
2021-07-14 11:07:19 -07:00
async function getByTypePaged ( type , page , perPage ) {
assert . strictEqual ( typeof type , 'string' ) ;
assert ( typeof page === 'number' && page > 0 ) ;
assert ( typeof perPage === 'number' && perPage > 0 ) ;
2018-07-27 06:55:54 -07:00
2021-07-14 11:07:19 -07:00
const results = await database . query ( ` SELECT ${ BACKUPS _FIELDS } FROM backups WHERE type = ? ORDER BY creationTime DESC LIMIT ?,? ` , [ type , ( page - 1 ) * perPage , perPage ] ) ;
2018-07-27 06:55:54 -07:00
2021-07-14 11:07:19 -07:00
results . forEach ( function ( result ) { postProcess ( result ) ; } ) ;
2018-07-27 11:46:42 -07:00
2021-07-14 11:07:19 -07:00
return results ;
2018-07-27 11:46:42 -07:00
}
2022-04-02 17:09:08 -07:00
function validateLabel ( label ) {
assert . strictEqual ( typeof label , 'string' ) ;
if ( label . length >= 200 ) return new BoxError ( BoxError . BAD _FIELD , 'label too long' ) ;
2022-06-24 09:18:51 -07:00
if ( /[^a-zA-Z0-9._() -]/ . test ( label ) ) return new BoxError ( BoxError . BAD _FIELD , 'label can only contain alphanumerals, space, dot, hyphen, brackets or underscore' ) ;
2022-04-02 17:09:08 -07:00
return null ;
}
2023-07-12 10:01:53 +05:30
async function validatePolicy ( policy ) {
assert . strictEqual ( typeof policy , 'object' ) ;
const job = safe . safeCall ( function ( ) { return new CronJob ( policy . schedule ) ; } ) ;
if ( ! job ) return new BoxError ( BoxError . BAD _FIELD , 'Invalid schedule pattern' ) ;
const retention = policy . retention ;
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' ) ;
}
2022-04-04 21:23:59 -07:00
// this is called by REST API
2022-04-02 17:09:08 -07:00
async function update ( id , data ) {
2021-07-14 11:07:19 -07:00
assert . strictEqual ( typeof id , 'string' ) ;
2022-04-02 17:09:08 -07:00
assert . strictEqual ( typeof data , 'object' ) ;
2020-05-15 16:05:12 -07:00
2022-04-02 17:09:08 -07:00
let error ;
if ( 'label' in data ) {
error = validateLabel ( data . label ) ;
if ( error ) throw error ;
}
const fields = [ ] , values = [ ] ;
for ( const p in data ) {
2022-04-04 21:23:59 -07:00
if ( p === 'label' || p === 'preserveSecs' ) {
2022-04-02 17:09:08 -07:00
fields . push ( p + ' = ?' ) ;
values . push ( data [ p ] ) ;
}
2020-05-10 21:40:25 -07:00
}
2021-07-14 11:07:19 -07:00
values . push ( id ) ;
2020-05-10 21:40:25 -07:00
2022-04-04 21:23:59 -07:00
const backup = await get ( id ) ;
if ( backup === null ) throw new BoxError ( BoxError . NOT _FOUND , 'Backup not found' ) ;
2021-07-14 11:07:19 -07:00
const result = await database . query ( 'UPDATE backups SET ' + fields . join ( ', ' ) + ' WHERE id = ?' , values ) ;
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Backup not found' ) ;
2022-04-04 21:23:59 -07:00
if ( 'preserveSecs' in data ) {
// update the dependancies
for ( const depId of backup . dependsOn ) {
await database . query ( 'UPDATE backups SET preserveSecs=? WHERE id = ?' , [ data . preserveSecs , depId ] ) ;
}
}
}
async function setState ( id , state ) {
assert . strictEqual ( typeof id , 'string' ) ;
assert . strictEqual ( typeof state , 'string' ) ;
const result = await database . query ( 'UPDATE backups SET state = ? WHERE id = ?' , [ state , id ] ) ;
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Backup not found' ) ;
2020-05-10 21:40:25 -07:00
}
2021-09-10 12:10:10 -07:00
async function startBackupTask ( auditSource ) {
2021-07-14 11:07:19 -07:00
let error = locker . lock ( locker . OP _FULL _BACKUP ) ;
2021-09-10 12:10:10 -07:00
if ( error ) throw new BoxError ( BoxError . BAD _STATE , ` Cannot backup now: ${ error . message } ` ) ;
2020-05-10 21:40:25 -07:00
2023-08-04 11:24:28 +05:30
const backupConfig = await getConfig ( ) ;
2018-07-27 06:55:54 -07:00
2024-02-20 22:12:13 +01:00
const memoryLimit = backupConfig . limits ? . memoryLimit ? Math . max ( backupConfig . limits . memoryLimit / 1024 / 1024 , 1024 ) : 1024 ;
2018-07-27 06:55:54 -07:00
2021-09-10 12:10:10 -07:00
const taskId = await tasks . add ( tasks . TASK _BACKUP , [ { /* 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
2021-09-10 12:10:10 -07:00
tasks . startTask ( taskId , { timeout : 24 * 60 * 60 * 1000 /* 24 hours */ , nice : 15 , memoryLimit } , async function ( error , backupId ) {
locker . unlock ( locker . OP _FULL _BACKUP ) ;
2020-05-10 21:40:25 -07:00
2021-09-10 12:10:10 -07:00
const errorMessage = error ? error . message : '' ;
const timedOut = error ? error . code === tasks . ETIMEOUT : false ;
2020-05-10 21:40:25 -07:00
2022-08-01 14:13:09 +02:00
const backup = backupId ? await get ( backupId ) : null ;
2022-04-05 09:28:30 -07:00
await safe ( eventlog . add ( eventlog . ACTION _BACKUP _FINISH , auditSource , { taskId , errorMessage , timedOut , backupId , remotePath : backup ? . remotePath } ) , { debug } ) ;
2019-12-05 14:51:15 -08:00
} ) ;
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-07-14 11:07:19 -07:00
async function list ( page , perPage ) {
assert ( typeof page === 'number' && page > 0 ) ;
assert ( typeof perPage === 'number' && perPage > 0 ) ;
2020-05-15 15:24:12 -07:00
2021-07-14 11:07:19 -07:00
const results = await database . query ( 'SELECT ' + BACKUPS _FIELDS + ' FROM backups ORDER BY creationTime DESC LIMIT ?,?' , [ ( page - 1 ) * perPage , perPage ] ) ;
2020-05-10 21:40:25 -07:00
2021-07-14 11:07:19 -07:00
results . forEach ( function ( result ) { postProcess ( result ) ; } ) ;
2019-12-05 14:51:15 -08:00
2021-07-14 11:07:19 -07:00
return results ;
2018-07-27 11:46:42 -07:00
}
2021-07-14 11:07:19 -07:00
async function del ( id ) {
assert . strictEqual ( typeof id , 'string' ) ;
2017-09-20 09:57:16 -07:00
2021-07-14 11:07:19 -07:00
const result = await database . query ( 'DELETE FROM backups WHERE id=?' , [ id ] ) ;
if ( result . affectedRows !== 1 ) throw new BoxError ( BoxError . NOT _FOUND , 'Backup not found' ) ;
}
2017-09-20 09:57:16 -07:00
2021-10-06 13:09:04 -07:00
// this function is used in migrations - 20200512172301-settings-backup-encryption.js
2021-07-14 11:07:19 -07:00
function cleanupCacheFilesSync ( ) {
2021-09-10 12:10:10 -07:00
const files = safe . fs . readdirSync ( path . join ( paths . BACKUP _INFO _DIR ) ) ;
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
}
2021-07-14 11:07:19 -07:00
async function startCleanupTask ( auditSource ) {
assert . strictEqual ( typeof auditSource , 'object' ) ;
2017-09-19 20:27:36 -07:00
2021-07-14 11:07:19 -07:00
const taskId = await tasks . add ( tasks . TASK _CLEAN _BACKUPS , [ ] ) ;
2017-09-19 20:27:36 -07:00
2022-04-04 14:13:27 -07:00
tasks . startTask ( taskId , { } , async ( error , result ) => { // result is { removedBoxBackupPaths, removedAppBackupPaths, removedMailBackupPaths, missingBackupPaths }
2022-02-24 20:04:46 -08:00
await safe ( eventlog . add ( eventlog . ACTION _BACKUP _CLEANUP _FINISH , auditSource , {
2021-07-14 11:07:19 -07:00
taskId ,
errorMessage : error ? error . message : null ,
2022-04-04 14:13:27 -07:00
removedBoxBackupPaths : result ? result . removedBoxBackupPaths : [ ] ,
removedMailBackupPaths : result ? result . removedMailBackupPaths : [ ] ,
removedAppBackupPaths : result ? result . removedAppBackupPaths : [ ] ,
missingBackupPaths : result ? result . missingBackupPaths : [ ]
2022-02-24 20:04:46 -08:00
} ) , { debug } ) ;
2017-09-20 09:57:16 -07:00
} ) ;
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
}
2023-08-15 19:59:00 +05:30
async function testStorage ( storageConfig ) {
assert . strictEqual ( typeof storageConfig , 'object' ) ;
2017-09-20 09:57:16 -07:00
2023-08-15 19:59:00 +05:30
const func = storage . api ( storageConfig . provider ) ;
2022-02-07 13:19:59 -08:00
if ( ! func ) return new BoxError ( BoxError . BAD _FIELD , 'unknown storage provider' ) ;
2017-09-20 09:57:16 -07:00
2023-08-15 19:59:00 +05:30
await storage . api ( storageConfig . provider ) . testConfig ( storageConfig ) ;
2017-09-19 20:27:36 -07:00
}
2017-10-10 20:23:04 -07:00
2023-07-13 12:42:38 +05:30
async function validateEncryptionPassword ( password ) {
assert . strictEqual ( typeof password , 'string' ) ;
2018-09-26 12:39:33 -07:00
2023-07-13 12:42:38 +05:30
if ( password . length < 8 ) return new BoxError ( BoxError . BAD _FIELD , 'password must be atleast 8 characters' ) ;
2020-02-26 09:08:30 -08:00
}
2021-10-11 17:45:35 +02:00
2023-09-29 06:49:55 +05:30
function managedBackupMountObject ( backupConfig ) {
2023-08-15 08:53:02 +05:30
assert ( mounts . isManagedProvider ( backupConfig . provider ) ) ;
return {
name : 'backup' ,
hostPath : paths . MANAGED _BACKUP _MOUNT _DIR ,
mountType : backupConfig . provider ,
mountOptions : backupConfig . mountOptions
} ;
}
2021-10-11 17:45:35 +02:00
async function remount ( auditSource ) {
assert . strictEqual ( typeof auditSource , 'object' ) ;
2023-08-04 11:24:28 +05:30
const backupConfig = await getConfig ( ) ;
2021-10-11 17:45:35 +02:00
2023-08-15 08:51:25 +05:30
if ( mounts . isManagedProvider ( backupConfig . provider ) ) {
2023-09-29 06:49:55 +05:30
await mounts . remount ( managedBackupMountObject ( backupConfig ) ) ;
2023-08-15 08:51:25 +05:30
}
2021-10-11 17:45:35 +02:00
}
2023-04-30 17:21:18 +02:00
async function getMountStatus ( ) {
2023-08-04 11:24:28 +05:30
const backupConfig = await getConfig ( ) ;
2023-04-30 17:21:18 +02:00
let hostPath ;
if ( mounts . isManagedProvider ( backupConfig . provider ) ) {
hostPath = paths . MANAGED _BACKUP _MOUNT _DIR ;
} else if ( backupConfig . provider === 'mountpoint' ) {
hostPath = backupConfig . mountPoint ;
} else if ( backupConfig . provider === 'filesystem' ) {
hostPath = backupConfig . backupFolder ;
2023-05-15 10:32:39 +02:00
} else {
throw new BoxError ( BoxError . BAD _STATE , 'Backup location is not a mount' ) ;
2023-04-30 17:21:18 +02:00
}
return await mounts . getStatus ( backupConfig . provider , hostPath ) ; // { state, message }
}
2023-08-04 11:24:28 +05:30
async function getPolicy ( ) {
const result = await settings . getJson ( settings . BACKUP _POLICY _KEY ) ;
return result || {
retention : { keepWithinSecs : 2 * 24 * 60 * 60 } , // 2 days
schedule : '00 00 23 * * *' // every day at 11pm
} ;
}
async function setPolicy ( policy ) {
assert . strictEqual ( typeof policy , 'object' ) ;
const error = await validatePolicy ( policy ) ;
if ( error ) throw error ;
await settings . setJson ( settings . BACKUP _POLICY _KEY , policy ) ;
2023-08-04 11:43:39 +05:30
await cron . handleBackupPolicyChanged ( policy ) ;
2023-08-04 11:24:28 +05:30
}
2023-08-15 20:24:54 +05:30
function getRootPath ( storageConfig , mountPath ) {
assert . strictEqual ( typeof storageConfig , 'object' ) ;
assert . strictEqual ( typeof mountPath , 'string' ) ;
if ( mounts . isManagedProvider ( storageConfig . provider ) ) {
return path . join ( mountPath , storageConfig . prefix ) ;
} else if ( storageConfig . provider === 'mountpoint' ) {
return path . join ( storageConfig . mountPoint , storageConfig . prefix ) ;
} else if ( storageConfig . provider === 'filesystem' ) {
return storageConfig . backupFolder ;
} else {
return storageConfig . prefix ;
}
}
2023-08-04 11:24:28 +05:30
async function getConfig ( ) {
2023-08-15 08:14:35 +05:30
const result = await settings . getJson ( settings . BACKUP _STORAGE _KEY ) || { provider : 'filesystem' , backupFolder : paths . DEFAULT _BACKUP _DIR , format : 'tgz' , encryption : null } ;
const limits = await settings . getJson ( settings . BACKUP _LIMITS _KEY ) ;
if ( limits ) result . limits = limits ;
2023-08-15 20:24:54 +05:30
result . rootPath = getRootPath ( result , paths . MANAGED _BACKUP _MOUNT _DIR ) ; // note: rootPath will be dynamic for managed mount providers during app import
2023-08-15 08:14:35 +05:30
return result ;
2023-08-04 11:24:28 +05:30
}
async function setConfig ( backupConfig ) {
assert . strictEqual ( typeof backupConfig , 'object' ) ;
2023-08-15 08:14:35 +05:30
await settings . setJson ( settings . BACKUP _STORAGE _KEY , _ . omit ( backupConfig , 'limits' ) ) ;
await settings . setJson ( settings . BACKUP _LIMITS _KEY , backupConfig . limits || null ) ;
}
async function setLimits ( limits ) {
assert . strictEqual ( typeof limits , 'object' ) ;
await settings . setJson ( settings . BACKUP _LIMITS _KEY , limits ) ;
}
2023-08-15 20:24:54 +05:30
function validateFormat ( format ) {
2023-08-15 18:37:21 +05:30
assert . strictEqual ( typeof format , 'string' ) ;
if ( format === 'tgz' || format == 'rsync' ) return null ;
return new BoxError ( BoxError . BAD _FIELD , 'Invalid backup format' ) ;
}
2023-09-29 06:49:55 +05:30
async function setupStorage ( storageConfig , hostPath ) {
assert . strictEqual ( typeof storageConfig , 'object' ) ;
assert . strictEqual ( typeof hostPath , 'string' ) ;
if ( ! mounts . isManagedProvider ( storageConfig . provider ) ) return null ;
const error = mounts . validateMountOptions ( storageConfig . provider , storageConfig . mountOptions ) ;
if ( error ) throw error ;
debug ( ` setupStorage: setting up mount at ${ hostPath } with ${ storageConfig . provider } ` ) ;
const newMount = {
name : path . basename ( hostPath ) ,
hostPath : hostPath ,
mountType : storageConfig . provider ,
mountOptions : storageConfig . mountOptions
} ;
await mounts . tryAddMount ( newMount , { timeout : 10 } ) ; // 10 seconds
return newMount ;
}
2023-08-15 08:14:35 +05:30
async function setStorage ( storageConfig ) {
assert . strictEqual ( typeof storageConfig , 'object' ) ;
2023-08-04 11:24:28 +05:30
const oldConfig = await getConfig ( ) ;
2023-08-15 20:24:54 +05:30
if ( storageConfig . provider === oldConfig . provider ) storage . api ( storageConfig . provider ) . injectPrivateFields ( storageConfig , oldConfig ) ;
2023-08-04 11:24:28 +05:30
2023-09-29 06:49:55 +05:30
const foratmError = validateFormat ( storageConfig . format ) ;
if ( foratmError ) throw foratmError ;
2023-08-15 18:37:21 +05:30
2023-08-15 20:24:54 +05:30
debug ( 'setStorage: validating new storage configuration' ) ;
2023-09-29 06:49:55 +05:30
const rootPath = getRootPath ( storageConfig , '/mnt/backup-storage-validation' ) ;
const testStorageConfig = Object . assign ( { rootPath } , storageConfig ) ;
const testMountObject = await setupStorage ( testStorageConfig , '/mnt/backup-storage-validation' ) ;
const testStorageError = await testStorage ( testStorageConfig ) ;
if ( testMountObject ) await mounts . removeMount ( testMountObject ) ;
if ( testStorageError ) throw testStorageError ;
2023-08-04 11:24:28 +05:30
2023-08-15 20:24:54 +05:30
debug ( 'setStorage: removing old storage configuration' ) ;
2023-09-29 06:49:55 +05:30
if ( mounts . isManagedProvider ( oldConfig . provider ) ) await safe ( mounts . removeMount ( managedBackupMountObject ( oldConfig ) ) ) ;
2023-08-15 20:24:54 +05:30
debug ( 'setStorage: setting up new storage configuration' ) ;
await setupStorage ( storageConfig , paths . MANAGED _BACKUP _MOUNT _DIR ) ;
storageConfig . encryption = null ;
2023-08-15 08:14:35 +05:30
if ( 'password' in storageConfig ) { // user set password
2023-08-15 20:24:54 +05:30
if ( storageConfig . password === constants . SECRET _PLACEHOLDER ) {
storageConfig . encryption = oldConfig . encryption || null ;
} else {
2023-09-29 06:49:55 +05:30
const encryptionPasswordError = await validateEncryptionPassword ( storageConfig . password ) ;
if ( encryptionPasswordError ) throw encryptionPasswordError ;
2023-08-04 11:24:28 +05:30
2023-08-15 20:24:54 +05:30
storageConfig . encryption = generateEncryptionKeysSync ( storageConfig . password ) ;
}
2023-08-15 08:14:35 +05:30
delete storageConfig . password ;
2023-08-04 11:24:28 +05:30
}
2023-08-15 08:14:35 +05:30
debug ( 'setBackupConfig: clearing backup cache' ) ;
cleanupCacheFilesSync ( ) ;
2023-08-04 11:24:28 +05:30
2023-08-15 08:14:35 +05:30
await settings . setJson ( settings . BACKUP _STORAGE _KEY , storageConfig ) ;
2023-08-15 20:24:54 +05:30
}