diff --git a/src/addons.js b/src/addons.js index baa686a96..912e71981 100644 --- a/src/addons.js +++ b/src/addons.js @@ -852,7 +852,7 @@ function startServices(existingInfra, callback) { } else { assert.strictEqual(typeof existingInfra.images, 'object'); - if (!existingInfra.images.turn || infra.images.turn.tag !== existingInfra.images.turn.tag) startFuncs.push(startTurn.bind(null, existingInfra)); + if (infra.images.turn.tag !== existingInfra.images.turn.tag) startFuncs.push(startTurn.bind(null, existingInfra)); if (infra.images.mysql.tag !== existingInfra.images.mysql.tag) startFuncs.push(startMysql.bind(null, existingInfra)); if (infra.images.postgresql.tag !== existingInfra.images.postgresql.tag) startFuncs.push(startPostgresql.bind(null, existingInfra)); if (infra.images.mongodb.tag !== existingInfra.images.mongodb.tag) startFuncs.push(startMongodb.bind(null, existingInfra)); @@ -1192,7 +1192,11 @@ function startMysql(existingInfra, callback) { --label isCloudronManaged=true \ --read-only -v /tmp -v /run "${tag}"`; - shell.exec('startMysql', cmd, function (error) { + async.series([ + shell.exec.bind(null, 'stopMysql', 'docker stop mysql || true'), + shell.exec.bind(null, 'removeMysql', 'docker rm -f mysql || true'), + shell.exec.bind(null, 'startMysql', cmd) + ], function (error) { if (error) return callback(error); waitForContainer('mysql', 'CLOUDRON_MYSQL_TOKEN', function (error) { @@ -1408,7 +1412,11 @@ function startPostgresql(existingInfra, callback) { --label isCloudronManaged=true \ --read-only -v /tmp -v /run "${tag}"`; - shell.exec('startPostgresql', cmd, function (error) { + async.series([ + shell.exec.bind(null, 'stopPostgresql', 'docker stop postgresql || true'), + shell.exec.bind(null, 'removePostgresql', 'docker rm -f postgresql || true'), + shell.exec.bind(null, 'startPostgresql', cmd) + ], function (error) { if (error) return callback(error); waitForContainer('postgresql', 'CLOUDRON_POSTGRESQL_TOKEN', function (error) { @@ -1564,8 +1572,6 @@ function startTurn(existingInfra, callback) { const memoryLimit = 256; const realm = settings.adminFqdn(); - if (existingInfra.version === infra.version && existingInfra.images.turn && infra.images.turn.tag === existingInfra.images.turn.tag) return callback(); - // this exports 3478/tcp, 5349/tls and 50000-51000/udp const cmd = `docker run --restart=always -d --name="turn" \ --hostname turn \ @@ -1583,7 +1589,11 @@ function startTurn(existingInfra, callback) { --label isCloudronManaged=true \ --read-only -v /tmp -v /run "${tag}"`; - shell.exec('startTurn', cmd, callback); + async.series([ + shell.exec.bind(null, 'stopTurn', 'docker stop turn || true'), + shell.exec.bind(null, 'removeTurn', 'docker rm -f turn || true'), + shell.exec.bind(null, 'startTurn', cmd) + ], callback); } function startMongodb(existingInfra, callback) { @@ -1622,7 +1632,11 @@ function startMongodb(existingInfra, callback) { --label isCloudronManaged=true \ --read-only -v /tmp -v /run "${tag}"`; - shell.exec('startMongodb', cmd, function (error) { + async.series([ + shell.exec.bind(null, 'stopMongodb', 'docker stop mongodb || true'), + shell.exec.bind(null, 'removeMongodb', 'docker rm -f mongodb || true'), + shell.exec.bind(null, 'startMongodb', cmd) + ], function (error) { if (error) return callback(error); waitForContainer('mongodb', 'CLOUDRON_MONGODB_TOKEN', function (error) { @@ -1773,13 +1787,19 @@ function startRedis(existingInfra, callback) { async.eachSeries(allApps, function iterator (app, iteratorCallback) { if (!('redis' in app.manifest.addons)) return iteratorCallback(); // app doesn't use the addon - setupRedis(app, app.manifest.addons.redis, iteratorCallback); + const redisName = 'redis-' + app.id; + + async.series([ + shell.exec.bind(null, 'stopRedis', `docker stop ${redisName} || true`), + shell.exec.bind(null, 'removeRedis', `docker rm -f ${redisName} || true`), + setupRedis.bind(null, app, app.manifest.addons.redis) // starts the container + ], iteratorCallback); }, function (error) { if (error) return callback(error); if (!upgrading) return callback(); - importDatabase('redis', callback); // setupRedis currently starts the app container + importDatabase('redis', callback); }); }); } diff --git a/src/graphs.js b/src/graphs.js index 899d7ea72..b48eccee9 100644 --- a/src/graphs.js +++ b/src/graphs.js @@ -5,6 +5,7 @@ exports = module.exports = { }; var assert = require('assert'), + async = require('async'), infra = require('./infra_version.js'), paths = require('./paths.js'), shell = require('./shell.js'); @@ -37,5 +38,9 @@ function startGraphite(existingInfra, callback) { --label isCloudronManaged=true \ --read-only -v /tmp -v /run "${tag}"`; - shell.exec('startGraphite', cmd, callback); + async.series([ + shell.exec.bind(null, 'stopGraphite', 'docker stop graphite || true'), + shell.exec.bind(null, 'removeGraphite', 'docker rm -f graphite || true'), + shell.exec.bind(null, 'startGraphite', cmd) + ], callback); } diff --git a/src/mail.js b/src/mail.js index 94cf67da9..5650899ae 100644 --- a/src/mail.js +++ b/src/mail.js @@ -630,7 +630,10 @@ function configureMail(mailFqdn, mailDomain, callback) { if (!safe.child_process.execSync(`cp ${bundle.certFilePath} ${mailCertFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not create cert file:' + safe.error.message)); if (!safe.child_process.execSync(`cp ${bundle.keyFilePath} ${mailKeyFilePath}`)) return callback(new BoxError(BoxError.FS_ERROR, 'Could not create key file:' + safe.error.message)); - shell.exec('startMail', 'docker rm -f mail || true', function (error) { + async.series([ + shell.exec.bind(null, 'stopMail', 'docker stop mail || true'), + shell.exec.bind(null, 'removeMail', 'docker rm -f mail || true'), + ], function (error) { if (error) return callback(error); createMailConfig(mailFqdn, mailDomain, function (error, allowInbound) { diff --git a/src/platform.js b/src/platform.js index 6d0388ccc..3a698350c 100644 --- a/src/platform.js +++ b/src/platform.js @@ -56,7 +56,7 @@ function start(callback) { if (error) return callback(error); async.series([ - stopContainers.bind(null, existingInfra), + (next) => { if (existingInfra.version !== infra.version) removeAllContainers(existingInfra, next); else next(); }, markApps.bind(null, existingInfra), // mark app state before we start addons. this gives the db import logic a chance to mark an app as errored graphs.startGraphite.bind(null, existingInfra), sftp.startSftp.bind(null, existingInfra), @@ -129,29 +129,13 @@ function pruneInfraImages(callback) { }, callback); } -function stopContainers(existingInfra, callback) { - // always stop addons to restart them on any infra change, regardless of minor or major update - if (existingInfra.version !== infra.version) { - debug('stopping all containers for infra upgrade'); - async.series([ - shell.exec.bind(null, 'stopContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker stop'), - shell.exec.bind(null, 'stopContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker rm -f') - ], callback); - } else { - assert(typeof infra.images, 'object'); - var changedAddons = [ ]; - for (var imageName in existingInfra.images) { // do not use infra.images because we can only stop things which are existing - if (infra.images[imageName].tag !== existingInfra.images[imageName].tag) changedAddons.push(imageName); - } +function removeAllContainers(existingInfra, callback) { + debug('removeAllContainers: removing all containers for infra upgrade'); - debug('stopContainer: stopping addons for incremental infra update: %j', changedAddons); - let filterArg = changedAddons.map(function (c) { return `--filter 'name=${c}'`; }).join(' '); // name=c matches *c*. required for redis-{appid} - // ignore error if container not found (and fail later) so that this code works across restarts - async.series([ - shell.exec.bind(null, 'stopContainers', `docker ps -qa ${filterArg} --filter 'label=isCloudronManaged' | xargs --no-run-if-empty docker stop || true`), - shell.exec.bind(null, 'stopContainers', `docker ps -qa ${filterArg} --filter 'label=isCloudronManaged' | xargs --no-run-if-empty docker rm -f || true`) - ], callback); - } + async.series([ + shell.exec.bind(null, 'removeAllContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker stop'), + shell.exec.bind(null, 'removeAllContainers', 'docker ps -qa --filter \'label=isCloudronManaged\' | xargs --no-run-if-empty docker rm -f') + ], callback); } function markApps(existingInfra, callback) { diff --git a/src/sftp.js b/src/sftp.js index 1dee216c2..f0adc1a93 100644 --- a/src/sftp.js +++ b/src/sftp.js @@ -10,8 +10,6 @@ var apps = require('./apps.js'), async = require('async'), debug = require('debug')('box:sftp'), infra = require('./infra_version.js'), - path = require('path'), - paths = require('./paths.js'), shell = require('./shell.js'); function startSftp(existingInfra, callback) { @@ -55,34 +53,31 @@ function rebuild(options, callback) { debug('app volume mounts', dataDirs); + const appDataVolumes = dataDirs.map(function (v) { return `-v "${v.hostDir}:${v.mountDir}"`; }).join(' '); + + const cmd = `docker run --restart=always -d --name="sftp" \ + --hostname sftp \ + --net cloudron \ + --net-alias sftp \ + --log-driver syslog \ + --log-opt syslog-address=udp://127.0.0.1:2514 \ + --log-opt syslog-format=rfc5424 \ + --log-opt tag=sftp \ + -m ${memoryLimit}m \ + --memory-swap ${memoryLimit * 2}m \ + --dns 172.18.0.1 \ + --dns-search=. \ + -p 222:22 \ + ${appDataVolumes} \ + -v "/etc/ssh:/etc/ssh:ro" \ + --label isCloudronManaged=true \ + --read-only -v /tmp -v /run "${tag}"`; + // ignore error if container not found (and fail later) so that this code works across restarts async.series([ - shell.exec.bind(null, 'stopSftpContainer', 'docker stop sftp || true'), - shell.exec.bind(null, 'stopSftpContainer', 'docker rm -f sftp || true') - ], function (error) { - if (error) debug('Failed to stop sftp container. Possibly not running.'); - - const appDataVolumes = dataDirs.map(function (v) { return `-v "${v.hostDir}:${v.mountDir}"`; }).join(' '); - - const cmd = `docker run --restart=always -d --name="sftp" \ - --hostname sftp \ - --net cloudron \ - --net-alias sftp \ - --log-driver syslog \ - --log-opt syslog-address=udp://127.0.0.1:2514 \ - --log-opt syslog-format=rfc5424 \ - --log-opt tag=sftp \ - -m ${memoryLimit}m \ - --memory-swap ${memoryLimit * 2}m \ - --dns 172.18.0.1 \ - --dns-search=. \ - -p 222:22 \ - ${appDataVolumes} \ - -v "/etc/ssh:/etc/ssh:ro" \ - --label isCloudronManaged=true \ - --read-only -v /tmp -v /run "${tag}"`; - - shell.exec('startSftp', cmd, callback); - }); + shell.exec.bind(null, 'stopSftp', 'docker stop sftp || true'), + shell.exec.bind(null, 'removeSftp', 'docker rm -f sftp || true'), + shell.exec.bind(null, 'startSftp', cmd) + ], callback); }); }