diff --git a/CHANGES b/CHANGES index 6b607342e..238c2dac6 100644 --- a/CHANGES +++ b/CHANGES @@ -2698,3 +2698,6 @@ * oidc: add oidc logo as login indicator for apps * dyndns: update dns every 10 mins +[7.6.1] +* Cleanup backup validation mount point + diff --git a/src/backups.js b/src/backups.js index d95b1c977..6ffb2a1bf 100644 --- a/src/backups.js +++ b/src/backups.js @@ -349,7 +349,7 @@ async function validateEncryptionPassword(password) { if (password.length < 8) return new BoxError(BoxError.BAD_FIELD, 'password must be atleast 8 characters'); } -function mountObjectFromBackupConfig(backupConfig) { +function managedBackupMountObject(backupConfig) { assert(mounts.isManagedProvider(backupConfig.provider)); return { @@ -366,7 +366,7 @@ async function remount(auditSource) { const backupConfig = await getConfig(); if (mounts.isManagedProvider(backupConfig.provider)) { - await mounts.remount(mountObjectFromBackupConfig(backupConfig)); + await mounts.remount(managedBackupMountObject(backupConfig)); } } @@ -449,48 +449,6 @@ function validateFormat(format) { return new BoxError(BoxError.BAD_FIELD, 'Invalid backup format'); } -async function setStorage(storageConfig) { - assert.strictEqual(typeof storageConfig, 'object'); - - const oldConfig = await getConfig(); - - if (storageConfig.provider === oldConfig.provider) storage.api(storageConfig.provider).injectPrivateFields(storageConfig, oldConfig); - - 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; - - debug('setStorage: removing old storage configuration'); - if (mounts.isManagedProvider(oldConfig.provider)) await safe(mounts.removeMount(mountObjectFromBackupConfig(oldConfig))); - - 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; - } - - debug('setBackupConfig: clearing backup cache'); - cleanupCacheFilesSync(); - - await settings.setJson(settings.BACKUP_STORAGE_KEY, storageConfig); -} - async function setupStorage(storageConfig, hostPath) { assert.strictEqual(typeof storageConfig, 'object'); assert.strictEqual(typeof hostPath, 'string'); @@ -500,6 +458,8 @@ async function setupStorage(storageConfig, hostPath) { 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, @@ -511,3 +471,46 @@ async function setupStorage(storageConfig, hostPath) { return newMount; } + +async function setStorage(storageConfig) { + assert.strictEqual(typeof storageConfig, 'object'); + + const oldConfig = await getConfig(); + + if (storageConfig.provider === oldConfig.provider) storage.api(storageConfig.provider).injectPrivateFields(storageConfig, oldConfig); + + const foratmError = validateFormat(storageConfig.format); + if (foratmError) throw foratmError; + + debug('setStorage: validating new storage configuration'); + 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; + + debug('setStorage: removing old storage configuration'); + if (mounts.isManagedProvider(oldConfig.provider)) await safe(mounts.removeMount(managedBackupMountObject(oldConfig))); + + 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 encryptionPasswordError = await validateEncryptionPassword(storageConfig.password); + if (encryptionPasswordError) throw encryptionPasswordError; + + storageConfig.encryption = generateEncryptionKeysSync(storageConfig.password); + } + delete storageConfig.password; + } + + debug('setBackupConfig: clearing backup cache'); + cleanupCacheFilesSync(); + + await settings.setJson(settings.BACKUP_STORAGE_KEY, storageConfig); +} diff --git a/src/mounts.js b/src/mounts.js index b3ee39ca0..25be721e8 100644 --- a/src/mounts.js +++ b/src/mounts.js @@ -195,20 +195,6 @@ async function tryAddMount(mount, options) { if (constants.TEST) return; - // first try to mount at /mnt/volumes/-attempt - const originalHostPath = mount.hostPath; - mount.hostPath = originalHostPath + '-attempt'; - - const [attemptError] = await safe(shell.promises.sudo('addMount', [ ADD_MOUNT_CMD, renderMountFile(mount), options.timeout ], {})); - if (attemptError && attemptError.code === 2) throw new BoxError(BoxError.MOUNT_ERROR, 'Failed to unmount existing mount'); // at this point, the old mount config is still there - - const attemptStatus = await getStatus(mount.mountType, mount.hostPath); - await removeMount(mount); - if (attemptStatus.state !== 'active') throw new BoxError(BoxError.MOUNT_ERROR, `Failed to mount (${attemptStatus.state}): ${attemptStatus.message}`); - - // now create the real mount - mount.hostPath = originalHostPath; - const [error] = await safe(shell.promises.sudo('addMount', [ ADD_MOUNT_CMD, renderMountFile(mount), options.timeout ], {})); if (error && error.code === 2) throw new BoxError(BoxError.MOUNT_ERROR, 'Failed to unmount existing mount'); // at this point, the old mount config is still there diff --git a/src/scripts/addmount.sh b/src/scripts/addmount.sh index 0ec4eb93c..abf68be85 100755 --- a/src/scripts/addmount.sh +++ b/src/scripts/addmount.sh @@ -28,7 +28,7 @@ mount_file="/etc/systemd/system/${mount_filename}" # cleanup any previous mount of same name (after midway box crash?) if systemctl -q is-active "${mount_filename}"; then - echo "Previous mount active, unmounting" + echo "Previous mount ${mount_filename} active, unmounting" # unmounting can fail if a user is cd'ed into the directory. if we go ahead and mount anyway, systemd says "active" because it's referring to the previous mount config if ! systemctl stop "${mount_filename}"; then echo "Failed to unmount" @@ -45,7 +45,7 @@ if ! timeout "${timeout}" systemctl enable --now "${mount_filename}"; then exit 3 fi -echo "Mount succeeded" +echo "Mount ${mount_filename} succeeded" # this has to be done post-mount because permissions come from the underlying mount file system and not the mount point chmod 777 "${where}" diff --git a/src/volumes.js b/src/volumes.js index 40333bc2e..e5348bdd0 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -159,10 +159,15 @@ async function update(id, mountOptions, auditSource) { const hostPath = path.join(paths.VOLUMES_MOUNT_DIR, id); const mount = { name, hostPath, mountType: mountType, mountOptions }; + + // first try to mount at /mnt/volumes/-validation + const testMount = Object.assign({}, mount, { hostPath: `${hostPath}-validation` }); + await mounts.tryAddMount(testMount, { timeout: 10 }); // 10 seconds + await mounts.removeMount(testMount); + + // update the mount await mounts.tryAddMount(mount, { timeout: 10 }); // 10 seconds - await database.query('UPDATE volumes SET hostPath=?,mountOptionsJson=? WHERE id=?', [ hostPath, JSON.stringify(mountOptions), id ]); - await eventlog.add(eventlog.ACTION_VOLUME_UPDATE, auditSource, { id, name, hostPath }); // in theory, we only need to do this mountpoint volumes. but for some reason a restart is required to detect new "mounts" safe(services.rebuildService('sftp', auditSource), { debug });