diff --git a/setup/start.sh b/setup/start.sh index 0e35ea271..ff2c279e8 100755 --- a/setup/start.sh +++ b/setup/start.sh @@ -99,6 +99,7 @@ mkdir -p "${PLATFORM_DATA_DIR}/mongodb" mkdir -p "${PLATFORM_DATA_DIR}/snapshots" mkdir -p "${PLATFORM_DATA_DIR}/addons/mail" mkdir -p "${PLATFORM_DATA_DIR}/collectd/collectd.conf.d" +mkdir -p "${PLATFORM_DATA_DIR}/logrotate.d" mkdir -p "${PLATFORM_DATA_DIR}/acme" mkdir -p "${BOX_DATA_DIR}/appicons" @@ -173,6 +174,11 @@ ln -sfF "${PLATFORM_DATA_DIR}/collectd" /etc/collectd cp "${script_dir}/start/collectd/collectd.conf" "${PLATFORM_DATA_DIR}/collectd/collectd.conf" systemctl restart collectd +echo "==> Configuring logrotate" +if ! grep -q "^include ${PLATFORM_DATA_DIR}/logrotate.d" /etc/logrotate.conf; then + echo -e "\ninclude ${PLATFORM_DATA_DIR}/logrotate.d\n" >> /etc/logrotate.conf +fi + echo "==> Adding motd message for admins" cp "${script_dir}/start/cloudron-motd" /etc/update-motd.d/92-cloudron @@ -281,7 +287,7 @@ CONF_END echo "==> Changing ownership" chown "${USER}:${USER}" -R "${CONFIG_DIR}" -chown "${USER}:${USER}" -R "${PLATFORM_DATA_DIR}/nginx" "${PLATFORM_DATA_DIR}/collectd" "${PLATFORM_DATA_DIR}/addons" "${PLATFORM_DATA_DIR}/acme" +chown "${USER}:${USER}" -R "${PLATFORM_DATA_DIR}/nginx" "${PLATFORM_DATA_DIR}/collectd" "${PLATFORM_DATA_DIR}/logrotate.d" "${PLATFORM_DATA_DIR}/addons" "${PLATFORM_DATA_DIR}/acme" chown "${USER}:${USER}" -R "${BOX_DATA_DIR}" chown "${USER}:${USER}" -R "${PLATFORM_DATA_DIR}/mail/dkim" # this is owned by box currently since it generates the keys chown "${USER}:${USER}" "${PLATFORM_DATA_DIR}/INFRA_VERSION" 2>/dev/null || true diff --git a/setup/start/sudoers b/setup/start/sudoers index 8149c3d8b..3439d3ad4 100644 --- a/setup/start/sudoers +++ b/setup/start/sudoers @@ -30,3 +30,9 @@ yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/authorized_keys Defaults!/home/yellowtent/box/src/scripts/node.sh env_keep="HOME BOX_ENV NODE_ENV" yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/node.sh + +Defaults!/home/yellowtent/box/src/scripts/mvlogrotateconfig.sh env_keep="HOME BOX_ENV" +yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/mvlogrotateconfig.sh + +Defaults!/home/yellowtent/box/src/scripts/rmlogrotateconfig.sh env_keep="HOME BOX_ENV" +yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmlogrotateconfig.sh diff --git a/src/apptask.js b/src/apptask.js index c80916bd2..876b60464 100644 --- a/src/apptask.js +++ b/src/apptask.js @@ -42,6 +42,7 @@ var addons = require('./addons.js'), manifestFormat = require('cloudron-manifestformat'), net = require('net'), nginx = require('./nginx.js'), + os = require('os'), path = require('path'), paths = require('./paths.js'), safe = require('safetydance'), @@ -56,6 +57,9 @@ var addons = require('./addons.js'), var COLLECTD_CONFIG_EJS = fs.readFileSync(__dirname + '/collectd.config.ejs', { encoding: 'utf8' }), RELOAD_COLLECTD_CMD = path.join(__dirname, 'scripts/reloadcollectd.sh'), + LOGROTATE_CONFIG_EJS = fs.readFileSync(__dirname + '/logrotate.ejs', { encoding: 'utf8' }), + MV_LOGROTATE_CONFIG_CMD = path.join(__dirname, 'scripts/mvlogrotateconfig.sh'), + RM_LOGROTATE_CONFIG_CMD = path.join(__dirname, 'scripts/rmlogrotateconfig.sh'), RMAPPDIR_CMD = path.join(__dirname, 'scripts/rmappdir.sh'), CREATEAPPDIR_CMD = path.join(__dirname, 'scripts/createappdir.sh'); @@ -171,6 +175,32 @@ function removeCollectdProfile(app, callback) { }); } +function addLogrotateConfig(app, callback) { + assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof callback, 'function'); + + docker.inspect(app.containerId, function (error, result) { + if (error) return callback(error); + + var runVolume = result.Mounts.find(function (mount) { return mount.Destination === '/run'; }); + if (!runVolume) return callback(new Error('App does not have /run mounted')); + + var logrotateConf = ejs.render(LOGROTATE_CONFIG_EJS, { volumePath: runVolume.Source }); + var tmpFilePath = path.join(os.tmpdir(), app.id + '.logrotate'); + fs.writeFile(tmpFilePath, logrotateConf, function (error) { + if (error) return callback(error); + shell.sudo('addLogrotateConfig', [ MV_LOGROTATE_CONFIG_CMD, tmpFilePath, app.id ], callback); + }); + }); +} + +function removeLogrotateConfig(app, callback) { + assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof callback, 'function'); + + shell.sudo('removeLogrotateConfig', [ RM_LOGROTATE_CONFIG_CMD, app.id ], callback); +} + function verifyManifest(app, callback) { assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof callback, 'function'); @@ -362,6 +392,7 @@ function install(app, callback) { updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }), unconfigureNginx.bind(null, app), removeCollectdProfile.bind(null, app), + removeLogrotateConfig.bind(null, app), stopApp.bind(null, app), deleteContainers.bind(null, app), // oldConfig can be null during upgrades @@ -406,6 +437,9 @@ function install(app, callback) { updateApp.bind(null, app, { installationProgress: '70, Creating container' }), createContainer.bind(null, app), + updateApp.bind(null, app, { installationProgress: '75, Setting up logrotate config' }), + addLogrotateConfig.bind(null, app), + updateApp.bind(null, app, { installationProgress: '80, Setting up collectd profile' }), addCollectdProfile.bind(null, app), @@ -467,6 +501,7 @@ function configure(app, callback) { updateApp.bind(null, app, { installationProgress: '10, Cleaning up old install' }), unconfigureNginx.bind(null, app), removeCollectdProfile.bind(null, app), + removeLogrotateConfig.bind(null, app), stopApp.bind(null, app), deleteContainers.bind(null, app), function (next) { @@ -496,6 +531,9 @@ function configure(app, callback) { updateApp.bind(null, app, { installationProgress: '60, Creating container' }), createContainer.bind(null, app), + updateApp.bind(null, app, { installationProgress: '65, Setting up logrotate config' }), + addLogrotateConfig.bind(null, app), + updateApp.bind(null, app, { installationProgress: '70, Add collectd profile' }), addCollectdProfile.bind(null, app), @@ -548,6 +586,7 @@ function update(app, callback) { // we cannot easily 'recover' from backup failures because we have to revert manfest and portBindings updateApp.bind(null, app, { installationProgress: '25, Cleaning up old install' }), removeCollectdProfile.bind(null, app), + removeLogrotateConfig.bind(null, app), stopApp.bind(null, app), deleteContainers.bind(null, app), function deleteImageIfChanged(done) { @@ -579,6 +618,9 @@ function update(app, callback) { updateApp.bind(null, app, { installationProgress: '80, Creating container' }), createContainer.bind(null, app), + updateApp.bind(null, app, { installationProgress: '85, Setting up logrotate config' }), + addLogrotateConfig.bind(null, app), + updateApp.bind(null, app, { installationProgress: '90, Add collectd profile' }), addCollectdProfile.bind(null, app), @@ -608,6 +650,9 @@ function uninstall(app, callback) { updateApp.bind(null, app, { installationProgress: '0, Remove collectd profile' }), removeCollectdProfile.bind(null, app), + updateApp.bind(null, app, { installationProgress: '5, Remove logrotate config' }), + removeLogrotateConfig.bind(null, app), + updateApp.bind(null, app, { installationProgress: '10, Stopping app' }), stopApp.bind(null, app), diff --git a/src/logrotate.ejs b/src/logrotate.ejs new file mode 100644 index 000000000..4a7cf4f03 --- /dev/null +++ b/src/logrotate.ejs @@ -0,0 +1,11 @@ +# Generated by apptask for the /run mount + +<%= volumePath %>/*/*.log { + rotate 7 + daily + compress + size=1M + missingok + delaycompress + copytruncate +} \ No newline at end of file diff --git a/src/paths.js b/src/paths.js index 63641a48d..b8c306d1b 100644 --- a/src/paths.js +++ b/src/paths.js @@ -17,6 +17,7 @@ exports = module.exports = { ACME_CHALLENGES_DIR: path.join(config.baseDir(), 'platformdata/acme'), ADDON_CONFIG_DIR: path.join(config.baseDir(), 'platformdata/addons'), COLLECTD_APPCONFIG_DIR: path.join(config.baseDir(), 'platformdata/collectd/collectd.conf.d'), + LOGROTATE_CONFIG_DIR: path.join(config.baseDir(), 'platformdata/logrotate.d'), MAIL_DATA_DIR: path.join(config.baseDir(), 'platformdata/mail'), NGINX_CONFIG_DIR: path.join(config.baseDir(), 'platformdata/nginx'), NGINX_APPCONFIG_DIR: path.join(config.baseDir(), 'platformdata/nginx/applications'), diff --git a/src/scripts/mvlogrotateconfig.sh b/src/scripts/mvlogrotateconfig.sh new file mode 100755 index 000000000..0a789b867 --- /dev/null +++ b/src/scripts/mvlogrotateconfig.sh @@ -0,0 +1,28 @@ +#!/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 + +# TODO prevent this script from moving the file from $1 into a random dir with using a relative ../ path +if [[ "${BOX_ENV}" == "cloudron" ]]; then + readonly destination_file_path="${HOME}/platformdata/logrotate.d/$2" +else + readonly destination_file_path="${HOME}/.cloudron_test/platformdata/logrotate.d/$2" +fi + +mv "${1}" "${destination_file_path}" +chown root:root "${destination_file_path}" diff --git a/src/scripts/rmlogrotateconfig.sh b/src/scripts/rmlogrotateconfig.sh new file mode 100755 index 000000000..8190f9a58 --- /dev/null +++ b/src/scripts/rmlogrotateconfig.sh @@ -0,0 +1,24 @@ +#!/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 + +if [[ "${BOX_ENV}" == "cloudron" ]]; then + rm -rf "${HOME}/platformdata/logrotate.d/$1" +else + rm -rf "${HOME}/.cloudron_test/platformdata/logrotate.d/$1" +fi diff --git a/src/test/checkInstall b/src/test/checkInstall index 818874dc3..ece77c1f7 100755 --- a/src/test/checkInstall +++ b/src/test/checkInstall @@ -17,7 +17,9 @@ scripts=("${SOURCE_DIR}/src/scripts/rmappdir.sh" \ "${SOURCE_DIR}/src/scripts/collectlogs.sh" \ "${SOURCE_DIR}/src/scripts/reloadcollectd.sh" \ "${SOURCE_DIR}/src/scripts/authorized_keys.sh" \ - "${SOURCE_DIR}/src/scripts/node.sh") + "${SOURCE_DIR}/src/scripts/node.sh" \ + "${SOURCE_DIR}/src/scripts/mvlogrotateconfig.sh" \ + "${SOURCE_DIR}/src/scripts/rmlogrotateconfig.sh") for script in "${scripts[@]}"; do if [[ $(sudo -n "${script}" --check 2>/dev/null) != "OK" ]]; then