diff --git a/src/cloudron.js b/src/cloudron.js index 3c4fa4e6a..fc00df8dd 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -16,6 +16,7 @@ exports = module.exports = { onActivated: onActivated, setDashboardDomain: setDashboardDomain, + renewCerts: renewCerts, checkDiskSpace: checkDiskSpace, @@ -44,6 +45,7 @@ var assert = require('assert'), spawn = require('child_process').spawn, split = require('split'), sysinfo = require('./sysinfo.js'), + tasks = require('./tasks.js'), users = require('./users.js'), util = require('util'); @@ -375,3 +377,13 @@ function setDashboardDomain(domain, callback) { }); }); } + +function renewCerts(options, auditSource, callback) { + assert.strictEqual(typeof option, 'object'); + assert.strictEqual(typeof auditSource, 'object'); + assert.strictEqual(typeof callback, 'function'); + + let task = tasks.startTask(tasks.TASK_RENEW_CERTS, [ options, auditSource ]); + task.on('error', (error) => callback(new CloudronError(CloudronError.INTERNAL_ERROR, error))); + task.on('start', (taskId) => callback(null, taskId)); +} diff --git a/src/cron.js b/src/cron.js index a56787036..7201d60bb 100644 --- a/src/cron.js +++ b/src/cron.js @@ -186,7 +186,7 @@ function recreateJobs(tz) { if (gJobs.certificateRenew) gJobs.certificateRenew.stop(); gJobs.certificateRenew = new CronJob({ cronTime: '00 00 */12 * * *', // every 12 hours - onTick: reverseProxy.renewAll.bind(null, AUDIT_SOURCE, NOOP_CALLBACK), + onTick: cloudron.renewCerts.bind(null, {}, AUDIT_SOURCE, NOOP_CALLBACK), start: true, timeZone: tz }); diff --git a/src/domains.js b/src/domains.js index a534dbab1..5f1e69077 100644 --- a/src/domains.js +++ b/src/domains.js @@ -9,8 +9,6 @@ module.exports = exports = { clear: clear, isLocked: isLocked, - renewCerts: renewCerts, - fqdn: fqdn, getDnsRecords: getDnsRecords, @@ -496,16 +494,3 @@ function makeWildcard(hostname) { parts[0] = '*'; return parts.join('.'); } - -function renewCerts(domain, auditSource, callback) { - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof auditSource, 'object'); - assert.strictEqual(typeof callback, 'function'); - - // trigger renewal in the background - reverseProxy.renewCerts({ domain: domain }, auditSource, function (error) { - debug('renewCerts', error); - }); - - callback(); -} diff --git a/src/reverseproxy.js b/src/reverseproxy.js index ac2ddc45c..a761ca867 100644 --- a/src/reverseproxy.js +++ b/src/reverseproxy.js @@ -505,9 +505,10 @@ function unconfigureApp(app, callback) { }); } -function renewCerts(options, auditSource, callback) { +function renewCerts(options, auditSource, progressCallback, callback) { assert.strictEqual(typeof options, 'object'); assert.strictEqual(typeof auditSource, 'object'); + assert.strictEqual(typeof progressCallback, 'function'); assert.strictEqual(typeof callback, 'function'); apps.getAll(function (error, allApps) { @@ -530,7 +531,11 @@ function renewCerts(options, auditSource, callback) { if (options.domain) appDomains = appDomains.filter(function (appDomain) { return appDomain.domain === options.domain; }); + let progress = 1; async.eachSeries(appDomains, function (appDomain, iteratorCallback) { + progressCallback({ percent: progress, message: `Renewing certs of ${appDomain.fqdn}` }); + progress += Math.round(100/appDomains.length); + ensureCertificate(appDomain.fqdn, appDomain.domain, auditSource, function (error, bundle) { if (error) return iteratorCallback(error); // this can happen if cloudron is not setup yet diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index 6c7287961..de1e06356 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -12,7 +12,8 @@ exports = module.exports = { getLogs: getLogs, getLogStream: getLogStream, getStatus: getStatus, - setDashboardDomain: setDashboardDomain + setDashboardDomain: setDashboardDomain, + renewCerts: renewCerts }; var appstore = require('../appstore.js'), @@ -192,3 +193,12 @@ function getStatus(req, res, next) { next(new HttpSuccess(200, status)); }); } + +function renewCerts(req, res, next) { + cloudron.renewCerts({ domain: req.query.domain || null }, auditSource(req), function (error) { + if (error && error.reason === CloudronError.NOT_FOUND) return next(new HttpError(404, error.message)); + if (error) return next(new HttpError(500, error)); + + next(new HttpSuccess(202, {})); + }); +} diff --git a/src/routes/domains.js b/src/routes/domains.js index 148a04cd0..2ec34a40e 100644 --- a/src/routes/domains.js +++ b/src/routes/domains.js @@ -7,8 +7,6 @@ exports = module.exports = { update: update, del: del, - renewCerts: renewCerts, - verifyDomainLock: verifyDomainLock }; @@ -154,14 +152,3 @@ function del(req, res, next) { next(new HttpSuccess(204)); }); } - -function renewCerts(req, res, next) { - assert.strictEqual(typeof req.params.domain, 'string'); - - domains.renewCerts(req.params.domain, auditSource(req), function (error) { - if (error && error.reason === DomainsError.NOT_FOUND) return next(new HttpError(404, error.message)); - if (error) return next(new HttpError(500, error)); - - next(new HttpSuccess(202, {})); - }); -} diff --git a/src/server.js b/src/server.js index e786d366f..5fae44d54 100644 --- a/src/server.js +++ b/src/server.js @@ -122,6 +122,7 @@ function initializeExpressSync() { router.get ('/api/v1/cloudron/update', cloudronScope, routes.cloudron.getUpdateInfo); router.post('/api/v1/cloudron/update', cloudronScope, routes.cloudron.update); router.post('/api/v1/cloudron/set_dashboard_domain', cloudronScope, routes.cloudron.setDashboardDomain); + router.post('/api/v1/cloudron/renew_certs', cloudronScope, routes.cloudron.renewCerts); router.post('/api/v1/cloudron/check_for_updates', cloudronScope, routes.cloudron.checkForUpdates); router.get ('/api/v1/cloudron/reboot', cloudronScope, routes.cloudron.isRebootRequired); router.post('/api/v1/cloudron/reboot', cloudronScope, routes.cloudron.reboot); @@ -286,7 +287,6 @@ function initializeExpressSync() { router.get ('/api/v1/domains', domainsReadScope, routes.domains.getAll); router.get ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.domains.get); // this is manage scope because it returns non-restricted fields router.put ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.domains.update); - router.post('/api/v1/domains/:domain/renew_certs', domainsManageScope, verifyDomainLock, routes.domains.renewCerts); router.del ('/api/v1/domains/:domain', domainsManageScope, verifyDomainLock, routes.users.verifyPassword, routes.domains.del); // addon routes diff --git a/src/tasks.js b/src/tasks.js index 9799a1324..e81d1d9ea 100644 --- a/src/tasks.js +++ b/src/tasks.js @@ -15,7 +15,8 @@ exports = module.exports = { // task types. if you add a task here, fill up the function table in taskworker TASK_BACKUP: 'backup', TASK_UPDATE: 'update', - TASK_MIGRATE: 'migrate' + TASK_MIGRATE: 'migrate', + TASK_RENEW_CERTS: 'renewcerts' }; let assert = require('assert'), diff --git a/src/taskworker.js b/src/taskworker.js index 2a13f18ff..9b5f0c636 100755 --- a/src/taskworker.js +++ b/src/taskworker.js @@ -6,6 +6,7 @@ var assert = require('assert'), backups = require('./backups.js'), database = require('./database.js'), debug = require('debug')('box:taskworker'), + reverseProxy = require('./reverseproxy.js'), tasks = require('./tasks.js'), updater = require('./updater.js'); @@ -14,6 +15,7 @@ const NOOP_CALLBACK = function (error) { if (error) debug(error); }; const TASKS = { // indexed by task type backup: backups.backupBoxAndApps, update: updater.update, + renewcerts: reverseProxy.renewCerts }; process.on('SIGTERM', function () {