diff --git a/src/mailserver.js b/src/mailserver.js index dc50e8d62..1011f15d1 100644 --- a/src/mailserver.js +++ b/src/mailserver.js @@ -171,7 +171,7 @@ async function configureMail(mailFqdn, mailDomain, serviceConfig) { const cmd = serviceConfig.recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : ''; const logLevel = serviceConfig.recoveryMode ? 'data' : 'info'; - const runCmd = `docker run --restart=always -d --name="mail" \ + const runCmd = `docker run --restart=always -d --name=mail \ --net cloudron \ --net-alias mail \ --log-driver syslog \ @@ -182,16 +182,16 @@ async function configureMail(mailFqdn, mailDomain, serviceConfig) { --memory-swap ${memoryLimit} \ --dns 172.18.0.1 \ --dns-search=. \ - -e CLOUDRON_MAIL_TOKEN="${cloudronToken}" \ - -e CLOUDRON_RELAY_TOKEN="${relayToken}" \ + -e CLOUDRON_MAIL_TOKEN=${cloudronToken} \ + -e CLOUDRON_RELAY_TOKEN=${relayToken} \ -e LOGLEVEL=${logLevel} \ - -v "${paths.MAIL_DATA_DIR}:/app/data" \ - -v "${paths.MAIL_CONFIG_DIR}:/etc/mail:ro" \ + -v ${paths.MAIL_DATA_DIR}:/app/data \ + -v ${paths.MAIL_CONFIG_DIR}:/etc/mail:ro \ ${ports} \ --label isCloudronManaged=true \ ${readOnly} -v /run -v /tmp ${image} ${cmd}`; - await shell.exec('startMail', runCmd, {}); + await shell.exec('startMail', runCmd, { shell: '/bin/bash' }); } async function restart() { diff --git a/src/services.js b/src/services.js index 84c02f398..b9fa46d2f 100644 --- a/src/services.js +++ b/src/services.js @@ -936,7 +936,7 @@ async function startTurn(existingInfra) { // this exports 3478/tcp, 5349/tls and 50000-51000/udp. note that this runs on the host network because docker's userland proxy // is spun for every port. we can disable this in some future release with --userland-proxy=false // https://github.com/moby/moby/issues/8356 and https://github.com/moby/moby/issues/14856 - const runCmd = `docker run --restart=always -d --name="turn" \ + const runCmd = `docker run --restart=always -d --name=turn \ --hostname turn \ --net host \ --log-driver syslog \ @@ -947,10 +947,10 @@ async function startTurn(existingInfra) { --memory-swap ${memoryLimit} \ --dns 172.18.0.1 \ --dns-search=. \ - -e CLOUDRON_TURN_SECRET="${turnSecret}" \ + -e CLOUDRON_TURN_SECRET=${turnSecret} \ -e CLOUDRON_REALM="${realm}" \ --label isCloudronManaged=true \ - ${readOnly} -v /tmp -v /run "${image}" ${cmd}`; + ${readOnly} -v /tmp -v /run ${image} ${cmd}`; await safe(shell.exec('stopTurn', 'docker stop turn', {})); // ignore error await safe(shell.exec('removeTurn', 'docker rm -f turn', {})); // ignore error @@ -1140,7 +1140,7 @@ async function startMysql(existingInfra) { const cmd = serviceConfig.recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : ''; // memory options are applied dynamically. import requires all the memory we can get - const runCmd = `docker run --restart=always -d --name="mysql" \ + const runCmd = `docker run --restart=always -d --name=mysql \ --hostname mysql \ --net cloudron \ --net-alias mysql \ @@ -1154,14 +1154,14 @@ async function startMysql(existingInfra) { -e CLOUDRON_MYSQL_TOKEN=${cloudronToken} \ -e CLOUDRON_MYSQL_ROOT_HOST=172.18.0.1 \ -e CLOUDRON_MYSQL_ROOT_PASSWORD=${rootPassword} \ - -v "${dataDir}/mysql:/var/lib/mysql" \ + -v ${dataDir}/mysql:/var/lib/mysql \ --label isCloudronManaged=true \ --cap-add SYS_NICE \ - ${readOnly} -v /tmp -v /run "${image}" ${cmd}`; + ${readOnly} -v /tmp -v /run ${image} ${cmd}`; await safe(shell.exec('stopMysql', 'docker stop mysql', {})); // ignore error await safe(shell.exec('removeMysql', 'docker rm -f mysql', {})); // ignore error - await shell.exec('startMysql', runCmd, {}); + await shell.exec('startMysql', runCmd, { shell: '/bin/bash' }); if (!serviceConfig.recoveryMode) { await waitForContainer('mysql', 'CLOUDRON_MYSQL_TOKEN'); @@ -1359,7 +1359,7 @@ async function startPostgresql(existingInfra) { const useVectorRsExtension = !!serviceConfig.useVectorRsExtension; // memory options are applied dynamically. import requires all the memory we can get - const runCmd = `docker run --restart=always -d --name="postgresql" \ + const runCmd = `docker run --restart=always -d --name=postgresql \ --hostname postgresql \ --net cloudron \ --net-alias postgresql \ @@ -1371,16 +1371,16 @@ async function startPostgresql(existingInfra) { --dns-search=. \ --ip ${constants.POSTGRESQL_SERVICE_IPv4} \ --shm-size=128M \ - -e CLOUDRON_POSTGRESQL_ROOT_PASSWORD="${rootPassword}" \ - -e CLOUDRON_POSTGRESQL_TOKEN="${cloudronToken}" \ - -e CLOUDRON_POSTGRESQL_USE_VECTOR_RS="${ useVectorRsExtension ? 'true': '' }" \ - -v "${dataDir}/postgresql:/var/lib/postgresql" \ + -e CLOUDRON_POSTGRESQL_ROOT_PASSWORD=${rootPassword} \ + -e CLOUDRON_POSTGRESQL_TOKEN=${cloudronToken} \ + -e CLOUDRON_POSTGRESQL_USE_VECTOR_RS=${ useVectorRsExtension ? 'true': '' } \ + -v ${dataDir}/postgresql:/var/lib/postgresql \ --label isCloudronManaged=true \ - ${readOnly} -v /tmp -v /run "${image}" ${cmd}`; + ${readOnly} -v /tmp -v /run ${image} ${cmd}`; await safe(shell.exec('stopPostgresql', 'docker stop postgresql', {})); // ignore error await safe(shell.exec('removePostgresql', 'docker rm -f postgresql', {})); // ignore error - await shell.exec('startPostgresql', runCmd, {}); + await shell.exec('startPostgresql', runCmd, { shell: '/bin/bash' }); if (!serviceConfig.recoveryMode) { await waitForContainer('postgresql', 'CLOUDRON_POSTGRESQL_TOKEN'); @@ -1504,7 +1504,7 @@ async function startMongodb(existingInfra) { const cmd = serviceConfig.recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : ''; // memory options are applied dynamically. import requires all the memory we can get - const runCmd = `docker run --restart=always -d --name="mongodb" \ + const runCmd = `docker run --restart=always -d --name=mongodb \ --hostname mongodb \ --net cloudron \ --net-alias mongodb \ @@ -1515,15 +1515,15 @@ async function startMongodb(existingInfra) { --dns 172.18.0.1 \ --dns-search=. \ --ip ${constants.MONGODB_SERVICE_IPv4} \ - -e CLOUDRON_MONGODB_ROOT_PASSWORD="${rootPassword}" \ - -e CLOUDRON_MONGODB_TOKEN="${cloudronToken}" \ - -v "${dataDir}/mongodb:/var/lib/mongodb" \ + -e CLOUDRON_MONGODB_ROOT_PASSWORD=${rootPassword} \ + -e CLOUDRON_MONGODB_TOKEN=${cloudronToken} \ + -v ${dataDir}/mongodb:/var/lib/mongodb \ --label isCloudronManaged=true \ - ${readOnly} -v /tmp -v /run "${image}" ${cmd}`; + ${readOnly} -v /tmp -v /run ${image} ${cmd}`; await safe(shell.exec('stopMongodb', 'docker stop mongodb', {})); // ignore error await safe(shell.exec('removeMongodb', 'docker rm -f mongodb', {})); // ignore error - await shell.exec('startMongodb', runCmd, {}); + await shell.exec('startMongodb', runCmd, { shell: '/bin/bash' }); if (!serviceConfig.recoveryMode) { await waitForContainer('mongodb', 'CLOUDRON_MONGODB_TOKEN'); @@ -1652,7 +1652,7 @@ async function startGraphite(existingInfra) { const cmd = serviceConfig.recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : ''; // port 2003 is used by collectd - const runCmd = `docker run --restart=always -d --name="graphite" \ + const runCmd = `docker run --restart=always -d --name=graphite \ --hostname graphite \ --net cloudron \ --net-alias graphite \ @@ -1665,14 +1665,14 @@ async function startGraphite(existingInfra) { --dns 172.18.0.1 \ --dns-search=. \ -p 127.0.0.1:2003:2003 \ - -v "${paths.PLATFORM_DATA_DIR}/graphite:/var/lib/graphite" \ + -v ${paths.PLATFORM_DATA_DIR}/graphite:/var/lib/graphite \ --label isCloudronManaged=true \ - ${readOnly} -v /tmp -v /run "${image}" ${cmd}`; + ${readOnly} -v /tmp -v /run ${image} ${cmd}`; await safe(shell.exec('stopGraphite', 'docker stop graphite', {})); // ignore error await safe(shell.exec('removeGraphite', 'docker rm -f graphite', {})); // ignore error if (upgrading) await shell.promises.sudo('removeGraphiteDir', [ RMADDONDIR_CMD, 'graphite' ], {}); - await shell.exec('startGraphite', runCmd, {}); + await shell.exec('startGraphite', runCmd, { shell: '/bin/bash' }); // restart collectd to get the disk stats after graphite starts. currently, there is no way to do graphite health check setTimeout(async () => await safe(shell.promises.sudo('restartcollectd', [ RESTART_SERVICE_CMD, 'collectd' ], {})), 60000); @@ -1756,14 +1756,14 @@ async function setupRedis(app, options) { --log-driver syslog \ --log-opt syslog-address=udp://127.0.0.1:2514 \ --log-opt syslog-format=rfc5424 \ - --log-opt tag="${redisName}" \ + --log-opt tag=${redisName} \ -m ${memory} \ --memory-swap ${memoryLimit} \ --dns 172.18.0.1 \ --dns-search=. \ - -e CLOUDRON_REDIS_PASSWORD="${redisPassword}" \ - -e CLOUDRON_REDIS_TOKEN="${redisServiceToken}" \ - -v "${paths.PLATFORM_DATA_DIR}/redis/${app.id}:/var/lib/redis" \ + -e CLOUDRON_REDIS_PASSWORD=${redisPassword} \ + -e CLOUDRON_REDIS_TOKEN=${redisServiceToken} \ + -v ${paths.PLATFORM_DATA_DIR}/redis/${app.id}:/var/lib/redis \ --label isCloudronManaged=true \ ${readOnly} -v /tmp -v /run ${image} ${cmd}`; @@ -1776,7 +1776,7 @@ async function setupRedis(app, options) { const [inspectError, result] = await safe(docker.inspect(redisName)); if (inspectError) { - await shell.exec('startRedis', runCmd, {}); + await shell.exec('startRedis', runCmd, { shell: '/bin/bash' }); } else { // fast path debug(`Re-using existing redis container with state: ${JSON.stringify(result.State)}`); } diff --git a/src/sftp.js b/src/sftp.js index 5c1caef32..3ff0dcc86 100644 --- a/src/sftp.js +++ b/src/sftp.js @@ -103,7 +103,7 @@ async function start(existingInfra) { const cmd = serviceConfig.recoveryMode ? '/bin/bash -c \'echo "Debug mode. Sleeping" && sleep infinity\'' : ''; const mounts = dataDirs.map(v => `-v "${v.hostDir}:${v.mountDir}"`).join(' '); - const runCmd = `docker run --restart=always -d --name="sftp" \ + const runCmd = `docker run --restart=always -d --name=sftp \ --hostname sftp \ --net cloudron \ --net-alias sftp \ @@ -117,15 +117,15 @@ async function start(existingInfra) { --dns-search=. \ -p 222:22 \ ${mounts} \ - -e CLOUDRON_SFTP_TOKEN="${cloudronToken}" \ - -v "${paths.SFTP_KEYS_DIR}:/etc/ssh:ro" \ + -e CLOUDRON_SFTP_TOKEN=${cloudronToken} \ + -v ${paths.SFTP_KEYS_DIR}:/etc/ssh:ro \ --label isCloudronManaged=true \ - ${readOnly} -v /tmp -v /run "${image}" ${cmd}`; + ${readOnly} -v /tmp -v /run ${image} ${cmd}`; // ignore error if container not found (and fail later) so that this code works across restarts await safe(shell.exec('stopSftp', 'docker stop sftp', {})); // ignore error await safe(shell.exec('removeSftp', 'docker rm -f sftp', {})); // ignore error - await shell.exec('startSftp', runCmd, {}); + await shell.exec('startSftp', runCmd, { shell: '/bin/bash' }); } async function status() { diff --git a/src/shell.js b/src/shell.js index a31388c02..a3d964a8c 100644 --- a/src/shell.js +++ b/src/shell.js @@ -50,12 +50,13 @@ async function execArgs(tag, file, args, options) { }); } -// default encoding utf8, shell, handles input, full command +// default encoding utf8, no shell, handles input, full command async function exec(tag, cmd, options) { assert.strictEqual(typeof tag, 'string'); assert.strictEqual(typeof cmd, 'string'); assert.strictEqual(typeof options, 'object'); + cmd = options.shell ? cmd : cmd.replace(/\s+/g, ' '); // collapse spaces when not using shell. note: no more complexity like parsing quotes here! const [file, ...args] = cmd.split(' '); return await execArgs(tag, file, args, options); }