From 7ee740695f3e35328cbd624fba727029fe2b1603 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Wed, 29 Apr 2026 15:24:25 +0000 Subject: [PATCH] feat: add dcrouter external gateway sync --- test/external_gateway_test.ts | 213 +++++++++++ test/secret_settings_test.ts | 12 + ts/classes/external-gateway.ts | 352 ++++++++++++++++++ ts/classes/onebox.ts | 13 + ts/classes/services.ts | 34 +- ts/database/index.ts | 2 +- .../repositories/certificate.repository.ts | 2 +- ts/database/secret-settings.ts | 1 + ts/opsserver/handlers/settings.handler.ts | 6 + ts/types.ts | 7 +- ts_interfaces/data/domain.ts | 2 +- ts_interfaces/data/settings.ts | 5 + 12 files changed, 643 insertions(+), 6 deletions(-) create mode 100644 test/external_gateway_test.ts create mode 100644 ts/classes/external-gateway.ts diff --git a/test/external_gateway_test.ts b/test/external_gateway_test.ts new file mode 100644 index 0000000..a3afce8 --- /dev/null +++ b/test/external_gateway_test.ts @@ -0,0 +1,213 @@ +import { assert, assertEquals } from '@std/assert'; + +import { ExternalGatewayManager } from '../ts/classes/external-gateway.ts'; +import type { IDomain, IService, ISslCertificate } from '../ts/types.ts'; + +class FakeDatabase { + public settings = new Map(); + public secretSettings = new Map(); + public domains: IDomain[] = []; + public certificates = new Map(); + private nextDomainId = 1; + + getSetting(key: string): string | null { + return this.settings.get(key) ?? null; + } + + setSetting(key: string, value: string): void { + this.settings.set(key, value); + } + + async getSecretSetting(key: string): Promise { + return this.secretSettings.get(key) ?? null; + } + + getDomainByName(domain: string): IDomain | null { + return this.domains.find((entry) => entry.domain === domain) ?? null; + } + + createDomain(domain: Omit): IDomain { + const createdDomain = { ...domain, id: this.nextDomainId++ }; + this.domains.push(createdDomain); + return createdDomain; + } + + updateDomain(id: number, updates: Partial): void { + const index = this.domains.findIndex((entry) => entry.id === id); + if (index === -1) return; + this.domains[index] = { ...this.domains[index], ...updates }; + } + + getDomainsByProvider(provider: NonNullable): IDomain[] { + return this.domains.filter((entry) => entry.dnsProvider === provider); + } + + getSSLCertificate(domain: string): ISslCertificate | null { + return this.certificates.get(domain) ?? null; + } + + updateSSLCertificate(domain: string, updates: Partial): void { + const existing = this.certificates.get(domain); + if (!existing) return; + this.certificates.set(domain, { ...existing, ...updates }); + } + + async createSSLCertificate(cert: Omit): Promise { + const storedCert = { ...cert, id: this.certificates.size + 1 }; + this.certificates.set(cert.domain, storedCert); + return storedCert; + } +} + +const makeOneboxRef = () => { + const database = new FakeDatabase(); + database.settings.set('dcrouterGatewayUrl', 'https://edge.example.com'); + database.settings.set('dcrouterWorkHosterId', 'onebox-1'); + database.secretSettings.set('dcrouterGatewayApiToken', 'dcr-token'); + + let reloadCount = 0; + return { + database, + reverseProxy: { + reloadCertificates: async () => { + reloadCount++; + }, + get reloadCount() { + return reloadCount; + }, + }, + }; +}; + +Deno.test('ExternalGatewayManager syncs dcrouter domains into Onebox domains', async () => { + const oneboxRef = makeOneboxRef(); + oneboxRef.database.domains.push({ + id: 99, + domain: 'old.example.com', + dnsProvider: 'dcrouter', + isObsolete: false, + defaultWildcard: true, + createdAt: 1, + updatedAt: 1, + }); + + const manager = new ExternalGatewayManager(oneboxRef as any); + (manager as any).fireDcRouterRequest = async (method: string) => { + assertEquals(method, 'getWorkHosterDomains'); + return { + domains: [ + { + name: 'example.com', + capabilities: { + canCreateSubdomains: true, + canManageDnsRecords: true, + canIssueCertificates: true, + canHostEmail: true, + }, + }, + ], + }; + }; + + const domains = await manager.syncDomains(); + + assertEquals(domains.length, 2); + assertEquals(oneboxRef.database.getDomainByName('example.com')?.dnsProvider, 'dcrouter'); + assertEquals(oneboxRef.database.getDomainByName('example.com')?.defaultWildcard, true); + assertEquals(oneboxRef.database.getDomainByName('old.example.com')?.isObsolete, true); +}); + +Deno.test('ExternalGatewayManager syncs service routes to dcrouter WorkHoster API', async () => { + const oneboxRef = makeOneboxRef(); + oneboxRef.database.settings.set('serverIP', '203.0.113.10'); + oneboxRef.database.settings.set('httpPort', '8080'); + + const service: IService = { + id: 1, + name: 'hello', + image: 'nginx:latest', + envVars: {}, + port: 3000, + domain: 'hello.example.com', + status: 'running', + createdAt: 1, + updatedAt: 1, + }; + + const requests: Array<{ method: string; requestData: Record }> = []; + const manager = new ExternalGatewayManager(oneboxRef as any); + (manager as any).fireDcRouterRequest = async (method: string, requestData: Record) => { + requests.push({ method, requestData }); + if (method === 'exportCertificate') { + return { success: false }; + } + return { success: true, action: 'created', routeId: 'route-1' }; + }; + + await manager.syncServiceRoute(service); + + const syncRequest = requests.find((request) => request.method === 'syncWorkAppRoute')!; + const route = syncRequest.requestData.route as any; + const ownership = syncRequest.requestData.ownership as any; + + assertEquals(ownership, { + workHosterType: 'onebox', + workHosterId: 'onebox-1', + workAppId: 'hello', + hostname: 'hello.example.com', + }); + assertEquals(route.match, { ports: [443], domains: ['hello.example.com'] }); + assertEquals(route.action.targets, [{ host: '203.0.113.10', port: 8080 }]); + assertEquals(route.action.tls, { mode: 'terminate', certificate: 'auto' }); + assertEquals(syncRequest.requestData.enabled, true); +}); + +Deno.test('ExternalGatewayManager deletes service routes through dcrouter WorkHoster API', async () => { + const oneboxRef = makeOneboxRef(); + const manager = new ExternalGatewayManager(oneboxRef as any); + let deleteRequest: Record | null = null; + + (manager as any).fireDcRouterRequest = async (method: string, requestData: Record) => { + assertEquals(method, 'syncWorkAppRoute'); + deleteRequest = requestData; + return { success: true, action: 'deleted', routeId: 'route-1' }; + }; + + await manager.deleteServiceRoute({ + id: 1, + name: 'hello', + domain: 'hello.example.com', + }); + + assert(deleteRequest); + const capturedDeleteRequest = deleteRequest as Record; + assertEquals(capturedDeleteRequest.delete, true); + assertEquals((capturedDeleteRequest.ownership as any).hostname, 'hello.example.com'); +}); + +Deno.test('ExternalGatewayManager imports exported dcrouter certificates into Onebox', async () => { + const oneboxRef = makeOneboxRef(); + const manager = new ExternalGatewayManager(oneboxRef as any); + (manager as any).fireDcRouterRequest = async (method: string, requestData: Record) => { + assertEquals(method, 'exportCertificate'); + assertEquals(requestData.domain, 'hello.example.com'); + return { + success: true, + cert: { + id: 'cert-1', + domainName: 'hello.example.com', + created: 1, + validUntil: 2, + privateKey: '-----BEGIN PRIVATE KEY-----\nfake\n-----END PRIVATE KEY-----', + publicKey: '-----BEGIN CERTIFICATE-----\nfake\n-----END CERTIFICATE-----', + csr: '', + }, + }; + }; + + const imported = await manager.importCertificateForDomain('hello.example.com'); + + assert(imported); + assertEquals(oneboxRef.database.getSSLCertificate('hello.example.com')?.issuer, 'dcrouter'); + assertEquals(oneboxRef.reverseProxy.reloadCount, 1); +}); diff --git a/test/secret_settings_test.ts b/test/secret_settings_test.ts index 19cb906..3de337c 100644 --- a/test/secret_settings_test.ts +++ b/test/secret_settings_test.ts @@ -59,3 +59,15 @@ Deno.test('secret settings canonicalize aliases and clear old secret entries', a secretSettings.clear('backupPassword'); assertEquals(await secretSettings.get('backupPassword'), null); }); + +Deno.test('secret settings treat dcrouter gateway token as encrypted secret', async () => { + const authRepo = new FakeAuthRepository(); + authRepo.setSetting('externalGatewayApiToken', 'dcr-secret-token'); + + const secretSettings = new SecretSettingsManager(authRepo as any); + const token = await secretSettings.get('dcrouterGatewayApiToken'); + + assertEquals(token, 'dcr-secret-token'); + assertEquals(authRepo.getSetting('externalGatewayApiToken'), null); + assert(authRepo.getSecretSetting('dcrouterGatewayApiToken')?.startsWith('enc:v1:')); +}); diff --git a/ts/classes/external-gateway.ts b/ts/classes/external-gateway.ts new file mode 100644 index 0000000..b79b896 --- /dev/null +++ b/ts/classes/external-gateway.ts @@ -0,0 +1,352 @@ +import * as plugins from '../plugins.ts'; +import { logger } from '../logging.ts'; +import { getErrorMessage } from '../utils/error.ts'; +import { OneboxDatabase } from './database.ts'; +import type { IDomain, IService } from '../types.ts'; + +type TWorkHosterType = 'onebox'; + +interface IExternalGatewayConfig { + url: string; + apiToken: string; + workHosterId: string; + targetHost?: string; + targetPort?: number; +} + +interface IWorkHosterDomain { + name: string; + capabilities?: { + canCreateSubdomains: boolean; + canManageDnsRecords: boolean; + canIssueCertificates: boolean; + canHostEmail: boolean; + }; +} + +interface IWorkAppRouteOwnership { + workHosterType: TWorkHosterType; + workHosterId: string; + workAppId: string; + hostname: string; +} + +interface IWorkAppRouteSyncResult { + success: boolean; + action?: 'created' | 'updated' | 'deleted' | 'unchanged'; + routeId?: string; + message?: string; +} + +interface IDcRouterCertificateExport { + success: boolean; + cert?: { + id: string; + domainName: string; + created: number; + validUntil: number; + privateKey: string; + publicKey: string; + csr: string; + }; + message?: string; +} + +interface IDcRouterRouteConfig { + name: string; + match: { + ports: number[]; + domains: string[]; + }; + action: { + type: 'forward'; + targets: Array<{ host: string; port: number }>; + tls: { + mode: 'terminate'; + certificate: 'auto'; + }; + websocket: { + enabled: boolean; + }; + }; +} + +export class ExternalGatewayManager { + private database: OneboxDatabase; + + constructor(private oneboxRef: any) { + this.database = oneboxRef.database; + } + + public async init(): Promise { + if (!(await this.isConfigured())) { + logger.info('External dcrouter gateway not configured'); + return; + } + + await this.syncDomains(); + } + + public async isConfigured(): Promise { + const config = await this.getConfig({ requireTarget: false }); + return Boolean(config); + } + + public async syncDomains(): Promise { + const config = await this.requireConfig({ requireTarget: false }); + const response = await this.fireDcRouterRequest<{ domains: IWorkHosterDomain[] }>( + 'getWorkHosterDomains', + {}, + config, + ); + + const activeDomainNames = new Set(); + const now = Date.now(); + + for (const gatewayDomain of response.domains) { + const domainName = gatewayDomain.name.trim().toLowerCase(); + if (!domainName) continue; + + activeDomainNames.add(domainName); + const existingDomain = this.database.getDomainByName(domainName); + const defaultWildcard = gatewayDomain.capabilities?.canIssueCertificates !== false; + + if (existingDomain) { + this.database.updateDomain(existingDomain.id!, { + dnsProvider: 'dcrouter', + isObsolete: false, + defaultWildcard, + updatedAt: now, + }); + } else { + this.database.createDomain({ + domain: domainName, + dnsProvider: 'dcrouter', + isObsolete: false, + defaultWildcard, + createdAt: now, + updatedAt: now, + }); + } + } + + for (const domain of this.database.getDomainsByProvider('dcrouter')) { + if (!activeDomainNames.has(domain.domain)) { + this.database.updateDomain(domain.id!, { + isObsolete: true, + updatedAt: now, + }); + } + } + + logger.success(`Synced ${activeDomainNames.size} domain(s) from external dcrouter gateway`); + return this.database.getDomainsByProvider('dcrouter'); + } + + public async syncServiceRoute(service: IService): Promise { + if (!service.domain) return; + + const config = await this.getConfig({ requireTarget: true }); + if (!config) return; + + const result = await this.fireDcRouterRequest( + 'syncWorkAppRoute', + { + ownership: this.buildOwnership(service, service.domain, config), + route: this.buildRoute(service, config), + enabled: service.status === 'running', + }, + config, + ); + + if (!result.success) { + throw new Error(result.message || `dcrouter route sync failed for ${service.domain}`); + } + + logger.success(`External gateway route ${result.action || 'synced'} for ${service.domain}`); + await this.importCertificateForDomain(service.domain).catch((error) => { + logger.debug(`External gateway certificate import skipped for ${service.domain}: ${getErrorMessage(error)}`); + }); + } + + public async deleteServiceRoute(service: Pick): Promise { + if (!service.domain) return; + + const config = await this.getConfig({ requireTarget: false }); + if (!config) return; + + const result = await this.fireDcRouterRequest( + 'syncWorkAppRoute', + { + ownership: this.buildOwnership(service, service.domain, config), + delete: true, + }, + config, + ); + + if (!result.success) { + throw new Error(result.message || `dcrouter route delete failed for ${service.domain}`); + } + + logger.info(`External gateway route ${result.action || 'deleted'} for ${service.domain}`); + } + + public async importCertificateForDomain(domain: string): Promise { + const config = await this.getConfig({ requireTarget: false }); + if (!config) return false; + + const result = await this.fireDcRouterRequest( + 'exportCertificate', + { domain }, + config, + ); + + if (!result.success || !result.cert) { + return false; + } + + const now = Date.now(); + const existingCertificate = this.database.getSSLCertificate(domain); + if (existingCertificate) { + this.database.updateSSLCertificate(domain, { + certPem: result.cert.publicKey, + keyPem: result.cert.privateKey, + fullchainPem: result.cert.publicKey, + expiryDate: result.cert.validUntil, + updatedAt: now, + }); + } else { + await this.database.createSSLCertificate({ + domain, + certPem: result.cert.publicKey, + keyPem: result.cert.privateKey, + fullchainPem: result.cert.publicKey, + expiryDate: result.cert.validUntil, + issuer: 'dcrouter', + createdAt: now, + updatedAt: now, + }); + } + + await this.oneboxRef.reverseProxy.reloadCertificates(); + logger.success(`Imported external gateway certificate for ${domain}`); + return true; + } + + private async getConfig(options: { requireTarget?: boolean } = {}): Promise { + const url = this.normalizeUrl(this.database.getSetting('dcrouterGatewayUrl') || ''); + const apiToken = await this.database.getSecretSetting('dcrouterGatewayApiToken'); + if (!url || !apiToken) { + return null; + } + + const config: IExternalGatewayConfig = { + url, + apiToken, + workHosterId: this.ensureWorkHosterId(), + }; + + if (options.requireTarget !== false) { + config.targetHost = this.database.getSetting('dcrouterTargetHost') + || this.database.getSetting('serverIP') + || undefined; + const targetPort = this.parsePort( + this.database.getSetting('dcrouterTargetPort') + || this.database.getSetting('httpPort') + || '80', + ); + config.targetPort = targetPort; + + if (!config.targetHost) { + throw new Error('dcrouterTargetHost or serverIP must be configured for external gateway route sync'); + } + } + + return config; + } + + private async requireConfig(options: { requireTarget?: boolean } = {}): Promise { + const config = await this.getConfig(options); + if (!config) { + throw new Error('External dcrouter gateway is not configured'); + } + return config; + } + + private normalizeUrl(url: string): string { + const trimmedUrl = url.trim().replace(/\/+$/, ''); + if (!trimmedUrl) return ''; + if (/^https?:\/\//.test(trimmedUrl)) return trimmedUrl; + return `https://${trimmedUrl}`; + } + + private parsePort(portValue: string): number { + const port = Number(portValue); + if (!Number.isInteger(port) || port < 1 || port > 65535) { + throw new Error(`Invalid dcrouter target port: ${portValue}`); + } + return port; + } + + private ensureWorkHosterId(): string { + let workHosterId = this.database.getSetting('dcrouterWorkHosterId'); + if (!workHosterId) { + workHosterId = crypto.randomUUID(); + this.database.setSetting('dcrouterWorkHosterId', workHosterId); + } + return workHosterId; + } + + private buildOwnership( + service: Pick, + hostname: string, + config: IExternalGatewayConfig, + ): IWorkAppRouteOwnership { + return { + workHosterType: 'onebox', + workHosterId: config.workHosterId, + workAppId: service.name || `service-${service.id}`, + hostname, + }; + } + + private buildRoute(service: IService, config: IExternalGatewayConfig): IDcRouterRouteConfig { + return { + name: this.routeName(service.domain!), + match: { + ports: [443], + domains: [service.domain!], + }, + action: { + type: 'forward', + targets: [{ host: config.targetHost!, port: config.targetPort! }], + tls: { + mode: 'terminate', + certificate: 'auto', + }, + websocket: { + enabled: true, + }, + }, + }; + } + + private routeName(domain: string): string { + return `onebox-${domain.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-|-$/g, '')}`; + } + + private async fireDcRouterRequest( + method: string, + requestData: Record, + config: IExternalGatewayConfig, + ): Promise { + const typedRequest = new plugins.typedrequest.TypedRequest( + `${config.url}/typedrequest`, + method, + ); + return await typedRequest.fire({ + ...requestData, + apiToken: config.apiToken, + }) as TResponse; + } +} diff --git a/ts/classes/onebox.ts b/ts/classes/onebox.ts index 3454b4a..318e66e 100644 --- a/ts/classes/onebox.ts +++ b/ts/classes/onebox.ts @@ -24,6 +24,7 @@ import { AppStoreManager } from './appstore.ts'; import { ProxyLogReceiver } from './proxy-log-receiver.ts'; import { BackupManager } from './backup-manager.ts'; import { BackupScheduler } from './backup-scheduler.ts'; +import { ExternalGatewayManager } from './external-gateway.ts'; import { OpsServer } from '../opsserver/index.ts'; export class Onebox { @@ -44,6 +45,7 @@ export class Onebox { public proxyLogReceiver: ProxyLogReceiver; public backupManager: BackupManager; public backupScheduler: BackupScheduler; + public externalGateway: ExternalGatewayManager; public opsServer: OpsServer; private initialized = false; @@ -86,6 +88,9 @@ export class Onebox { // Initialize Backup scheduler this.backupScheduler = new BackupScheduler(this); + // Initialize optional dcrouter edge gateway integration + this.externalGateway = new ExternalGatewayManager(this); + // Initialize OpsServer (TypedRequest-based server) this.opsServer = new OpsServer(this); } @@ -160,6 +165,14 @@ export class Onebox { logger.warn('Cloudflare domain sync initialization failed - domain sync will be limited'); } + // Initialize external dcrouter gateway (non-critical) + try { + await this.externalGateway.init(); + } catch (error) { + logger.warn('External dcrouter gateway initialization failed - edge sync will be disabled'); + logger.warn(`Error: ${getErrorMessage(error)}`); + } + // Initialize Onebox Registry (non-critical) try { await this.registry.init(); diff --git a/ts/classes/services.ts b/ts/classes/services.ts index c185e48..622efbb 100644 --- a/ts/classes/services.ts +++ b/ts/classes/services.ts @@ -34,6 +34,24 @@ export class OneboxServicesManager { ); } + private async syncExternalGatewayRoute(service: IService): Promise { + if (!this.oneboxRef.externalGateway) return; + try { + await this.oneboxRef.externalGateway.syncServiceRoute(service); + } catch (error) { + logger.warn(`Failed to sync external gateway route for ${service.domain}: ${getErrorMessage(error)}`); + } + } + + private async deleteExternalGatewayRoute(service: Pick): Promise { + if (!this.oneboxRef.externalGateway) return; + try { + await this.oneboxRef.externalGateway.deleteServiceRoute(service); + } catch (error) { + logger.warn(`Failed to delete external gateway route for ${service.domain}: ${getErrorMessage(error)}`); + } + } + /** * Deploy a new service (full workflow) */ @@ -210,6 +228,8 @@ export class OneboxServicesManager { // Note: SSL certificates are now handled automatically by CertRequirementManager // which processes pending requirements created above. No direct obtainCertificate call needed. + + await this.syncExternalGatewayRoute(this.database.getServiceByName(options.name)!); } logger.success(`Service deployed successfully: ${options.name}`); @@ -252,6 +272,8 @@ export class OneboxServicesManager { } catch (routeError) { logger.warn(`Failed to add proxy route for ${service.domain}: ${getErrorMessage(routeError)}`); } + + await this.syncExternalGatewayRoute(this.database.getServiceByName(name)!); } logger.success(`Service started: ${name}`); @@ -291,7 +313,8 @@ export class OneboxServicesManager { // Remove reverse proxy route if service has a domain if (service.domain) { - this.oneboxRef.reverseProxy.removeRoute(service.domain); + await this.oneboxRef.reverseProxy.removeRoute(service.domain); + await this.deleteExternalGatewayRoute(service); } logger.success(`Service stopped: ${name}`); @@ -359,6 +382,8 @@ export class OneboxServicesManager { logger.warn(`Failed to remove reverse proxy route: ${getErrorMessage(error)}`); } + await this.deleteExternalGatewayRoute(service); + // Note: We don't remove DNS records or SSL certs automatically // as they might be used by other services or need manual cleanup } @@ -617,10 +642,12 @@ export class OneboxServicesManager { // Remove old route if it existed if (oldDomain) { try { - this.oneboxRef.reverseProxy.removeRoute(oldDomain); + await this.oneboxRef.reverseProxy.removeRoute(oldDomain); } catch (error) { logger.warn(`Failed to remove old reverse proxy route: ${getErrorMessage(error)}`); } + + await this.deleteExternalGatewayRoute({ ...service, domain: oldDomain }); } // Add new route if domain specified @@ -650,6 +677,9 @@ export class OneboxServicesManager { } const refreshedService = this.database.getServiceByName(name)!; + if (refreshedService.domain && refreshedService.status === 'running') { + await this.syncExternalGatewayRoute(refreshedService); + } await this.broadcastServiceUpdate(name, 'updated'); return refreshedService; } catch (error) { diff --git a/ts/database/index.ts b/ts/database/index.ts index bbb8e66..d239a25 100644 --- a/ts/database/index.ts +++ b/ts/database/index.ts @@ -454,7 +454,7 @@ export class OneboxDatabase { return this.certificateRepo.getAllDomains(); } - getDomainsByProvider(provider: 'cloudflare' | 'manual'): IDomain[] { + getDomainsByProvider(provider: NonNullable): IDomain[] { return this.certificateRepo.getDomainsByProvider(provider); } diff --git a/ts/database/repositories/certificate.repository.ts b/ts/database/repositories/certificate.repository.ts index b1892ea..fdb5e3f 100644 --- a/ts/database/repositories/certificate.repository.ts +++ b/ts/database/repositories/certificate.repository.ts @@ -43,7 +43,7 @@ export class CertificateRepository extends BaseRepository { return rows.map((row) => this.rowToDomain(row)); } - getDomainsByProvider(provider: 'cloudflare' | 'manual'): IDomain[] { + getDomainsByProvider(provider: NonNullable): IDomain[] { const rows = this.query('SELECT * FROM domains WHERE dns_provider = ? ORDER BY domain ASC', [provider]); return rows.map((row) => this.rowToDomain(row)); } diff --git a/ts/database/secret-settings.ts b/ts/database/secret-settings.ts index be027a9..c57a515 100644 --- a/ts/database/secret-settings.ts +++ b/ts/database/secret-settings.ts @@ -6,6 +6,7 @@ const encryptedSecretPrefix = 'enc:v1:'; const secretSettingAliases = { backupPassword: ['backup_encryption_password'], cloudflareToken: ['cloudflareAPIKey'], + dcrouterGatewayApiToken: ['externalGatewayApiToken'], } as const; type TCanonicalSecretSettingKey = keyof typeof secretSettingAliases; diff --git a/ts/opsserver/handlers/settings.handler.ts b/ts/opsserver/handlers/settings.handler.ts index 689e9a0..9662df5 100644 --- a/ts/opsserver/handlers/settings.handler.ts +++ b/ts/opsserver/handlers/settings.handler.ts @@ -14,11 +14,17 @@ export class SettingsHandler { private async getSettingsObject(): Promise { const db = this.opsServerRef.oneboxRef.database; const cloudflareToken = await db.getSecretSetting('cloudflareToken'); + const dcrouterGatewayApiToken = await db.getSecretSetting('dcrouterGatewayApiToken'); const settingsMap = db.getAllSettings(); return { cloudflareToken: cloudflareToken || '', cloudflareZoneId: settingsMap['cloudflareZoneId'] || '', + dcrouterGatewayUrl: settingsMap['dcrouterGatewayUrl'] || '', + dcrouterGatewayApiToken: dcrouterGatewayApiToken || '', + dcrouterWorkHosterId: settingsMap['dcrouterWorkHosterId'] || '', + dcrouterTargetHost: settingsMap['dcrouterTargetHost'] || '', + dcrouterTargetPort: parseInt(settingsMap['dcrouterTargetPort'] || '0', 10), autoRenewCerts: settingsMap['autoRenewCerts'] === 'true', renewalThreshold: parseInt(settingsMap['renewalThreshold'] || '30', 10), acmeEmail: settingsMap['acmeEmail'] || '', diff --git a/ts/types.ts b/ts/types.ts index 3611b5b..ad8748b 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -148,7 +148,7 @@ export interface INginxConfig { export interface IDomain { id?: number; domain: string; - dnsProvider: 'cloudflare' | 'manual' | null; + dnsProvider: 'cloudflare' | 'manual' | 'dcrouter' | null; cloudflareZoneId?: string; isObsolete: boolean; defaultWildcard: boolean; @@ -259,6 +259,11 @@ export interface IAppSettings { serverIP?: string; cloudflareToken?: string; cloudflareZoneId?: string; + dcrouterGatewayUrl?: string; + dcrouterGatewayApiToken?: string; + dcrouterWorkHosterId?: string; + dcrouterTargetHost?: string; + dcrouterTargetPort?: number; acmeEmail?: string; dataDir?: string; httpPort?: number; diff --git a/ts_interfaces/data/domain.ts b/ts_interfaces/data/domain.ts index cfca695..3aa2448 100644 --- a/ts_interfaces/data/domain.ts +++ b/ts_interfaces/data/domain.ts @@ -5,7 +5,7 @@ export interface IDomain { id?: number; domain: string; - dnsProvider: 'cloudflare' | 'manual' | null; + dnsProvider: 'cloudflare' | 'manual' | 'dcrouter' | null; cloudflareZoneId?: string; isObsolete: boolean; defaultWildcard: boolean; diff --git a/ts_interfaces/data/settings.ts b/ts_interfaces/data/settings.ts index 19eb142..9f437d9 100644 --- a/ts_interfaces/data/settings.ts +++ b/ts_interfaces/data/settings.ts @@ -5,6 +5,11 @@ export interface ISettings { cloudflareToken: string; cloudflareZoneId: string; + dcrouterGatewayUrl: string; + dcrouterGatewayApiToken: string; + dcrouterWorkHosterId: string; + dcrouterTargetHost: string; + dcrouterTargetPort: number; autoRenewCerts: boolean; renewalThreshold: number; acmeEmail: string;