2025-07-24 13:19:27 +02:00
'use strict' ;
2025-08-14 11:17:38 +05:30
const child _process = require ( 'node:child_process' ) ,
crypto = require ( 'node:crypto' ) ,
fs = require ( 'node:fs' ) ,
path = require ( 'node:path' ) ,
2025-07-28 12:53:27 +02:00
paths = require ( '../src/paths.js' ) ;
2025-07-24 13:19:27 +02:00
2025-09-22 16:33:51 +02:00
async function deleteOldSettings ( db ) {
await db . runSql ( 'DELETE FROM settings WHERE name=? OR name=? OR name=?' , [ 'backup_storage' , 'backup_limits' , 'backup_policy' ] ) ;
}
2025-07-24 13:19:27 +02:00
exports . up = async function ( db ) {
2025-09-12 09:48:37 +02:00
const cmd = 'CREATE TABLE IF NOT EXISTS backupSites(' +
2025-07-24 13:19:27 +02:00
'id VARCHAR(128) NOT NULL UNIQUE,' +
2025-08-04 14:19:49 +02:00
'name VARCHAR(128) NOT NULL,' +
2025-07-24 19:02:02 +02:00
'provider VARCHAR(32) NOT NULL,' +
2025-07-24 13:19:27 +02:00
'configJson TEXT,' +
'limitsJson TEXT,' +
'retentionJson TEXT,' +
'encryptionJson TEXT,' +
2025-08-11 19:30:22 +05:30
'integrityKeyPairJson TEXT,' +
2025-07-24 13:19:27 +02:00
'format VARCHAR(16) NOT NULL,' +
'schedule VARCHAR(128),' +
2025-09-23 16:42:57 +02:00
'enableForUpdates BOOLEAN DEFAULT false,' +
2025-09-22 13:27:26 +02:00
'contentsJson TEXT,' +
2025-07-24 13:19:27 +02:00
'creationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,' +
'ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,' +
'PRIMARY KEY (id)) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin' ;
await db . runSql ( cmd ) ;
const results = await db . runSql ( 'SELECT name, value FROM settings WHERE name=? OR name=? OR name=?' , [ 'backup_storage' , 'backup_limits' , 'backup_policy' ] ) ;
2025-08-06 10:51:46 +02:00
const domainCountResults = await db . runSql ( 'SELECT COUNT(*) AS total FROM domains' ) ;
if ( domainCountResults [ 0 ] . total === 0 ) {
console . log ( 'This cloudron is not activated. Deleting the default backup config from 20171205124434-settings-default-backupConfig.js' ) ; // will be added at provision time
2025-09-22 16:33:51 +02:00
await deleteOldSettings ( db ) ;
2025-08-06 10:51:46 +02:00
return ;
}
2025-09-23 16:42:57 +02:00
const name = 'Default' , enableForUpdates = true ;
2025-07-24 19:02:02 +02:00
let config = null , limits = null , encryption = null , format = null , provider = null ;
2025-07-24 13:19:27 +02:00
let retention = { keepWithinSecs : 2 * 24 * 60 * 60 } ;
2025-07-24 19:02:02 +02:00
let schedule = '00 00 23 * * *' ;
2025-08-11 19:30:22 +05:30
const integrityKeyPair = crypto . generateKeyPairSync ( 'ed25519' , {
publicKeyEncoding : { type : 'spki' , format : 'pem' } ,
privateKeyEncoding : { type : 'pkcs8' , format : 'pem' }
} ) ;
2025-10-01 10:27:14 +02:00
const id = crypto . randomUUID ( ) ;
2025-07-24 13:19:27 +02:00
2025-08-06 10:51:46 +02:00
// convert existing configuration into a backup target
for ( const r of results ) {
if ( r . name === 'backup_storage' ) {
const tmp = JSON . parse ( r . value ) ;
// provider is top level
provider = tmp . provider ;
2025-09-22 16:33:51 +02:00
if ( provider === 'noop' ) return await deleteOldSettings ( db ) ; // noop is migrated as 0 sites. user has to manually update with skipBackup
2025-08-06 10:51:46 +02:00
// the s3 and filesystem backend use the _provider internal property
2025-09-22 16:33:51 +02:00
if ( provider !== 'gcs' ) tmp . _provider = tmp . provider ;
2025-08-06 10:51:46 +02:00
delete tmp . provider ;
// backupFolder is now backupDir
if ( 'backupFolder' in tmp ) {
tmp . backupDir = tmp . backupFolder ;
delete tmp . backupFolder ;
2025-07-24 13:19:27 +02:00
}
2025-08-06 10:51:46 +02:00
// encryption is not part of config anymore, it is top level
encryption = tmp . encryption || null ;
delete tmp . encryption ;
// format is not part of config anymore, it is top level
format = tmp . format ;
delete tmp . format ;
// previous releases only had a single "managed" mount at /mnt/cloudronbackup .
// new release has it under /mnt/managedbackups .
if ( tmp . mountOptions ) tmp . _managedMountPath = '/mnt/cloudronbackup' ;
config = tmp ;
} else if ( r . name === 'backup_limits' ) {
limits = JSON . parse ( r . value ) ;
} else if ( r . name === 'backup_policy' ) {
const tmp = JSON . parse ( r . value ) ;
retention = tmp . retention ;
schedule = tmp . schedule ;
2025-07-24 13:19:27 +02:00
}
}
2025-07-30 11:19:07 +02:00
const targetInfoDir = path . join ( paths . BACKUP _INFO _DIR , id ) ;
console . log ( ` Moving existing cache and snapshot file into ${ targetInfoDir } ` ) ;
2025-11-17 08:56:46 +01:00
fs . mkdirSync ( targetInfoDir , { recursive : true } ) ; // this gets chown'ed by start.sh
2025-07-30 11:19:07 +02:00
child _process . execSync ( ` find ${ paths . BACKUP _INFO _DIR } / -maxdepth 1 -type f -exec mv -t ${ targetInfoDir } / {} + ` ) ;
2025-08-14 10:40:36 +05:30
console . log ( ` Delete any existing rsync cache files since old one has no integrity information ` ) ;
child _process . execSync ( ` rm -f ${ targetInfoDir } /*.cache ` ) ;
2025-08-06 10:51:46 +02:00
await db . runSql ( 'START TRANSACTION' ) ;
2025-09-23 16:42:57 +02:00
await db . runSql ( 'INSERT INTO backupSites (id, name, provider, configJson, limitsJson, integrityKeyPairJson, retentionJson, schedule, encryptionJson, format, enableForUpdates) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' ,
2025-11-04 09:02:54 +01:00
[ id , name , provider , JSON . stringify ( config ) , JSON . stringify ( limits ) , JSON . stringify ( integrityKeyPair ) , JSON . stringify ( retention ) , schedule , encryption ? JSON . stringify ( encryption ) : null , format , enableForUpdates ] ) ;
2025-09-22 16:33:51 +02:00
await deleteOldSettings ( db ) ;
2025-10-06 13:59:49 +02:00
await db . runSql ( 'UPDATE tasks SET type=? WHERE type=?' , [ ` backup_ ${ id } ` , 'backup' ] ) ; // migrate the tasks
2025-07-24 13:19:27 +02:00
await db . runSql ( 'COMMIT' ) ;
} ;
exports . down = async function ( db ) {
2025-09-12 09:48:37 +02:00
await db . runSql ( 'DROP TABLE backupSites' ) ;
2025-07-24 13:19:27 +02:00
} ;