import * as plugins from '../../plugins.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; import { requireAdminIdentity } from '../helpers/guards.ts'; import { logger } from '../../logging.ts'; import { getErrorMessage } from '../../utils/error.ts'; export class SettingsHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } 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'] || '', httpPort: parseInt(settingsMap['httpPort'] || '80', 10), httpsPort: parseInt(settingsMap['httpsPort'] || '443', 10), forceHttps: settingsMap['forceHttps'] === 'true', }; } private registerHandlers(): void { this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getSettings', async (dataArg) => { await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg); const settings = await this.getSettingsObject(); return { settings }; }, ), ); this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateSettings', async (dataArg) => { await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg); const db = this.opsServerRef.oneboxRef.database; const updates = dataArg.settings; // Store each setting as key-value pair for (const [key, value] of Object.entries(updates)) { if (value !== undefined) { if (db.isSecretSettingKey(key)) { await db.setSecretSetting(key, String(value)); } else { db.setSetting(key, String(value)); } } } if (this.hasExternalGatewaySetting(updates)) { this.refreshExternalGateway().catch((error) => { logger.warn(`External gateway settings refresh failed: ${getErrorMessage(error)}`); }); } const settings = await this.getSettingsObject(); return { settings }; }, ), ); this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'setBackupPassword', async (dataArg) => { await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg); await this.opsServerRef.oneboxRef.database.setSecretSetting('backupPassword', dataArg.password); return { ok: true }; }, ), ); this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getBackupPasswordStatus', async (dataArg) => { await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg); const isConfigured = await this.opsServerRef.oneboxRef.database.hasSecretSetting('backupPassword'); return { status: { isConfigured } }; }, ), ); } private hasExternalGatewaySetting(settings: Partial): boolean { return [ 'dcrouterGatewayUrl', 'dcrouterGatewayApiToken', 'dcrouterWorkHosterId', 'dcrouterTargetHost', 'dcrouterTargetPort', ].some((key) => Object.prototype.hasOwnProperty.call(settings, key)); } private async refreshExternalGateway(): Promise { const onebox = this.opsServerRef.oneboxRef; await onebox.externalGateway.syncDomains(); const services = onebox.database.getAllServices().filter((service) => service.domain); await Promise.all(services.map(async (service) => { try { await onebox.externalGateway.syncServiceRoute(service); } catch (error) { logger.warn(`Failed to sync external gateway route for ${service.domain}: ${getErrorMessage(error)}`); } })); } }