diff --git a/dashboard/src/js/client.js b/dashboard/src/js/client.js index e0fab8fcd..11fae1d49 100644 --- a/dashboard/src/js/client.js +++ b/dashboard/src/js/client.js @@ -2790,7 +2790,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout domain: domain }; - post('/api/v1/cloudron/prepare_dashboard_domain', data, null, function (error, data, status) { + post('/api/v1/dashboard/prepare_domain', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 202) return callback(new ClientError(status, data)); @@ -2803,7 +2803,7 @@ angular.module('Application').service('Client', ['$http', '$interval', '$timeout domain: domain }; - post('/api/v1/cloudron/set_dashboard_domain', data, null, function (error, data, status) { + post('/api/v1/dashboard/switch_domain', data, null, function (error, data, status) { if (error) return callback(error); if (status !== 204) return callback(new ClientError(status, data)); diff --git a/src/cloudron.js b/src/cloudron.js index 763b10da6..65e00d59b 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -3,12 +3,6 @@ exports = module.exports = { getStatus, - setupDnsAndCert, - - prepareDashboardDomain, - setDashboardDomain, - updateDashboardDomain, - getTimeZone, setTimeZone, @@ -16,24 +10,12 @@ exports = module.exports = { setLanguage, }; -const apps = require('./apps.js'), - appstore = require('./appstore.js'), - assert = require('assert'), +const assert = require('assert'), BoxError = require('./boxerror.js'), constants = require('./constants.js'), cron = require('./cron.js'), - debug = require('debug')('box:cloudron'), - dashboard = require('./dashboard.js'), - dns = require('./dns.js'), - eventlog = require('./eventlog.js'), moment = require('moment-timezone'), - network = require('./network.js'), - oidc = require('./oidc.js'), - reverseProxy = require('./reverseproxy.js'), - safe = require('safetydance'), - services = require('./services.js'), settings = require('./settings.js'), - tasks = require('./tasks.js'), translation = require('./translation.js'); async function getStatus() { @@ -42,86 +24,6 @@ async function getStatus() { }; } -async function prepareDashboardDomain(domain, auditSource) { - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof auditSource, 'object'); - - debug(`prepareDashboardDomain: ${domain}`); - - if (constants.DEMO) throw new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode'); - - const fqdn = dns.fqdn(constants.DASHBOARD_SUBDOMAIN, domain); - - const result = await apps.list(); - if (result.some(app => app.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, 'Dashboard location conflicts with an existing app'); - - const taskId = await tasks.add(tasks.TASK_SETUP_DNS_AND_CERT, [ constants.DASHBOARD_SUBDOMAIN, domain, auditSource ]); - - tasks.startTask(taskId, {}); - - return taskId; -} - -// call this only pre activation since it won't start mail server -async function setDashboardDomain(domain, auditSource) { - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof auditSource, 'object'); - - debug(`setDashboardDomain: ${domain}`); - - await reverseProxy.writeDashboardConfig(domain); - const fqdn = dns.fqdn(constants.DASHBOARD_SUBDOMAIN, domain); - - await dashboard.setLocation(domain, fqdn); - - await safe(appstore.updateCloudron({ domain }), { debug }); - - await eventlog.add(eventlog.ACTION_DASHBOARD_DOMAIN_UPDATE, auditSource, { domain, fqdn }); -} - -// call this only post activation because it will restart mail server -async function updateDashboardDomain(domain, auditSource) { - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof auditSource, 'object'); - - debug(`updateDashboardDomain: ${domain}`); - - if (constants.DEMO) throw new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode'); - - await setDashboardDomain(domain, auditSource); - - // mark apps using oidc addon to be reconfigured - const [, installedApps] = await safe(apps.list()); - await safe(apps.configureInstalledApps(installedApps.filter((a) => !!a.manifest.addons.oidc), auditSource)); - - await safe(services.rebuildService('turn', auditSource), { debug }); // to update the realm variable - - await oidc.stop(); - await oidc.start(); -} - -async function setupDnsAndCert(subdomain, domain, auditSource, progressCallback) { - assert.strictEqual(typeof subdomain, 'string'); - assert.strictEqual(typeof domain, 'string'); - assert.strictEqual(typeof auditSource, 'object'); - assert.strictEqual(typeof progressCallback, 'function'); - - const dashboardFqdn = dns.fqdn(subdomain, domain); - - const ipv4 = await network.getIPv4(); - const ipv6 = await network.getIPv6(); - - progressCallback({ percent: 20, message: `Updating DNS of ${dashboardFqdn}` }); - await dns.upsertDnsRecords(subdomain, domain, 'A', [ ipv4 ]); - if (ipv6) await dns.upsertDnsRecords(subdomain, domain, 'AAAA', [ ipv6 ]); - progressCallback({ percent: 40, message: `Waiting for DNS of ${dashboardFqdn}` }); - await dns.waitForDnsRecord(subdomain, domain, 'A', ipv4, { interval: 30000, times: 50000 }); - if (ipv6) await dns.waitForDnsRecord(subdomain, domain, 'AAAA', ipv6, { interval: 30000, times: 50000 }); - progressCallback({ percent: 60, message: `Getting certificate of ${dashboardFqdn}` }); - const location = { subdomain, domain, fqdn: dashboardFqdn, type: apps.LOCATION_TYPE_DASHBOARD, certificate: null }; - await reverseProxy.ensureCertificate(location, {}, auditSource); -} - async function getTimeZone() { const tz = await settings.get(settings.TIME_ZONE_KEY); return tz || 'UTC'; diff --git a/src/dashboard.js b/src/dashboard.js index 52032ef3e..4b5541448 100644 --- a/src/dashboard.js +++ b/src/dashboard.js @@ -4,16 +4,33 @@ exports = module.exports = { getLocation, setLocation, + setupDnsAndCert, + + prepareDomain, + setDashboardDomain, + switchDomain, + getConfig, }; -const appstore = require('./appstore.js'), +const apps = require('./apps.js'), + appstore = require('./appstore.js'), assert = require('assert'), + BoxError = require('./boxerror.js'), branding = require('./branding.js'), constants = require('./constants.js'), + debug = require('debug')('box:dashboard'), + eventlog = require('./eventlog.js'), + dns = require('./dns.js'), mailServer = require('./mailserver.js'), + network = require('./network.js'), + oidc = require('./oidc.js'), + reverseProxy = require('./reverseproxy.js'), + safe = require('safetydance'), + services = require('./services.js'), settings = require('./settings.js'), system = require('./system.js'), + tasks = require('./tasks.js'), users = require('./users.js'); async function setLocation(domain, fqdn) { @@ -55,3 +72,83 @@ async function getConfig() { mandatory2FA: profileConfig.mandatory2FA, }; } + +async function prepareDomain(domain, auditSource) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof auditSource, 'object'); + + debug(`prepareDomain: ${domain}`); + + if (constants.DEMO) throw new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode'); + + const fqdn = dns.fqdn(constants.DASHBOARD_SUBDOMAIN, domain); + + const result = await apps.list(); + if (result.some(app => app.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, 'Dashboard location conflicts with an existing app'); + + const taskId = await tasks.add(tasks.TASK_SETUP_DNS_AND_CERT, [ constants.DASHBOARD_SUBDOMAIN, domain, auditSource ]); + + tasks.startTask(taskId, {}); + + return taskId; +} + +// call this only pre activation since it won't start mail server +async function setDashboardDomain(domain, auditSource) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof auditSource, 'object'); + + debug(`setDashboardDomain: ${domain}`); + + await reverseProxy.writeDashboardConfig(domain); + const fqdn = dns.fqdn(constants.DASHBOARD_SUBDOMAIN, domain); + + await setLocation(domain, fqdn); + + await safe(appstore.updateCloudron({ domain }), { debug }); + + await eventlog.add(eventlog.ACTION_DASHBOARD_DOMAIN_UPDATE, auditSource, { domain, fqdn }); +} + +// call this only post activation because it will restart mail server +async function switchDomain(domain, auditSource) { + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof auditSource, 'object'); + + debug(`switchDomain: ${domain}`); + + if (constants.DEMO) throw new BoxError(BoxError.CONFLICT, 'Not allowed in demo mode'); + + await setDashboardDomain(domain, auditSource); + + // mark apps using oidc addon to be reconfigured + const [, installedApps] = await safe(apps.list()); + await safe(apps.configureInstalledApps(installedApps.filter((a) => !!a.manifest.addons.oidc), auditSource)); + + await safe(services.rebuildService('turn', auditSource), { debug }); // to update the realm variable + + await oidc.stop(); + await oidc.start(); +} + +async function setupDnsAndCert(subdomain, domain, auditSource, progressCallback) { + assert.strictEqual(typeof subdomain, 'string'); + assert.strictEqual(typeof domain, 'string'); + assert.strictEqual(typeof auditSource, 'object'); + assert.strictEqual(typeof progressCallback, 'function'); + + const dashboardFqdn = dns.fqdn(subdomain, domain); + + const ipv4 = await network.getIPv4(); + const ipv6 = await network.getIPv6(); + + progressCallback({ percent: 20, message: `Updating DNS of ${dashboardFqdn}` }); + await dns.upsertDnsRecords(subdomain, domain, 'A', [ ipv4 ]); + if (ipv6) await dns.upsertDnsRecords(subdomain, domain, 'AAAA', [ ipv6 ]); + progressCallback({ percent: 40, message: `Waiting for DNS of ${dashboardFqdn}` }); + await dns.waitForDnsRecord(subdomain, domain, 'A', ipv4, { interval: 30000, times: 50000 }); + if (ipv6) await dns.waitForDnsRecord(subdomain, domain, 'AAAA', ipv6, { interval: 30000, times: 50000 }); + progressCallback({ percent: 60, message: `Getting certificate of ${dashboardFqdn}` }); + const location = { subdomain, domain, fqdn: dashboardFqdn, type: apps.LOCATION_TYPE_DASHBOARD, certificate: null }; + await reverseProxy.ensureCertificate(location, {}, auditSource); +} diff --git a/src/mailserver.js b/src/mailserver.js index 863f552e0..b8541dbad 100644 --- a/src/mailserver.js +++ b/src/mailserver.js @@ -24,9 +24,9 @@ exports = module.exports = { const assert = require('assert'), BoxError = require('./boxerror.js'), - cloudron = require('./cloudron.js'), constants = require('./constants.js'), crypto = require('crypto'), + dashboard = require('./dashboard.js'), debug = require('debug')('box:mailserver'), dns = require('./dns.js'), docker = require('./docker.js'), @@ -270,7 +270,7 @@ async function changeLocation(auditSource, progressCallback) { let progress = 20; progressCallback({ percent: progress, message: `Setting up DNS of certs of mail server ${fqdn}` }); - await cloudron.setupDnsAndCert(subdomain, domain, auditSource, (progress) => progressCallback({ message: progress.message })); // remove the percent + await dashboard.setupDnsAndCert(subdomain, domain, auditSource, (progress) => progressCallback({ message: progress.message })); // remove the percent const allDomains = await domains.list(); for (let idx = 0; idx < allDomains.length; idx++) { diff --git a/src/provision.js b/src/provision.js index 6d3c64458..e69f908e1 100644 --- a/src/provision.js +++ b/src/provision.js @@ -75,7 +75,7 @@ async function setupTask(domain, auditSource) { assert.strictEqual(typeof auditSource, 'object'); try { - await cloudron.setupDnsAndCert(constants.DASHBOARD_SUBDOMAIN, domain, auditSource, (progress) => setProgress('setup', progress.message)); + await dashboard.setupDnsAndCert(constants.DASHBOARD_SUBDOMAIN, domain, auditSource, (progress) => setProgress('setup', progress.message)); await ensureDhparams(); await cloudron.setDashboardDomain(domain, auditSource); setProgress('setup', 'Done'), @@ -179,7 +179,7 @@ async function restoreTask(backupConfig, remotePath, ipv4Config, options, auditS await reverseProxy.restoreFallbackCertificates(); const { subdomain:dashboardSubdomain, domain:dashboardDomain } = await dashboard.getLocation(); // load this fresh from after the backup.restore - if (!options.skipDnsSetup) await cloudron.setupDnsAndCert(dashboardSubdomain, dashboardDomain, auditSource, (progress) => setProgress('restore', progress.message)); + if (!options.skipDnsSetup) await dashboard.setupDnsAndCert(dashboardSubdomain, dashboardDomain, auditSource, (progress) => setProgress('restore', progress.message)); await cloudron.setDashboardDomain(dashboardDomain, auditSource); await backups.setConfig(backupConfig); diff --git a/src/routes/cloudron.js b/src/routes/cloudron.js index 0944cbf47..e112199a2 100644 --- a/src/routes/cloudron.js +++ b/src/routes/cloudron.js @@ -3,9 +3,6 @@ exports = module.exports = { getStatus, - updateDashboardDomain, - prepareDashboardDomain, - listLanguages, getLanguage, setLanguage, @@ -15,7 +12,6 @@ exports = module.exports = { }; const assert = require('assert'), - AuditSource = require('../auditsource.js'), BoxError = require('../boxerror.js'), cloudron = require('../cloudron.js'), HttpError = require('connect-lastmile').HttpError, @@ -30,24 +26,6 @@ async function getStatus(req, res, next) { next(new HttpSuccess(200, status)); } -async function updateDashboardDomain(req, res, next) { - if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string')); - - const [error] = await safe(cloudron.updateDashboardDomain(req.body.domain, AuditSource.fromRequest(req))); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(204, {})); -} - -async function prepareDashboardDomain(req, res, next) { - if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string')); - - const [error, taskId] = await safe(cloudron.prepareDashboardDomain(req.body.domain, AuditSource.fromRequest(req))); - if (error) return next(BoxError.toHttpError(error)); - - next(new HttpSuccess(202, { taskId })); -} - async function listLanguages(req, res, next) { const [error, languages] = await safe(translation.listLanguages()); if (error) return next(new BoxError.toHttpError(error)); diff --git a/src/routes/dashboard.js b/src/routes/dashboard.js index e509e89c7..409d5ef01 100644 --- a/src/routes/dashboard.js +++ b/src/routes/dashboard.js @@ -2,10 +2,15 @@ exports = module.exports = { getConfig, + + prepareDomain, + switchDomain }; -const BoxError = require('../boxerror.js'), +const AuditSource = require('../auditsource.js'), + BoxError = require('../boxerror.js'), dashboard = require('../dashboard.js'), + HttpError = require('connect-lastmile').HttpError, HttpSuccess = require('connect-lastmile').HttpSuccess, safe = require('safetydance'); @@ -15,3 +20,21 @@ async function getConfig(req, res, next) { next(new HttpSuccess(200, cloudronConfig)); } + +async function prepareDomain(req, res, next) { + if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string')); + + const [error, taskId] = await safe(dashboard.prepareDomain(req.body.domain, AuditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(202, { taskId })); +} + +async function switchDomain(req, res, next) { + if (!req.body.domain || typeof req.body.domain !== 'string') return next(new HttpError(400, 'domain must be a string')); + + const [error] = await safe(dashboard.switchDomain(req.body.domain, AuditSource.fromRequest(req))); + if (error) return next(BoxError.toHttpError(error)); + + next(new HttpSuccess(204, {})); +} diff --git a/src/server.js b/src/server.js index 4b7f54459..474a72b3e 100644 --- a/src/server.js +++ b/src/server.js @@ -115,9 +115,9 @@ async function initializeExpressSync() { router.post('/api/v1/cloudron/time_zone', json, token, authorizeAdmin, routes.cloudron.setTimeZone); // config route for dashboard that any auth user (not just admin) can access - router.get ('/api/v1/dashboard/config', token, authorizeUser, routes.dashboard.getConfig); - 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.get ('/api/v1/dashboard/config', token, authorizeUser, routes.dashboard.getConfig); + router.post('/api/v1/dashboard/prepare_domain', json, token, authorizeAdmin, routes.dashboard.prepareDomain); + router.post('/api/v1/dashboard/switch_domain', json, token, authorizeAdmin, routes.dashboard.switchDomain); // system (vm/server) router.get ('/api/v1/system/reboot', token, authorizeAdmin, routes.system.isRebootRequired); diff --git a/src/taskworker.js b/src/taskworker.js index 27e1cc720..c84844bea 100755 --- a/src/taskworker.js +++ b/src/taskworker.js @@ -6,7 +6,7 @@ const apptask = require('./apptask.js'), async = require('async'), backupCleaner = require('./backupcleaner.js'), backuptask = require('./backuptask.js'), - cloudron = require('./cloudron.js'), + dashboard = require('./dashboard.js'), database = require('./database.js'), dns = require('./dns.js'), dyndns = require('./dyndns.js'), @@ -15,7 +15,6 @@ const apptask = require('./apptask.js'), mailServer = require('./mailserver.js'), reverseProxy = require('./reverseproxy.js'), safe = require('safetydance'), - settings = require('./settings.js'), system = require('./system.js'), tasks = require('./tasks.js'), updater = require('./updater.js'); @@ -25,7 +24,7 @@ const TASKS = { // indexed by task type backup: backuptask.fullBackup, update: updater.update, checkCerts: reverseProxy.checkCerts, - setupDnsAndCert: cloudron.setupDnsAndCert, + setupDnsAndCert: dashboard.setupDnsAndCert, cleanBackups: backupCleaner.run, syncExternalLdap: externalLdap.sync, changeMailLocation: mailServer.changeLocation,