backups: fix various mount issues
This commit is contained in:
@@ -25,9 +25,11 @@ const PROVIDER_MOUNTPOINT = 'mountpoint';
|
||||
const PROVIDER_SSHFS = 'sshfs';
|
||||
const PROVIDER_CIFS = 'cifs';
|
||||
const PROVIDER_NFS = 'nfs';
|
||||
const PROVIDER_EXT4 = 'ext4';
|
||||
|
||||
var assert = require('assert'),
|
||||
const assert = require('assert'),
|
||||
BoxError = require('../boxerror.js'),
|
||||
constants = require('../constants.js'),
|
||||
DataLayout = require('../datalayout.js'),
|
||||
debug = require('debug')('box:storage/filesystem'),
|
||||
df = require('@sindresorhus/df'),
|
||||
@@ -49,6 +51,7 @@ function getBackupPath(apiConfig) {
|
||||
case PROVIDER_MOUNTPOINT:
|
||||
case PROVIDER_NFS:
|
||||
case PROVIDER_CIFS:
|
||||
case PROVIDER_EXT4:
|
||||
return path.join(apiConfig.mountPoint, apiConfig.prefix);
|
||||
default:
|
||||
return apiConfig.backupFolder;
|
||||
@@ -251,10 +254,10 @@ function removeDir(apiConfig, pathPrefix) {
|
||||
function validateBackupTarget(folder) {
|
||||
assert.strictEqual(typeof folder, 'string');
|
||||
|
||||
if (path.normalize(folder) !== folder) return new BoxError(BoxError.BAD_FIELD, 'backupFolder must contain a normalized path', { field: 'backupFolder' });
|
||||
if (!path.isAbsolute(folder)) return new BoxError(BoxError.BAD_FIELD, 'backupFolder must be an absolute path', { field: 'backupFolder' });
|
||||
if (path.normalize(folder) !== folder) return new BoxError(BoxError.BAD_FIELD, 'backupFolder/mountpoint must contain a normalized path', { field: 'backupFolder' });
|
||||
if (!path.isAbsolute(folder)) return new BoxError(BoxError.BAD_FIELD, 'backupFolder/mountpoint must be an absolute path', { field: 'backupFolder' });
|
||||
|
||||
if (folder === '/') return new BoxError(BoxError.BAD_FIELD, 'backupFolder cannot be /', { field: 'backupFolder' });
|
||||
if (folder === '/') return new BoxError(BoxError.BAD_FIELD, 'backupFolder/mountpoint cannot be /', { field: 'backupFolder' });
|
||||
|
||||
if (!folder.endsWith('/')) folder = folder + '/'; // ensure trailing slash for the prefix matching to work
|
||||
const PROTECTED_PREFIXES = [ '/boot/', '/usr/', '/bin/', '/lib/', '/root/', '/var/lib/', paths.baseDir() ];
|
||||
@@ -268,13 +271,13 @@ function testConfig(apiConfig, callback) {
|
||||
assert.strictEqual(typeof apiConfig, 'object');
|
||||
assert.strictEqual(typeof callback, 'function');
|
||||
|
||||
if ('noHardlinks' in apiConfig && typeof apiConfig.noHardlinks !== 'boolean') return callback(new BoxError(BoxError.BAD_FIELD, 'noHardlinks must be boolean', { field: 'noHardLinks' }));
|
||||
|
||||
if (apiConfig.provider === PROVIDER_FILESYSTEM) {
|
||||
if (!apiConfig.backupFolder || typeof apiConfig.backupFolder !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'backupFolder must be non-empty string', { field: 'backupFolder' }));
|
||||
let error = validateBackupTarget(apiConfig.backupFolder);
|
||||
if (error) return callback(error);
|
||||
}
|
||||
|
||||
if (apiConfig.provider === PROVIDER_MOUNTPOINT) {
|
||||
} else { // cifs/ext4/nfs/mountpoint/sshfs
|
||||
if (!apiConfig.mountPoint || typeof apiConfig.mountPoint !== 'string') return callback(new BoxError(BoxError.BAD_FIELD, 'mountPoint must be non-empty string', { field: 'mountPoint' }));
|
||||
let error = validateBackupTarget(apiConfig.mountPoint);
|
||||
if (error) return callback(error);
|
||||
@@ -290,35 +293,36 @@ function testConfig(apiConfig, callback) {
|
||||
if (!mountInfo) return callback(new BoxError(BoxError.BAD_FIELD, `${apiConfig.mountPoint} is not mounted`, { field: 'mountPoint' }));
|
||||
}
|
||||
|
||||
// common checks
|
||||
const backupPath = getBackupPath(apiConfig);
|
||||
const field = apiConfig.provider === PROVIDER_FILESYSTEM ? 'backupFolder' : 'prefix';
|
||||
if (apiConfig.provider === PROVIDER_FILESYSTEM || apiConfig.provider === PROVIDER_MOUNTPOINT) {
|
||||
const backupPath = getBackupPath(apiConfig);
|
||||
const field = apiConfig.provider === PROVIDER_FILESYSTEM ? 'backupFolder' : 'mountPoint';
|
||||
|
||||
const stat = safe.fs.statSync(backupPath);
|
||||
if (!stat) return callback(new BoxError(BoxError.BAD_FIELD, 'Directory does not exist or cannot be accessed: ' + safe.error.message), { field });
|
||||
if (!stat.isDirectory()) return callback(new BoxError(BoxError.BAD_FIELD, 'Backup location is not a directory', { field }));
|
||||
const stat = safe.fs.statSync(backupPath);
|
||||
if (!stat) return callback(new BoxError(BoxError.BAD_FIELD, 'Directory does not exist or cannot be accessed: ' + safe.error.message), { field });
|
||||
if (!stat.isDirectory()) return callback(new BoxError(BoxError.BAD_FIELD, 'Backup location is not a directory', { field }));
|
||||
|
||||
if (!safe.fs.mkdirSync(path.join(backupPath, 'snapshot')) && safe.error.code !== 'EEXIST') {
|
||||
if (safe.error && safe.error.code === 'EACCES') return callback(new BoxError(BoxError.BAD_FIELD, `Access denied. Run "chown yellowtent:yellowtent ${backupPath}" on the server`, { field }));
|
||||
return callback(new BoxError(BoxError.BAD_FIELD, safe.error.message, { field }));
|
||||
if (!safe.fs.mkdirSync(path.join(backupPath, 'snapshot')) && safe.error.code !== 'EEXIST') {
|
||||
if (safe.error && safe.error.code === 'EACCES') return callback(new BoxError(BoxError.BAD_FIELD, `Access denied. Run "chown yellowtent:yellowtent ${backupPath}" on the server`, { field }));
|
||||
return callback(new BoxError(BoxError.BAD_FIELD, safe.error.message, { field }));
|
||||
}
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(backupPath, 'cloudron-testfile'), 'testcontent')) {
|
||||
return callback(new BoxError(BoxError.BAD_FIELD, `Unable to create test file as 'yellowtent' user in ${backupPath}: ${safe.error.message}. Check dir/mount permissions`, { field }));
|
||||
}
|
||||
|
||||
if (!safe.fs.unlinkSync(path.join(backupPath, 'cloudron-testfile'))) {
|
||||
return callback(new BoxError(BoxError.BAD_FIELD, `Unable to remove test file as 'yellowtent' user in ${backupPath}: ${safe.error.message}. Check dir/mount permissions`, { field }));
|
||||
}
|
||||
}
|
||||
|
||||
if (!safe.fs.writeFileSync(path.join(backupPath, 'cloudron-testfile'), 'testcontent')) {
|
||||
return callback(new BoxError(BoxError.BAD_FIELD, `Unable to create test file as 'yellowtent' user in ${backupPath}: ${safe.error.message}. Check dir/mount permissions`, { field }));
|
||||
}
|
||||
|
||||
if (!safe.fs.unlinkSync(path.join(backupPath, 'cloudron-testfile'))) {
|
||||
return callback(new BoxError(BoxError.BAD_FIELD, `Unable to remove test file as 'yellowtent' user in ${backupPath}: ${safe.error.message}. Check dir/mount permissions`, { field }));
|
||||
}
|
||||
|
||||
if ('noHardlinks' in apiConfig && typeof apiConfig.noHardlinks !== 'boolean') return callback(new BoxError(BoxError.BAD_FIELD, 'noHardlinks must be boolean', { field: 'noHardLinks' }));
|
||||
|
||||
callback(null);
|
||||
}
|
||||
|
||||
function removePrivateFields(apiConfig) {
|
||||
if (apiConfig.mountOptions && apiConfig.mountOptions.password) apiConfig.mountOptions.password = constants.SECRET_PLACEHOLDER;
|
||||
return apiConfig;
|
||||
}
|
||||
|
||||
function injectPrivateFields(/* newConfig, currentConfig */) {
|
||||
function injectPrivateFields(newConfig, currentConfig) {
|
||||
if (newConfig.mountOptions && currentConfig.mountOptions && newConfig.mountOptions.password === constants.SECRET_PLACEHOLDER) newConfig.mountOptions.password = currentConfig.mountOptions.password;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user