@push.rocks/smartproxy 19.3.13 → 19.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist_ts/proxies/smart-proxy/certificate-manager.d.ts +4 -0
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +127 -28
- package/dist_ts/proxies/smart-proxy/port-manager.js +30 -15
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +164 -43
- package/package.json +1 -1
- package/readme.plan.md +18 -18
- package/ts/proxies/smart-proxy/certificate-manager.ts +121 -33
- package/ts/proxies/smart-proxy/port-manager.ts +30 -16
- package/ts/proxies/smart-proxy/smart-proxy.ts +167 -46
|
@@ -93,6 +93,12 @@ export class SmartCertManager {
|
|
|
93
93
|
*/
|
|
94
94
|
public setUpdateRoutesCallback(callback: (routes: IRouteConfig[]) => Promise<void>): void {
|
|
95
95
|
this.updateRoutesCallback = callback;
|
|
96
|
+
try {
|
|
97
|
+
logger.log('debug', 'Route update callback set successfully', { component: 'certificate-manager' });
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// Silently handle logging errors
|
|
100
|
+
console.log('[DEBUG] Route update callback set successfully');
|
|
101
|
+
}
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
/**
|
|
@@ -395,17 +401,31 @@ export class SmartCertManager {
|
|
|
395
401
|
|
|
396
402
|
/**
|
|
397
403
|
* Add challenge route to SmartProxy
|
|
404
|
+
*
|
|
405
|
+
* This method adds a special route for ACME HTTP-01 challenges, which typically uses port 80.
|
|
406
|
+
* Since we may already be listening on port 80 for regular routes, we need to be
|
|
407
|
+
* careful about how we add this route to avoid binding conflicts.
|
|
398
408
|
*/
|
|
399
409
|
private async addChallengeRoute(): Promise<void> {
|
|
400
|
-
// Check with state manager first
|
|
410
|
+
// Check with state manager first - avoid duplication
|
|
401
411
|
if (this.acmeStateManager && this.acmeStateManager.isChallengeRouteActive()) {
|
|
402
|
-
|
|
412
|
+
try {
|
|
413
|
+
logger.log('info', 'Challenge route already active in global state, skipping', { component: 'certificate-manager' });
|
|
414
|
+
} catch (error) {
|
|
415
|
+
// Silently handle logging errors
|
|
416
|
+
console.log('[INFO] Challenge route already active in global state, skipping');
|
|
417
|
+
}
|
|
403
418
|
this.challengeRouteActive = true;
|
|
404
419
|
return;
|
|
405
420
|
}
|
|
406
421
|
|
|
407
422
|
if (this.challengeRouteActive) {
|
|
408
|
-
|
|
423
|
+
try {
|
|
424
|
+
logger.log('info', 'Challenge route already active locally, skipping', { component: 'certificate-manager' });
|
|
425
|
+
} catch (error) {
|
|
426
|
+
// Silently handle logging errors
|
|
427
|
+
console.log('[INFO] Challenge route already active locally, skipping');
|
|
428
|
+
}
|
|
409
429
|
return;
|
|
410
430
|
}
|
|
411
431
|
|
|
@@ -421,6 +441,7 @@ export class SmartCertManager {
|
|
|
421
441
|
const challengePort = this.globalAcmeDefaults?.port || 80;
|
|
422
442
|
|
|
423
443
|
// Check if any existing routes are already using this port
|
|
444
|
+
// This helps us determine if we need to create a new binding or can reuse existing one
|
|
424
445
|
const portInUseByRoutes = this.routes.some(route => {
|
|
425
446
|
const routePorts = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports];
|
|
426
447
|
return routePorts.some(p => {
|
|
@@ -434,19 +455,37 @@ export class SmartCertManager {
|
|
|
434
455
|
return false;
|
|
435
456
|
});
|
|
436
457
|
});
|
|
437
|
-
|
|
438
|
-
if (portInUseByRoutes) {
|
|
439
|
-
logger.log('info', `Port ${challengePort} is already used by another route, merging ACME challenge route`, {
|
|
440
|
-
port: challengePort,
|
|
441
|
-
component: 'certificate-manager'
|
|
442
|
-
});
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// Add the challenge route
|
|
446
|
-
const challengeRoute = this.challengeRoute;
|
|
447
|
-
|
|
458
|
+
|
|
448
459
|
try {
|
|
460
|
+
// Log whether port is already in use by other routes
|
|
461
|
+
if (portInUseByRoutes) {
|
|
462
|
+
try {
|
|
463
|
+
logger.log('info', `Port ${challengePort} is already used by another route, merging ACME challenge route`, {
|
|
464
|
+
port: challengePort,
|
|
465
|
+
component: 'certificate-manager'
|
|
466
|
+
});
|
|
467
|
+
} catch (error) {
|
|
468
|
+
// Silently handle logging errors
|
|
469
|
+
console.log(`[INFO] Port ${challengePort} is already used by another route, merging ACME challenge route`);
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
try {
|
|
473
|
+
logger.log('info', `Adding new ACME challenge route on port ${challengePort}`, {
|
|
474
|
+
port: challengePort,
|
|
475
|
+
component: 'certificate-manager'
|
|
476
|
+
});
|
|
477
|
+
} catch (error) {
|
|
478
|
+
// Silently handle logging errors
|
|
479
|
+
console.log(`[INFO] Adding new ACME challenge route on port ${challengePort}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Add the challenge route to the existing routes
|
|
484
|
+
const challengeRoute = this.challengeRoute;
|
|
449
485
|
const updatedRoutes = [...this.routes, challengeRoute];
|
|
486
|
+
|
|
487
|
+
// With the re-ordering of start(), port binding should already be done
|
|
488
|
+
// This updateRoutes call should just add the route without binding again
|
|
450
489
|
await this.updateRoutesCallback(updatedRoutes);
|
|
451
490
|
this.challengeRouteActive = true;
|
|
452
491
|
|
|
@@ -455,29 +494,63 @@ export class SmartCertManager {
|
|
|
455
494
|
this.acmeStateManager.addChallengeRoute(challengeRoute);
|
|
456
495
|
}
|
|
457
496
|
|
|
458
|
-
|
|
497
|
+
try {
|
|
498
|
+
logger.log('info', 'ACME challenge route successfully added', { component: 'certificate-manager' });
|
|
499
|
+
} catch (error) {
|
|
500
|
+
// Silently handle logging errors
|
|
501
|
+
console.log('[INFO] ACME challenge route successfully added');
|
|
502
|
+
}
|
|
459
503
|
} catch (error) {
|
|
460
|
-
//
|
|
504
|
+
// Enhanced error handling based on error type
|
|
461
505
|
if ((error as any).code === 'EADDRINUSE') {
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
506
|
+
try {
|
|
507
|
+
logger.log('warn', `Challenge port ${challengePort} is unavailable - it's already in use by another process. Consider configuring a different ACME port.`, {
|
|
508
|
+
port: challengePort,
|
|
509
|
+
error: (error as Error).message,
|
|
510
|
+
component: 'certificate-manager'
|
|
511
|
+
});
|
|
512
|
+
} catch (logError) {
|
|
513
|
+
// Silently handle logging errors
|
|
514
|
+
console.log(`[WARN] Challenge port ${challengePort} is unavailable - it's already in use by another process. Consider configuring a different ACME port.`);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Provide a more informative and actionable error message
|
|
518
|
+
throw new Error(
|
|
519
|
+
`ACME HTTP-01 challenge port ${challengePort} is already in use by another process. ` +
|
|
520
|
+
`Please configure a different port using the acme.port setting (e.g., 8080).`
|
|
521
|
+
);
|
|
522
|
+
} else if (error.message && error.message.includes('EADDRINUSE')) {
|
|
523
|
+
// Some Node.js versions embed the error code in the message rather than the code property
|
|
524
|
+
try {
|
|
525
|
+
logger.log('warn', `Port ${challengePort} conflict detected: ${error.message}`, {
|
|
526
|
+
port: challengePort,
|
|
527
|
+
component: 'certificate-manager'
|
|
528
|
+
});
|
|
529
|
+
} catch (logError) {
|
|
530
|
+
// Silently handle logging errors
|
|
531
|
+
console.log(`[WARN] Port ${challengePort} conflict detected: ${error.message}`);
|
|
532
|
+
}
|
|
467
533
|
|
|
468
|
-
//
|
|
534
|
+
// More detailed error message with suggestions
|
|
469
535
|
throw new Error(
|
|
470
|
-
`
|
|
471
|
-
`
|
|
472
|
-
`
|
|
536
|
+
`ACME HTTP challenge port ${challengePort} conflict detected. ` +
|
|
537
|
+
`To resolve this issue, try one of these approaches:\n` +
|
|
538
|
+
`1. Configure a different port in ACME settings (acme.port)\n` +
|
|
539
|
+
`2. Add a regular route that uses port ${challengePort} before initializing the certificate manager\n` +
|
|
540
|
+
`3. Stop any other services that might be using port ${challengePort}`
|
|
473
541
|
);
|
|
474
542
|
}
|
|
475
543
|
|
|
476
|
-
// Log and rethrow other errors
|
|
477
|
-
|
|
478
|
-
error: error.message
|
|
479
|
-
|
|
480
|
-
|
|
544
|
+
// Log and rethrow other types of errors
|
|
545
|
+
try {
|
|
546
|
+
logger.log('error', `Failed to add challenge route: ${(error as Error).message}`, {
|
|
547
|
+
error: (error as Error).message,
|
|
548
|
+
component: 'certificate-manager'
|
|
549
|
+
});
|
|
550
|
+
} catch (logError) {
|
|
551
|
+
// Silently handle logging errors
|
|
552
|
+
console.log(`[ERROR] Failed to add challenge route: ${(error as Error).message}`);
|
|
553
|
+
}
|
|
481
554
|
throw error;
|
|
482
555
|
}
|
|
483
556
|
}
|
|
@@ -487,7 +560,12 @@ export class SmartCertManager {
|
|
|
487
560
|
*/
|
|
488
561
|
private async removeChallengeRoute(): Promise<void> {
|
|
489
562
|
if (!this.challengeRouteActive) {
|
|
490
|
-
|
|
563
|
+
try {
|
|
564
|
+
logger.log('info', 'Challenge route not active, skipping removal', { component: 'certificate-manager' });
|
|
565
|
+
} catch (error) {
|
|
566
|
+
// Silently handle logging errors
|
|
567
|
+
console.log('[INFO] Challenge route not active, skipping removal');
|
|
568
|
+
}
|
|
491
569
|
return;
|
|
492
570
|
}
|
|
493
571
|
|
|
@@ -505,9 +583,19 @@ export class SmartCertManager {
|
|
|
505
583
|
this.acmeStateManager.removeChallengeRoute('acme-challenge');
|
|
506
584
|
}
|
|
507
585
|
|
|
508
|
-
|
|
586
|
+
try {
|
|
587
|
+
logger.log('info', 'ACME challenge route successfully removed', { component: 'certificate-manager' });
|
|
588
|
+
} catch (error) {
|
|
589
|
+
// Silently handle logging errors
|
|
590
|
+
console.log('[INFO] ACME challenge route successfully removed');
|
|
591
|
+
}
|
|
509
592
|
} catch (error) {
|
|
510
|
-
|
|
593
|
+
try {
|
|
594
|
+
logger.log('error', `Failed to remove challenge route: ${error.message}`, { error: error.message, component: 'certificate-manager' });
|
|
595
|
+
} catch (logError) {
|
|
596
|
+
// Silently handle logging errors
|
|
597
|
+
console.log(`[ERROR] Failed to remove challenge route: ${error.message}`);
|
|
598
|
+
}
|
|
511
599
|
// Reset the flag even on error to avoid getting stuck
|
|
512
600
|
this.challengeRouteActive = false;
|
|
513
601
|
throw error;
|
|
@@ -46,10 +46,14 @@ export class PortManager {
|
|
|
46
46
|
if (this.servers.has(port)) {
|
|
47
47
|
// Port is already bound, just increment the reference count
|
|
48
48
|
this.incrementPortRefCount(port);
|
|
49
|
-
|
|
50
|
-
port,
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
try {
|
|
50
|
+
logger.log('debug', `PortManager: Port ${port} is already bound by SmartProxy, reusing binding`, {
|
|
51
|
+
port,
|
|
52
|
+
component: 'port-manager'
|
|
53
|
+
});
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.log(`[DEBUG] PortManager: Port ${port} is already bound by SmartProxy, reusing binding`);
|
|
56
|
+
}
|
|
53
57
|
return;
|
|
54
58
|
}
|
|
55
59
|
|
|
@@ -68,24 +72,34 @@ export class PortManager {
|
|
|
68
72
|
// Delegate to route connection handler
|
|
69
73
|
this.routeConnectionHandler.handleConnection(socket);
|
|
70
74
|
}).on('error', (err: Error) => {
|
|
71
|
-
|
|
72
|
-
port
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
75
|
+
try {
|
|
76
|
+
logger.log('error', `Server Error on port ${port}: ${err.message}`, {
|
|
77
|
+
port,
|
|
78
|
+
error: err.message,
|
|
79
|
+
component: 'port-manager'
|
|
80
|
+
});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error(`[ERROR] Server Error on port ${port}: ${err.message}`);
|
|
83
|
+
}
|
|
76
84
|
});
|
|
77
85
|
|
|
78
86
|
// Start listening on the port
|
|
79
87
|
return new Promise<void>((resolve, reject) => {
|
|
80
88
|
server.listen(port, () => {
|
|
81
89
|
const isHttpProxyPort = this.settings.useHttpProxy?.includes(port);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
try {
|
|
91
|
+
logger.log('info', `SmartProxy -> OK: Now listening on port ${port}${
|
|
92
|
+
isHttpProxyPort ? ' (HttpProxy forwarding enabled)' : ''
|
|
93
|
+
}`, {
|
|
94
|
+
port,
|
|
95
|
+
isHttpProxyPort: !!isHttpProxyPort,
|
|
96
|
+
component: 'port-manager'
|
|
97
|
+
});
|
|
98
|
+
} catch (e) {
|
|
99
|
+
console.log(`[INFO] SmartProxy -> OK: Now listening on port ${port}${
|
|
100
|
+
isHttpProxyPort ? ' (HttpProxy forwarding enabled)' : ''
|
|
101
|
+
}`);
|
|
102
|
+
}
|
|
89
103
|
|
|
90
104
|
// Store the server reference
|
|
91
105
|
this.servers.set(port, server);
|
|
@@ -313,21 +313,6 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
313
313
|
return;
|
|
314
314
|
}
|
|
315
315
|
|
|
316
|
-
// Initialize certificate manager before starting servers
|
|
317
|
-
await this.initializeCertificateManager();
|
|
318
|
-
|
|
319
|
-
// Initialize and start HttpProxy if needed
|
|
320
|
-
if (this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
|
|
321
|
-
await this.httpProxyBridge.initialize();
|
|
322
|
-
|
|
323
|
-
// Connect HttpProxy with certificate manager
|
|
324
|
-
if (this.certManager) {
|
|
325
|
-
this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
await this.httpProxyBridge.start();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
316
|
// Validate the route configuration
|
|
332
317
|
const configWarnings = this.routeManager.validateConfiguration();
|
|
333
318
|
|
|
@@ -362,9 +347,25 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
362
347
|
}
|
|
363
348
|
}
|
|
364
349
|
|
|
365
|
-
//
|
|
350
|
+
// Initialize and start HttpProxy if needed - before port binding
|
|
351
|
+
if (this.settings.useHttpProxy && this.settings.useHttpProxy.length > 0) {
|
|
352
|
+
await this.httpProxyBridge.initialize();
|
|
353
|
+
await this.httpProxyBridge.start();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Start port listeners using the PortManager BEFORE initializing certificate manager
|
|
357
|
+
// This ensures all required ports are bound and ready when adding ACME challenge routes
|
|
366
358
|
await this.portManager.addPorts(listeningPorts);
|
|
367
359
|
|
|
360
|
+
// Initialize certificate manager AFTER port binding is complete
|
|
361
|
+
// This ensures the ACME challenge port is already bound and ready when needed
|
|
362
|
+
await this.initializeCertificateManager();
|
|
363
|
+
|
|
364
|
+
// Connect certificate manager with HttpProxy if both are available
|
|
365
|
+
if (this.certManager && this.httpProxyBridge.getHttpProxy()) {
|
|
366
|
+
this.certManager.setHttpProxy(this.httpProxyBridge.getHttpProxy());
|
|
367
|
+
}
|
|
368
|
+
|
|
368
369
|
// Now that ports are listening, provision any required certificates
|
|
369
370
|
if (this.certManager) {
|
|
370
371
|
logger.log('info', 'Starting certificate provisioning now that ports are ready', { component: 'certificate-manager' });
|
|
@@ -521,7 +522,12 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
521
522
|
const challengeRouteExists = this.settings.routes.some(r => r.name === 'acme-challenge');
|
|
522
523
|
|
|
523
524
|
if (!challengeRouteExists) {
|
|
524
|
-
|
|
525
|
+
try {
|
|
526
|
+
logger.log('info', 'Challenge route successfully removed from routes');
|
|
527
|
+
} catch (error) {
|
|
528
|
+
// Silently handle logging errors
|
|
529
|
+
console.log('[INFO] Challenge route successfully removed from routes');
|
|
530
|
+
}
|
|
525
531
|
return;
|
|
526
532
|
}
|
|
527
533
|
|
|
@@ -530,7 +536,12 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
530
536
|
}
|
|
531
537
|
|
|
532
538
|
const error = `Failed to verify challenge route removal after ${maxRetries} attempts`;
|
|
533
|
-
|
|
539
|
+
try {
|
|
540
|
+
logger.log('error', error);
|
|
541
|
+
} catch (logError) {
|
|
542
|
+
// Silently handle logging errors
|
|
543
|
+
console.log(`[ERROR] ${error}`);
|
|
544
|
+
}
|
|
534
545
|
throw new Error(error);
|
|
535
546
|
}
|
|
536
547
|
|
|
@@ -559,31 +570,74 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
559
570
|
*/
|
|
560
571
|
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
|
|
561
572
|
return this.routeUpdateLock.runExclusive(async () => {
|
|
562
|
-
|
|
573
|
+
try {
|
|
574
|
+
logger.log('info', `Updating routes (${newRoutes.length} routes)`, {
|
|
575
|
+
routeCount: newRoutes.length,
|
|
576
|
+
component: 'route-manager'
|
|
577
|
+
});
|
|
578
|
+
} catch (error) {
|
|
579
|
+
// Silently handle logging errors
|
|
580
|
+
console.log(`[INFO] Updating routes (${newRoutes.length} routes)`);
|
|
581
|
+
}
|
|
563
582
|
|
|
564
583
|
// Track port usage before and after updates
|
|
565
584
|
const oldPortUsage = this.updatePortUsageMap(this.settings.routes);
|
|
566
585
|
const newPortUsage = this.updatePortUsageMap(newRoutes);
|
|
567
586
|
|
|
587
|
+
// Get the lists of currently listening ports and new ports needed
|
|
588
|
+
const currentPorts = new Set(this.portManager.getListeningPorts());
|
|
589
|
+
const newPortsSet = new Set(newPortUsage.keys());
|
|
590
|
+
|
|
591
|
+
// Log the port usage for debugging
|
|
592
|
+
try {
|
|
593
|
+
logger.log('debug', `Current listening ports: ${Array.from(currentPorts).join(', ')}`, {
|
|
594
|
+
ports: Array.from(currentPorts),
|
|
595
|
+
component: 'smart-proxy'
|
|
596
|
+
});
|
|
597
|
+
|
|
598
|
+
logger.log('debug', `Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`, {
|
|
599
|
+
ports: Array.from(newPortsSet),
|
|
600
|
+
component: 'smart-proxy'
|
|
601
|
+
});
|
|
602
|
+
} catch (error) {
|
|
603
|
+
// Silently handle logging errors
|
|
604
|
+
console.log(`[DEBUG] Current listening ports: ${Array.from(currentPorts).join(', ')}`);
|
|
605
|
+
console.log(`[DEBUG] Ports needed for new routes: ${Array.from(newPortsSet).join(', ')}`);
|
|
606
|
+
}
|
|
607
|
+
|
|
568
608
|
// Find orphaned ports - ports that no longer have any routes
|
|
569
609
|
const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
|
|
570
610
|
|
|
571
|
-
// Find new ports that need binding
|
|
572
|
-
const currentPorts = new Set(this.portManager.getListeningPorts());
|
|
573
|
-
const newPortsSet = new Set(newPortUsage.keys());
|
|
611
|
+
// Find new ports that need binding (only ports that we aren't already listening on)
|
|
574
612
|
const newBindingPorts = Array.from(newPortsSet).filter(p => !currentPorts.has(p));
|
|
613
|
+
|
|
614
|
+
// Check for ACME challenge port to give it special handling
|
|
615
|
+
const acmePort = this.settings.acme?.port || 80;
|
|
616
|
+
const acmePortNeeded = newPortsSet.has(acmePort);
|
|
617
|
+
const acmePortListed = newBindingPorts.includes(acmePort);
|
|
618
|
+
|
|
619
|
+
if (acmePortNeeded && acmePortListed) {
|
|
620
|
+
try {
|
|
621
|
+
logger.log('info', `Adding ACME challenge port ${acmePort} to routes`, {
|
|
622
|
+
port: acmePort,
|
|
623
|
+
component: 'smart-proxy'
|
|
624
|
+
});
|
|
625
|
+
} catch (error) {
|
|
626
|
+
// Silently handle logging errors
|
|
627
|
+
console.log(`[INFO] Adding ACME challenge port ${acmePort} to routes`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
575
630
|
|
|
576
|
-
// Get existing routes that use NFTables
|
|
631
|
+
// Get existing routes that use NFTables and update them
|
|
577
632
|
const oldNfTablesRoutes = this.settings.routes.filter(
|
|
578
633
|
r => r.action.forwardingEngine === 'nftables'
|
|
579
634
|
);
|
|
580
635
|
|
|
581
|
-
// Get new routes that use NFTables
|
|
582
636
|
const newNfTablesRoutes = newRoutes.filter(
|
|
583
637
|
r => r.action.forwardingEngine === 'nftables'
|
|
584
638
|
);
|
|
585
639
|
|
|
586
|
-
//
|
|
640
|
+
// Update existing NFTables routes
|
|
587
641
|
for (const oldRoute of oldNfTablesRoutes) {
|
|
588
642
|
const newRoute = newNfTablesRoutes.find(r => r.name === oldRoute.name);
|
|
589
643
|
|
|
@@ -596,7 +650,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
596
650
|
}
|
|
597
651
|
}
|
|
598
652
|
|
|
599
|
-
//
|
|
653
|
+
// Add new NFTables routes
|
|
600
654
|
for (const newRoute of newNfTablesRoutes) {
|
|
601
655
|
const oldRoute = oldNfTablesRoutes.find(r => r.name === newRoute.name);
|
|
602
656
|
|
|
@@ -609,22 +663,63 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
609
663
|
// Update routes in RouteManager
|
|
610
664
|
this.routeManager.updateRoutes(newRoutes);
|
|
611
665
|
|
|
612
|
-
// Release orphaned ports first
|
|
666
|
+
// Release orphaned ports first to free resources
|
|
613
667
|
if (orphanedPorts.length > 0) {
|
|
614
|
-
|
|
615
|
-
ports: orphanedPorts,
|
|
616
|
-
|
|
617
|
-
|
|
668
|
+
try {
|
|
669
|
+
logger.log('info', `Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`, {
|
|
670
|
+
ports: orphanedPorts,
|
|
671
|
+
component: 'smart-proxy'
|
|
672
|
+
});
|
|
673
|
+
} catch (error) {
|
|
674
|
+
// Silently handle logging errors
|
|
675
|
+
console.log(`[INFO] Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`);
|
|
676
|
+
}
|
|
618
677
|
await this.portManager.removePorts(orphanedPorts);
|
|
619
678
|
}
|
|
620
679
|
|
|
621
|
-
// Add new ports
|
|
680
|
+
// Add new ports if needed
|
|
622
681
|
if (newBindingPorts.length > 0) {
|
|
623
|
-
|
|
624
|
-
ports: newBindingPorts,
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
682
|
+
try {
|
|
683
|
+
logger.log('info', `Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`, {
|
|
684
|
+
ports: newBindingPorts,
|
|
685
|
+
component: 'smart-proxy'
|
|
686
|
+
});
|
|
687
|
+
} catch (error) {
|
|
688
|
+
// Silently handle logging errors
|
|
689
|
+
console.log(`[INFO] Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Handle port binding with improved error recovery
|
|
693
|
+
try {
|
|
694
|
+
await this.portManager.addPorts(newBindingPorts);
|
|
695
|
+
} catch (error) {
|
|
696
|
+
// Special handling for port binding errors
|
|
697
|
+
// This provides better diagnostics for ACME challenge port conflicts
|
|
698
|
+
if ((error as any).code === 'EADDRINUSE') {
|
|
699
|
+
const port = (error as any).port || newBindingPorts[0];
|
|
700
|
+
const isAcmePort = port === acmePort;
|
|
701
|
+
|
|
702
|
+
if (isAcmePort) {
|
|
703
|
+
try {
|
|
704
|
+
logger.log('warn', `Could not bind to ACME challenge port ${port}. It may be in use by another application.`, {
|
|
705
|
+
port,
|
|
706
|
+
component: 'smart-proxy'
|
|
707
|
+
});
|
|
708
|
+
} catch (logError) {
|
|
709
|
+
console.log(`[WARN] Could not bind to ACME challenge port ${port}. It may be in use by another application.`);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Re-throw with more helpful message
|
|
713
|
+
throw new Error(
|
|
714
|
+
`ACME challenge port ${port} is already in use by another application. ` +
|
|
715
|
+
`Configure a different port in settings.acme.port (e.g., 8080) or free up port ${port}.`
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Re-throw the original error for other cases
|
|
721
|
+
throw error;
|
|
722
|
+
}
|
|
628
723
|
}
|
|
629
724
|
|
|
630
725
|
// Update settings with the new routes
|
|
@@ -646,6 +741,22 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
646
741
|
// Store global state before stopping
|
|
647
742
|
this.globalChallengeRouteActive = existingState.challengeRouteActive;
|
|
648
743
|
|
|
744
|
+
// Only stop the cert manager if absolutely necessary
|
|
745
|
+
// First check if there's an ACME route on the same port already
|
|
746
|
+
const acmePort = existingAcmeOptions?.port || 80;
|
|
747
|
+
const acmePortInUse = newPortUsage.has(acmePort) && newPortUsage.get(acmePort)!.size > 0;
|
|
748
|
+
|
|
749
|
+
try {
|
|
750
|
+
logger.log('debug', `ACME port ${acmePort} ${acmePortInUse ? 'is' : 'is not'} already in use by other routes`, {
|
|
751
|
+
port: acmePort,
|
|
752
|
+
inUse: acmePortInUse,
|
|
753
|
+
component: 'smart-proxy'
|
|
754
|
+
});
|
|
755
|
+
} catch (error) {
|
|
756
|
+
// Silently handle logging errors
|
|
757
|
+
console.log(`[DEBUG] ACME port ${acmePort} ${acmePortInUse ? 'is' : 'is not'} already in use by other routes`);
|
|
758
|
+
}
|
|
759
|
+
|
|
649
760
|
await this.certManager.stop();
|
|
650
761
|
|
|
651
762
|
// Verify the challenge route has been properly removed
|
|
@@ -721,11 +832,16 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
721
832
|
|
|
722
833
|
// Log port usage for debugging
|
|
723
834
|
for (const [port, routes] of portUsage.entries()) {
|
|
724
|
-
|
|
725
|
-
port,
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
835
|
+
try {
|
|
836
|
+
logger.log('debug', `Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`, {
|
|
837
|
+
port,
|
|
838
|
+
routeCount: routes.size,
|
|
839
|
+
component: 'smart-proxy'
|
|
840
|
+
});
|
|
841
|
+
} catch (error) {
|
|
842
|
+
// Silently handle logging errors
|
|
843
|
+
console.log(`[DEBUG] Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`);
|
|
844
|
+
}
|
|
729
845
|
}
|
|
730
846
|
|
|
731
847
|
return portUsage;
|
|
@@ -740,10 +856,15 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
740
856
|
for (const [port, routes] of oldUsage.entries()) {
|
|
741
857
|
if (!newUsage.has(port) || newUsage.get(port)!.size === 0) {
|
|
742
858
|
orphanedPorts.push(port);
|
|
743
|
-
|
|
744
|
-
port,
|
|
745
|
-
|
|
746
|
-
|
|
859
|
+
try {
|
|
860
|
+
logger.log('info', `Port ${port} no longer has any associated routes, will be released`, {
|
|
861
|
+
port,
|
|
862
|
+
component: 'smart-proxy'
|
|
863
|
+
});
|
|
864
|
+
} catch (error) {
|
|
865
|
+
// Silently handle logging errors
|
|
866
|
+
console.log(`[INFO] Port ${port} no longer has any associated routes, will be released`);
|
|
867
|
+
}
|
|
747
868
|
}
|
|
748
869
|
}
|
|
749
870
|
|