diff --git a/setup/start/sudoers b/setup/start/sudoers index 103b20ac8..9b50bdc84 100644 --- a/setup/start/sudoers +++ b/setup/start/sudoers @@ -1,6 +1,9 @@ # sudo logging breaks journalctl output with very long urls (systemd bug) Defaults !syslog +Defaults!/home/yellowtent/box/src/scripts/checkvolume.sh env_keep="HOME BOX_ENV" +yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/checkvolume.sh + Defaults!/home/yellowtent/box/src/scripts/clearvolume.sh env_keep="HOME BOX_ENV" yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/clearvolume.sh diff --git a/src/apps.js b/src/apps.js index 112fd7bbe..44ca0554c 100644 --- a/src/apps.js +++ b/src/apps.js @@ -168,6 +168,7 @@ const appstore = require('./appstore.js'), semver = require('semver'), services = require('./services.js'), settings = require('./settings.js'), + shell = require('./shell.js'), spawn = require('child_process').spawn, split = require('split'), superagent = require('superagent'), @@ -191,6 +192,8 @@ const APPS_FIELDS_PREFIXED = [ 'apps.id', 'apps.appStoreId', 'apps.installationS // const PORT_BINDINGS_FIELDS = [ 'hostPort', 'type', 'environmentVariable', 'appId' ].join(','); +const CHECKVOLUME_CMD = path.join(__dirname, 'scripts/checkvolume.sh'); + function validatePortBindings(portBindings, manifest) { assert.strictEqual(typeof portBindings, 'object'); assert.strictEqual(typeof manifest, 'object'); @@ -478,13 +481,24 @@ function validateEnv(env) { return null; } -function validateStorage(volume, prefix) { - assert.strictEqual(typeof volume, 'object'); +async function checkStorage(volumeId, prefix) { + assert.strictEqual(typeof volumeId, 'string'); assert.strictEqual(typeof prefix, 'string'); - if (path.isAbsolute(prefix)) return new BoxError(BoxError.BAD_FIELD, `prefix "${prefix}" must be a relative path`); - if (prefix.endsWith('/')) return new BoxError(BoxError.BAD_FIELD, `prefix "${prefix}" contains trailing slash`); - if (path.normalize(prefix) !== prefix) return new BoxError(BoxError.BAD_FIELD, `prefix "${prefix}" is not a normalized path`); + const volume = await volumes.get(volumeId); + if (volume === null) throw new BoxError(BoxError.BAD_FIELD, 'Storage volume not found'); + + const status = await volumes.getStatus(volume); + if (status.state !== 'active') throw new BoxError(BoxError.BAD_FIELD, 'Volume is not active'); + + if (path.isAbsolute(prefix)) throw new BoxError(BoxError.BAD_FIELD, `prefix "${prefix}" must be a relative path`); + if (prefix.endsWith('/')) throw new BoxError(BoxError.BAD_FIELD, `prefix "${prefix}" contains trailing slash`); + if (prefix !== '' && path.normalize(prefix) !== prefix) throw new BoxError(BoxError.BAD_FIELD, `prefix "${prefix}" is not a normalized path`); + + const storageDir = path.join(volume.hostPath, prefix); + const [error] = await safe(shell.promises.sudo('checkStorage', [ CHECKVOLUME_CMD, storageDir ], {})); + if (error && error.code === 2) throw new BoxError(BoxError.BAD_FIELD, `Targer directory ${storageDir} is not empty`); + if (error && error.code === 3) throw new BoxError(BoxError.BAD_FIELD, `Targer directory ${storageDir} does not support chown`); return null; } @@ -1794,14 +1808,7 @@ async function setStorage(app, volumeId, volumePrefix, auditSource) { if (error) throw error; if (volumeId) { - const volume = await volumes.get(volumeId); - if (volume === null) throw new BoxError(BoxError.BAD_FIELD, 'Storage volume not found'); - - const status = await volumes.getStatus(volume); - if (status.state !== 'active') throw new BoxError(BoxError.BAD_FIELD, 'Volume is not active'); - - error = validateStorage(volume, volumePrefix); - if (error) throw error; + await checkStorage(volumeId, volumePrefix); } else { volumeId = volumePrefix = null; } diff --git a/src/scripts/checkvolume.sh b/src/scripts/checkvolume.sh new file mode 100755 index 000000000..719577a98 --- /dev/null +++ b/src/scripts/checkvolume.sh @@ -0,0 +1,39 @@ +#!/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 + +volume_dir="$1" + +readonly test_file="${volume_dir}/.chown-test" + +mkdir -p "${volume_dir}" +rm -f "${test_file}" # clean up any from previous run + +if [[ -n "$(ls -A \"${volume_dir}\")" ]]; then + echo "volume dir is not empty" + exit 2 +fi + +touch "${test_file}" +if ! chown yellowtent:yellowtent "${test_file}"; then + echo "chown does not work" + exit 3 +fi +rm -f "${test_file}" +rm -r "${volume_dir}" # will get recreated by the local storage addon + diff --git a/src/scripts/mkdirvolume.sh b/src/scripts/mkdirvolume.sh index b17e933f8..ce0d84d2b 100755 --- a/src/scripts/mkdirvolume.sh +++ b/src/scripts/mkdirvolume.sh @@ -20,4 +20,3 @@ fi volume_dir="$1" mkdir -p "${volume_dir}" -