@push.rocks/smartproxy 19.3.12 → 19.3.14
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 +117 -9
- package/dist_ts/proxies/smart-proxy/port-manager.d.ts +39 -0
- package/dist_ts/proxies/smart-proxy/port-manager.js +164 -9
- package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +12 -0
- package/dist_ts/proxies/smart-proxy/smart-proxy.js +161 -8
- 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 +115 -8
- package/ts/proxies/smart-proxy/port-manager.ts +182 -11
- package/ts/proxies/smart-proxy/smart-proxy.ts +171 -8
|
@@ -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,22 @@ 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
|
+
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
|
+
}
|
|
42
57
|
return;
|
|
43
58
|
}
|
|
44
59
|
|
|
60
|
+
// Initialize reference count for new port
|
|
61
|
+
this.portRefCounts.set(port, 1);
|
|
62
|
+
|
|
45
63
|
// Create a server for this port
|
|
46
64
|
const server = plugins.net.createServer((socket) => {
|
|
47
65
|
// Check if shutting down
|
|
@@ -54,24 +72,66 @@ export class PortManager {
|
|
|
54
72
|
// Delegate to route connection handler
|
|
55
73
|
this.routeConnectionHandler.handleConnection(socket);
|
|
56
74
|
}).on('error', (err: Error) => {
|
|
57
|
-
|
|
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
|
+
}
|
|
58
84
|
});
|
|
59
85
|
|
|
60
86
|
// Start listening on the port
|
|
61
87
|
return new Promise<void>((resolve, reject) => {
|
|
62
88
|
server.listen(port, () => {
|
|
63
89
|
const isHttpProxyPort = this.settings.useHttpProxy?.includes(port);
|
|
64
|
-
|
|
65
|
-
`SmartProxy -> OK: Now listening on port ${port}${
|
|
90
|
+
try {
|
|
91
|
+
logger.log('info', `SmartProxy -> OK: Now listening on port ${port}${
|
|
66
92
|
isHttpProxyPort ? ' (HttpProxy forwarding enabled)' : ''
|
|
67
|
-
}
|
|
68
|
-
|
|
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
|
+
}
|
|
69
103
|
|
|
70
104
|
// Store the server reference
|
|
71
105
|
this.servers.set(port, server);
|
|
72
106
|
resolve();
|
|
73
107
|
}).on('error', (err) => {
|
|
74
|
-
|
|
108
|
+
// Check if this is an external conflict
|
|
109
|
+
const { isConflict, isExternal } = this.isPortConflict(err);
|
|
110
|
+
|
|
111
|
+
if (isConflict && !isExternal) {
|
|
112
|
+
// This is an internal conflict (port already bound by SmartProxy)
|
|
113
|
+
// This shouldn't normally happen because we check servers.has(port) above
|
|
114
|
+
logger.log('warn', `Port ${port} binding conflict: already in use by SmartProxy`, {
|
|
115
|
+
port,
|
|
116
|
+
component: 'port-manager'
|
|
117
|
+
});
|
|
118
|
+
// Still increment reference count to maintain tracking
|
|
119
|
+
this.incrementPortRefCount(port);
|
|
120
|
+
resolve();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Log the error and propagate it
|
|
125
|
+
logger.log('error', `Failed to listen on port ${port}: ${err.message}`, {
|
|
126
|
+
port,
|
|
127
|
+
error: err.message,
|
|
128
|
+
code: (err as any).code,
|
|
129
|
+
component: 'port-manager'
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Clean up reference count since binding failed
|
|
133
|
+
this.portRefCounts.delete(port);
|
|
134
|
+
|
|
75
135
|
reject(err);
|
|
76
136
|
});
|
|
77
137
|
});
|
|
@@ -84,10 +144,28 @@ export class PortManager {
|
|
|
84
144
|
* @returns Promise that resolves when the server is closed
|
|
85
145
|
*/
|
|
86
146
|
public async removePort(port: number): Promise<void> {
|
|
147
|
+
// Decrement the reference count first
|
|
148
|
+
const newRefCount = this.decrementPortRefCount(port);
|
|
149
|
+
|
|
150
|
+
// If there are still references to this port, keep it open
|
|
151
|
+
if (newRefCount > 0) {
|
|
152
|
+
logger.log('debug', `PortManager: Port ${port} still has ${newRefCount} references, keeping open`, {
|
|
153
|
+
port,
|
|
154
|
+
refCount: newRefCount,
|
|
155
|
+
component: 'port-manager'
|
|
156
|
+
});
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
87
160
|
// Get the server for this port
|
|
88
161
|
const server = this.servers.get(port);
|
|
89
162
|
if (!server) {
|
|
90
|
-
|
|
163
|
+
logger.log('warn', `PortManager: Not listening on port ${port}`, {
|
|
164
|
+
port,
|
|
165
|
+
component: 'port-manager'
|
|
166
|
+
});
|
|
167
|
+
// Ensure reference count is reset
|
|
168
|
+
this.portRefCounts.delete(port);
|
|
91
169
|
return;
|
|
92
170
|
}
|
|
93
171
|
|
|
@@ -95,13 +173,21 @@ export class PortManager {
|
|
|
95
173
|
return new Promise<void>((resolve) => {
|
|
96
174
|
server.close((err) => {
|
|
97
175
|
if (err) {
|
|
98
|
-
|
|
176
|
+
logger.log('error', `Error closing server on port ${port}: ${err.message}`, {
|
|
177
|
+
port,
|
|
178
|
+
error: err.message,
|
|
179
|
+
component: 'port-manager'
|
|
180
|
+
});
|
|
99
181
|
} else {
|
|
100
|
-
|
|
182
|
+
logger.log('info', `SmartProxy -> Stopped listening on port ${port}`, {
|
|
183
|
+
port,
|
|
184
|
+
component: 'port-manager'
|
|
185
|
+
});
|
|
101
186
|
}
|
|
102
187
|
|
|
103
|
-
// Remove the server reference
|
|
188
|
+
// Remove the server reference and clean up reference counting
|
|
104
189
|
this.servers.delete(port);
|
|
190
|
+
this.portRefCounts.delete(port);
|
|
105
191
|
resolve();
|
|
106
192
|
});
|
|
107
193
|
});
|
|
@@ -192,4 +278,89 @@ export class PortManager {
|
|
|
192
278
|
public getServers(): Map<number, plugins.net.Server> {
|
|
193
279
|
return new Map(this.servers);
|
|
194
280
|
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Check if a port is bound by this SmartProxy instance
|
|
284
|
+
*
|
|
285
|
+
* @param port The port number to check
|
|
286
|
+
* @returns True if the port is currently bound by SmartProxy
|
|
287
|
+
*/
|
|
288
|
+
public isPortBoundBySmartProxy(port: number): boolean {
|
|
289
|
+
return this.servers.has(port);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Get the current reference count for a port
|
|
294
|
+
*
|
|
295
|
+
* @param port The port number to check
|
|
296
|
+
* @returns The number of routes using this port, 0 if none
|
|
297
|
+
*/
|
|
298
|
+
public getPortRefCount(port: number): number {
|
|
299
|
+
return this.portRefCounts.get(port) || 0;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Increment the reference count for a port
|
|
304
|
+
*
|
|
305
|
+
* @param port The port number to increment
|
|
306
|
+
* @returns The new reference count
|
|
307
|
+
*/
|
|
308
|
+
public incrementPortRefCount(port: number): number {
|
|
309
|
+
const currentCount = this.portRefCounts.get(port) || 0;
|
|
310
|
+
const newCount = currentCount + 1;
|
|
311
|
+
this.portRefCounts.set(port, newCount);
|
|
312
|
+
|
|
313
|
+
logger.log('debug', `Port ${port} reference count increased to ${newCount}`, {
|
|
314
|
+
port,
|
|
315
|
+
refCount: newCount,
|
|
316
|
+
component: 'port-manager'
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
return newCount;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Decrement the reference count for a port
|
|
324
|
+
*
|
|
325
|
+
* @param port The port number to decrement
|
|
326
|
+
* @returns The new reference count
|
|
327
|
+
*/
|
|
328
|
+
public decrementPortRefCount(port: number): number {
|
|
329
|
+
const currentCount = this.portRefCounts.get(port) || 0;
|
|
330
|
+
|
|
331
|
+
if (currentCount <= 0) {
|
|
332
|
+
logger.log('warn', `Attempted to decrement reference count for port ${port} below zero`, {
|
|
333
|
+
port,
|
|
334
|
+
component: 'port-manager'
|
|
335
|
+
});
|
|
336
|
+
return 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const newCount = currentCount - 1;
|
|
340
|
+
this.portRefCounts.set(port, newCount);
|
|
341
|
+
|
|
342
|
+
logger.log('debug', `Port ${port} reference count decreased to ${newCount}`, {
|
|
343
|
+
port,
|
|
344
|
+
refCount: newCount,
|
|
345
|
+
component: 'port-manager'
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
return newCount;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Determine if a port binding error is due to an external or internal conflict
|
|
353
|
+
*
|
|
354
|
+
* @param error The error object from a failed port binding
|
|
355
|
+
* @returns Object indicating if this is a conflict and if it's external
|
|
356
|
+
*/
|
|
357
|
+
private isPortConflict(error: any): { isConflict: boolean; isExternal: boolean } {
|
|
358
|
+
if (error.code !== 'EADDRINUSE') {
|
|
359
|
+
return { isConflict: false, isExternal: false };
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Check if we already have this port
|
|
363
|
+
const isBoundInternally = this.servers.has(Number(error.port));
|
|
364
|
+
return { isConflict: true, isExternal: !isBoundInternally };
|
|
365
|
+
}
|
|
195
366
|
}
|
|
@@ -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') {
|
|
@@ -508,7 +521,12 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
508
521
|
const challengeRouteExists = this.settings.routes.some(r => r.name === 'acme-challenge');
|
|
509
522
|
|
|
510
523
|
if (!challengeRouteExists) {
|
|
511
|
-
|
|
524
|
+
try {
|
|
525
|
+
logger.log('info', 'Challenge route successfully removed from routes');
|
|
526
|
+
} catch (error) {
|
|
527
|
+
// Silently handle logging errors
|
|
528
|
+
console.log('[INFO] Challenge route successfully removed from routes');
|
|
529
|
+
}
|
|
512
530
|
return;
|
|
513
531
|
}
|
|
514
532
|
|
|
@@ -517,7 +535,12 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
517
535
|
}
|
|
518
536
|
|
|
519
537
|
const error = `Failed to verify challenge route removal after ${maxRetries} attempts`;
|
|
520
|
-
|
|
538
|
+
try {
|
|
539
|
+
logger.log('error', error);
|
|
540
|
+
} catch (logError) {
|
|
541
|
+
// Silently handle logging errors
|
|
542
|
+
console.log(`[ERROR] ${error}`);
|
|
543
|
+
}
|
|
521
544
|
throw new Error(error);
|
|
522
545
|
}
|
|
523
546
|
|
|
@@ -546,7 +569,24 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
546
569
|
*/
|
|
547
570
|
public async updateRoutes(newRoutes: IRouteConfig[]): Promise<void> {
|
|
548
571
|
return this.routeUpdateLock.runExclusive(async () => {
|
|
549
|
-
|
|
572
|
+
try {
|
|
573
|
+
logger.log('info', `Updating routes (${newRoutes.length} routes)`, { routeCount: newRoutes.length, component: 'route-manager' });
|
|
574
|
+
} catch (error) {
|
|
575
|
+
// Silently handle logging errors
|
|
576
|
+
console.log(`[INFO] Updating routes (${newRoutes.length} routes)`);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Track port usage before and after updates
|
|
580
|
+
const oldPortUsage = this.updatePortUsageMap(this.settings.routes);
|
|
581
|
+
const newPortUsage = this.updatePortUsageMap(newRoutes);
|
|
582
|
+
|
|
583
|
+
// Find orphaned ports - ports that no longer have any routes
|
|
584
|
+
const orphanedPorts = this.findOrphanedPorts(oldPortUsage, newPortUsage);
|
|
585
|
+
|
|
586
|
+
// Find new ports that need binding
|
|
587
|
+
const currentPorts = new Set(this.portManager.getListeningPorts());
|
|
588
|
+
const newPortsSet = new Set(newPortUsage.keys());
|
|
589
|
+
const newBindingPorts = Array.from(newPortsSet).filter(p => !currentPorts.has(p));
|
|
550
590
|
|
|
551
591
|
// Get existing routes that use NFTables
|
|
552
592
|
const oldNfTablesRoutes = this.settings.routes.filter(
|
|
@@ -584,14 +624,39 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
584
624
|
// Update routes in RouteManager
|
|
585
625
|
this.routeManager.updateRoutes(newRoutes);
|
|
586
626
|
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
627
|
+
// Release orphaned ports first
|
|
628
|
+
if (orphanedPorts.length > 0) {
|
|
629
|
+
try {
|
|
630
|
+
logger.log('info', `Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`, {
|
|
631
|
+
ports: orphanedPorts,
|
|
632
|
+
component: 'smart-proxy'
|
|
633
|
+
});
|
|
634
|
+
} catch (error) {
|
|
635
|
+
// Silently handle logging errors
|
|
636
|
+
console.log(`[INFO] Releasing ${orphanedPorts.length} orphaned ports: ${orphanedPorts.join(', ')}`);
|
|
637
|
+
}
|
|
638
|
+
await this.portManager.removePorts(orphanedPorts);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Add new ports
|
|
642
|
+
if (newBindingPorts.length > 0) {
|
|
643
|
+
try {
|
|
644
|
+
logger.log('info', `Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`, {
|
|
645
|
+
ports: newBindingPorts,
|
|
646
|
+
component: 'smart-proxy'
|
|
647
|
+
});
|
|
648
|
+
} catch (error) {
|
|
649
|
+
// Silently handle logging errors
|
|
650
|
+
console.log(`[INFO] Binding to ${newBindingPorts.length} new ports: ${newBindingPorts.join(', ')}`);
|
|
651
|
+
}
|
|
652
|
+
await this.portManager.addPorts(newBindingPorts);
|
|
653
|
+
}
|
|
592
654
|
|
|
593
655
|
// Update settings with the new routes
|
|
594
656
|
this.settings.routes = newRoutes;
|
|
657
|
+
|
|
658
|
+
// Save the new port usage map for future reference
|
|
659
|
+
this.portUsageMap = newPortUsage;
|
|
595
660
|
|
|
596
661
|
// If HttpProxy is initialized, resync the configurations
|
|
597
662
|
if (this.httpProxyBridge.getHttpProxy()) {
|
|
@@ -606,6 +671,22 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
606
671
|
// Store global state before stopping
|
|
607
672
|
this.globalChallengeRouteActive = existingState.challengeRouteActive;
|
|
608
673
|
|
|
674
|
+
// Only stop the cert manager if absolutely necessary
|
|
675
|
+
// First check if there's an ACME route on the same port already
|
|
676
|
+
const acmePort = existingAcmeOptions?.port || 80;
|
|
677
|
+
const acmePortInUse = newPortUsage.has(acmePort) && newPortUsage.get(acmePort)!.size > 0;
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
logger.log('debug', `ACME port ${acmePort} ${acmePortInUse ? 'is' : 'is not'} already in use by other routes`, {
|
|
681
|
+
port: acmePort,
|
|
682
|
+
inUse: acmePortInUse,
|
|
683
|
+
component: 'smart-proxy'
|
|
684
|
+
});
|
|
685
|
+
} catch (error) {
|
|
686
|
+
// Silently handle logging errors
|
|
687
|
+
console.log(`[DEBUG] ACME port ${acmePort} ${acmePortInUse ? 'is' : 'is not'} already in use by other routes`);
|
|
688
|
+
}
|
|
689
|
+
|
|
609
690
|
await this.certManager.stop();
|
|
610
691
|
|
|
611
692
|
// Verify the challenge route has been properly removed
|
|
@@ -637,6 +718,88 @@ export class SmartProxy extends plugins.EventEmitter {
|
|
|
637
718
|
|
|
638
719
|
await this.certManager.provisionCertificate(route);
|
|
639
720
|
}
|
|
721
|
+
|
|
722
|
+
/**
|
|
723
|
+
* Update the port usage map based on the provided routes
|
|
724
|
+
*
|
|
725
|
+
* This tracks which ports are used by which routes, allowing us to
|
|
726
|
+
* detect when a port is no longer needed and can be released.
|
|
727
|
+
*/
|
|
728
|
+
private updatePortUsageMap(routes: IRouteConfig[]): Map<number, Set<string>> {
|
|
729
|
+
// Reset the usage map
|
|
730
|
+
const portUsage = new Map<number, Set<string>>();
|
|
731
|
+
|
|
732
|
+
for (const route of routes) {
|
|
733
|
+
// Get the ports for this route
|
|
734
|
+
const portsConfig = Array.isArray(route.match.ports)
|
|
735
|
+
? route.match.ports
|
|
736
|
+
: [route.match.ports];
|
|
737
|
+
|
|
738
|
+
// Expand port range objects to individual port numbers
|
|
739
|
+
const expandedPorts: number[] = [];
|
|
740
|
+
for (const portConfig of portsConfig) {
|
|
741
|
+
if (typeof portConfig === 'number') {
|
|
742
|
+
expandedPorts.push(portConfig);
|
|
743
|
+
} else if (typeof portConfig === 'object' && 'from' in portConfig && 'to' in portConfig) {
|
|
744
|
+
// Expand the port range
|
|
745
|
+
for (let p = portConfig.from; p <= portConfig.to; p++) {
|
|
746
|
+
expandedPorts.push(p);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Use route name if available, otherwise generate a unique ID
|
|
752
|
+
const routeName = route.name || `unnamed_${Math.random().toString(36).substring(2, 9)}`;
|
|
753
|
+
|
|
754
|
+
// Add each port to the usage map
|
|
755
|
+
for (const port of expandedPorts) {
|
|
756
|
+
if (!portUsage.has(port)) {
|
|
757
|
+
portUsage.set(port, new Set());
|
|
758
|
+
}
|
|
759
|
+
portUsage.get(port)!.add(routeName);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Log port usage for debugging
|
|
764
|
+
for (const [port, routes] of portUsage.entries()) {
|
|
765
|
+
try {
|
|
766
|
+
logger.log('debug', `Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`, {
|
|
767
|
+
port,
|
|
768
|
+
routeCount: routes.size,
|
|
769
|
+
component: 'smart-proxy'
|
|
770
|
+
});
|
|
771
|
+
} catch (error) {
|
|
772
|
+
// Silently handle logging errors
|
|
773
|
+
console.log(`[DEBUG] Port ${port} is used by ${routes.size} routes: ${Array.from(routes).join(', ')}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
return portUsage;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Find ports that have no routes in the new configuration
|
|
782
|
+
*/
|
|
783
|
+
private findOrphanedPorts(oldUsage: Map<number, Set<string>>, newUsage: Map<number, Set<string>>): number[] {
|
|
784
|
+
const orphanedPorts: number[] = [];
|
|
785
|
+
|
|
786
|
+
for (const [port, routes] of oldUsage.entries()) {
|
|
787
|
+
if (!newUsage.has(port) || newUsage.get(port)!.size === 0) {
|
|
788
|
+
orphanedPorts.push(port);
|
|
789
|
+
try {
|
|
790
|
+
logger.log('info', `Port ${port} no longer has any associated routes, will be released`, {
|
|
791
|
+
port,
|
|
792
|
+
component: 'smart-proxy'
|
|
793
|
+
});
|
|
794
|
+
} catch (error) {
|
|
795
|
+
// Silently handle logging errors
|
|
796
|
+
console.log(`[INFO] Port ${port} no longer has any associated routes, will be released`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
return orphanedPorts;
|
|
802
|
+
}
|
|
640
803
|
|
|
641
804
|
/**
|
|
642
805
|
* Force renewal of a certificate
|