diff --git a/dashboard/src/components/BackupProviderForm.vue b/dashboard/src/components/BackupProviderForm.vue
index 1b0b09f7b..538417f21 100644
--- a/dashboard/src/components/BackupProviderForm.vue
+++ b/dashboard/src/components/BackupProviderForm.vue
@@ -172,12 +172,6 @@ onMounted(async () => {
-
-
-
-
-
-
diff --git a/dashboard/src/components/BackupSiteAddDialog.vue b/dashboard/src/components/BackupSiteAddDialog.vue
index 872622b1c..0505d87de 100644
--- a/dashboard/src/components/BackupSiteAddDialog.vue
+++ b/dashboard/src/components/BackupSiteAddDialog.vue
@@ -124,7 +124,7 @@ async function onSubmit() {
data.mountOptions.privateKey = providerConfig.value.mountOptionPrivateKey;
data.preserveAttributes = true;
}
- } else if (provider.value === 'ext4' || provider.value === 'xfs' || provider.value === 'disk') {
+ } else if (provider.value === 'ext4' || provider.value === 'xfs') {
data.mountOptions.diskPath = providerConfig.value.mountOptionDiskPath;
data.preserveAttributes = true;
} else if (provider.value === 'mountpoint') {
diff --git a/dashboard/src/constants.js b/dashboard/src/constants.js
index 64f734bba..044906e19 100644
--- a/dashboard/src/constants.js
+++ b/dashboard/src/constants.js
@@ -293,7 +293,6 @@ const STORAGE_PROVIDERS = [
{ name: 'Cloudflare R2', value: 'cloudflare-r2' },
{ name: 'Contabo Object Storage', value: 'contabo-objectstorage', regions: REGIONS_CONTABO },
{ name: 'DigitalOcean Spaces', value: 'digitalocean-spaces', regions: REGIONS_DIGITALOCEAN },
- { name: 'External/Local Disk (EXT4 or XFS)', value: 'disk' },
{ name: 'EXT4 Disk', value: 'ext4' },
{ name: 'Exoscale SOS', value: 'exoscale-sos', regions: REGIONS_EXOSCALE },
{ name: 'Filesystem', value: 'filesystem' },
diff --git a/dashboard/src/utils.js b/dashboard/src/utils.js
index 7bc69d7c0..b8fe53927 100644
--- a/dashboard/src/utils.js
+++ b/dashboard/src/utils.js
@@ -43,7 +43,7 @@ function download(filename, text) {
}
function mountlike(provider) {
- return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs' || provider === 'mountpoint' || provider === 'ext4' || provider === 'xfs' || provider === 'disk';
+ return provider === 'sshfs' || provider === 'cifs' || provider === 'nfs' || provider === 'mountpoint' || provider === 'ext4' || provider === 'xfs';
}
function s3like(provider) {
diff --git a/dashboard/src/views/RestoreView.vue b/dashboard/src/views/RestoreView.vue
index 910d587da..260519863 100644
--- a/dashboard/src/views/RestoreView.vue
+++ b/dashboard/src/views/RestoreView.vue
@@ -179,7 +179,7 @@ async function onSubmit() {
config.mountOptions.privateKey = providerConfig.value.mountOptionPrivateKey;
config.preserveAttributes = true;
}
- } else if (provider.value === 'ext4' || provider.value === 'xfs' || provider.value === 'disk') {
+ } else if (provider.value === 'ext4' || provider.value === 'xfs') {
config.mountOptions.diskPath = providerConfig.value.mountOptionDiskPath;
config.preserveAttributes = true;
} else if (provider.value === 'mountpoint') {
diff --git a/dashboard/src/views/VolumesView.vue b/dashboard/src/views/VolumesView.vue
index f9038e929..5bd99d3ef 100644
--- a/dashboard/src/views/VolumesView.vue
+++ b/dashboard/src/views/VolumesView.vue
@@ -327,7 +327,7 @@ onMounted(async () =>{
{{ volume.hostPath }}
- {{ volume.mountOptions.diskPath }}
+ {{ volume.mountOptions.diskPath }}
{{ volume.mountOptions.host + '/' + volume.mountOptions.remoteDir }}
{{ volume.mountOptions.host + volume.mountOptions.remoteDir }}
diff --git a/migrations/20260330000000-backupSites-remove-disk-provider.js b/migrations/20260330000000-backupSites-remove-disk-provider.js
new file mode 100644
index 000000000..af50c2797
--- /dev/null
+++ b/migrations/20260330000000-backupSites-remove-disk-provider.js
@@ -0,0 +1,46 @@
+'use strict';
+
+const child_process = require('node:child_process');
+
+function patchMountFile(mountPath, fstype) {
+ const mountFilename = child_process.execSync(`systemd-escape -p --suffix=mount "${mountPath}"`, { encoding: 'utf8' }).trim();
+ const mountFile = `/etc/systemd/system/${mountFilename}`;
+
+ try {
+ child_process.execSync(`sed -i 's/^Type=auto$/Type=${fstype}/' "${mountFile}"`);
+ child_process.execSync('systemctl daemon-reload');
+ console.log(`Patched ${mountFile}: Type=auto -> Type=${fstype}`);
+ } catch (e) {
+ console.log(`Warning: failed to patch ${mountFile}: ${e.message}`);
+ }
+}
+
+exports.up = async function (db) {
+ const results = await db.runSql('SELECT id, configJson FROM backupSites WHERE provider = ?', ['disk']);
+
+ for (const row of results) {
+ const config = JSON.parse(row.configJson);
+
+ let fstype = 'ext4';
+ try {
+ const diskPath = config.mountOptions.diskPath;
+ const output = child_process.execSync(`lsblk --paths --json --list --fs ${diskPath}`, { encoding: 'utf8' });
+ const info = JSON.parse(output);
+ if (info.blockdevices[0].fstype === 'xfs') fstype = 'xfs';
+ } catch (e) {
+ console.log(`Could not detect filesystem type for backup site ${row.id}, defaulting to ext4: ${e.message}`);
+ }
+
+ config._provider = fstype;
+ console.log(`Migrating backup site ${row.id} from disk to ${fstype}`);
+ await db.runSql('UPDATE backupSites SET provider = ?, configJson = ? WHERE id = ?', [fstype, JSON.stringify(config), row.id]);
+
+ if (config._managedMountPath) {
+ patchMountFile(config._managedMountPath, fstype);
+ }
+ }
+};
+
+exports.down = function (db, callback) {
+ callback();
+};
diff --git a/src/backupsites.js b/src/backupsites.js
index ead3e2591..46b979855 100644
--- a/src/backupsites.js
+++ b/src/backupsites.js
@@ -26,7 +26,7 @@ const { log } = logger('backups');
// config: depends on the 'provider' field. 'provider' is not stored in config object. but it is injected when calling the api backends
// s3 providers - accessKeyId, secretAccessKey, bucket, prefix etc . see s3.js
// gcs - bucket, prefix, projectId, credentials . see gcs.js
-// ext4/xfs/disk (managed providers) - mountOptions (diskPath), prefix, noHardlinks. disk is legacy.
+// ext4/xfs (managed providers) - mountOptions (diskPath), prefix, noHardlinks
// nfs/cifs/sshfs (managed providers) - mountOptions (host/username/password/seal/privateKey etc), prefix, noHardlinks
// filesystem - backupDir, noHardlinks
// mountpoint - mountPoint, prefix, noHardlinks
@@ -35,7 +35,7 @@ const BACKUP_TARGET_FIELDS = [ 'id', 'name', 'provider', 'configJson', 'limitsJs
const STORAGE_PROVIDERS = {
nfs: storageFilesystem, cifs: storageFilesystem, sshfs: storageFilesystem,
- mountpoint: storageFilesystem, disk: storageFilesystem, ext4: storageFilesystem,
+ mountpoint: storageFilesystem, ext4: storageFilesystem,
xfs: storageFilesystem, filesystem: storageFilesystem,
s3: storageS3, minio: storageS3, 's3-v4-compat': storageS3,
'digitalocean-spaces': storageS3, 'exoscale-sos': storageS3, wasabi: storageS3,
diff --git a/src/mounts.js b/src/mounts.js
index f9b80ed46..b33317d8a 100644
--- a/src/mounts.js
+++ b/src/mounts.js
@@ -19,7 +19,6 @@ const MOUNT_TYPE_NFS = 'nfs';
const MOUNT_TYPE_SSHFS = 'sshfs';
const MOUNT_TYPE_EXT4 = 'ext4'; // raw disk path
const MOUNT_TYPE_XFS = 'xfs'; // raw disk path
-const MOUNT_TYPE_DISK = 'disk'; // this provides a selector of block devices
const ADD_MOUNT_CMD = path.join(import.meta.dirname, 'scripts/addmount.sh');
@@ -56,8 +55,7 @@ async function validateMountOptions(type, options) {
if (typeof options.remoteDir !== 'string') return new BoxError(BoxError.BAD_FIELD, 'remoteDir is not a string');
return null;
case MOUNT_TYPE_EXT4:
- case MOUNT_TYPE_XFS:
- case MOUNT_TYPE_DISK: {
+ case MOUNT_TYPE_XFS: {
if (typeof options.diskPath !== 'string') return new BoxError(BoxError.BAD_FIELD, 'diskPath is not a string');
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}`);
@@ -88,7 +86,6 @@ function isManagedProvider(provider) {
case MOUNT_TYPE_NFS:
case MOUNT_TYPE_EXT4:
case MOUNT_TYPE_XFS:
- case MOUNT_TYPE_DISK:
return true;
default:
return false;
@@ -133,11 +130,6 @@ async function renderMountFile(mount) {
what = mountOptions.diskPath; // like /dev/disk/by-uuid/uuid or /dev/disk/by-id/scsi-id
options = 'discard,defaults,noatime,pquota';
break;
- case MOUNT_TYPE_DISK:
- type = 'auto';
- what = mountOptions.diskPath; // like /dev/disk/by-uuid/uuid or /dev/disk/by-id/scsi-id
- options = 'discard,defaults,noatime';
- break;
case MOUNT_TYPE_SSHFS: {
const keyFilePath = path.join(paths.SSHFS_KEYS_DIR, `identity_file_${path.basename(hostPath)}`);
if (!safe.fs.writeFileSync(keyFilePath, `${mount.mountOptions.privateKey}\n`, { mode: 0o600 })) throw new BoxError(BoxError.FS_ERROR, `Could not write private key: ${safe.error.message}`);
@@ -263,5 +255,4 @@ export default {
MOUNT_TYPE_SSHFS,
MOUNT_TYPE_EXT4,
MOUNT_TYPE_XFS,
- MOUNT_TYPE_DISK,
};
diff --git a/src/storage/filesystem.js b/src/storage/filesystem.js
index 2e46966c3..2cb5cbec0 100644
--- a/src/storage/filesystem.js
+++ b/src/storage/filesystem.js
@@ -66,7 +66,6 @@ function hasChownSupportSync(config) {
case mounts.MOUNT_TYPE_NFS:
case mounts.MOUNT_TYPE_EXT4:
case mounts.MOUNT_TYPE_XFS:
- case mounts.MOUNT_TYPE_DISK:
case mounts.MOUNT_TYPE_FILESYSTEM:
return true;
case mounts.MOUNT_TYPE_SSHFS:
@@ -414,7 +413,6 @@ function getLocationLabel(config) {
switch (config._provider) {
case mounts.MOUNT_TYPE_FILESYSTEM:
return config.backupDir + suffix;
- case mounts.MOUNT_TYPE_DISK:
case mounts.MOUNT_TYPE_EXT4:
case mounts.MOUNT_TYPE_XFS:
case mounts.MOUNT_TYPE_MOUNTPOINT: