import apps from './apps.js'; import appstore from './appstore.js'; import assert from 'node:assert'; import BoxError from './boxerror.js'; import branding from './branding.js'; import constants from './constants.js'; import debugModule from 'debug'; import dns from './dns.js'; import externalLdap from './externalldap.js'; import eventlog from './eventlog.js'; import Location from './location.js'; import mailServer from './mailserver.js'; import platform from './platform.js'; import reverseProxy from './reverseproxy.js'; import safe from 'safetydance'; import settings from './settings.js'; import system from './system.js'; import tasks from './tasks.js'; import userDirectory from './user-directory.js'; const debug = debugModule('box:dashboard'); async function getLocation() { const domain = await settings.get(settings.DASHBOARD_DOMAIN_KEY); const subdomain = await settings.get(settings.DASHBOARD_SUBDOMAIN_KEY); return new Location(subdomain, domain, Location.TYPE_DASHBOARD); } async function setLocation(subdomain, domain) { assert.strictEqual(typeof subdomain, 'string'); assert.strictEqual(typeof domain, 'string'); await settings.set(settings.DASHBOARD_SUBDOMAIN_KEY, subdomain); await settings.set(settings.DASHBOARD_DOMAIN_KEY, domain); debug(`setLocation: ${domain || ''}`); } async function clearLocation() { await setLocation('', ''); } async function getConfig() { const ubuntuVersion = await system.getUbuntuVersion(); const kernelVersion = await system.getKernelVersion(); const profileConfig = await userDirectory.getProfileConfig(); const externalLdapConfig = await externalLdap.getConfig(); const { fqdn:adminFqdn, domain:adminDomain } = await getLocation(); // be picky about what we send out here since this is sent for 'normal' users as well return { apiServerOrigin: await appstore.getApiServerOrigin(), webServerOrigin: await appstore.getWebServerOrigin(), consoleServerOrigin: await appstore.getConsoleServerOrigin(), adminDomain, adminFqdn, mailFqdn: (await mailServer.getLocation()).fqdn, version: constants.VERSION, ubuntuVersion, kernelVersion, isDemo: constants.DEMO, cloudronName: await branding.getCloudronName(), footer: await branding.renderFooter(), features: appstore.getFeatures(), profileLocked: profileConfig.lockUserProfiles, mandatory2FA: profileConfig.mandatory2FA, external2FA: externalLdap.supports2FA(externalLdapConfig), ldapGroupsSynced: externalLdapConfig.syncGroups }; } async function prepareLocation(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 location = { subdomain, domain }; const fqdn = dns.fqdn(subdomain, domain); progressCallback({ percent: 20, message: `Updating DNS of ${fqdn}` }); await dns.registerLocations([location], { overwriteDns: true }, progressCallback); progressCallback({ percent: 40, message: `Waiting for DNS of ${fqdn}` }); await dns.waitForLocations([location], progressCallback); progressCallback({ percent: 60, message: `Getting certificate of ${fqdn}` }); await reverseProxy.ensureCertificate(location, {}, auditSource); } async function startPrepareLocation(domain, auditSource) { assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof auditSource, 'object'); debug(`prepareLocation: ${domain}`); if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); const fqdn = dns.fqdn(constants.DASHBOARD_SUBDOMAIN, domain); for (const app of await apps.list()) { const appName = `${app.label || app.fqdn} (${app.id})`; if (app.fqdn === fqdn) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with primary location of app '${appName}'`); if (app.secondaryDomains.some(sd => sd.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with secondary location of app '${appName}'`); if (app.redirectDomains.some(rd => rd.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with redirect location of app '${appName}'`); if (app.aliasDomains.some(ad => ad.fqdn === fqdn)) throw new BoxError(BoxError.BAD_STATE, `Dashboard location conflicts with alias location of app '${appName}'`); } const taskId = await tasks.add(tasks.TASK_PREPARE_DASHBOARD_LOCATION, [ constants.DASHBOARD_SUBDOMAIN, domain, auditSource ]); safe(tasks.startTask(taskId, {}), { debug }); // background return taskId; } async function setupLocation(subdomain, domain, auditSource) { assert.strictEqual(typeof subdomain, 'string'); assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof auditSource, 'object'); debug(`setupLocation: ${domain}`); if (constants.DEMO) throw new BoxError(BoxError.BAD_STATE, 'Not allowed in demo mode'); await setLocation(subdomain, domain); await platform.onDashboardLocationSet(subdomain, domain); } async function changeLocation(subdomain, domain, auditSource) { assert.strictEqual(typeof subdomain, 'string'); assert.strictEqual(typeof domain, 'string'); assert.strictEqual(typeof auditSource, 'object'); const oldLocation = await getLocation(); await setupLocation(subdomain, domain, auditSource); debug(`setupLocation: notifying appstore and platform of domain change to ${domain}`); await eventlog.add(eventlog.ACTION_DASHBOARD_DOMAIN_UPDATE, auditSource, { subdomain, domain }); await safe(appstore.updateCloudron({ domain }), { debug }); await platform.onDashboardLocationChanged(auditSource); await safe(reverseProxy.removeDashboardConfig(oldLocation.subdomain, oldLocation.domain), { debug }); } const _setLocation = setLocation; export default { getLocation, clearLocation, startPrepareLocation, // starts the task, prepareLocation, // the task to setup dns and cert, setupLocation, // initial setup from setup/restore, changeLocation, // only on dashboard change (post setup/restore), getConfig, _setLocation, };