rework backup root

notes:
* backup root cannot come from backend. for dynamic mounts backend cannot know where it is mounted
* backupConfig is 3 parts - format / mount / password . there is also this rootPath (which should not be in db)
* password should be stored separately in settings at some point
* format has to be passed along everywhere because we allow restore from  same backupConfig but different format. we do this by saving the format in the backups table

fixes #819
This commit is contained in:
Girish Ramakrishnan
2023-08-15 20:24:54 +05:30
parent da49a69562
commit aa8c23c8b3
14 changed files with 94 additions and 154 deletions
+63 -43
View File
@@ -15,7 +15,6 @@ exports = module.exports = {
startCleanupTask,
cleanupCacheFilesSync,
injectPrivateFields,
removePrivateFields,
generateEncryptionKeysSync,
@@ -26,7 +25,7 @@ exports = module.exports = {
validatePolicy,
validateEncryptionPassword,
testStorage,
validateBackupFormat,
validateFormat,
getPolicy,
setPolicy,
@@ -36,6 +35,8 @@ exports = module.exports = {
setStorage,
setLimits,
setupStorage,
remount,
getMountStatus,
@@ -85,24 +86,13 @@ function postProcess(result) {
return result;
}
function injectPrivateFields(newConfig, currentConfig) {
if ('password' in newConfig) {
if (newConfig.password === constants.SECRET_PLACEHOLDER) {
delete newConfig.password;
}
newConfig.encryption = currentConfig.encryption || null;
} else {
newConfig.encryption = null;
}
if (newConfig.provider === currentConfig.provider) storage.api(newConfig.provider).injectPrivateFields(newConfig, currentConfig);
}
function removePrivateFields(backupConfig) {
assert.strictEqual(typeof backupConfig, 'object');
if (backupConfig.encryption) {
delete backupConfig.encryption;
backupConfig.password = constants.SECRET_PLACEHOLDER;
}
delete backupConfig.rootPath;
return storage.api(backupConfig.provider).removePrivateFields(backupConfig);
}
@@ -414,10 +404,26 @@ async function setPolicy(policy) {
await cron.handleBackupPolicyChanged(policy);
}
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;
}
}
async function getConfig() {
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;
result.rootPath = getRootPath(result, paths.MANAGED_BACKUP_MOUNT_DIR); // note: rootPath will be dynamic for managed mount providers during app import
return result;
}
@@ -434,7 +440,7 @@ async function setLimits(limits) {
await settings.setJson(settings.BACKUP_LIMITS_KEY, limits);
}
function validateBackupFormat(format) {
function validateFormat(format) {
assert.strictEqual(typeof format, 'string');
if (format === 'tgz' || format == 'rsync') return null;
@@ -447,36 +453,34 @@ async function setStorage(storageConfig) {
const oldConfig = await getConfig();
injectPrivateFields(storageConfig, oldConfig);
if (storageConfig.provider === oldConfig.provider) storage.api(storageConfig.provider).injectPrivateFields(storageConfig, oldConfig);
if (mounts.isManagedProvider(storageConfig.provider)) {
let error = mounts.validateMountOptions(storageConfig.provider, storageConfig.mountOptions);
if (error) throw error;
[error] = await safe(mounts.tryAddMount(mountObjectFromBackupConfig(storageConfig), { timeout: 10 })); // 10 seconds
if (error) {
if (mounts.isManagedProvider(oldConfig.provider)) { // put back the old mount configuration
debug('setBackupConfig: rolling back to previous mount configuration');
await safe(mounts.tryAddMount(mountObjectFromBackupConfig(oldConfig), { timeout: 10 }));
}
throw error;
}
}
let error = validateBackupFormat(storageConfig.format);
let error = validateFormat(storageConfig.format);
if (error) throw error;
debug('setStorage: validating new storage configuration');
await setupStorage(storageConfig, '/mnt/backup-storage-validation');
storageConfig.rootPath = getRootPath(storageConfig, '/mnt/backup-storage-validation');
error = await testStorage(storageConfig);
delete storageConfig.rootPath;
if (error) throw error;
if ('password' in storageConfig) { // user set password
const error = await validateEncryptionPassword(storageConfig.password);
if (error) throw error;
debug('setStorage: removing old storage configuration');
if (mounts.isManagedProvider(oldConfig.provider)) await safe(mounts.removeMount(mountObjectFromBackupConfig(oldConfig)));
storageConfig.encryption = generateEncryptionKeysSync(storageConfig.password);
debug('setStorage: setting up new storage configuration');
await setupStorage(storageConfig, paths.MANAGED_BACKUP_MOUNT_DIR);
storageConfig.encryption = null;
if ('password' in storageConfig) { // user set password
if (storageConfig.password === constants.SECRET_PLACEHOLDER) {
storageConfig.encryption = oldConfig.encryption || null;
} else {
const error = await validateEncryptionPassword(storageConfig.password);
if (error) throw error;
storageConfig.encryption = generateEncryptionKeysSync(storageConfig.password);
}
delete storageConfig.password;
}
@@ -484,9 +488,25 @@ async function setStorage(storageConfig) {
cleanupCacheFilesSync();
await settings.setJson(settings.BACKUP_STORAGE_KEY, storageConfig);
if (mounts.isManagedProvider(oldConfig.provider) && !mounts.isManagedProvider(storageConfig.provider)) {
debug('setBackupConfig: removing old backup mount point');
await safe(mounts.removeMount(mountObjectFromBackupConfig(oldConfig)));
}
}
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;
const newMount = {
name: path.basename(hostPath),
hostPath: hostPath,
mountType: storageConfig.provider,
mountOptions: storageConfig.mountOptions
};
await mounts.tryAddMount(newMount, { timeout: 10 }); // 10 seconds
return newMount;
}