diff --git a/setup/start/sudoers b/setup/start/sudoers index 1770141af..f88936a05 100644 --- a/setup/start/sudoers +++ b/setup/start/sudoers @@ -7,6 +7,9 @@ yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmvolume.sh Defaults!/home/yellowtent/box/src/scripts/rmredis.sh env_keep="HOME BOX_ENV" yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmredis.sh +Defaults!/home/yellowtent/box/src/scripts/rmaddon.sh env_keep="HOME BOX_ENV" +yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/rmaddon.sh + Defaults!/home/yellowtent/box/src/scripts/reloadnginx.sh env_keep="HOME BOX_ENV" yellowtent ALL=(root) NOPASSWD: /home/yellowtent/box/src/scripts/reloadnginx.sh diff --git a/src/addons.js b/src/addons.js index 05ca7f1d6..4ffda1225 100644 --- a/src/addons.js +++ b/src/addons.js @@ -141,7 +141,7 @@ var KNOWN_ADDONS = { function debugApp(app, args) { assert(typeof app === 'object'); - debug(app.fqdn + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1))); + debug((app.fqdn || app.location) + ' ' + util.format.apply(util, Array.prototype.slice.call(arguments, 1))); } function getAddonDetails(containerName, tokenEnvName, callback) { @@ -291,8 +291,14 @@ function importDatabase(addon, callback) { if (error) return callback(error); async.eachSeries(apps, function iterator (app, iteratorCallback) { - debug(`importDatabase: Importing app ${app.id}`) - KNOWN_ADDONS[addon].restore(app, addon, iteratorCallback); + if (!(addon in app.manifest.addons)) return iteratorCallback(); // app doesn't use the addon + + debug(`importDatabase: Importing addon ${addon} of app ${app.id}`); + + async.series([ + KNOWN_ADDONS[addon].setup.bind(null, app, app.manifest.addons[addon]), + KNOWN_ADDONS[addon].restore.bind(null, app, app.manifest.addons[addon]) + ], iteratorCallback); }, callback); }); } diff --git a/src/platform.js b/src/platform.js index c7cb9f7cb..2a9c5c53d 100644 --- a/src/platform.js +++ b/src/platform.js @@ -19,6 +19,7 @@ var addons = require('./addons.js'), locker = require('./locker.js'), mail = require('./mail.js'), os = require('os'), + path = require('path'), paths = require('./paths.js'), reverseProxy = require('./reverseproxy.js'), safe = require('safetydance'), @@ -30,6 +31,8 @@ var addons = require('./addons.js'), var gPlatformReadyTimer = null; +const RMADDON_CMD = path.join(__dirname, 'scripts/rmaddon.sh'); + var NOOP_CALLBACK = function (error) { if (error) debug(error); }; function start(callback) { @@ -176,6 +179,20 @@ function startGraphite(callback) { callback(); } +function parseImageTag(tag) { + let repository = tag.split(':', 1)[0]; + let version = tag.substr(repository.length + 1).split('@', 1)[0]; + let digest = tag.substr(repository.length + 1 + version.length + 1).split(':', 2)[1]; + + return { repository, version: semver.parse(version), digest }; +} + +function requiresUpgrade(existingTag, currentTag) { + let etag = parseImageTag(existingTag), ctag = parseImageTag(currentTag); + + return etag.version.major !== ctag.version.major; +} + function startMysql(existingInfra, callback) { assert.strictEqual(typeof existingInfra, 'object'); assert.strictEqual(typeof callback, 'function'); @@ -186,6 +203,13 @@ function startMysql(existingInfra, callback) { const cloudronToken = hat(8 * 128); const memoryLimit = (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256; + const upgrading = existingInfra.version !== 'none' && requiresUpgrade(existingInfra.images.mysql.tag, tag); + + if (upgrading) { + debug('startMysql: mysql will be upgraded'); + shell.sudoSync('startMysql', `${RMADDON_CMD} mysql`); + } + const cmd = `docker run --restart=always -d --name="mysql" \ --net cloudron \ --net-alias mysql \ @@ -205,7 +229,12 @@ function startMysql(existingInfra, callback) { shell.execSync('startMysql', cmd); - addons.waitForAddon('mysql', 'CLOUDRON_MYSQL_TOKEN', callback); + addons.waitForAddon('mysql', 'CLOUDRON_MYSQL_TOKEN', function (error) { + if (error) return callback(error); + if (!upgrading) return callback(null); + + addons.importDatabase('mysql', callback); + }); } function startPostgresql(existingInfra, callback) { @@ -218,6 +247,13 @@ function startPostgresql(existingInfra, callback) { const cloudronToken = hat(8 * 128); const memoryLimit = (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 256; + const upgrading = existingInfra.version !== 'none' && requiresUpgrade(existingInfra.images.postgresql.tag, tag); + + if (upgrading) { + debug('startPostgresql: postgresql will be upgraded'); + shell.sudoSync('startPostgresql', `${RMADDON_CMD} postgresql`); + } + const cmd = `docker run --restart=always -d --name="postgresql" \ --net cloudron \ --net-alias postgresql \ @@ -236,7 +272,12 @@ function startPostgresql(existingInfra, callback) { shell.execSync('startPostgresql', cmd); - addons.waitForAddon('postgresql', 'CLOUDRON_POSTGRESQL_TOKEN', callback); + addons.waitForAddon('postgresql', 'CLOUDRON_POSTGRESQL_TOKEN', function (error) { + if (error) return callback(error); + if (!upgrading) return callback(null); + + addons.importDatabase('postgresql', callback); + }); } function startMongodb(existingInfra, callback) { @@ -249,6 +290,14 @@ function startMongodb(existingInfra, callback) { const cloudronToken = hat(8 * 128); const memoryLimit = (1 + Math.round(os.totalmem()/(1024*1024*1024)/4)) * 200; + + const upgrading = existingInfra.version !== 'none' && requiresUpgrade(existingInfra.images.mongodb.tag, tag); + + if (upgrading) { + debug('startMongodb: mongodb will be upgraded'); + shell.sudoSync('startMongodb', `${RMADDON_CMD} mongodb`); + } + const cmd = `docker run --restart=always -d --name="mongodb" \ --net cloudron \ --net-alias mongodb \ @@ -267,7 +316,12 @@ function startMongodb(existingInfra, callback) { shell.execSync('startMongodb', cmd); - addons.waitForAddon('mongodb', 'CLOUDRON_MONGODB_TOKEN', callback); + addons.waitForAddon('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error) { + if (error) return callback(error); + if (!upgrading) return callback(null); + + addons.importDatabase('mongodb', callback); + }); } function startAddons(existingInfra, callback) { diff --git a/src/scripts/rmaddon.sh b/src/scripts/rmaddon.sh new file mode 100755 index 000000000..f500e3143 --- /dev/null +++ b/src/scripts/rmaddon.sh @@ -0,0 +1,37 @@ +#!/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 + +addon="$1" +appid="${2:-}" # only valid for redis +if [[ "${addon}" != "postgresql" && "${addon}" != "mysql" && "${addon}" != "mongodb" && "${addon}" != "redis" ]]; then + echo "${addon} must be postgresql/mysql/mongodb/redis" + exit 1 +fi + +if [[ "${BOX_ENV}" == "cloudron" ]]; then + readonly addon_dir="${HOME}/platformdata/${addon}" +else + readonly addon_dir="${HOME}/.cloudron_test/platformdata/${addon}" +fi + +rm -rf "${addon_dir}" +if [[ "${addon}" != "redis" ]]; then + mkdir "${addon_dir}" +fi + diff --git a/src/test/checkInstall b/src/test/checkInstall index 8db93c482..f03df8c43 100755 --- a/src/test/checkInstall +++ b/src/test/checkInstall @@ -11,6 +11,7 @@ sudo -k || sudo --reset-timestamp # checks if all scripts are sudo access scripts=("${SOURCE_DIR}/src/scripts/rmvolume.sh" \ "${SOURCE_DIR}/src/scripts/rmredis.sh" \ + "${SOURCE_DIR}/src/scripts/rmaddon.sh" \ "${SOURCE_DIR}/src/scripts/reloadnginx.sh" \ "${SOURCE_DIR}/src/scripts/reboot.sh" \ "${SOURCE_DIR}/src/scripts/update.sh" \