import * as plugins from '../../plugins.js'; import * as paths from '../../paths.js'; import type { OpsServer } from '../classes.opsserver.js'; import * as interfaces from '../../../ts_interfaces/index.js'; export class ConfigHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(private opsServerRef: OpsServer) { // Add this handler's router to the parent this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { // Get Configuration Handler (read-only) this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getConfiguration', async (dataArg, toolsArg) => { const config = await this.getConfiguration(); return { config, section: dataArg.section, }; } ) ); } private async getConfiguration(): Promise { const dcRouter = this.opsServerRef.dcRouterRef; const opts = dcRouter.options; const resolvedPaths = dcRouter.resolvedPaths; // --- System --- const storageBackend: 'filesystem' | 'custom' | 'memory' = opts.storage?.readFunction ? 'custom' : opts.storage?.fsPath ? 'filesystem' : 'memory'; // Resolve proxy IPs: fall back to SmartProxy's runtime proxyIPs if not in opts let proxyIps = opts.proxyIps || []; if (proxyIps.length === 0 && dcRouter.smartProxy) { const spSettings = (dcRouter.smartProxy as any).settings; if (spSettings?.proxyIPs?.length > 0) { proxyIps = spSettings.proxyIPs; } } const system: interfaces.requests.IConfigData['system'] = { baseDir: resolvedPaths.dcrouterHomeDir, dataDir: resolvedPaths.dataDir, publicIp: opts.publicIp || dcRouter.detectedPublicIp || null, proxyIps, uptime: Math.floor(process.uptime()), storageBackend, storagePath: opts.storage?.fsPath || null, }; // --- SmartProxy --- let acmeInfo: interfaces.requests.IConfigData['smartProxy']['acme'] = null; if (opts.smartProxyConfig?.acme) { const acme = opts.smartProxyConfig.acme; acmeInfo = { enabled: acme.enabled !== false, accountEmail: acme.accountEmail || '', useProduction: acme.useProduction !== false, autoRenew: acme.autoRenew !== false, renewThresholdDays: acme.renewThresholdDays || 30, }; } let routeCount = 0; if (dcRouter.routeConfigManager) { try { const merged = await dcRouter.routeConfigManager.getMergedRoutes(); routeCount = merged.routes.length; } catch { routeCount = opts.smartProxyConfig?.routes?.length || 0; } } else if (opts.smartProxyConfig?.routes) { routeCount = opts.smartProxyConfig.routes.length; } const smartProxy: interfaces.requests.IConfigData['smartProxy'] = { enabled: !!dcRouter.smartProxy, routeCount, acme: acmeInfo, }; // --- Email --- let emailDomains: string[] = []; if (dcRouter.emailServer && (dcRouter.emailServer as any).domainRegistry) { emailDomains = (dcRouter.emailServer as any).domainRegistry.getAllDomains(); } else if (opts.emailConfig?.domains) { emailDomains = opts.emailConfig.domains.map((d: any) => typeof d === 'string' ? d : d.domain ); } let portMapping: Record | null = null; if (opts.emailPortConfig?.portMapping) { portMapping = {}; for (const [ext, int] of Object.entries(opts.emailPortConfig.portMapping)) { portMapping[String(ext)] = int as number; } } const email: interfaces.requests.IConfigData['email'] = { enabled: !!dcRouter.emailServer, ports: opts.emailConfig?.ports || [], portMapping, hostname: opts.emailConfig?.hostname || null, domains: emailDomains, emailRouteCount: opts.emailConfig?.routes?.length || 0, receivedEmailsPath: opts.emailPortConfig?.receivedEmailsPath || null, }; // --- DNS --- const dnsRecords = (opts.dnsRecords || []).map(r => ({ name: r.name, type: r.type, value: r.value, ttl: r.ttl, })); const dns: interfaces.requests.IConfigData['dns'] = { enabled: !!dcRouter.dnsServer, port: 53, nsDomains: opts.dnsNsDomains || [], scopes: opts.dnsScopes || [], recordCount: dnsRecords.length, records: dnsRecords, dnsChallenge: !!opts.dnsChallenge?.cloudflareApiKey, }; // --- TLS --- let tlsSource: 'acme' | 'static' | 'none' = 'none'; if (opts.tls?.certPath && opts.tls?.keyPath) { tlsSource = 'static'; } else if (opts.smartProxyConfig?.acme?.enabled !== false && opts.smartProxyConfig?.acme) { tlsSource = 'acme'; } const tls: interfaces.requests.IConfigData['tls'] = { contactEmail: opts.tls?.contactEmail || opts.smartProxyConfig?.acme?.accountEmail || null, domain: opts.tls?.domain || null, source: tlsSource, certPath: opts.tls?.certPath || null, keyPath: opts.tls?.keyPath || null, }; // --- Cache --- const cacheConfig = opts.cacheConfig; const cache: interfaces.requests.IConfigData['cache'] = { enabled: cacheConfig?.enabled !== false, storagePath: cacheConfig?.storagePath || resolvedPaths.defaultTsmDbPath, dbName: cacheConfig?.dbName || 'dcrouter', defaultTTLDays: cacheConfig?.defaultTTLDays || 30, cleanupIntervalHours: cacheConfig?.cleanupIntervalHours || 1, ttlConfig: cacheConfig?.ttlConfig ? { ...cacheConfig.ttlConfig } as Record : {}, }; // --- RADIUS --- const radiusCfg = opts.radiusConfig; const radius: interfaces.requests.IConfigData['radius'] = { enabled: !!dcRouter.radiusServer, authPort: radiusCfg?.authPort || null, acctPort: radiusCfg?.acctPort || null, bindAddress: radiusCfg?.bindAddress || null, clientCount: radiusCfg?.clients?.length || 0, vlanDefaultVlan: radiusCfg?.vlanAssignment?.defaultVlan ?? null, vlanAllowUnknownMacs: radiusCfg?.vlanAssignment?.allowUnknownMacs ?? null, vlanMappingCount: radiusCfg?.vlanAssignment?.mappings?.length || 0, }; // --- Remote Ingress --- const riCfg = opts.remoteIngressConfig; const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || []; // Determine TLS mode: custom certs > ACME from cert store > self-signed fallback let tlsMode: 'custom' | 'acme' | 'self-signed' = 'self-signed'; if (riCfg?.tls?.certPath && riCfg?.tls?.keyPath) { tlsMode = 'custom'; } else if (riCfg?.hubDomain) { try { const stored = await dcRouter.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`); if (stored?.publicKey && stored?.privateKey) { tlsMode = 'acme'; } } catch { /* no stored cert */ } } const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = { enabled: !!dcRouter.remoteIngressManager, tunnelPort: riCfg?.tunnelPort || null, hubDomain: riCfg?.hubDomain || null, tlsMode, connectedEdgeIps, }; return { system, smartProxy, email, dns, tls, cache, radius, remoteIngress, }; } }