diff --git a/CHANGES b/CHANGES index f5aa87cc2..916651327 100644 --- a/CHANGES +++ b/CHANGES @@ -2289,3 +2289,7 @@ [6.3.2] * Avatar was migrated as base64 instead of binary * Fix issue where filemanager came up empty for CIFS mounts + +[6.3.3] +* volumes: add filesystem volume type for shared folders + diff --git a/migrations/20210625061443-volumes-change-noop-to-mountpoint.js b/migrations/20210625061443-volumes-change-noop-to-mountpoint.js new file mode 100644 index 000000000..ab61ea3d2 --- /dev/null +++ b/migrations/20210625061443-volumes-change-noop-to-mountpoint.js @@ -0,0 +1,26 @@ +'use strict'; + +const async = require('async'), + safe = require('safetydance'); + +exports.up = function(db, callback) { + db.all('SELECT * FROM volumes', function (error, volumes) { + if (error || volumes.length === 0) return callback(error); + + async.eachSeries(volumes, function (volume, iteratorDone) { + if (volume.mountType !== 'noop') return iteratorDone(); + + let mountType; + if (safe.child_process.execSync(`mountpoint -q -- ${volume.hostPath}`)) { + mountType = 'mountpoint'; + } else { + mountType = 'filesystem'; + } + db.runSql('UPDATE volumes SET mountType=? WHERE id=?', [ mountType, volume.id ], iteratorDone); + }, callback); + }); +}; + +exports.down = function(db, callback) { + callback(); +}; diff --git a/package-lock.json b/package-lock.json index 652c9c6f0..e32a2c6a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -705,9 +705,9 @@ } }, "connect-lastmile": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-2.1.0.tgz", - "integrity": "sha512-nLV3loAO+1N5GK8OmwbNMEhObgrhNwn9qR1j3tgjfM63BPru5badZYxzQ5qsOP/MiXteFJCmRpga1IH8UV6JgQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-2.1.1.tgz", + "integrity": "sha512-723vDmZuy6KBUAmuXff1mb+l9ZMs+JqXJuAGHgWNI3fNYAu9DKXC+GYdxqY0+9oMXyVJNf5AscoONcq9Nqb0Ig==", "requires": { "underscore": "^1.13.1" } diff --git a/package.json b/package.json index 812c66d68..be3098e33 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "body-parser": "^1.19.0", "cloudron-manifestformat": "^5.10.2", "connect": "^3.7.0", - "connect-lastmile": "^2.1.0", + "connect-lastmile": "^2.1.1", "connect-timeout": "^1.9.0", "cookie-parser": "^1.4.5", "cookie-session": "^1.4.0", @@ -30,7 +30,6 @@ "debug": "^4.3.1", "delay": "^5.0.0", "dockerode": "^3.3.0", - "delay": "^5.0.0", "ejs": "^3.1.6", "ejs-cli": "^2.2.1", "express": "^4.17.1", diff --git a/src/mounts.js b/src/mounts.js index 3dd5343f3..6e13502be 100644 --- a/src/mounts.js +++ b/src/mounts.js @@ -27,8 +27,8 @@ function validateMountOptions(type, options) { assert.strictEqual(typeof options, 'object'); switch (type) { - case 'noop': // volume provider - case 'mountpoint': // backup provider + case 'filesystem': + case 'mountpoint': return null; case 'cifs': if (typeof options.username !== 'string') return new BoxError(BoxError.BAD_FIELD, 'username is not a string'); @@ -88,8 +88,8 @@ function renderMountFile(volume) { options = `allow_other,port=${mountOptions.port},IdentityFile=${keyFilePath},StrictHostKeyChecking=no,reconnect`; // allow_other means non-root users can access it break; } - case 'noop': // volume provider - case 'mountpoint': // backup provider + case 'filesystem': + case 'mountpoint': return; } @@ -115,9 +115,10 @@ async function getStatus(mountType, hostPath) { assert.strictEqual(typeof mountType, 'string'); assert.strictEqual(typeof hostPath, 'string'); - if (mountType === 'noop' || mountType === 'mountpoint') { // noop is from volume provider and mountpoint is from backup provider - safe.child_process.execSync(`mountpoint -q -- ${hostPath}`, { encoding: 'utf8' }); - if (!safe.error) { + if (mountType === 'filesystem') return { state: 'active', message: 'Mounted' }; + + if (mountType === 'mountpoint') { + if (safe.child_process.execSync(`mountpoint -q -- ${hostPath}`)) { return { state: 'active', message: 'Mounted' }; } else { return { state: 'inactive', message: 'Not mounted' }; @@ -148,7 +149,7 @@ async function tryAddMount(volume, options) { assert.strictEqual(typeof volume, 'object'); assert.strictEqual(typeof options, 'object'); - if (volume.mountType === 'noop' || volume.mountType === 'mountpoint') return; // noop is from volume provider and mountpoint is from backup provider + if (volume.mountType === 'mountpoint') return; if (constants.TEST) return; diff --git a/src/volumes.js b/src/volumes.js index 282fe9868..3628e4f1a 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -61,7 +61,7 @@ function validateHostPath(hostPath, mountType) { if (!allowedPaths.some(p => hostPath.startsWith(p))) return new BoxError(BoxError.BAD_FIELD, 'hostPath must be under /mnt, /media, /opt or /srv', { field: 'hostPath' }); - if (!constants.TEST && mountType === 'noop') { // we expect user to have already mounted this + if (!constants.TEST) { // we expect user to have already mounted this const stat = safe.fs.lstatSync(hostPath); if (!stat) return new BoxError(BoxError.BAD_FIELD, 'mount point does not exist. Please create it on the server first', { field: 'hostPath' }); if (!stat.isDirectory()) return new BoxError(BoxError.BAD_FIELD, 'mount point is not a directory', { field: 'hostPath' }); @@ -84,15 +84,14 @@ async function add(volume, auditSource) { const id = uuid.v4().replace(/-/g, ''); // to make systemd mount file names more readable - if (mountType === 'noop') { + if (mountType === 'mountpoint' || mountType === 'filesystem') { error = validateHostPath(volume.hostPath, mountType); if (error) throw error; } else { volume.hostPath = path.join(paths.VOLUMES_MOUNT_DIR, id); + await mounts.tryAddMount(volume, { timeout: 10 }); // 10 seconds } - if (volume.mountType !== 'noop') await mounts.tryAddMount(volume, { timeout: 10 }); // 10 seconds - try { await database.query('INSERT INTO volumes (id, name, hostPath, mountType, mountOptionsJson) VALUES (?, ?, ?, ?, ?)', [ id, name, volume.hostPath, mountType, JSON.stringify(mountOptions) ]); } catch (error) { @@ -103,7 +102,7 @@ async function add(volume, auditSource) { } eventlog.add(eventlog.ACTION_VOLUME_ADD, auditSource, { id, name, hostPath: volume.hostPath }); - // in theory, we only need to do this noop volumes. but for some reason a restart is required to detect new "mounts" + // in theory, we only need to do this mountpoint volumes. but for some reason a restart is required to detect new "mounts" services.rebuildService('sftp', NOOP_CALLBACK); const collectdConf = ejs.render(COLLECTD_CONFIG_EJS, { volumeId: id, hostPath: volume.hostPath }); @@ -159,7 +158,7 @@ async function del(volume, auditSource) { eventlog.add(eventlog.ACTION_VOLUME_REMOVE, auditSource, { volume }); - if (volume.mountType === 'noop') { + if (volume.mountType === 'mountpoint' || volume.mountType === 'filesystem') { services.rebuildService('sftp', NOOP_CALLBACK); } else { await safe(mounts.removeMount(volume));