diff --git a/dashboard/src/components/BackupProviderForm.vue b/dashboard/src/components/BackupProviderForm.vue
index b01b3b014..6f4555f40 100644
--- a/dashboard/src/components/BackupProviderForm.vue
+++ b/dashboard/src/components/BackupProviderForm.vue
@@ -27,8 +27,8 @@ const systemModel = SystemModel.create();
const provisionModel = ProvisionModel.create();
const storageProviders = Array.from(STORAGE_PROVIDERS);
-const blockDevices = ref([]);
-const disk = ref('');
+const ext4BlockDevices = ref([]);
+const xfsBlockDevices = ref([]);
const gcsKeyFileName = ref('');
const gcsFileParseError = ref('');
@@ -67,28 +67,21 @@ function onGcsKeyChange(event) {
}
async function getBlockDevices() {
- let error, result;
+ let error, blockDevices;
- if (props.provisioning) [error, result] = await provisionModel.blockDevices();
- else [error, result] = await systemModel.blockDevices();
+ if (props.provisioning) [error, blockDevices] = await provisionModel.blockDevices();
+ else [error, blockDevices] = await systemModel.blockDevices();
if (error) return console.error(error);
- // amend label for UI
- result.forEach(d => {
- d.label = d.path;
-
- // pre-select current if set
- if (d.path === providerConfig.value.mountOptionDiskPath || ('/dev/disk/by-uuid/' + d.uuid) === providerConfig.value.mountOptionDiskPath) {
- disk.value = d.path;
- }
- });
-
- // only offer non /, /boot or /home disks
- // only offer xfs and ext4 disks
- blockDevices.value = result
- .filter(d => { return d.mountpoint !== '/' && d.mountpoint !== '/home' && d.mountpoint !== '/boot'; })
- .filter(d => { return d.type === 'xfs' || d.type === 'ext4'; });
+ ext4BlockDevices.value = [];
+ xfsBlockDevices.value = [];
+ for (const blockDevice of blockDevices) {
+ if (blockDevice.mountpoints.some((mountpoint) => mountpoint === '/' || mountpoint.startsWith('/home') || mountpoint.startsWith('/boot'))) continue;
+ blockDevice.label = blockDevice.path; // // amend label for UI
+ if (blockDevice.type === 'ext4') ext4BlockDevices.value.push(blockDevice);
+ else if (blockDevice.type === 'xfs') xfsBlockDevices.value.push(blockDevice);
+ }
}
watch(provider, (newProvider) => {
@@ -153,14 +146,15 @@ onMounted(async () => {
-
-
+
+
+
-
+
-
+
diff --git a/dashboard/src/views/VolumesView.vue b/dashboard/src/views/VolumesView.vue
index 8b3d97f02..290a82f88 100644
--- a/dashboard/src/views/VolumesView.vue
+++ b/dashboard/src/views/VolumesView.vue
@@ -163,6 +163,7 @@ async function openVolumeDialog(volume) {
const ext4BlockDevices = [], xfsBlockDevices = [];
for (const blockDevice of blockDevices) {
+ if (blockDevice.mountpoints.some((mountpoint) => mountpoint === '/' || mountpoint.startsWith('/home') || mountpoint.startsWith('/boot'))) continue;
blockDevice.label = blockDevice.path; // // amend label for UI
if (blockDevice.type === 'ext4') ext4BlockDevices.push(blockDevice);
else if (blockDevice.type === 'xfs') xfsBlockDevices.push(blockDevice);
@@ -295,8 +296,8 @@ onMounted(async () =>{
-
-
+
+
diff --git a/src/backupsites.js b/src/backupsites.js
index 5150490c5..9ca2797cc 100644
--- a/src/backupsites.js
+++ b/src/backupsites.js
@@ -76,6 +76,7 @@ function storageApi(backupSite) {
case 'mountpoint': return require('./storage/filesystem.js');
case 'disk': return require('./storage/filesystem.js');
case 'ext4': return require('./storage/filesystem.js');
+ case 'xfs': return require('./storage/filesystem.js');
case 's3': return require('./storage/s3.js');
case 'gcs': return require('./storage/gcs.js');
case 'filesystem': return require('./storage/filesystem.js');
diff --git a/src/mounts.js b/src/mounts.js
index 4bcbc1555..68c1acadb 100644
--- a/src/mounts.js
+++ b/src/mounts.js
@@ -13,9 +13,9 @@ exports = module.exports = {
MOUNT_TYPE_CIFS: 'cifs',
MOUNT_TYPE_NFS: 'nfs',
MOUNT_TYPE_SSHFS: 'sshfs',
- MOUNT_TYPE_EXT4: 'ext4',
- MOUNT_TYPE_XFS: 'xfs',
- MOUNT_TYPE_DISK: 'disk',
+ MOUNT_TYPE_EXT4: 'ext4', // raw disk path
+ MOUNT_TYPE_XFS: 'xfs', // raw disk path
+ MOUNT_TYPE_DISK: 'disk', // this provides a selector of block devices
MOUNT_TYPE_LOOPBACK: 'loopback'
};
@@ -36,7 +36,7 @@ const REMOUNT_MOUNT_CMD = path.join(__dirname, 'scripts/remountmount.sh');
const SYSTEMD_MOUNT_EJS = fs.readFileSync(path.join(__dirname, 'systemd-mount.ejs'), { encoding: 'utf8' });
// https://man7.org/linux/man-pages/man8/mount.8.html
-function validateMountOptions(type, options) {
+async function validateMountOptions(type, options) {
assert.strictEqual(typeof type, 'string');
assert.strictEqual(typeof options, 'object');
@@ -66,10 +66,18 @@ function validateMountOptions(type, options) {
case exports.MOUNT_TYPE_EXT4:
case exports.MOUNT_TYPE_XFS:
case exports.MOUNT_TYPE_DISK:
- case exports.MOUNT_TYPE_LOOPBACK:
+ case exports.MOUNT_TYPE_LOOPBACK: {
if (typeof options.diskPath !== 'string') return new BoxError(BoxError.BAD_FIELD, 'diskPath is not a string');
- // TODO: check if this diskPath is not mounted on '/' or somewhere dangerous
+ const [error, output] = await safe(shell.spawn('lsblk', ['--paths', '--json', '--list', '--fs', options.diskPath ], { encoding: 'utf8' }));
+ if (error) return new BoxError(BoxError.BAD_FIELD, `Bad disk path: ${error.message}`);
+ const info = safe.JSON.parse(output);
+ if (!info) return new BoxError(BoxError.BAD_FIELD, `Bad disk path: ${safe.error.message}`);
+ for (const mountpoint of info.blockdevices[0].mountpoints) {
+ if (mountpoint === null) break; // [ null ] means not mounted anywhere
+ if (mountpoint === '/' || mountpoint.startsWith('/home') || mountpoint.startsWith('/boot')) return new BoxError(BoxError.BAD_FIELD, 'Disk is mounted in a protected location');
+ }
return null;
+ }
default:
return new BoxError(BoxError.BAD_FIELD, 'Bad mount type');
}
diff --git a/src/storage/filesystem.js b/src/storage/filesystem.js
index 836888d7c..0ec94b580 100644
--- a/src/storage/filesystem.js
+++ b/src/storage/filesystem.js
@@ -352,7 +352,7 @@ async function verifyConfig({ id, provider, config }) {
if (mounts.isManagedProvider(provider)) {
if (!config.mountOptions || typeof config.mountOptions !== 'object') throw new BoxError(BoxError.BAD_FIELD, 'mountOptions must be an object');
- const error = mounts.validateMountOptions(provider, config.mountOptions);
+ const error = await mounts.validateMountOptions(provider, config.mountOptions);
if (error) throw error;
const tmpConfig = {
diff --git a/src/system.js b/src/system.js
index ea14de0ed..0f5943b9e 100644
--- a/src/system.js
+++ b/src/system.js
@@ -327,13 +327,14 @@ async function getBlockDevices() {
const result = [];
for (const device of devices) {
- const mountpoints = Array.isArray(device.mountpoints) ? device.mountpoints : [ device.mountpoint ]; // we only support one mountpoint here old lsblk only exposed one via .mountpoint
- if (mountpoints.includes('/') || mountpoints.includes('/boot')) continue; // cannot be used for backups and volumes
+ const mountpoints = Array.isArray(device.mountpoints)
+ ? (device.mountpoints[0] === null ? [] : device.mountpoints) // convert [ null ] to []
+ : (device.mountpoint ? [ device.mountpoint ] : []); // old lsblk only exposed one .mountpoint
result.push({
path: device.name,
size: device.fsavail || 0,
- type: device.fstype,
+ type: device.fstype, // when null, it is not formatted
uuid: device.uuid,
rota: device.rota, // false (ssd) true (hdd) . unforuntately, this is not set correctly when virtualized (like in DO)
mountpoints
diff --git a/src/volumes.js b/src/volumes.js
index e5e595f8f..049936884 100644
--- a/src/volumes.js
+++ b/src/volumes.js
@@ -80,7 +80,7 @@ async function add(volume, auditSource) {
let error = validateName(name);
if (error) throw error;
- error = mounts.validateMountOptions(mountType, mountOptions);
+ error = await mounts.validateMountOptions(mountType, mountOptions);
if (error) throw error;
const id = crypto.randomUUID().replace(/-/g, ''); // to make systemd mount file names more readable
@@ -149,7 +149,7 @@ async function update(id, mountOptions, auditSource) {
if (mountType !== mounts.MOUNT_TYPE_CIFS && mountType !== mounts.MOUNT_TYPE_SSHFS && mountType !== mounts.MOUNT_TYPE_NFS) throw new BoxError(BoxError.BAD_FIELD, 'Only network mounts can be updated');
- const error = mounts.validateMountOptions(mountType, mountOptions);
+ const error = await mounts.validateMountOptions(mountType, mountOptions);
if (error) throw error;
// put old secret back in place if no new secret is provided