import type DcRouter from '../classes.dcrouter.js'; import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import * as handlers from './handlers/index.js'; import * as interfaces from '../../ts_interfaces/index.js'; import { McpManager } from './classes.mcpmanager.js'; export class OpsServer { public dcRouterRef: DcRouter; public server!: plugins.typedserver.utilityservers.UtilityWebsiteServer; // Main TypedRouter — unauthenticated endpoints (login/logout/verify) and own-auth handlers public typedrouter = new plugins.typedrequest.TypedRouter(); // Grouped routers. Handlers enforce auth explicitly with per-endpoint scopes. public viewRouter = new plugins.typedrequest.TypedRouter<{ request: { identity?: interfaces.data.IIdentity; apiToken?: string } }>(); public adminRouter = new plugins.typedrequest.TypedRouter<{ request: { identity?: interfaces.data.IIdentity; apiToken?: string } }>(); // Handler instances public adminHandler!: handlers.AdminHandler; public realtimeHandler!: handlers.RealtimeHandler; private configHandler!: handlers.ConfigHandler; private logsHandler!: handlers.LogsHandler; private securityHandler!: handlers.SecurityHandler; private statsHandler!: handlers.StatsHandler; private radiusHandler!: handlers.RadiusHandler; private emailOpsHandler!: handlers.EmailOpsHandler; private emailSettingsHandler!: handlers.EmailSettingsHandler; private certificateHandler!: handlers.CertificateHandler; private remoteIngressHandler!: handlers.RemoteIngressHandler; private routeManagementHandler!: handlers.RouteManagementHandler; private apiTokenHandler!: handlers.ApiTokenHandler; private vpnHandler!: handlers.VpnHandler; private sourceProfileHandler!: handlers.SourceProfileHandler; private targetProfileHandler!: handlers.TargetProfileHandler; private networkTargetHandler!: handlers.NetworkTargetHandler; private usersHandler!: handlers.UsersHandler; private dnsProviderHandler!: handlers.DnsProviderHandler; private domainHandler!: handlers.DomainHandler; private dnsAuthorityHandler!: handlers.DnsAuthorityHandler; private dnsRecordHandler!: handlers.DnsRecordHandler; private acmeConfigHandler!: handlers.AcmeConfigHandler; private emailDomainHandler!: handlers.EmailDomainHandler; private smtpAccountHandler!: handlers.SmtpAccountHandler; private gatewayClientHandler!: handlers.GatewayClientHandler; private webPushControlHandler!: handlers.WebPushControlHandler; private webPushAppHandler!: handlers.WebPushAppHandler; private configEventHandler!: handlers.ConfigEventHandler; private smtpRealtimeUnsubscribe?: () => void; private emailQueueRealtimeUnsubscribe?: () => void; constructor(dcRouterRefArg: DcRouter) { this.dcRouterRef = dcRouterRefArg; // Add our typedrouter to the dcRouter's main typedrouter this.dcRouterRef.typedrouter.addTypedRouter(this.typedrouter); } public async start() { this.server = new plugins.typedserver.utilityservers.UtilityWebsiteServer({ domain: 'localhost', feedMetadata: undefined, serveDir: paths.distServe, addCustomRoutes: async (typedserver) => this.registerCustomRoutes(typedserver), }); // The server has a built-in typedrouter at /typedrequest // Add the main dcRouter typedrouter to the server's typedrouter this.server.typedrouter.addTypedRouter(this.dcRouterRef.typedrouter); // Set up handlers await this.setupHandlers(); await this.server.start(this.dcRouterRef.options.opsServerPort ?? 3000); } /** * Set up all TypedRequest handlers */ private async setupHandlers(): Promise { // AdminHandler must be initialized first (JWT setup needed for guards) this.adminHandler = new handlers.AdminHandler(this); await this.adminHandler.initialize(); // Connect auth routers to the main typedrouter this.typedrouter.addTypedRouter(this.viewRouter); this.typedrouter.addTypedRouter(this.adminRouter); // Instantiate all handlers — they self-register with the appropriate router this.realtimeHandler = new handlers.RealtimeHandler(this); this.configHandler = new handlers.ConfigHandler(this); this.logsHandler = new handlers.LogsHandler(this); this.securityHandler = new handlers.SecurityHandler(this); this.statsHandler = new handlers.StatsHandler(this); this.radiusHandler = new handlers.RadiusHandler(this); this.emailOpsHandler = new handlers.EmailOpsHandler(this); this.emailSettingsHandler = new handlers.EmailSettingsHandler(this); this.certificateHandler = new handlers.CertificateHandler(this); this.remoteIngressHandler = new handlers.RemoteIngressHandler(this); this.routeManagementHandler = new handlers.RouteManagementHandler(this); this.apiTokenHandler = new handlers.ApiTokenHandler(this); this.vpnHandler = new handlers.VpnHandler(this); this.sourceProfileHandler = new handlers.SourceProfileHandler(this); this.targetProfileHandler = new handlers.TargetProfileHandler(this); this.networkTargetHandler = new handlers.NetworkTargetHandler(this); this.usersHandler = new handlers.UsersHandler(this); this.dnsProviderHandler = new handlers.DnsProviderHandler(this); this.domainHandler = new handlers.DomainHandler(this); this.dnsAuthorityHandler = new handlers.DnsAuthorityHandler(this); this.dnsRecordHandler = new handlers.DnsRecordHandler(this); this.acmeConfigHandler = new handlers.AcmeConfigHandler(this); this.emailDomainHandler = new handlers.EmailDomainHandler(this); this.smtpAccountHandler = new handlers.SmtpAccountHandler(this); this.gatewayClientHandler = new handlers.GatewayClientHandler(this); this.webPushControlHandler = new handlers.WebPushControlHandler(this); this.webPushAppHandler = new handlers.WebPushAppHandler(this); this.configEventHandler = new handlers.ConfigEventHandler(this); this.smtpRealtimeUnsubscribe = this.dcRouterRef.acceptedEmailSpool?.onSmtpTransactionPersisted?.((eventArg) => { this.invalidateRealtime(['smtpAttempts', 'emailQueue'], { changedIds: [eventArg.cachedEmailId], reason: 'smtp-transaction-persisted', }); }); this.emailQueueRealtimeUnsubscribe = this.dcRouterRef.acceptedEmailSpool?.onEmailQueuePersisted?.((eventArg) => { this.invalidateRealtime('emailQueue', { changedIds: [eventArg.cachedEmailId], reason: eventArg.reason, }); }); console.log('✅ OpsServer TypedRequest handlers initialized'); } private registerCustomRoutes(typedserver: plugins.typedserver.TypedServer): void { const mcpManager = new McpManager(this); typedserver.addRoute( '/mcp', 'ALL', async (ctx) => mcpManager.handleRequest(ctx.request), ); } public invalidateRealtime( resourcesArg: | interfaces.requests.TOpsRealtimeResource | interfaces.requests.TOpsRealtimeResource[], optionsArg: { changedIds?: string[]; reason?: string } = {}, ): void { this.realtimeHandler?.invalidate(resourcesArg, optionsArg); } public async getAuthenticatedRealtimeConnections(): Promise< plugins.typedsocket.ISmartServeConnectionWrapper[] > { return this.realtimeHandler ? await this.realtimeHandler.getAuthenticatedConnections() : []; } public async stop() { if (this.adminHandler) { await this.adminHandler.stop(); } this.smtpRealtimeUnsubscribe?.(); this.smtpRealtimeUnsubscribe = undefined; this.emailQueueRealtimeUnsubscribe?.(); this.emailQueueRealtimeUnsubscribe = undefined; if (this.realtimeHandler) { this.realtimeHandler.cleanup(); } // Clean up log handler streams and push destination before stopping the server if (this.logsHandler) { this.logsHandler.cleanup(); } if (this.server) { await this.server.stop(); } } }