'use strict'; const child_process = require('node:child_process'), crypto = require('node:crypto'), fs = require('node:fs'), path = require('node:path'), paths = require('../src/paths.js'); async function deleteOldSettings(db) { await db.runSql('DELETE FROM settings WHERE name=? OR name=? OR name=?', [ 'backup_storage', 'backup_limits', 'backup_policy' ]); } exports.up = async function (db) { const cmd = 'CREATE TABLE IF NOT EXISTS backupSites(' + 'id VARCHAR(128) NOT NULL UNIQUE,' + 'name VARCHAR(128) NOT NULL,' + 'provider VARCHAR(32) NOT NULL,' + 'configJson TEXT,' + 'limitsJson TEXT,' + 'retentionJson TEXT,' + 'encryptionJson TEXT,' + 'integrityKeyPairJson TEXT,' + 'format VARCHAR(16) NOT NULL,' + 'schedule VARCHAR(128),' + 'enableForUpdates BOOLEAN DEFAULT false,' + 'contentsJson TEXT,' + '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' ]); 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 await deleteOldSettings(db); return; } const name = 'Default', enableForUpdates = true; let config = null, limits = null, encryption = null, format = null, provider = null; let retention = { keepWithinSecs: 2 * 24 * 60 * 60 }; let schedule = '00 00 23 * * *'; const integrityKeyPair = crypto.generateKeyPairSync('ed25519', { publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); const id = crypto.randomUUID(); // 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; if (provider === 'noop') return await deleteOldSettings(db); // noop is migrated as 0 sites. user has to manually update with skipBackup // the s3 and filesystem backend use the _provider internal property if (provider !== 'gcs') tmp._provider = tmp.provider; delete tmp.provider; // backupFolder is now backupDir if ('backupFolder' in tmp) { tmp.backupDir = tmp.backupFolder; delete tmp.backupFolder; } // 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; } } const targetInfoDir = path.join(paths.BACKUP_INFO_DIR, id); console.log(`Moving existing cache and snapshot file into ${targetInfoDir}`); fs.mkdirSync(targetInfoDir, { recursive: true }); child_process.execSync(`find ${paths.BACKUP_INFO_DIR}/ -maxdepth 1 -type f -exec mv -t ${targetInfoDir}/ {} +`); console.log(`Delete any existing rsync cache files since old one has no integrity information`); child_process.execSync(`rm -f ${targetInfoDir}/*.cache`); await db.runSql('START TRANSACTION'); await db.runSql('INSERT INTO backupSites (id, name, provider, configJson, limitsJson, integrityKeyPairJson, retentionJson, schedule, encryptionJson, format, enableForUpdates) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', [ id, name, provider, JSON.stringify(config), JSON.stringify(limits), JSON.stringify(integrityKeyPair), JSON.stringify(retention), schedule, JSON.stringify(encryption), format, enableForUpdates ]); await deleteOldSettings(db); await db.runSql('COMMIT'); }; exports.down = async function (db) { await db.runSql('DROP TABLE backupSites'); };