import * as plugins from './plugins.js'; import * as paths from './paths.js'; import { UnifiedEmailServer, type IUnifiedEmailServerOptions, type IEmailRoute, type IStorageManagerLike } from '@push.rocks/smartmta'; import { CertProvisionScheduler } from './classes.cert-provision-scheduler.js'; import { DcRouterDb, CacheCleaner } from './db/index.js'; import { OpsServer } from './opsserver/index.js'; import { MetricsManager, OpsEventManager } from './monitoring/index.js'; import { RadiusServer, type IRadiusServerConfig } from './radius/index.js'; import { RemoteIngressHubLifecycle, RemoteIngressManager, TunnelManager } from './remoteingress/index.js'; import { VpnAccessResolver, VpnManager } from './vpn/index.js'; import { RouteConfigManager, ApiTokenManager, GatewayClientManager, ReferenceResolver, TargetProfileManager } from './config/index.js'; import { SecurityPolicyManager, RoutePolicyAugmenter, AuthenticationEventManager } from './security/index.js'; import { type IHttp3Config } from './http3/index.js'; import { DnsManager } from './dns/manager.dns.js'; import { DnsAuthorityManager } from './dns/manager.dns-authority.js'; import { DnsServerRuntime } from './dns/classes.dns-server-runtime.js'; import { GatewayRouteDnsReconciler } from './dns/classes.gateway-route-dns-reconciler.js'; import { AcmeConfigManager } from './acme/manager.acme-config.js'; import { SmartAcmeLifecycle } from './acme/classes.smartacme-lifecycle.js'; import { AcceptedEmailSpool, EmailDomainManager, EmailRouteBuilder, EmailSettingsManager, GatewayMailDomainStore, MailDnsSync, MailEdgeEligibility, MailEgressCoordinator, SmartMtaBlobStorageManager, SmtpAccountManager, WorkAppMailManager, type ISmartMtaBlobStorageConfig } from './email/index.js'; import { WebPushManager } from './webpush/index.js'; import type { IEmailOutboundEgressStatus, IEmailPortConfig, IEmailServerSettings, TEmailOutboundMode, TEmailServerSettingsUpdate } from '../dist_ts_interfaces/data/email-settings.js'; import type { IDcRouterRouteConfig, IRemoteIngressHubSettings, TRemoteIngressHubSettingsUpdate } from '../dist_ts_interfaces/data/remoteingress.js'; /** * dcrouter's superset of SmartMTA's SMTP TLS options. * * SmartMTA consumes PEM content (`certPem`/`keyPem`). Deployments configure * certificate FILES, so dcrouter accepts the path form too and loads it eagerly * at startup — a path that cannot be read fails startup rather than silently * leaving the listener without TLS. */ export interface IDcRouterEmailTlsConfig extends NonNullable { /** Path to the certificate chain PEM file. Requires keyPath. */ certPath?: string; /** Path to the private key PEM file. Requires certPath. */ keyPath?: string; } /** dcrouter's email server options: SmartMTA's, with the path-shaped tls block. */ export interface IDcRouterEmailConfig extends Omit { tls?: IDcRouterEmailTlsConfig; } /** Convention-correct alias for dcrouter's email server options. */ export type TDcRouterEmailConfig = IDcRouterEmailConfig; export interface IDcRouterOptions { /** Base directory for all dcrouter data. Defaults to ~/.serve.zone/dcrouter */ baseDir?: string; /** * Direct CoreTraffic configuration - gives full control over HTTP/HTTPS and TCP/SNI traffic * This is the preferred way to configure HTTP/HTTPS and general TCP/SNI traffic */ coreTrafficConfig?: plugins.smartproxy.ISmartProxyOptions; /** Legacy name for coreTrafficConfig. */ smartProxyConfig?: plugins.smartproxy.ISmartProxyOptions; /** * Email server configuration * This enables all email handling with pattern-based routing */ emailConfig?: TDcRouterEmailConfig; /** SmartBucket configuration for durable SmartMTA queue and attachment blobs. */ emailBlobStorage?: ISmartMtaBlobStorageConfig; /** Outbound SMTP connection mode. Defaults to direct unless DB settings override it. */ emailOutboundMode?: TEmailOutboundMode; /** * Custom email port configuration * Allows configuring specific ports for email handling * This overrides the default port mapping in the emailConfig */ emailPortConfig?: IEmailPortConfig; /** TLS/certificate configuration */ tls?: { /** Contact email for ACME certificates */ contactEmail: string; /** Domain for main certificate */ domain?: string; /** Path to certificate file (if not using auto-provisioning) */ certPath?: string; /** Path to key file (if not using auto-provisioning) */ keyPath?: string; /** Path to CA certificate file (for custom CAs) */ caPath?: string; }; /** * The nameserver domains (e.g., ['ns1.example.com', 'ns2.example.com']) * These will automatically get A records pointing to publicIp or proxyIps[0] * A zone becomes authoritative by having its public NS records observed * naming one of these — that is the only way into the authority set. * * There is deliberately no `dnsScopes` counterpart. Which zones dcrouter * answers for is database state carrying its own delegation evidence, not * deployment configuration; see ts/dns/manager.dns-authority.ts. */ dnsNsDomains?: string[]; /** Explicit UDP bind address for the embedded DNS server. Defaults to auto-detection. */ dnsBindInterface?: string; /** * IPs of proxies that forward traffic to your server (optional) * When defined AND useIngressProxy is true, A records with server IP are replaced with proxy IPs * If not defined or empty, all A records use the real server IP * Helps hide real server IP for security/privacy */ proxyIps?: string[]; /** * Public IP address for nameserver A records (required if proxyIps not set) * This is the IP that will be used for the nameserver domains (dnsNsDomains) * If proxyIps is set, the first proxy IP will be used instead */ publicIp?: string; /** * DNS records to register * Must be within a delegation-verified zone (or receive warning) * Only need A, CNAME, TXT, MX records (NS records auto-generated, SOA handled by smartdns) * Can use `useIngressProxy: false` to expose real server IP (defaults to true) */ dnsRecords?: Array<{ name: string; type: 'A' | 'AAAA' | 'CNAME' | 'MX' | 'TXT' | 'NS' | 'SOA'; value: string; ttl?: number; useIngressProxy?: boolean; }>; /** * Unified database configuration. * All persistent data (config, certs, VPN, cache, etc.) is stored via smartdata. * If mongoDbUrl is provided, connects to external MongoDB. * Otherwise, starts an embedded LocalSmartDb automatically. */ dbConfig?: { /** Enable database (default: true). Set to false in tests to skip DB startup. */ enabled?: boolean; /** External MongoDB connection URL. If absent, uses embedded LocalSmartDb. */ mongoDbUrl?: string; /** Storage path for embedded database data (default: ~/.serve.zone/dcrouter/tsmdb) */ storagePath?: string; /** Database name (default: dcrouter) */ dbName?: string; /** Cache cleanup interval in hours (default: 1) */ cleanupIntervalHours?: number; /** Seed default security profiles and network targets when DB is empty on first startup. */ seedOnEmpty?: boolean; /** Custom seed data for profiles and targets (overrides built-in defaults). */ seedData?: import('./config/classes.db-seeder.js').ISeedData; }; /** * RADIUS server configuration for network authentication * Enables MAC Authentication Bypass (MAB) and VLAN assignment */ radiusConfig?: IRadiusServerConfig; /** * Remote Ingress configuration for edge tunnel nodes * Enables edge nodes to accept incoming connections and tunnel them to this DcRouter */ /** * HTTP/3 (QUIC) configuration for HTTPS routes. * Enabled by default — qualifying HTTPS routes on port 443 are automatically * augmented with QUIC/H3 fields. Set { enabled: false } to disable globally. * Individual routes can opt out via action.options.http3 = false. */ http3?: IHttp3Config; /** Port for the OpsServer web UI (default: 3000) */ opsServerPort?: number; /** Optional OpsServer account authentication settings. */ adminAuth?: { /** Optional idp.global password-authentication URL override. Defaults to the SDK's hosted https://idp.global endpoint. Can also be set through DCROUTER_IDP_GLOBAL_URL. */ idpGlobalUrl?: string; /** Test/integration hook for injecting an idp.global-compatible password client. */ idpClient?: Pick; }; remoteIngressConfig?: { /** Enable remote ingress hub (default: false) */ enabled?: boolean; /** Port for tunnel connections from edge nodes (default: 8443) */ tunnelPort?: number; /** External hostname of this hub, embedded in connection tokens */ hubDomain?: string; /** TLS configuration for the tunnel server */ tls?: { certPath?: string; keyPath?: string; }; /** Performance profile and limits for remote ingress hub/edge tunnels. */ performance?: import('../dist_ts_interfaces/data/remoteingress.js').IRemoteIngressPerformanceConfig; }; /** * VPN server configuration. * Enables VPN-based access control: routes with vpnOnly are only * accessible from VPN clients whose TargetProfile matches. Supports WireGuard + native (WS/QUIC) transports. */ vpnConfig?: { /** Enable VPN server (default: false) */ enabled?: boolean; /** VPN subnet CIDR (default: '10.8.0.0/24') */ subnet?: string; /** WireGuard UDP listen port (default: 51820) */ wgListenPort?: number; /** DNS servers pushed to VPN clients */ dns?: string[]; /** Server endpoint hostname for client configs (e.g. 'vpn.example.com') */ serverEndpoint?: string; /** Pre-defined VPN clients created on startup */ clients?: Array<{ clientId: string; targetProfileIds?: string[]; description?: string; }>; /** Destination routing policy for VPN client traffic. * Default in socket mode: { default: 'forceTarget', target: '127.0.0.1' } (all traffic → SmartProxy). * Default in tun mode: not set (all traffic passes through). */ destinationPolicy?: { default: 'forceTarget' | 'block' | 'allow'; target?: string; allowList?: string[]; blockList?: string[]; }; /** Forwarding mode: 'socket' (default, userspace NAT), 'bridge' (L2 bridge to host LAN), * or 'hybrid' (socket default, bridge for clients with useHostIp=true) */ forwardingMode?: 'socket' | 'bridge' | 'hybrid'; /** LAN subnet CIDR for bridge mode (e.g., '192.168.1.0/24') */ bridgeLanSubnet?: string; /** Physical network interface for bridge mode (auto-detected if omitted) */ bridgePhysicalInterface?: string; /** Start of VPN client IP range in LAN subnet (host offset, default: 200) */ bridgeIpRangeStart?: number; /** End of VPN client IP range in LAN subnet (host offset, default: 250) */ bridgeIpRangeEnd?: number; }; } /** * DcRouter can be run on ingress and egress to and from a datacenter site. */ /** * Context passed to HTTP routing rules */ /** * Context passed to port proxy (SmartProxy) routing rules */ export interface PortProxyRuleContext { proxy: plugins.smartproxy.SmartProxy; routes: plugins.smartproxy.IRouteConfig[]; } export declare class DcRouter { options: IDcRouterOptions; resolvedPaths: ReturnType; smartProxy?: plugins.smartproxy.SmartProxy; smartAcme?: plugins.smartacme.SmartAcme; dnsServer?: plugins.smartdns.dnsServerMod.DnsServer; emailServer?: UnifiedEmailServer; radiusServer?: RadiusServer; opsServer: OpsServer; private opsServerStopped; metricsManager?: MetricsManager; private emailEventSubscriptions; storageManager: IStorageManagerLike; private smartMtaBlobStorageManager?; private databaseMigrationsReady; getSmartMtaBlobStorageManager(): SmartMtaBlobStorageManager | undefined; dcRouterDb?: DcRouterDb; cacheCleaner?: CacheCleaner; remoteIngressManager?: RemoteIngressManager; tunnelManager?: TunnelManager; private smartProxyLifecycleChain; private emailLifecycleChain; vpnManager?: VpnManager; routeConfigManager?: RouteConfigManager; apiTokenManager?: ApiTokenManager; gatewayClientManager?: GatewayClientManager; referenceResolver?: ReferenceResolver; targetProfileManager?: TargetProfileManager; dnsManager?: DnsManager; /** Owns which zones dcrouter may answer for authoritatively, and proves it. */ dnsAuthorityManager?: DnsAuthorityManager; gatewayRouteDnsReconciler?: GatewayRouteDnsReconciler; opsEventManager?: OpsEventManager; acmeConfigManager?: AcmeConfigManager; emailSettingsManager?: EmailSettingsManager; emailDomainManager?: EmailDomainManager; gatewayMailDomainStore: GatewayMailDomainStore; workAppMailManager: WorkAppMailManager; /** Single composition owner of the email runtime auth block and route list. */ smtpAccountManager: SmtpAccountManager; acceptedEmailSpool: AcceptedEmailSpool; mailEgressCoordinator: MailEgressCoordinator; mailDnsSync: MailDnsSync; mailEdgeEligibility: MailEdgeEligibility; securityPolicyManager?: SecurityPolicyManager; authenticationEventManager: AuthenticationEventManager; detectedPublicIp: string | null; certificateStatusMap: Map; certProvisionScheduler?: CertProvisionScheduler; serviceManager: plugins.taskbuffer.ServiceManager; private serviceSubjectSubscription?; smartAcmeLifecycle: SmartAcmeLifecycle; vpnAccessResolver: VpnAccessResolver; remoteIngressHubLifecycle: RemoteIngressHubLifecycle; dnsServerRuntime: DnsServerRuntime; emailRouteBuilder: EmailRouteBuilder; routePolicyAugmenter: RoutePolicyAugmenter; webPushManager: WebPushManager; typedrouter: plugins.typedrequest.TypedRouter; private seedConfigRoutes; seedEmailRoutes: IDcRouterRouteConfig[]; private seedDnsRoutes; private runtimeDnsRoutes; private qenv; constructor(optionsArg: IDcRouterOptions); /** * Register all dcrouter services with the ServiceManager. * Services are started in dependency order, with failure isolation for optional services. */ private registerServices; isRemoteIngressHubEnabled(): boolean; private getRemoteIngressHubSettingsMigrationSeed; private getEmailSettingsMigrationSeed; start(): Promise; /** * Detect OS-level resource limits and warn if they are too low for production use. * This is detection only — no attempts to raise limits. */ private checkSystemLimits; /** * Log comprehensive startup summary */ private logStartupSummary; /** * Set up the unified database (smartdata + LocalSmartDb or external MongoDB) */ private setupDcRouterDb; /** * Set up SmartProxy with direct configuration and automatic email routes */ private setupSmartProxy; private assertGeneratedRuntimeCertificateOwnership; private handleRemoteIngressEdgesChanged; applySecurityPolicy(): Promise; /** * Generate SmartProxy routes for DNS configuration */ private generateDnsRoutes; /** * Check if a domain matches a pattern (including wildcard support) * @param domain The domain to check * @param pattern The pattern to match against (e.g., "*.example.com") * @returns Whether the domain matches the pattern */ private isDomainMatch; /** * Find ALL route names that match a given domain */ findRouteNamesForDomain(domain: string): string[]; private stopOpsServer; stop(): Promise; /** * Update SmartProxy configuration * @param config New SmartProxy configuration */ updateSmartProxyConfig(config: plugins.smartproxy.ISmartProxyOptions): Promise; /** * Set up unified email handling with pattern-based routing * This implements the consolidated emailConfig approach */ private setupUnifiedEmailHandling; /** * Resolve the PEM material for the SMTP listener. * * SmartMTA consumes `tls.certPem`/`tls.keyPem`; a path-shaped `tls` block is * ignored, which leaves the Rust listener with no TLS material at all — no * STARTTLS on the plain submission ports. Resolution order matches the * RemoteIngress tunnel precedent: explicit paths, then the ACME cert store. * * Explicitly configured paths that cannot be read FAIL STARTUP. Silently * continuing without TLS is what produced a cleartext submission port in * production, so a broken explicit configuration must be impossible to miss. */ private resolveEmailTlsMaterial; /** * Push renewed TLS material to the running SMTP listener. * * Only reacts to the mail hostname, and only when explicit paths are NOT * configured — an operator-managed file pair is not superseded by an ACME * renewal for the same name. SmartMTA restarts just the Rust listener when * the PEM material actually changes. */ private reapplyEmailTlsMaterial; /** * Internal SMTP ports whose public leg is TLS-terminated by CoreTraffic. * * Those backend legs arrive as plaintext even though the client's channel was * encrypted, so without declaring them the AUTH-requires-encryption gate would * refuse authentication on the only encrypted submission port. Derived from * the generated route set rather than hardcoding 465, so it stays true when * emailPortConfig changes. */ private resolveEdgeTerminatedEmailPorts; /** * State the transport posture of every SMTP listener port at startup. * * AUTH is only offered on an encrypted transport, so an operator has to be * able to see at a glance which submission ports can actually authenticate — * missing certificate material silently disabling AUTH on 587 would otherwise * look like a client problem. */ private logEmailAuthTransportPosture; /** * Readiness of the RemoteIngress outbound mail egress path (mail-tagged edges). */ getOutboundEgressStatus(): IEmailOutboundEgressStatus; /** * Update the unified email configuration * @param config New email configuration */ updateEmailConfig(config: IUnifiedEmailServerOptions): Promise; updateEmailServerSettings(settings: TEmailServerSettingsUpdate, updatedBy?: string): Promise; /** * Stop all unified email components */ private stopUnifiedEmailComponents; /** * Update domain rules for email routing * @param rules New domain rules to apply */ updateEmailRoutes(routes: IEmailRoute[]): Promise; /** * Get statistics from all components */ getStats(): any; /** * Register DNS records with the DNS server * @param records Array of DNS records to register */ private addEmailEventSubscription; private clearEmailEventSubscriptions; /** * Set up Remote Ingress hub for edge tunnel connections */ private queueSmartProxyLifecycleTask; private queueEmailLifecycleTask; /** Serialized edge mutation on the RemoteIngress hub (delegates to the hub lifecycle). */ mutateRemoteIngressEdges(mutation: (manager: RemoteIngressManager) => Promise, syncAllowedEdges?: boolean): Promise; updateRemoteIngressHubSettings(updates: TRemoteIngressHubSettingsUpdate, updatedBy: string): Promise; /** * Re-derive every piece of runtime state that depends on the effective DNS * authority set, after a zone gained or lost delegation-verified authority. * * Deliberately reuses the existing machinery rather than paralleling it: * `reconcileAuthoritativeZones()` drives the same `runtimeRegistrations` * registry that domain deletion uses, and the overlay and route-warning * refreshes are the same calls the domain mutation paths make. Throws on the * first failure so `DnsAuthorityManager` can roll the change back rather than * leave the router half-converted. */ reconcileDnsAuthority(reasonArg: string): Promise; /** * Re-derive the private-route DNS overlay from the currently applied route set. * * The overlay is derived from routes but gated on domain ownership, so a domain * mutation can invalidate it without any route changing. Deleting a DomainDoc * is the case that matters: without this the overlay keeps answering `aa` for a * hostname whose zone we no longer manage until some unrelated route apply * happens. Named here so the mutation site can trigger its own invalidation * instead of forcing a full route re-apply. */ resyncPrivateRouteDnsOverlay(reasonArg: string): Promise; /** Restart SmartProxy after RemoteIngress hub settings changed listener wiring. Called by RemoteIngressHubLifecycle. */ restartSmartProxyForRemoteIngressSettings(): Promise; /** Bootstrap routes the RemoteIngress hub uses to derive edge ports before the DB route set is applied. */ getRemoteIngressBootstrapRoutes(): plugins.smartproxy.IRouteConfig[]; /** Edge-derivation-only routes (see buildEdgeOnlyDerivationRoutes) — never passed to SmartProxy. */ getEdgeOnlyDerivationRoutes(): IDcRouterRouteConfig[]; /** * Set up VPN server for VPN-based route access control. */ private setupVpnServer; /** * Set up RADIUS server for network authentication */ private setupRadiusServer; /** * Update RADIUS configuration at runtime */ updateRadiusConfig(config: IRadiusServerConfig): Promise; /** * Update VPN configuration at runtime. */ updateVpnConfig(config: IDcRouterOptions['vpnConfig']): Promise; } export type { IUnifiedEmailServerOptions }; export type { IRadiusServerConfig }; export default DcRouter;