diff --git a/CHANGES b/CHANGES index b812defbb..1486fb2ab 100644 --- a/CHANGES +++ b/CHANGES @@ -2272,4 +2272,5 @@ * security: send new browser login location notification email * backups: add fqdn to the backup filename * import all boxdata settings into the database +* volumes: generate systemd mount configs based on type diff --git a/baseimage/initializeBaseUbuntuImage.sh b/baseimage/initializeBaseUbuntuImage.sh index c2708db94..d4e2a6662 100755 --- a/baseimage/initializeBaseUbuntuImage.sh +++ b/baseimage/initializeBaseUbuntuImage.sh @@ -48,6 +48,7 @@ apt-get -y install --no-install-recommends \ linux-generic \ logrotate \ $mysql_package \ + nfs-common \ openssh-server \ pwgen \ resolvconf \ diff --git a/migrations/20210512210953-volumes-add-mountType-and-mountOptionsJson.js b/migrations/20210512210953-volumes-add-mountType-and-mountOptionsJson.js new file mode 100644 index 000000000..208671161 --- /dev/null +++ b/migrations/20210512210953-volumes-add-mountType-and-mountOptionsJson.js @@ -0,0 +1,17 @@ +'use strict'; + +const async = require('async'); + +exports.up = function(db, callback) { + async.series([ + db.runSql.bind(db, 'ALTER TABLE volumes ADD COLUMN mountType VARCHAR(16) DEFAULT "noop"'), + db.runSql.bind(db, 'ALTER TABLE volumes ADD COLUMN mountOptionsJson TEXT') + ], callback); +}; + +exports.down = function(db, callback) { + async.series([ + db.runSql.bind(db, 'ALTER TABLE volumes DROP COLUMN mountType'), + db.runSql.bind(db, 'ALTER TABLE volumes DROP COLUMN mountOptionsJson') + ], callback); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index d7fa8fbe6..67b295371 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -257,6 +257,8 @@ CREATE TABLE IF NOT EXISTS volumes( name VARCHAR(256) NOT NULL UNIQUE, hostPath VARCHAR(1024) NOT NULL UNIQUE, creationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + mountType VARCHAR(16) DEFAULT "noop", + mountOptionsJson TEXT, PRIMARY KEY (id) ); diff --git a/package-lock.json b/package-lock.json index 7acd69f50..5cb916dd3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -208,7 +208,7 @@ }, "amdefine": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, @@ -316,9 +316,9 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "aws-sdk": { - "version": "2.903.0", - "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.903.0.tgz", - "integrity": "sha512-BP/giYLP8QJ63Jta59kph1F76oPITxRt/wNr3BdoEs9BtshWlGKk149UaseDB4wJtI+0TER5jtzBIUBcP6E+wA==", + "version": "2.906.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.906.0.tgz", + "integrity": "sha512-u/kmVILew/9HFpHwVrc3VMK24m+XrazXEooMxkzbWXEBvtVm1xTYv8xPmdgiYvogWIkWTkeIF9ME4LBeHenYkw==", "requires": { "buffer": "4.9.2", "events": "1.1.1", @@ -650,7 +650,7 @@ }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, @@ -738,11 +738,11 @@ } }, "connect-lastmile": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-2.0.0.tgz", - "integrity": "sha512-kMQlR0OVtV9x/p/8m/DhfVyB/7PnS88vg/OmHLRuPxnOhTQdsnZvovg7OiApqAaDnUKUSnevWYd9oEDFV2Bh3w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/connect-lastmile/-/connect-lastmile-2.1.0.tgz", + "integrity": "sha512-nLV3loAO+1N5GK8OmwbNMEhObgrhNwn9qR1j3tgjfM63BPru5badZYxzQ5qsOP/MiXteFJCmRpga1IH8UV6JgQ==", "requires": { - "underscore": "^1.9.1" + "underscore": "^1.13.1" } }, "connect-timeout": { @@ -1370,9 +1370,9 @@ "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" }, "env-paths": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", - "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", "dev": true }, "error-ex": { @@ -2017,9 +2017,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "http-errors": { @@ -2154,7 +2154,7 @@ }, "is-arrayish": { "version": "0.2.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", "dev": true }, @@ -3196,9 +3196,9 @@ } }, "node-sass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-5.0.0.tgz", - "integrity": "sha512-opNgmlu83ZCF792U281Ry7tak9IbVC+AKnXGovcQ8LG8wFaJv6cLnRlc6DIHlmNxWEexB5bZxi9SZ9JyUuOYjw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-6.0.0.tgz", + "integrity": "sha512-GDzDmNgWNc9GNzTcSLTi6DU6mzSPupVJoStIi7cF3GjwSE9q1cVakbvAAVSt59vzUjV9JJoSZFKoo9krbjKd2g==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -3351,7 +3351,7 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, @@ -3440,7 +3440,7 @@ }, "parse-json": { "version": "2.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "dev": true, "requires": { diff --git a/package.json b/package.json index 20af4b7ba..b33103fd3 100644 --- a/package.json +++ b/package.json @@ -15,12 +15,12 @@ "@google-cloud/storage": "^5.8.5", "@sindresorhus/df": "git+https://github.com/cloudron-io/df.git#type", "async": "^3.2.0", - "aws-sdk": "^2.903.0", + "aws-sdk": "^2.906.0", "basic-auth": "^2.0.1", "body-parser": "^1.19.0", "cloudron-manifestformat": "^5.10.1", "connect": "^3.7.0", - "connect-lastmile": "^2.0.0", + "connect-lastmile": "^2.1.0", "connect-timeout": "^1.9.0", "cookie-parser": "^1.4.5", "cookie-session": "^1.4.0", @@ -82,7 +82,7 @@ "mocha": "^8.4.0", "mock-aws-s3": "git+https://github.com/cloudron-io/mock-aws-s3.git", "nock": "^13.0.11", - "node-sass": "^5.0.0", + "node-sass": "^6.0.0", "recursive-readdir": "^2.2.2" }, "scripts": { diff --git a/scripts/installer.sh b/scripts/installer.sh index dde8dfcb3..8a5944ae2 100755 --- a/scripts/installer.sh +++ b/scripts/installer.sh @@ -71,6 +71,11 @@ if [[ "${nginx_version}" != *"1.18."* ]]; then rm /tmp/nginx.deb fi +if ! which mount.nfs; then + log "installing nfs-common" + apt install -y nfs-common +fi + log "updating node" readonly node_version=14.15.4 if [[ "$(node --version)" != "v${node_version}" ]]; then diff --git a/setup/start/sudoers b/setup/start/sudoers index f5f1f4eac..fc441daf9 100644 --- a/setup/start/sudoers +++ b/setup/start/sudoers @@ -53,3 +53,9 @@ yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/stoptask.sh Defaults!/home/yellowtent/box/src/scripts/setblocklist.sh env_keep="HOME BOX_ENV" yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/setblocklist.sh +Defaults!/home/yellowtent/box/src/scripts/addmount.sh env_keep="HOME BOX_ENV" +yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/addmount.sh + +Defaults!/home/yellowtent/box/src/scripts/rmmount.sh env_keep="HOME BOX_ENV" +yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmmount.sh + diff --git a/src/routes/volumes.js b/src/routes/volumes.js index 27607290b..1925ad4cc 100644 --- a/src/routes/volumes.js +++ b/src/routes/volumes.js @@ -5,7 +5,9 @@ exports = module.exports = { get, del, list, - load + load, + setMountConfig, + getMountStatus }; const assert = require('assert'), @@ -31,8 +33,10 @@ async function add(req, res, next) { if (typeof req.body.name !== 'string') return next(new HttpError(400, 'name must be a string')); if (typeof req.body.hostPath !== 'string') return next(new HttpError(400, 'hostPath must be a string')); + if (typeof req.body.mountType !== 'string') return next(new HttpError(400, 'mountType must be a string')); + if (typeof req.body.mountOptions !== 'object') return next(new HttpError(400, 'mountOptions must be a object')); - const [error, id] = await safe(volumes.add(req.body.name, req.body.hostPath, auditSource.fromRequest(req))); + const [error, id] = await safe(volumes.add(req.body, auditSource.fromRequest(req))); if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(201, { id })); } @@ -56,3 +60,22 @@ async function list(req, res, next) { if (error) return next(BoxError.toHttpError(error)); next(new HttpSuccess(200, { volumes: result })); } + +async function setMountConfig(req, res, next) { + assert.strictEqual(typeof req.params.id, 'string'); + + if (typeof req.body.mountType !== 'string') return next(new HttpError(400, 'mountType must be a string')); + if (typeof req.body.mountOptions !== 'object') return next(new HttpError(400, 'mountOptions must be a object')); + + const [error] = await safe(volumes.setMountConfig(req.resource, req.body.mountType, req.body.mountOptions)); + if (error) return next(BoxError.toHttpError(error)); + next(new HttpSuccess(200, {})); +} + +async function getMountStatus(req, res, next) { + assert.strictEqual(typeof req.params.id, 'string'); + + const [error, status] = await safe(volumes.getMountStatus(req.resource)); + if (error) return next(BoxError.toHttpError(error)); + next(new HttpSuccess(200, { status })); +} diff --git a/src/scripts/addmount.sh b/src/scripts/addmount.sh new file mode 100755 index 000000000..7e89e15ac --- /dev/null +++ b/src/scripts/addmount.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +set -eu -o pipefail + +if [[ ${EUID} -ne 0 ]]; then + echo "This script should be run as root." > /dev/stderr + exit 1 +fi + +if [[ $# -eq 0 ]]; then + echo "No arguments supplied" + exit 1 +fi + +if [[ "$1" == "--check" ]]; then + echo "OK" + exit 0 +fi + +mount_file_contents="$1" + +# mount units must be named after the mount point directories they control +where=$(echo "${mount_file_contents}" | grep "^Where=" | cut -d'=' -f 2) + +mount_filename=$(systemd-escape -p --suffix=mount "$where") + +echo "$mount_file_contents" > "/etc/systemd/system/${mount_filename}" + +systemctl daemon-reload +systemctl enable --no-block --now "${mount_filename}" || true diff --git a/src/scripts/rmmount.sh b/src/scripts/rmmount.sh new file mode 100755 index 000000000..23c9de8ed --- /dev/null +++ b/src/scripts/rmmount.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -eu -o pipefail + +if [[ ${EUID} -ne 0 ]]; then + echo "This script should be run as root." > /dev/stderr + exit 1 +fi + +if [[ $# -eq 0 ]]; then + echo "No arguments supplied" + exit 1 +fi + +if [[ "$1" == "--check" ]]; then + echo "OK" + exit 0 +fi + +where="$1" + +mount_filename=$(systemd-escape -p --suffix=mount "$where") + +systemctl stop "${mount_filename}" || true +rm -f "/etc/systemd/system/${mount_filename}" +systemctl daemon-reload diff --git a/src/server.js b/src/server.js index 69b97a5f0..f60282754 100644 --- a/src/server.js +++ b/src/server.js @@ -310,6 +310,8 @@ function initializeExpressSync() { router.get ('/api/v1/volumes', token, authorizeAdmin, routes.volumes.list); router.get ('/api/v1/volumes/:id', token, authorizeAdmin, routes.volumes.load, routes.volumes.get); router.del ('/api/v1/volumes/:id', token, authorizeAdmin, routes.volumes.load, routes.volumes.del); + router.post('/api/v1/volumes/:id/mount_config', token, authorizeAdmin, routes.volumes.load, routes.volumes.setMountConfig); + router.get ('/api/v1/volumes/:id/mount_status', token, authorizeAdmin, routes.volumes.load, routes.volumes.getMountStatus); router.use ('/api/v1/volumes/:id/files/*', token, authorizeAdmin, routes.filemanager.proxy); // service routes diff --git a/src/systemd-mount.ejs b/src/systemd-mount.ejs new file mode 100644 index 000000000..b251a651c --- /dev/null +++ b/src/systemd-mount.ejs @@ -0,0 +1,14 @@ +[Unit] +Description=mount script for <%= name %> +Requires=network-online.target unbound.service +After=network-online.service unbound.service + +[Mount] +What=<%= what %> +Where=<%= where %> +Options=<%= options %> +Type=<%= type %> + +[Install] +WantedBy=multi-user.target + diff --git a/src/volumes.js b/src/volumes.js index db8336072..a12c9184b 100644 --- a/src/volumes.js +++ b/src/volumes.js @@ -4,7 +4,9 @@ exports = module.exports = { add, get, del, - list + list, + setMountConfig, + getMountStatus, }; const assert = require('assert'), @@ -18,13 +20,26 @@ const assert = require('assert'), path = require('path'), safe = require('safetydance'), services = require('./services.js'), + shell = require('./shell.js'), uuid = require('uuid'); -const VOLUMES_FIELDS = [ 'id', 'name', 'hostPath', 'creationTime' ].join(','); +const VOLUMES_FIELDS = [ 'id', 'name', 'hostPath', 'creationTime', 'mountType', 'mountOptionsJson' ].join(','); +const ADD_MOUNT_CMD = path.join(__dirname, 'scripts/addmount.sh'); +const RM_MOUNT_CMD = path.join(__dirname, 'scripts/rmmount.sh'); +const SYSTEMD_MOUNT_EJS = fs.readFileSync(path.join(__dirname, 'systemd-mount.ejs'), { encoding: 'utf8' }); const COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd/volume.ejs', { encoding: 'utf8' }); const NOOP_CALLBACK = function (error) { if (error) debug(error); }; +function postProcess(result) { + assert.strictEqual(typeof result, 'object'); + + result.mountOptions = safe.JSON.parse(result.mountOptionsJson) || {}; + delete result.mountOptionsJson; + + return result; +} + function validateName(name) { assert.strictEqual(typeof name, 'string'); @@ -33,8 +48,35 @@ function validateName(name) { return null; } -function validateHostPath(hostPath) { +// https://man7.org/linux/man-pages/man8/mount.8.html +function validateMountOptions(type, options) { + assert.strictEqual(typeof type, 'string'); + assert.strictEqual(typeof options, 'object'); + + switch (type) { + case 'noop': + return null; + case 'cifs': + if (typeof options.username !== 'string') return new BoxError(BoxError.BAD_FIELD, 'username is not a string'); + if (typeof options.password !== 'string') return new BoxError(BoxError.BAD_FIELD, 'password is not a string'); + if (typeof options.host !== 'string') return new BoxError(BoxError.BAD_FIELD, 'host is not a string'); + if (typeof options.remoteDir !== 'string') return new BoxError(BoxError.BAD_FIELD, 'remoteDir is not a string'); + return null; + case 'nfs': + if (typeof options.host !== 'string') return new BoxError(BoxError.BAD_FIELD, 'host is not a string'); + if (typeof options.remoteDir !== 'string') return new BoxError(BoxError.BAD_FIELD, 'remoteDir is not a string'); + return null; + case 'ext4': + if (typeof options.diskPath !== 'string') return new BoxError(BoxError.BAD_FIELD, 'diskPath is not a string'); + return null; + default: + return new BoxError(BoxError.BAD_FIELD, 'Bad volume mount type'); + } +} + +function validateHostPath(hostPath, mountType) { assert.strictEqual(typeof hostPath, 'string'); + assert.strictEqual(typeof mountType, 'string'); if (path.normalize(hostPath) !== hostPath) return new BoxError(BoxError.BAD_FIELD, 'hostPath must contain a normalized path', { field: 'hostPath' }); if (!path.isAbsolute(hostPath)) return new BoxError(BoxError.BAD_FIELD, 'backupFolder must be an absolute path', { field: 'hostPath' }); @@ -46,28 +88,96 @@ function validateHostPath(hostPath) { if (!allowedPaths.some(p => hostPath.startsWith(p))) return new BoxError(BoxError.BAD_FIELD, 'hostPath must be under /mnt, /media, /opt or /srv', { field: 'hostPath' }); - const stat = safe.fs.lstatSync(hostPath); - if (!stat) return new BoxError(BoxError.BAD_FIELD, 'hostPath does not exist. Please create it on the server first', { field: 'hostPath' }); - if (!stat.isDirectory()) return new BoxError(BoxError.BAD_FIELD, 'hostPath is not a directory', { field: 'hostPath' }); + if (mountType === 'noop') { // we expect user to have already mounted this + const stat = safe.fs.lstatSync(hostPath); + if (!stat) return new BoxError(BoxError.BAD_FIELD, 'hostPath does not exist. Please create it on the server first', { field: 'hostPath' }); + if (!stat.isDirectory()) return new BoxError(BoxError.BAD_FIELD, 'hostPath is not a directory', { field: 'hostPath' }); + } return null; } -async function add(name, hostPath, auditSource) { - assert.strictEqual(typeof name, 'string'); - assert.strictEqual(typeof hostPath, 'string'); +async function writeMountFile(volume) { + assert.strictEqual(typeof volume, 'object'); + + const {name, hostPath, mountType, mountOptions} = volume; + + let options, what, type; + switch (mountType) { + case 'cifs': + type = 'cifs'; + what = `${mountOptions.host}:${mountOptions.remoteDir}`; + options = `username=${mountOptions.username},password=${mountOptions.password},rw`; // uid=1000 ? + break; + case 'nfs': + type = 'nfs'; + what = `${mountOptions.host}:${mountOptions.remoteDir}`; + options = 'noauto,x-systemd.automount'; // _netdev is implicit + break; + case 'ext4': + type = 'ext4'; + what = mountOptions.diskPath; // like /dev/disk/by-uuid/uuid + options = 'defaults'; + break; + case 'sshfs': + // type = 'sshfs'; + // What={{ USER }}@{{ HOST }}:{{ REMOTE DIR }} + // Options=_netdev,allow_other,IdentityFile=/home/{{ MY LOCAL USER WITH SSH KEY IN ITS HOME DIRECTORY }}/.ssh/id_rsa,reconnect,x-systemd.automount,uid=1000,gid=1000 + } + + const mountFileContents = ejs.render(SYSTEMD_MOUNT_EJS, { name, what, where: hostPath, options, type }); + const [error] = await safe(shell.promises.sudo('generateMountFile', [ ADD_MOUNT_CMD, mountFileContents ], {})); + if (error) throw error; +} + +async function removeMountFile(volume) { + assert.strictEqual(typeof volume, 'object'); + + await safe(shell.promises.sudo('generateMountFile', [ RM_MOUNT_CMD, volume.hostPath ], {})); // ignore any error +} + +async function setMountConfig(volume, mountType, mountOptions) { + assert.strictEqual(typeof volume, 'object'); + assert.strictEqual(typeof mountType, 'string'); + assert.strictEqual(typeof mountOptions, 'object'); + + let error = validateMountOptions(mountType, mountOptions); + if (error) throw error; + + let result; + [error, result] = await safe(database.query('UPDATE volumes SET mountType=? AND mountOptionsJson=? WHERE id=?', [ mountType, JSON.stringify(mountOptions), volume.id ])); + if (error) throw error; + if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Volume not found'); +} + +async function getMountStatus(volume) { + assert.strictEqual(typeof volume, 'object'); + + let [error, activeState] = await safe(shell.promises.exec('getMountStatus', `systemctl is-active $(systemd-escape -p ${volume.hostPath})`)); + activeState = activeState || 'no status'; + return { activeState }; +} + +async function add(volume, auditSource) { + assert.strictEqual(typeof volume, 'object'); assert.strictEqual(typeof auditSource, 'object'); + const {name, hostPath, mountType, mountOptions} = volume; + let error = validateName(name); if (error) throw error; - error = validateHostPath(hostPath); + error = validateHostPath(hostPath, mountType); + if (error) throw error; + + error = validateMountOptions(mountType, mountOptions); if (error) throw error; const id = uuid.v4(); try { - await database.query('INSERT INTO volumes (id, name, hostPath) VALUES (?, ?, ?)', [ id, name, hostPath ]); + if (mountType !== 'noop') await writeMountFile(volume); + await database.query('INSERT INTO volumes (id, name, hostPath, mountType, mountOptionsJson) VALUES (?, ?, ?, ?, ?)', [ id, name, hostPath, mountType, JSON.stringify(mountOptions) ]); } catch (error) { if (error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('name') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'name already exists'); if (error.code === 'ER_DUP_ENTRY' && error.sqlMessage.indexOf('hostPath') !== -1) throw new BoxError(BoxError.ALREADY_EXISTS, 'hostPath already exists'); @@ -90,12 +200,13 @@ async function get(id) { const result = await database.query(`SELECT ${VOLUMES_FIELDS} FROM volumes WHERE id=?`, [ id ]); if (result.length === 0) return null; - return result[0]; + return postProcess(result[0]); } async function list() { - const result = await database.query(`SELECT ${VOLUMES_FIELDS} FROM volumes ORDER BY name`); - return result; + const results = await database.query(`SELECT ${VOLUMES_FIELDS} FROM volumes ORDER BY name`); + results.forEach(postProcess); + return results; } async function del(volume, auditSource) { @@ -111,6 +222,8 @@ async function del(volume, auditSource) { } eventlog.add(eventlog.ACTION_VOLUME_REMOVE, auditSource, { volume }); - services.rebuildService('sftp', NOOP_CALLBACK); + services.rebuildService('sftp', async function () { + await safe(removeMountFile(volume)); + }); collectd.removeProfile(volume.id, NOOP_CALLBACK); }