diff --git a/src/backups.js b/src/backups.js index 7135cd6f7..e5f851f86 100644 --- a/src/backups.js +++ b/src/backups.js @@ -27,6 +27,8 @@ exports = module.exports = { testConfig, testProviderConfig, + remount, + BACKUP_IDENTIFIER_BOX: 'box', BACKUP_IDENTIFIER_MAIL: 'mail', @@ -63,6 +65,12 @@ const COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd/cloudron-back const BACKUPS_FIELDS = [ 'id', 'identifier', 'creationTime', 'packageVersion', 'type', 'dependsOn', 'state', 'manifestJson', 'format', 'preserveSecs', 'encryptionVersion' ]; +// helper until all storage providers have been ported +function maybePromisify(func) { + if (util.types.isAsyncFunction(func)) return func; + return util.promisify(func); +} + function postProcess(result) { assert.strictEqual(typeof result, 'object'); @@ -323,3 +331,15 @@ async function testProviderConfig(backupConfig) { const [error] = await safe(util.promisify(storage.api(backupConfig.provider).testConfig)(backupConfig)); return error; } + +async function remount(auditSource) { + assert.strictEqual(typeof auditSource, 'object'); + + const backupConfig = await settings.getBackupConfig(); + + const func = storage.api(backupConfig.provider); + if (!func) throw new BoxError(BoxError.BAD_FIELD, 'unknown storage provider', { field: 'provider' }); + + const [error] = await safe(maybePromisify(storage.api(backupConfig.provider).remount)(backupConfig)); + return error; +} diff --git a/src/mounts.js b/src/mounts.js index 9836a6af3..e70d75b28 100644 --- a/src/mounts.js +++ b/src/mounts.js @@ -2,6 +2,7 @@ exports = module.exports = { isMountProvider, + mountObjectFromBackupConfig, tryAddMount, removeMount, validateMountOptions, @@ -63,6 +64,15 @@ function isMountProvider(provider) { return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs' || provider === 'ext4'; } +function mountObjectFromBackupConfig(backupConfig) { + return { + name: 'backup', + hostPath: backupConfig.mountPoint, + mountType: backupConfig.provider, + mountOptions: backupConfig.mountOptions + }; +} + // https://www.man7.org/linux/man-pages/man8/mount.8.html for various mount option flags // nfs - no_root_squash is mode on server to map all root to 'nobody' user. all_squash does this for all users (making it like ftp) // sshfs - supports users/permissions diff --git a/src/routes/backups.js b/src/routes/backups.js index 2354c8198..50fd21376 100644 --- a/src/routes/backups.js +++ b/src/routes/backups.js @@ -4,6 +4,7 @@ exports = module.exports = { list, startBackup, cleanup, + remount }; const AuditSource = require('../auditsource.js'), @@ -39,3 +40,10 @@ async function cleanup(req, res, next) { next(new HttpSuccess(202, { taskId })); } + +async function remount(req, res, next) { + const [error] = await safe(backups.remount(AuditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(202, {})); +} diff --git a/src/server.js b/src/server.js index 10a4b4421..ac997c1cd 100644 --- a/src/server.js +++ b/src/server.js @@ -142,6 +142,7 @@ function initializeExpressSync() { router.get ('/api/v1/backups', token, authorizeAdmin, routes.backups.list); router.post('/api/v1/backups/create', token, authorizeAdmin, routes.backups.startBackup); router.post('/api/v1/backups/cleanup', json, token, authorizeAdmin, routes.backups.cleanup); + router.post('/api/v1/backups/remount', json, token, authorizeAdmin, routes.backups.remount); // config route (for dashboard). can return some private configuration unlike status router.get ('/api/v1/config', token, routes.cloudron.getConfig); diff --git a/src/settings.js b/src/settings.js index 9673cda9b..f12d3d616 100644 --- a/src/settings.js +++ b/src/settings.js @@ -383,15 +383,6 @@ function mountOptionsChanged(currentConfig, backupConfig) { || !_.isEqual(currentConfig.mountOptions, backupConfig.mountOptions); } -function mountObject(backupConfig) { - return { - name: 'backup', - hostPath: backupConfig.mountPoint, - mountType: backupConfig.provider, - mountOptions: backupConfig.mountOptions - }; -} - async function setBackupConfig(backupConfig) { assert.strictEqual(typeof backupConfig, 'object'); @@ -404,13 +395,13 @@ async function setBackupConfig(backupConfig) { let error = mounts.validateMountOptions(backupConfig.provider, backupConfig.mountOptions); if (error) throw error; - [error] = await safe(mounts.tryAddMount(mountObject(backupConfig), { timeout: 10 })); // 10 seconds + [error] = await safe(mounts.tryAddMount(mounts.mountObjectFromBackupConfig(backupConfig), { timeout: 10 })); // 10 seconds if (error) { if (mounts.isMountProvider(oldConfig.provider)) { // put back the old mount configuration debug('setBackupConfig: rolling back to previous mount configuration'); - await safe(mounts.tryAddMount(mountObject(oldConfig), { timeout: 10 })); + await safe(mounts.tryAddMount(mounts.mountObjectFromBackupConfig(oldConfig), { timeout: 10 })); } throw error; @@ -435,7 +426,7 @@ async function setBackupConfig(backupConfig) { if (mounts.isMountProvider(oldConfig.provider) && !mounts.isMountProvider(backupConfig.provider)) { debug('setBackupConfig: removing old backup mount point'); - await safe(mounts.removeMount(mountObject(oldConfig))); + await safe(mounts.removeMount(mounts.mountObjectFromBackupConfig(oldConfig))); } notifyChange(exports.BACKUP_CONFIG_KEY, backupConfig); diff --git a/src/storage/filesystem.js b/src/storage/filesystem.js index 23120e7e2..1aa967f01 100644 --- a/src/storage/filesystem.js +++ b/src/storage/filesystem.js @@ -15,6 +15,8 @@ exports = module.exports = { remove, removeDir, + remount, + testConfig, removePrivateFields, injectPrivateFields @@ -35,6 +37,7 @@ const assert = require('assert'), df = require('@sindresorhus/df'), EventEmitter = require('events'), fs = require('fs'), + mounts = require('../mounts.js'), path = require('path'), paths = require('../paths.js'), prettyBytes = require('pretty-bytes'), @@ -281,6 +284,14 @@ function validateBackupTarget(folder) { return null; } +async function remount(apiConfig) { + assert.strictEqual(typeof apiConfig, 'object'); + + if (apiConfig.provider === PROVIDER_FILESYSTEM || apiConfig.provider === PROVIDER_MOUNTPOINT) throw new BoxError(BoxError.NOT_SUPPORTED); + + await mounts.remount(mounts.mountObjectFromBackupConfig(apiConfig)); +} + function testConfig(apiConfig, callback) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof callback, 'function'); diff --git a/src/storage/gcs.js b/src/storage/gcs.js index 642f5186a..b5ca80473 100644 --- a/src/storage/gcs.js +++ b/src/storage/gcs.js @@ -14,6 +14,8 @@ exports = module.exports = { remove, removeDir, + remount, + testConfig, removePrivateFields, injectPrivateFields, @@ -263,6 +265,13 @@ function removeDir(apiConfig, pathPrefix) { return events; } +function remount(apiConfig, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof callback, 'function'); + + return callback(new BoxError(BoxError.NOT_SUPPORTED)); +} + function testConfig(apiConfig, callback) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof callback, 'function'); diff --git a/src/storage/interface.js b/src/storage/interface.js index 159643203..fb6582d02 100644 --- a/src/storage/interface.js +++ b/src/storage/interface.js @@ -27,6 +27,8 @@ exports = module.exports = { remove, removeDir, + remount, + testConfig, removePrivateFields, injectPrivateFields @@ -141,6 +143,15 @@ function removeDir(apiConfig, pathPrefix) { return events; } +function remount(apiConfig, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof callback, 'function'); + + // Result: none + + callback(new BoxError(BoxError.NOT_IMPLEMENTED, 'remount is not implemented')); +} + function testConfig(apiConfig, callback) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof callback, 'function'); diff --git a/src/storage/noop.js b/src/storage/noop.js index 596ba8835..b385ba125 100644 --- a/src/storage/noop.js +++ b/src/storage/noop.js @@ -15,6 +15,8 @@ exports = module.exports = { remove, removeDir, + remount, + testConfig, removePrivateFields, injectPrivateFields @@ -127,6 +129,13 @@ function removeDir(apiConfig, pathPrefix) { return events; } +function remount(apiConfig, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof callback, 'function'); + + callback(null); +} + function testConfig(apiConfig, callback) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof callback, 'function'); diff --git a/src/storage/s3.js b/src/storage/s3.js index a962124f5..0e4d8a886 100644 --- a/src/storage/s3.js +++ b/src/storage/s3.js @@ -14,6 +14,8 @@ exports = module.exports = { remove, removeDir, + remount, + testConfig, removePrivateFields, injectPrivateFields, @@ -474,6 +476,13 @@ function removeDir(apiConfig, pathPrefix) { return events; } +function remount(apiConfig, callback) { + assert.strictEqual(typeof apiConfig, 'object'); + assert.strictEqual(typeof callback, 'function'); + + return callback(new BoxError(BoxError.NOT_SUPPORTED)); +} + function testConfig(apiConfig, callback) { assert.strictEqual(typeof apiConfig, 'object'); assert.strictEqual(typeof callback, 'function');