diff --git a/src/cloudron.js b/src/cloudron.js index f0f27a293..677d21c49 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -17,6 +17,7 @@ exports = module.exports = { setDashboardDomain, updateDashboardDomain, renewCerts, + syncDnsRecords, runSystemChecks }; @@ -401,3 +402,16 @@ function setupDnsAndCert(subdomain, domain, auditSource, progressCallback, callb }); }); } + +function syncDnsRecords(options, callback) { + assert.strictEqual(typeof options, 'object'); + assert.strictEqual(typeof callback, 'function'); + + tasks.add(tasks.TASK_SYNC_DNS_RECORDS, [ options ], function (error, taskId) { + if (error) return callback(error); + + tasks.startTask(taskId, {}, NOOP_CALLBACK); + + callback(null, taskId); + }); +} diff --git a/src/domains.js b/src/domains.js index ab7b624e7..f433c3daa 100644 --- a/src/domains.js +++ b/src/domains.js @@ -29,10 +29,12 @@ module.exports = exports = { registerLocations, unregisterLocations, - checkDnsRecords + checkDnsRecords, + syncDnsRecords }; -var assert = require('assert'), +const apps = require('./apps.js'), + assert = require('assert'), async = require('async'), BoxError = require('./boxerror.js'), constants = require('./constants.js'), @@ -550,3 +552,48 @@ function unregisterLocations(locations, progressCallback, callback) { }, callback); }); } + +function syncDnsRecords(options, progressCallback, callback) { + assert.strictEqual(typeof options, 'object'); + assert.strictEqual(typeof progressCallback, 'function'); + assert.strictEqual(typeof callback, 'function'); + + if (options.domain && options.type === 'mail') return mail.setDnsRecords(options.domain, callback); + + getAll(function (error, domains) { + if (error) return callback(error); + + if (options.domain) domains = domains.filter(d => d.domain === options.domain); + + const mailSubdomain = settings.mailFqdn().substr(0, settings.mailFqdn().length - settings.mailDomain().length - 1); + + apps.getAll(function (error, allApps) { + if (error) return callback(error); + + let progress = 1, errors = []; + + // we sync by domain only to get some nice progress + async.eachSeries(domains, function (domain, iteratorDone) { + progressCallback({ percent: progress, message: `Updating DNS of ${domain.domain}`}); + progress += Math.round(100/domains.length); + + let locations = []; + if (domain.domain === settings.adminDomain()) locations.push({ subdomain: constants.ADMIN_LOCATION, domain: settings.adminDomain() }); + if (domain.domain === settings.mailDomain() && settings.mailFqdn() !== settings.adminFqdn()) locations.push({ subdomain: mailSubdomain, domain: settings.mailDomain() }); + + allApps.forEach(function (app) { + locations.concat([{ subdomain: app.location, domain: app.domain }].concat(app.alternateDomains).concat(app.aliasDomains).filter(al => al.domain === domain)); + }); + + async.series([ + registerLocations.bind(null, locations, { overwrite: true }, progressCallback), + progressCallback.bind(null, { message: `Updating mail DNS of ${domain.domain}`}), + mail.setDnsRecords.bind(null, domain.domain) + ], function (error) { + if (error) errors.push({ domain: domain.domain, message: error.message }); + iteratorDone(); + }); + }, () => callback(null, { errors })); + }); + }); +} diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index 9080548a9..ad7445b72 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -21,7 +21,8 @@ exports = module.exports = { renewCerts, getServerIp, getLanguages, - syncExternalLdap + syncExternalLdap, + syncDnsRecords }; let assert = require('assert'), @@ -280,6 +281,8 @@ function prepareDashboardDomain(req, res, next) { } function renewCerts(req, res, next) { + if ('domain' in req.body && typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string')); + cloudron.renewCerts({ domain: req.body.domain || null }, auditSource.fromRequest(req), function (error, taskId) { if (error) return next(BoxError.toHttpError(error)); @@ -291,7 +294,7 @@ function syncExternalLdap(req, res, next) { externalLdap.startSyncer(function (error, taskId) { if (error) return next(new HttpError(500, error.message)); - next(new HttpSuccess(202, { taskId: taskId })); + next(new HttpSuccess(202, { taskId })); }); } @@ -310,3 +313,17 @@ function getLanguages(req, res, next) { next(new HttpSuccess(200, { languages })); }); } + +function syncDnsRecords(req, res, next) { + assert.strictEqual(typeof req.body, 'object'); + + if ('domain' in req.body && typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string')); + if ('type' in req.body && typeof req.body.type !== 'string') return next(new HttpError(400, 'type must be a string')); + + cloudron.syncDnsRecords(req.body, function (error, taskId) { + if (error && error.reason === BoxError.ACCESS_DENIED) return next(new HttpSuccess(200, { error: { reason: error.reason, message: error.message }})); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(201, { taskId })); + }); +} diff --git a/src/routes/domains.js b/src/routes/domains.js index 9c8fc2e2e..d9a9665d0 100644 --- a/src/routes/domains.js +++ b/src/routes/domains.js @@ -1,13 +1,13 @@ 'use strict'; exports = module.exports = { - add: add, - get: get, - getAll: getAll, - update: update, - del: del, + add, + get, + getAll, + update, + del, - checkDnsRecords: checkDnsRecords, + checkDnsRecords, }; var assert = require('assert'), diff --git a/src/routes/mail.js b/src/routes/mail.js index 8575a1f48..94f6228c6 100644 --- a/src/routes/mail.js +++ b/src/routes/mail.js @@ -3,8 +3,6 @@ exports = module.exports = { getDomain, - setDnsRecords, - getStatus, setMailFromValidation, @@ -50,21 +48,6 @@ function getDomain(req, res, next) { }); } -function setDnsRecords(req, res, next) { - assert.strictEqual(typeof req.body, 'object'); - assert.strictEqual(typeof req.params.domain, 'string'); - - // can take a setup all the DNS entries. this is mostly because some backends try to list DNS entries (DO) - // for upsert and this takes a lot of time - req.clearTimeout(); - - mail.setDnsRecords(req.params.domain, function (error) { - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(201)); - }); -} - function getStatus(req, res, next) { assert.strictEqual(typeof req.params.domain, 'string'); diff --git a/src/server.js b/src/server.js index ed92a194e..45462b93d 100644 --- a/src/server.js +++ b/src/server.js @@ -109,6 +109,7 @@ function initializeExpressSync() { router.post('/api/v1/cloudron/prepare_dashboard_domain', json, token, authorizeAdmin, routes.cloudron.prepareDashboardDomain); router.post('/api/v1/cloudron/set_dashboard_domain', json, token, authorizeAdmin, routes.cloudron.updateDashboardDomain); router.post('/api/v1/cloudron/renew_certs', json, token, authorizeAdmin, routes.cloudron.renewCerts); + router.post('/api/v1/cloudron/sync_dns', json, token, authorizeAdmin, routes.cloudron.syncDnsRecords); router.post('/api/v1/cloudron/check_for_updates', json, token, authorizeAdmin, routes.cloudron.checkForUpdates); router.get ('/api/v1/cloudron/reboot', token, authorizeAdmin, routes.cloudron.isRebootRequired); router.post('/api/v1/cloudron/reboot', json, token, authorizeAdmin, routes.cloudron.reboot); @@ -275,7 +276,6 @@ function initializeExpressSync() { router.post('/api/v1/mail/:domain/catch_all', json, token, authorizeAdmin, routes.mail.setCatchAllAddress); router.post('/api/v1/mail/:domain/relay', json, token, authorizeAdmin, routes.mail.setMailRelay); router.post('/api/v1/mail/:domain/enable', json, token, authorizeAdmin, routes.mail.setMailEnabled); - router.post('/api/v1/mail/:domain/dns', json, token, authorizeAdmin, routes.mail.setDnsRecords); router.post('/api/v1/mail/:domain/banner', json, token, authorizeAdmin, routes.mail.setBanner); router.post('/api/v1/mail/:domain/send_test_mail', json, token, authorizeAdmin, routes.mail.sendTestMail); router.get ('/api/v1/mail/:domain/mailbox_count', token, authorizeAdmin, routes.mail.getMailboxCount); diff --git a/src/tasks.js b/src/tasks.js index 952fa8a55..d3fe4bac3 100644 --- a/src/tasks.js +++ b/src/tasks.js @@ -25,6 +25,7 @@ exports = module.exports = { TASK_CLEAN_BACKUPS: 'cleanBackups', TASK_SYNC_EXTERNAL_LDAP: 'syncExternalLdap', TASK_CHANGE_MAIL_LOCATION: 'changeMailLocation', + TASK_SYNC_DNS_RECORDS: 'syncDnsRecords', // error codes ESTOPPED: 'stopped', diff --git a/src/taskworker.js b/src/taskworker.js index 5aeed0ec2..125599b59 100755 --- a/src/taskworker.js +++ b/src/taskworker.js @@ -7,6 +7,7 @@ var apptask = require('./apptask.js'), backups = require('./backups.js'), cloudron = require('./cloudron.js'), database = require('./database.js'), + domains = require('./domains.js'), externalLdap = require('./externalldap.js'), fs = require('fs'), mail = require('./mail.js'), @@ -25,6 +26,7 @@ const TASKS = { // indexed by task type cleanBackups: backups.cleanup, syncExternalLdap: externalLdap.sync, changeMailLocation: mail.changeLocation, + syncDnsRecords: domains.syncDnsRecords, _identity: (arg, progressCallback, callback) => callback(null, arg), _error: (arg, progressCallback, callback) => callback(new Error(`Failed for arg: ${arg}`)),