BREAKING CHANGE(smartproxy/configuration): Migrate SmartProxy to a fully unified route‐based configuration by removing legacy domain-based settings and conversion code. CertProvisioner, NetworkProxyBridge, and RouteManager now use IRouteConfig exclusively, and related legacy interfaces and files have been removed.
This commit is contained in:
		| @@ -3,6 +3,6 @@ | ||||
|  */ | ||||
| export const commitinfo = { | ||||
|   name: '@push.rocks/smartproxy', | ||||
|   version: '15.1.0', | ||||
|   version: '16.0.0', | ||||
|   description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.' | ||||
| } | ||||
|   | ||||
| @@ -54,8 +54,30 @@ export function createCertificateProvisioner( | ||||
|   } = acmeOptions; | ||||
|  | ||||
|   // Create and return the certificate provisioner | ||||
|   // Convert domain configs to route configs for the new CertProvisioner | ||||
|   const routeConfigs = domainConfigs.map(config => { | ||||
|     // Create a basic route config with the minimum required properties | ||||
|     return { | ||||
|       match: { | ||||
|         ports: 443, | ||||
|         domains: config.domains | ||||
|       }, | ||||
|       action: { | ||||
|         type: 'forward' as const, | ||||
|         target: config.forwarding.target, | ||||
|         tls: { | ||||
|           mode: config.forwarding.type === 'https-terminate-to-https' ? | ||||
|             'terminate-and-reencrypt' as const : | ||||
|             'terminate' as const, | ||||
|           certificate: 'auto' as 'auto' | ||||
|         }, | ||||
|         security: config.forwarding.security | ||||
|       } | ||||
|     }; | ||||
|   }); | ||||
|  | ||||
|   return new CertProvisioner( | ||||
|     domainConfigs, | ||||
|     routeConfigs, | ||||
|     port80Handler, | ||||
|     networkProxyBridge, | ||||
|     certProvider, | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| import * as plugins from '../../plugins.js'; | ||||
| import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js'; | ||||
|  | ||||
| /** | ||||
|  * Certificate data structure containing all necessary information | ||||
| @@ -12,6 +13,11 @@ export interface ICertificateData { | ||||
|   // Optional source and renewal information for event emissions | ||||
|   source?: 'static' | 'http01' | 'dns01'; | ||||
|   isRenewal?: boolean; | ||||
|   // Reference to the route that requested this certificate (if available) | ||||
|   routeReference?: { | ||||
|     routeId?: string; | ||||
|     routeName?: string; | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -29,6 +35,10 @@ export interface ICertificateFailure { | ||||
|   domain: string; | ||||
|   error: string; | ||||
|   isRenewal: boolean; | ||||
|   routeReference?: { | ||||
|     routeId?: string; | ||||
|     routeName?: string; | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -38,35 +48,46 @@ export interface ICertificateExpiring { | ||||
|   domain: string; | ||||
|   expiryDate: Date; | ||||
|   daysRemaining: number; | ||||
|   routeReference?: { | ||||
|     routeId?: string; | ||||
|     routeName?: string; | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Domain forwarding configuration | ||||
|  * Route-specific forwarding configuration for ACME challenges | ||||
|  */ | ||||
| export interface IForwardConfig { | ||||
|   ip: string; | ||||
|   port: number; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Domain-specific forwarding configuration for ACME challenges | ||||
|  */ | ||||
| export interface IDomainForwardConfig { | ||||
| export interface IRouteForwardConfig { | ||||
|   domain: string; | ||||
|   forwardConfig?: IForwardConfig; | ||||
|   acmeForwardConfig?: IForwardConfig; | ||||
|   target: { | ||||
|     host: string; | ||||
|     port: number; | ||||
|   }; | ||||
|   sslRedirect?: boolean; | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Domain configuration options | ||||
|  * Domain configuration options for Port80Handler | ||||
|  * | ||||
|  * This is used internally by the Port80Handler to manage domains | ||||
|  * but will eventually be replaced with route-based options. | ||||
|  */ | ||||
| export interface IDomainOptions { | ||||
|   domainName: string; | ||||
|   sslRedirect: boolean;   // if true redirects the request to port 443 | ||||
|   acmeMaintenance: boolean; // tries to always have a valid cert for this domain | ||||
|   forward?: IForwardConfig; // forwards all http requests to that target | ||||
|   acmeForward?: IForwardConfig; // forwards letsencrypt requests to this config | ||||
|   forward?: { | ||||
|     ip: string; | ||||
|     port: number; | ||||
|   }; // forwards all http requests to that target | ||||
|   acmeForward?: { | ||||
|     ip: string; | ||||
|     port: number; | ||||
|   }; // forwards letsencrypt requests to this config | ||||
|   routeReference?: { | ||||
|     routeId?: string; | ||||
|     routeName?: string; | ||||
|   }; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -83,6 +104,6 @@ export interface IAcmeOptions { | ||||
|   autoRenew?: boolean;            // Whether to automatically renew certificates | ||||
|   certificateStore?: string;      // Directory to store certificates | ||||
|   skipConfiguredCerts?: boolean;  // Skip domains with existing certificates | ||||
|   domainForwards?: IDomainForwardConfig[]; // Domain-specific forwarding configs | ||||
|   routeForwards?: IRouteForwardConfig[]; // Route-specific forwarding configs | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import * as plugins from '../../plugins.js'; | ||||
| import type { IDomainConfig } from '../../forwarding/config/domain-config.js'; | ||||
| import type { IRouteConfig } from '../../proxies/smart-proxy/models/route-types.js'; | ||||
| import type { ICertificateData, IDomainForwardConfig, IDomainOptions } from '../models/certificate-types.js'; | ||||
| import { Port80HandlerEvents, CertProvisionerEvents } from '../events/certificate-events.js'; | ||||
| import { Port80Handler } from '../../http/port80/port80-handler.js'; | ||||
| @@ -28,6 +29,45 @@ export class CertProvisioner extends plugins.EventEmitter { | ||||
|   private port80Handler: Port80Handler; | ||||
|   private networkProxyBridge: INetworkProxyBridge; | ||||
|   private certProvisionFunction?: (domain: string) => Promise<TCertProvisionObject>; | ||||
|  | ||||
|   /** | ||||
|    * Extract domains from route configurations for certificate management | ||||
|    * @param routes Route configurations | ||||
|    */ | ||||
|   private extractDomainsFromRoutes(routes: IRouteConfig[]): void { | ||||
|     // Process all HTTPS routes that need certificates | ||||
|     for (const route of routes) { | ||||
|       // Only process routes with TLS termination that need certificates | ||||
|       if (route.action.type === 'forward' && | ||||
|           route.action.tls && | ||||
|           (route.action.tls.mode === 'terminate' || route.action.tls.mode === 'terminate-and-reencrypt') && | ||||
|           route.match.domains) { | ||||
|  | ||||
|         // Extract domains from the route | ||||
|         const domains = Array.isArray(route.match.domains) | ||||
|           ? route.match.domains | ||||
|           : [route.match.domains]; | ||||
|  | ||||
|         // Skip wildcard domains that can't use ACME | ||||
|         const eligibleDomains = domains.filter(d => !d.includes('*')); | ||||
|  | ||||
|         if (eligibleDomains.length > 0) { | ||||
|           // Create a domain config object for certificate provisioning | ||||
|           const domainConfig: IDomainConfig = { | ||||
|             domains: eligibleDomains, | ||||
|             forwarding: { | ||||
|               type: route.action.tls.mode === 'terminate' ? 'https-terminate-to-http' : 'https-terminate-to-https', | ||||
|               target: route.action.target || { host: 'localhost', port: 80 }, | ||||
|               // Add any other required properties from the legacy format | ||||
|               security: route.action.security || {} | ||||
|             } | ||||
|           }; | ||||
|  | ||||
|           this.domainConfigs.push(domainConfig); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   }; | ||||
|   private forwardConfigs: IDomainForwardConfig[]; | ||||
|   private renewThresholdDays: number; | ||||
|   private renewCheckIntervalHours: number; | ||||
| @@ -47,7 +87,7 @@ export class CertProvisioner extends plugins.EventEmitter { | ||||
|    * @param forwardConfigs Domain forwarding configurations for ACME challenges | ||||
|    */ | ||||
|   constructor( | ||||
|     domainConfigs: IDomainConfig[], | ||||
|     routeConfigs: IRouteConfig[], | ||||
|     port80Handler: Port80Handler, | ||||
|     networkProxyBridge: INetworkProxyBridge, | ||||
|     certProvider?: (domain: string) => Promise<TCertProvisionObject>, | ||||
| @@ -57,7 +97,8 @@ export class CertProvisioner extends plugins.EventEmitter { | ||||
|     forwardConfigs: IDomainForwardConfig[] = [] | ||||
|   ) { | ||||
|     super(); | ||||
|     this.domainConfigs = domainConfigs; | ||||
|     this.domainConfigs = []; | ||||
|     this.extractDomainsFromRoutes(routeConfigs); | ||||
|     this.port80Handler = port80Handler; | ||||
|     this.networkProxyBridge = networkProxyBridge; | ||||
|     this.certProvisionFunction = certProvider; | ||||
|   | ||||
| @@ -377,10 +377,7 @@ export class NetworkProxyBridge { | ||||
|           publicKey: certCert, | ||||
|           destinationIps: targetHosts, | ||||
|           destinationPorts: [targetPort], | ||||
|           // Use backendProtocol for TLS re-encryption: | ||||
|           backendProtocol: route.action.tls.mode === 'terminate-and-reencrypt' ? 'http2' : 'http1', | ||||
|           // Add rewriteHostHeader for host header handling: | ||||
|           rewriteHostHeader: route.action.advanced?.headers ? true : false | ||||
|           // Headers handling happens in the request handler level | ||||
|         }; | ||||
|  | ||||
|         configs.push(config); | ||||
|   | ||||
| @@ -303,135 +303,9 @@ export class RouteManager extends plugins.EventEmitter { | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Convert a domain config to routes | ||||
|    * (For backward compatibility with code that still uses domainConfigs) | ||||
|    * Domain-based configuration methods have been removed | ||||
|    * as part of the migration to pure route-based configuration | ||||
|    */ | ||||
|   public domainConfigToRoutes(domainConfig: IDomainConfig): IRouteConfig[] { | ||||
|     const routes: IRouteConfig[] = []; | ||||
|     const { domains, forwarding } = domainConfig; | ||||
|      | ||||
|     // Determine the action based on forwarding type | ||||
|     let action: IRouteAction = { | ||||
|       type: 'forward', | ||||
|       target: { | ||||
|         host: forwarding.target.host, | ||||
|         port: forwarding.target.port | ||||
|       } | ||||
|     }; | ||||
|      | ||||
|     // Set TLS mode based on forwarding type | ||||
|     switch (forwarding.type) { | ||||
|       case 'http-only': | ||||
|         // No TLS settings needed | ||||
|         break; | ||||
|       case 'https-passthrough': | ||||
|         action.tls = { mode: 'passthrough' }; | ||||
|         break; | ||||
|       case 'https-terminate-to-http': | ||||
|         action.tls = {  | ||||
|           mode: 'terminate', | ||||
|           certificate: forwarding.https?.customCert ? { | ||||
|             key: forwarding.https.customCert.key, | ||||
|             cert: forwarding.https.customCert.cert | ||||
|           } : 'auto' | ||||
|         }; | ||||
|         break; | ||||
|       case 'https-terminate-to-https': | ||||
|         action.tls = {  | ||||
|           mode: 'terminate-and-reencrypt', | ||||
|           certificate: forwarding.https?.customCert ? { | ||||
|             key: forwarding.https.customCert.key, | ||||
|             cert: forwarding.https.customCert.cert | ||||
|           } : 'auto' | ||||
|         }; | ||||
|         break; | ||||
|     } | ||||
|      | ||||
|     // Add security settings if present | ||||
|     if (forwarding.security) { | ||||
|       action.security = { | ||||
|         allowedIps: forwarding.security.allowedIps, | ||||
|         blockedIps: forwarding.security.blockedIps, | ||||
|         maxConnections: forwarding.security.maxConnections | ||||
|       }; | ||||
|     } | ||||
|      | ||||
|     // Add advanced settings if present | ||||
|     if (forwarding.advanced) { | ||||
|       action.advanced = { | ||||
|         timeout: forwarding.advanced.timeout, | ||||
|         headers: forwarding.advanced.headers, | ||||
|         keepAlive: forwarding.advanced.keepAlive | ||||
|       }; | ||||
|     } | ||||
|      | ||||
|     // Determine which port to use based on forwarding type | ||||
|     const defaultPort = forwarding.type.startsWith('https') ? 443 : 80; | ||||
|      | ||||
|     // Add the main route | ||||
|     routes.push({ | ||||
|       match: { | ||||
|         ports: defaultPort, | ||||
|         domains | ||||
|       }, | ||||
|       action, | ||||
|       name: `Route for ${domains.join(', ')}` | ||||
|     }); | ||||
|      | ||||
|     // Add HTTP redirect if needed | ||||
|     if (forwarding.http?.redirectToHttps) { | ||||
|       routes.push({ | ||||
|         match: { | ||||
|           ports: 80, | ||||
|           domains | ||||
|         }, | ||||
|         action: { | ||||
|           type: 'redirect', | ||||
|           redirect: { | ||||
|             to: 'https://{domain}{path}', | ||||
|             status: 301 | ||||
|           } | ||||
|         }, | ||||
|         name: `HTTP Redirect for ${domains.join(', ')}`, | ||||
|         priority: 100 // Higher priority for redirects | ||||
|       }); | ||||
|     } | ||||
|      | ||||
|     // Add port ranges if specified | ||||
|     if (forwarding.advanced?.portRanges) { | ||||
|       for (const range of forwarding.advanced.portRanges) { | ||||
|         routes.push({ | ||||
|           match: { | ||||
|             ports: [{ from: range.from, to: range.to }], | ||||
|             domains | ||||
|           }, | ||||
|           action, | ||||
|           name: `Port Range ${range.from}-${range.to} for ${domains.join(', ')}` | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     return routes; | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Update routes based on domain configs | ||||
|    * (For backward compatibility with code that still uses domainConfigs) | ||||
|    */ | ||||
|   public updateFromDomainConfigs(domainConfigs: IDomainConfig[]): void { | ||||
|     const routes: IRouteConfig[] = []; | ||||
|      | ||||
|     // Convert each domain config to routes | ||||
|     for (const config of domainConfigs) { | ||||
|       routes.push(...this.domainConfigToRoutes(config)); | ||||
|     } | ||||
|      | ||||
|     // Merge with existing routes that aren't derived from domain configs | ||||
|     const nonDomainRoutes = this.routes.filter(r =>  | ||||
|       !r.name || !r.name.includes('for ')); | ||||
|      | ||||
|     this.updateRoutes([...nonDomainRoutes, ...routes]); | ||||
|   } | ||||
|    | ||||
|   /** | ||||
|    * Validate the route configuration and return any warnings | ||||
|   | ||||
| @@ -61,8 +61,8 @@ export class TimeoutManager { | ||||
|    * Calculate effective max lifetime based on connection type | ||||
|    */ | ||||
|   public getEffectiveMaxLifetime(record: IConnectionRecord): number { | ||||
|     // Use domain-specific timeout from forwarding.advanced if available | ||||
|     const baseTimeout = record.domainConfig?.forwarding?.advanced?.timeout || | ||||
|     // Use route-specific timeout if available from the routeConfig | ||||
|     const baseTimeout = record.routeConfig?.action.advanced?.timeout || | ||||
|                         this.settings.maxConnectionLifetime || | ||||
|                         86400000; // 24 hours default | ||||
|      | ||||
|   | ||||
		Reference in New Issue
	
	Block a user