This commit is contained in:
2025-05-20 16:01:32 +00:00
parent b2584fffb1
commit 6512551f02
5 changed files with 183 additions and 1749 deletions

View File

@ -313,21 +313,6 @@ export class SmartProxy extends plugins.EventEmitter {
return;
}
// Initialize certificate manager before starting servers
await this.initializeCertificateManager();
// Initialize and start HttpProxy if needed
if (this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
await this.httpProxyBridge.initialize();
// Connect HttpProxy with certificate manager
if (this.certManager) {
this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
}
await this.httpProxyBridge.start();
}
// Validate the route configuration
const configWarnings = this.routeManager.validateConfiguration();
@ -362,9 +347,25 @@ export class SmartProxy extends plugins.EventEmitter {
}
}
// Start port listeners using the PortManager
// Initialize and start HttpProxy if needed - before port binding
if (this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
await this.httpProxyBridge.initialize();
await this.httpProxyBridge.start();
}
// Start port listeners using the PortManager BEFORE initializing certificate manager
// This ensures all required ports are bound and ready when adding ACME challenge routes
await this.portManager.addPorts(listeningPorts);
// Initialize certificate manager AFTER port binding is complete
// This ensures the ACME challenge port is already bound and ready when needed
await this.initializeCertificateManager();
// Connect certificate manager with HttpProxy if both are available
if (this.certManager && this.httpProxyBridge.getHttpProxy()) {
this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
}
// Now that ports are listening, provision any required certificates
if (this.certManager) {
logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' });
@ -570,7 +571,10 @@ export class SmartProxy extends plugins.EventEmitter {
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
return this.routeUpdateLock.runExclusive(async () => {
try {
logger.log('info', `Updating routes (${newRoutes.length} routes)`, { routeCount: newRoutes.length, component: 'route-manager' });
logger.log('info', `Updating routes (${newRoutes.length} routes)`, {
routeCount: newRoutes.length,
component: 'route-manager'
});
} catch (error) {
// Silently handle logging errors
console.log(`[INFO] Updating routes (${newRoutes.length} routes)`);
@ -580,25 +584,60 @@ export class SmartProxy extends plugins.EventEmitter {
const oldPortUsage = this.updatePortUsageMap(this.settings.routes);
const newPortUsage = this.updatePortUsageMap(newRoutes);
// Get the lists of currently listening ports and new ports needed
const currentPorts = new Set(this.portManager.getListeningPorts());
const newPortsSet = new Set(newPortUsage.keys());
// Log the port usage for debugging
try {
logger.log('debug', `Current listening ports: ${Array.from(currentPorts).join(', ')}`, {
ports: Array.from(currentPorts),
component: 'smart-proxy'
});
logger.log('debug', `Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`, {
ports: Array.from(newPortsSet),
component: 'smart-proxy'
});
} catch (error) {
// Silently handle logging errors
console.log(`[DEBUG] Current listening ports: ${Array.from(currentPorts).join(', ')}`);
console.log(`[DEBUG] Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`);
}
// Find orphaned ports - ports that no longer have any routes
const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
// Find new ports that need binding
const currentPorts = new Set(this.portManager.getListeningPorts());
const newPortsSet = new Set(newPortUsage.keys());
// Find new ports that need binding (only ports that we aren't already listening on)
const newBindingPorts = Array.from(newPortsSet).filter(p => !currentPorts.has(p));
// Check for ACME challenge port to give it special handling
const acmePort = this.settings.acme?.port || 80;
const acmePortNeeded = newPortsSet.has(acmePort);
const acmePortListed = newBindingPorts.includes(acmePort);
if (acmePortNeeded && acmePortListed) {
try {
logger.log('info', `Adding ACME challenge port ${acmePort} to routes`, {
port: acmePort,
component: 'smart-proxy'
});
} catch (error) {
// Silently handle logging errors
console.log(`[INFO] Adding ACME challenge port ${acmePort} to routes`);
}
}
// Get existing routes that use NFTables
// Get existing routes that use NFTables and update them
const oldNfTablesRoutes = this.settings.routes.filter(
r => r.action.forwardingEngine === 'nftables'
);
// Get new routes that use NFTables
const newNfTablesRoutes = newRoutes.filter(
r => r.action.forwardingEngine === 'nftables'
);
// Find routes to remove, update, or add
// Update existing NFTables routes
for (const oldRoute of oldNfTablesRoutes) {
const newRoute = newNfTablesRoutes.find(r => r.name === oldRoute.name);
@ -611,7 +650,7 @@ export class SmartProxy extends plugins.EventEmitter {
}
}
// Find new routes to add
// Add new NFTables routes
for (const newRoute of newNfTablesRoutes) {
const oldRoute = oldNfTablesRoutes.find(r => r.name === newRoute.name);
@ -624,7 +663,7 @@ export class SmartProxy extends plugins.EventEmitter {
// Update routes in RouteManager
this.routeManager.updateRoutes(newRoutes);
// Release orphaned ports first
// Release orphaned ports first to free resources
if (orphanedPorts.length > 0) {
try {
logger.log('info', `Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`, {
@ -638,7 +677,7 @@ export class SmartProxy extends plugins.EventEmitter {
await this.portManager.removePorts(orphanedPorts);
}
// Add new ports
// Add new ports if needed
if (newBindingPorts.length > 0) {
try {
logger.log('info', `Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`, {
@ -649,7 +688,38 @@ export class SmartProxy extends plugins.EventEmitter {
// Silently handle logging errors
console.log(`[INFO] Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`);
}
await this.portManager.addPorts(newBindingPorts);
// Handle port binding with improved error recovery
try {
await this.portManager.addPorts(newBindingPorts);
} catch (error) {
// Special handling for port binding errors
// This provides better diagnostics for ACME challenge port conflicts
if ((error as any).code === 'EADDRINUSE') {
const port = (error as any).port || newBindingPorts[0];
const isAcmePort = port === acmePort;
if (isAcmePort) {
try {
logger.log('warn', `Could not bind to ACME challenge port ${port}. It may be in use by another application.`, {
port,
component: 'smart-proxy'
});
} catch (logError) {
console.log(`[WARN] Could not bind to ACME challenge port ${port}. It may be in use by another application.`);
}
// Re-throw with more helpful message
throw new Error(
`ACME challenge port ${port} is already in use by another application. ` +
`Configure a different port in settings.acme.port (e.g., 8080) or free up port ${port}.`
);
}
}
// Re-throw the original error for other cases
throw error;
}
}
// Update settings with the new routes