From 4eba247472d7f7c94e74f1444cfcb004b410600c Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Wed, 29 Apr 2026 15:34:48 +0000 Subject: [PATCH] feat: discover external gateway domains --- ts/classes.cloudly.ts | 1 + ts/manager.domain/classes.domainmanager.ts | 72 +++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/ts/classes.cloudly.ts b/ts/classes.cloudly.ts index 2a1ec66..af7ca27 100644 --- a/ts/classes.cloudly.ts +++ b/ts/classes.cloudly.ts @@ -137,6 +137,7 @@ export class Cloudly { await this.deploymentManager.start(); await this.taskManager.init(); await this.registryManager.start(); + await this.domainManager.init(); await this.cloudflareConnector.init(); await this.letsencryptConnector.init(); diff --git a/ts/manager.domain/classes.domainmanager.ts b/ts/manager.domain/classes.domainmanager.ts index 9c93e8a..4e7f4c9 100644 --- a/ts/manager.domain/classes.domainmanager.ts +++ b/ts/manager.domain/classes.domainmanager.ts @@ -2,6 +2,17 @@ import type { Cloudly } from '../classes.cloudly.js'; import * as plugins from '../plugins.js'; import { Domain } from './classes.domain.js'; +interface IWorkHosterDomain { + name: string; + nameservers?: string[]; + capabilities?: { + canCreateSubdomains: boolean; + canManageDnsRecords: boolean; + canIssueCertificates: boolean; + canHostEmail: boolean; + }; +} + export class DomainManager { public typedrouter = new plugins.typedrequest.TypedRouter(); public cloudlyRef: Cloudly; @@ -150,9 +161,68 @@ export class DomainManager { * Initialize the domain manager */ public async init() { + await this.syncExternalGatewayDomains().catch((error) => { + console.log(`External gateway domain sync failed: ${(error as Error).message}`); + }); console.log('Domain Manager initialized'); } + public async syncExternalGatewayDomains(): Promise { + const settings = await this.cloudlyRef.settingsManager.getSettings(); + if (!settings.dcrouterGatewayUrl || !settings.dcrouterGatewayApiToken) { + return 0; + } + + const typedRequest = new plugins.typedrequest.TypedRequest( + `${settings.dcrouterGatewayUrl.replace(/\/+$/, '')}/typedrequest`, + 'getWorkHosterDomains', + ); + const response = await typedRequest.fire({ + apiToken: settings.dcrouterGatewayApiToken, + }) as { domains: IWorkHosterDomain[] }; + + const activeDomainNames = new Set(); + for (const gatewayDomain of response.domains) { + const domainName = gatewayDomain.name.trim().toLowerCase(); + if (!domainName) continue; + + activeDomainNames.add(domainName); + const existingDomain = await this.CDomain.getDomainByName(domainName); + const tags = Array.from(new Set([...(existingDomain?.data.tags || []), 'dcrouter'])); + const domainData: Partial = { + name: domainName, + status: 'active', + verificationStatus: 'not_required', + nameservers: gatewayDomain.nameservers || existingDomain?.data.nameservers || [], + autoRenew: gatewayDomain.capabilities?.canIssueCertificates !== false, + activationState: 'available', + syncSource: 'manual', + lastSyncAt: Date.now(), + isExternal: true, + tags, + }; + + if (existingDomain) { + await this.CDomain.updateDomain(existingDomain.id, domainData); + } else { + await this.CDomain.createDomain(domainData as plugins.servezoneInterfaces.data.IDomain['data']); + } + } + + const knownDomains = await this.CDomain.getDomains(); + for (const domain of knownDomains) { + if (domain.data.tags?.includes('dcrouter') && !activeDomainNames.has(domain.data.name)) { + await this.CDomain.updateDomain(domain.id, { + activationState: 'ignored', + lastSyncAt: Date.now(), + }); + } + } + + console.log(`Synced ${activeDomainNames.size} domain(s) from external dcrouter gateway`); + return activeDomainNames.size; + } + /** * Stop the domain manager */ @@ -185,4 +255,4 @@ export class DomainManager { const existingDomain = await this.CDomain.getDomainByName(domainName); return !existingDomain; } -} \ No newline at end of file +}