@push.rocks/smartproxy 19.3.12 → 19.3.13
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/00_commitinfo_data.js +1 -1
- package/dist_ts/proxies/smart-proxy/certificate-manager.js +40 -3
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +39 -0
- package/dist_ts/proxies/smart-proxy/port-manager.js +149 -9
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +12 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +101 -5
- package/package.json +1 -1
- package/readme.plan.md +384 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/proxies/smart-proxy/certificate-manager.ts +46 -2
- package/ts/proxies/smart-proxy/port-manager.ts +169 -12
- package/ts/proxies/smart-proxy/smart-proxy.ts +117 -5
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartproxy',
|
|
6
|
-
version: '19.3.
|
|
6
|
+
version: '19.3.13',
|
|
7
7
|
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.'
|
|
8
8
|
}
|
|
@@ -416,6 +416,33 @@ export class SmartCertManager {
|
|
|
416
416
|
if (!this.challengeRoute) {
|
|
417
417
|
throw new Error('Challenge route not initialized');
|
|
418
418
|
}
|
|
419
|
+
|
|
420
|
+
// Get the challenge port
|
|
421
|
+
const challengePort = this.globalAcmeDefaults?.port || 80;
|
|
422
|
+
|
|
423
|
+
// Check if any existing routes are already using this port
|
|
424
|
+
const portInUseByRoutes = this.routes.some(route => {
|
|
425
|
+
const routePorts = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports];
|
|
426
|
+
return routePorts.some(p => {
|
|
427
|
+
// Handle both number and port range objects
|
|
428
|
+
if (typeof p === 'number') {
|
|
429
|
+
return p === challengePort;
|
|
430
|
+
} else if (typeof p === 'object' && 'from' in p && 'to' in p) {
|
|
431
|
+
// Port range case - check if challengePort is in range
|
|
432
|
+
return challengePort >= p.from && challengePort <= p.to;
|
|
433
|
+
}
|
|
434
|
+
return false;
|
|
435
|
+
});
|
|
436
|
+
});
|
|
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
|
|
419
446
|
const challengeRoute = this.challengeRoute;
|
|
420
447
|
|
|
421
448
|
try {
|
|
@@ -430,10 +457,27 @@ export class SmartCertManager {
|
|
|
430
457
|
|
|
431
458
|
logger.log('info', 'ACME challenge route successfully added', { component: 'certificate-manager' });
|
|
432
459
|
} catch (error) {
|
|
433
|
-
|
|
460
|
+
// Handle specific EADDRINUSE errors differently based on whether it's an internal conflict
|
|
434
461
|
if ((error as any).code === 'EADDRINUSE') {
|
|
435
|
-
|
|
462
|
+
logger.log('error', `Failed to add challenge route on port ${challengePort}: ${error.message}`, {
|
|
463
|
+
error: error.message,
|
|
464
|
+
port: challengePort,
|
|
465
|
+
component: 'certificate-manager'
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Provide a more informative error message
|
|
469
|
+
throw new Error(
|
|
470
|
+
`Port ${challengePort} is already in use. ` +
|
|
471
|
+
`If it's in use by an external process, configure a different port in the ACME settings. ` +
|
|
472
|
+
`If it's in use by SmartProxy, there may be a route configuration issue.`
|
|
473
|
+
);
|
|
436
474
|
}
|
|
475
|
+
|
|
476
|
+
// Log and rethrow other errors
|
|
477
|
+
logger.log('error', `Failed to add challenge route: ${error.message}`, {
|
|
478
|
+
error: error.message,
|
|
479
|
+
component: 'certificate-manager'
|
|
480
|
+
});
|
|
437
481
|
throw error;
|
|
438
482
|
}
|
|
439
483
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as plugins from '../../plugins.js';
|
|
2
2
|
import type { ISmartProxyOptions } from './models/interfaces.js';
|
|
3
3
|
import { RouteConnectionHandler } from './route-connection-handler.js';
|
|
4
|
+
import { logger } from '../../core/utils/logger.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* PortManager handles the dynamic creation and removal of port listeners
|
|
@@ -8,12 +9,17 @@ import { RouteConnectionHandler } from './route-connection-handler.js';
|
|
|
8
9
|
* This class provides methods to add and remove listening ports at runtime,
|
|
9
10
|
* allowing SmartProxy to adapt to configuration changes without requiring
|
|
10
11
|
* a full restart.
|
|
12
|
+
*
|
|
13
|
+
* It includes a reference counting system to track how many routes are using
|
|
14
|
+
* each port, so ports can be automatically released when they are no longer needed.
|
|
11
15
|
*/
|
|
12
16
|
export class PortManager {
|
|
13
17
|
private servers: Map<number, plugins.net.Server> = new Map();
|
|
14
18
|
private settings: ISmartProxyOptions;
|
|
15
19
|
private routeConnectionHandler: RouteConnectionHandler;
|
|
16
20
|
private isShuttingDown: boolean = false;
|
|
21
|
+
// Track how many routes are using each port
|
|
22
|
+
private portRefCounts: Map<number, number> = new Map();
|
|
17
23
|
|
|
18
24
|
/**
|
|
19
25
|
* Create a new PortManager
|
|
@@ -38,10 +44,18 @@ export class PortManager {
|
|
|
38
44
|
public async addPort(port: number): Promise<void> {
|
|
39
45
|
// Check if we're already listening on this port
|
|
40
46
|
if (this.servers.has(port)) {
|
|
41
|
-
|
|
47
|
+
// Port is already bound, just increment the reference count
|
|
48
|
+
this.incrementPortRefCount(port);
|
|
49
|
+
logger.log('debug', `PortManager: Port ${port} is already bound by SmartProxy, reusing binding`, {
|
|
50
|
+
port,
|
|
51
|
+
component: 'port-manager'
|
|
52
|
+
});
|
|
42
53
|
return;
|
|
43
54
|
}
|
|
44
55
|
|
|
56
|
+
// Initialize reference count for new port
|
|
57
|
+
this.portRefCounts.set(port, 1);
|
|
58
|
+
|
|
45
59
|
// Create a server for this port
|
|
46
60
|
const server = plugins.net.createServer((socket) => {
|
|
47
61
|
// Check if shutting down
|
|
@@ -54,24 +68,56 @@ export class PortManager {
|
|
|
54
68
|
// Delegate to route connection handler
|
|
55
69
|
this.routeConnectionHandler.handleConnection(socket);
|
|
56
70
|
}).on('error', (err: Error) => {
|
|
57
|
-
|
|
71
|
+
logger.log('error', `Server Error on port ${port}: ${err.message}`, {
|
|
72
|
+
port,
|
|
73
|
+
error: err.message,
|
|
74
|
+
component: 'port-manager'
|
|
75
|
+
});
|
|
58
76
|
});
|
|
59
77
|
|
|
60
78
|
// Start listening on the port
|
|
61
79
|
return new Promise<void>((resolve, reject) => {
|
|
62
80
|
server.listen(port, () => {
|
|
63
81
|
const isHttpProxyPort = this.settings.useHttpProxy?.includes(port);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
82
|
+
logger.log('info', `SmartProxy -> OK: Now listening on port ${port}${
|
|
83
|
+
isHttpProxyPort ? ' (HttpProxy forwarding enabled)' : ''
|
|
84
|
+
}`, {
|
|
85
|
+
port,
|
|
86
|
+
isHttpProxyPort: !!isHttpProxyPort,
|
|
87
|
+
component: 'port-manager'
|
|
88
|
+
});
|
|
69
89
|
|
|
70
90
|
// Store the server reference
|
|
71
91
|
this.servers.set(port, server);
|
|
72
92
|
resolve();
|
|
73
93
|
}).on('error', (err) => {
|
|
74
|
-
|
|
94
|
+
// Check if this is an external conflict
|
|
95
|
+
const { isConflict, isExternal } = this.isPortConflict(err);
|
|
96
|
+
|
|
97
|
+
if (isConflict && !isExternal) {
|
|
98
|
+
// This is an internal conflict (port already bound by SmartProxy)
|
|
99
|
+
// This shouldn't normally happen because we check servers.has(port) above
|
|
100
|
+
logger.log('warn', `Port ${port} binding conflict: already in use by SmartProxy`, {
|
|
101
|
+
port,
|
|
102
|
+
component: 'port-manager'
|
|
103
|
+
});
|
|
104
|
+
// Still increment reference count to maintain tracking
|
|
105
|
+
this.incrementPortRefCount(port);
|
|
106
|
+
resolve();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Log the error and propagate it
|
|
111
|
+
logger.log('error', `Failed to listen on port ${port}: ${err.message}`, {
|
|
112
|
+
port,
|
|
113
|
+
error: err.message,
|
|
114
|
+
code: (err as any).code,
|
|
115
|
+
component: 'port-manager'
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Clean up reference count since binding failed
|
|
119
|
+
this.portRefCounts.delete(port);
|
|
120
|
+
|
|
75
121
|
reject(err);
|
|
76
122
|
});
|
|
77
123
|
});
|
|
@@ -84,10 +130,28 @@ export class PortManager {
|
|
|
84
130
|
* @returns Promise that resolves when the server is closed
|
|
85
131
|
*/
|
|
86
132
|
public async removePort(port: number): Promise<void> {
|
|
133
|
+
// Decrement the reference count first
|
|
134
|
+
const newRefCount = this.decrementPortRefCount(port);
|
|
135
|
+
|
|
136
|
+
// If there are still references to this port, keep it open
|
|
137
|
+
if (newRefCount > 0) {
|
|
138
|
+
logger.log('debug', `PortManager: Port ${port} still has ${newRefCount} references, keeping open`, {
|
|
139
|
+
port,
|
|
140
|
+
refCount: newRefCount,
|
|
141
|
+
component: 'port-manager'
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
87
146
|
// Get the server for this port
|
|
88
147
|
const server = this.servers.get(port);
|
|
89
148
|
if (!server) {
|
|
90
|
-
|
|
149
|
+
logger.log('warn', `PortManager: Not listening on port ${port}`, {
|
|
150
|
+
port,
|
|
151
|
+
component: 'port-manager'
|
|
152
|
+
});
|
|
153
|
+
// Ensure reference count is reset
|
|
154
|
+
this.portRefCounts.delete(port);
|
|
91
155
|
return;
|
|
92
156
|
}
|
|
93
157
|
|
|
@@ -95,13 +159,21 @@ export class PortManager {
|
|
|
95
159
|
return new Promise<void>((resolve) => {
|
|
96
160
|
server.close((err) => {
|
|
97
161
|
if (err) {
|
|
98
|
-
|
|
162
|
+
logger.log('error', `Error closing server on port ${port}: ${err.message}`, {
|
|
163
|
+
port,
|
|
164
|
+
error: err.message,
|
|
165
|
+
component: 'port-manager'
|
|
166
|
+
});
|
|
99
167
|
} else {
|
|
100
|
-
|
|
168
|
+
logger.log('info', `SmartProxy -> Stopped listening on port ${port}`, {
|
|
169
|
+
port,
|
|
170
|
+
component: 'port-manager'
|
|
171
|
+
});
|
|
101
172
|
}
|
|
102
173
|
|
|
103
|
-
// Remove the server reference
|
|
174
|
+
// Remove the server reference and clean up reference counting
|
|
104
175
|
this.servers.delete(port);
|
|
176
|
+
this.portRefCounts.delete(port);
|
|
105
177
|
resolve();
|
|
106
178
|
});
|
|
107
179
|
});
|
|
@@ -192,4 +264,89 @@ export class PortManager {
|
|
|
192
264
|
public getServers(): Map<number, plugins.net.Server> {
|
|
193
265
|
return new Map(this.servers);
|
|
194
266
|
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Check if a port is bound by this SmartProxy instance
|
|
270
|
+
*
|
|
271
|
+
* @param port The port number to check
|
|
272
|
+
* @returns True if the port is currently bound by SmartProxy
|
|
273
|
+
*/
|
|
274
|
+
public isPortBoundBySmartProxy(port: number): boolean {
|
|
275
|
+
return this.servers.has(port);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get the current reference count for a port
|
|
280
|
+
*
|
|
281
|
+
* @param port The port number to check
|
|
282
|
+
* @returns The number of routes using this port, 0 if none
|
|
283
|
+
*/
|
|
284
|
+
public getPortRefCount(port: number): number {
|
|
285
|
+
return this.portRefCounts.get(port) || 0;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Increment the reference count for a port
|
|
290
|
+
*
|
|
291
|
+
* @param port The port number to increment
|
|
292
|
+
* @returns The new reference count
|
|
293
|
+
*/
|
|
294
|
+
public incrementPortRefCount(port: number): number {
|
|
295
|
+
const currentCount = this.portRefCounts.get(port) || 0;
|
|
296
|
+
const newCount = currentCount + 1;
|
|
297
|
+
this.portRefCounts.set(port, newCount);
|
|
298
|
+
|
|
299
|
+
logger.log('debug', `Port ${port} reference count increased to ${newCount}`, {
|
|
300
|
+
port,
|
|
301
|
+
refCount: newCount,
|
|
302
|
+
component: 'port-manager'
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return newCount;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Decrement the reference count for a port
|
|
310
|
+
*
|
|
311
|
+
* @param port The port number to decrement
|
|
312
|
+
* @returns The new reference count
|
|
313
|
+
*/
|
|
314
|
+
public decrementPortRefCount(port: number): number {
|
|
315
|
+
const currentCount = this.portRefCounts.get(port) || 0;
|
|
316
|
+
|
|
317
|
+
if (currentCount <= 0) {
|
|
318
|
+
logger.log('warn', `Attempted to decrement reference count for port ${port} below zero`, {
|
|
319
|
+
port,
|
|
320
|
+
component: 'port-manager'
|
|
321
|
+
});
|
|
322
|
+
return 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const newCount = currentCount - 1;
|
|
326
|
+
this.portRefCounts.set(port, newCount);
|
|
327
|
+
|
|
328
|
+
logger.log('debug', `Port ${port} reference count decreased to ${newCount}`, {
|
|
329
|
+
port,
|
|
330
|
+
refCount: newCount,
|
|
331
|
+
component: 'port-manager'
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return newCount;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Determine if a port binding error is due to an external or internal conflict
|
|
339
|
+
*
|
|
340
|
+
* @param error The error object from a failed port binding
|
|
341
|
+
* @returns Object indicating if this is a conflict and if it's external
|
|
342
|
+
*/
|
|
343
|
+
private isPortConflict(error: any): { isConflict: boolean; isExternal: boolean } {
|
|
344
|
+
if (error.code !== 'EADDRINUSE') {
|
|
345
|
+
return { isConflict: false, isExternal: false };
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Check if we already have this port
|
|
349
|
+
const isBoundInternally = this.servers.has(Number(error.port));
|
|
350
|
+
return { isConflict: true, isExternal: !isBoundInternally };
|
|
351
|
+
}
|
|
195
352
|
}
|
|
@@ -64,6 +64,9 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
64
64
|
private routeUpdateLock: any = null; // Will be initialized as AsyncMutex
|
|
65
65
|
private acmeStateManager: AcmeStateManager;
|
|
66
66
|
|
|
67
|
+
// Track port usage across route updates
|
|
68
|
+
private portUsageMap: Map<number, Set<string>> = new Map();
|
|
69
|
+
|
|
67
70
|
/**
|
|
68
71
|
* Constructor for SmartProxy
|
|
69
72
|
*
|
|
@@ -342,6 +345,16 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
342
345
|
// Get listening ports from RouteManager
|
|
343
346
|
const listeningPorts = this.routeManager.getListeningPorts();
|
|
344
347
|
|
|
348
|
+
// Initialize port usage tracking
|
|
349
|
+
this.portUsageMap = this.updatePortUsageMap(this.settings.routes);
|
|
350
|
+
|
|
351
|
+
// Log port usage for startup
|
|
352
|
+
logger.log('info', `SmartProxy starting with ${listeningPorts.length} ports: ${listeningPorts.join(', ')}`, {
|
|
353
|
+
portCount: listeningPorts.length,
|
|
354
|
+
ports: listeningPorts,
|
|
355
|
+
component: 'smart-proxy'
|
|
356
|
+
});
|
|
357
|
+
|
|
345
358
|
// Provision NFTables rules for routes that use NFTables
|
|
346
359
|
for (const route of this.settings.routes) {
|
|
347
360
|
if (route.action.forwardingEngine === 'nftables') {
|
|
@@ -548,6 +561,18 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
548
561
|
return this.routeUpdateLock.runExclusive(async () => {
|
|
549
562
|
logger.log('info', `Updating routes (${newRoutes.length} routes)`, { routeCount: newRoutes.length, component: 'route-manager' });
|
|
550
563
|
|
|
564
|
+
// Track port usage before and after updates
|
|
565
|
+
const oldPortUsage = this.updatePortUsageMap(this.settings.routes);
|
|
566
|
+
const newPortUsage = this.updatePortUsageMap(newRoutes);
|
|
567
|
+
|
|
568
|
+
// Find orphaned ports - ports that no longer have any routes
|
|
569
|
+
const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
|
|
570
|
+
|
|
571
|
+
// Find new ports that need binding
|
|
572
|
+
const currentPorts = new Set(this.portManager.getListeningPorts());
|
|
573
|
+
const newPortsSet = new Set(newPortUsage.keys());
|
|
574
|
+
const newBindingPorts = Array.from(newPortsSet).filter(p => !currentPorts.has(p));
|
|
575
|
+
|
|
551
576
|
// Get existing routes that use NFTables
|
|
552
577
|
const oldNfTablesRoutes = this.settings.routes.filter(
|
|
553
578
|
r => r.action.forwardingEngine === 'nftables'
|
|
@@ -584,14 +609,29 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
584
609
|
// Update routes in RouteManager
|
|
585
610
|
this.routeManager.updateRoutes(newRoutes);
|
|
586
611
|
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
612
|
+
// Release orphaned ports first
|
|
613
|
+
if (orphanedPorts.length > 0) {
|
|
614
|
+
logger.log('info', `Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`, {
|
|
615
|
+
ports: orphanedPorts,
|
|
616
|
+
component: 'smart-proxy'
|
|
617
|
+
});
|
|
618
|
+
await this.portManager.removePorts(orphanedPorts);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Add new ports
|
|
622
|
+
if (newBindingPorts.length > 0) {
|
|
623
|
+
logger.log('info', `Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`, {
|
|
624
|
+
ports: newBindingPorts,
|
|
625
|
+
component: 'smart-proxy'
|
|
626
|
+
});
|
|
627
|
+
await this.portManager.addPorts(newBindingPorts);
|
|
628
|
+
}
|
|
592
629
|
|
|
593
630
|
// Update settings with the new routes
|
|
594
631
|
this.settings.routes = newRoutes;
|
|
632
|
+
|
|
633
|
+
// Save the new port usage map for future reference
|
|
634
|
+
this.portUsageMap = newPortUsage;
|
|
595
635
|
|
|
596
636
|
// If HttpProxy is initialized, resync the configurations
|
|
597
637
|
if (this.httpProxyBridge.getHttpProxy()) {
|
|
@@ -637,6 +677,78 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
637
677
|
|
|
638
678
|
await this.certManager.provisionCertificate(route);
|
|
639
679
|
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Update the port usage map based on the provided routes
|
|
683
|
+
*
|
|
684
|
+
* This tracks which ports are used by which routes, allowing us to
|
|
685
|
+
* detect when a port is no longer needed and can be released.
|
|
686
|
+
*/
|
|
687
|
+
private updatePortUsageMap(routes: IRouteConfig[]): Map<number, Set<string>> {
|
|
688
|
+
// Reset the usage map
|
|
689
|
+
const portUsage = new Map<number, Set<string>>();
|
|
690
|
+
|
|
691
|
+
for (const route of routes) {
|
|
692
|
+
// Get the ports for this route
|
|
693
|
+
const portsConfig = Array.isArray(route.match.ports)
|
|
694
|
+
? route.match.ports
|
|
695
|
+
: [route.match.ports];
|
|
696
|
+
|
|
697
|
+
// Expand port range objects to individual port numbers
|
|
698
|
+
const expandedPorts: number[] = [];
|
|
699
|
+
for (const portConfig of portsConfig) {
|
|
700
|
+
if (typeof portConfig === 'number') {
|
|
701
|
+
expandedPorts.push(portConfig);
|
|
702
|
+
} else if (typeof portConfig === 'object' && 'from' in portConfig && 'to' in portConfig) {
|
|
703
|
+
// Expand the port range
|
|
704
|
+
for (let p = portConfig.from; p <= portConfig.to; p++) {
|
|
705
|
+
expandedPorts.push(p);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Use route name if available, otherwise generate a unique ID
|
|
711
|
+
const routeName = route.name || `unnamed_${Math.random().toString(36).substring(2, 9)}`;
|
|
712
|
+
|
|
713
|
+
// Add each port to the usage map
|
|
714
|
+
for (const port of expandedPorts) {
|
|
715
|
+
if (!portUsage.has(port)) {
|
|
716
|
+
portUsage.set(port, new Set());
|
|
717
|
+
}
|
|
718
|
+
portUsage.get(port)!.add(routeName);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Log port usage for debugging
|
|
723
|
+
for (const [port, routes] of portUsage.entries()) {
|
|
724
|
+
logger.log('debug', `Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`, {
|
|
725
|
+
port,
|
|
726
|
+
routeCount: routes.size,
|
|
727
|
+
component: 'smart-proxy'
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
return portUsage;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Find ports that have no routes in the new configuration
|
|
736
|
+
*/
|
|
737
|
+
private findOrphanedPorts(oldUsage: Map<number, Set<string>>, newUsage: Map<number, Set<string>>): number[] {
|
|
738
|
+
const orphanedPorts: number[] = [];
|
|
739
|
+
|
|
740
|
+
for (const [port, routes] of oldUsage.entries()) {
|
|
741
|
+
if (!newUsage.has(port) || newUsage.get(port)!.size === 0) {
|
|
742
|
+
orphanedPorts.push(port);
|
|
743
|
+
logger.log('info', `Port ${port} no longer has any associated routes, will be released`, {
|
|
744
|
+
port,
|
|
745
|
+
component: 'smart-proxy'
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return orphanedPorts;
|
|
751
|
+
}
|
|
640
752
|
|
|
641
753
|
/**
|
|
642
754
|
* Force renewal of a certificate
|