@push.rocks/smartproxy 19.2.3 → 19.2.4

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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '19.2.3',
6
+ version: '19.2.4',
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
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSx3QkFBd0I7SUFDOUIsT0FBTyxFQUFFLFFBQVE7SUFDakIsV0FBVyxFQUFFLHFQQUFxUDtDQUNuUSxDQUFBIn0=
@@ -29,6 +29,8 @@ export declare class SmartCertManager {
29
29
  private certStatus;
30
30
  private globalAcmeDefaults;
31
31
  private updateRoutesCallback?;
32
+ private challengeRouteActive;
33
+ private isProvisioning;
32
34
  constructor(routes: IRouteConfig[], certDir?: string, acmeOptions?: {
33
35
  email?: string;
34
36
  useProduction?: boolean;
@@ -54,7 +56,7 @@ export declare class SmartCertManager {
54
56
  /**
55
57
  * Provision certificate for a single route
56
58
  */
57
- provisionCertificate(route: IRouteConfig): Promise<void>;
59
+ provisionCertificate(route: IRouteConfig, allowConcurrent?: boolean): Promise<void>;
58
60
  /**
59
61
  * Provision ACME certificate
60
62
  */
@@ -15,6 +15,10 @@ export class SmartCertManager {
15
15
  this.certStatus = new Map();
16
16
  // Global ACME defaults from top-level configuration
17
17
  this.globalAcmeDefaults = null;
18
+ // Flag to track if challenge route is currently active
19
+ this.challengeRouteActive = false;
20
+ // Flag to track if provisioning is in progress
21
+ this.isProvisioning = false;
18
22
  this.certStore = new CertStore(certDir);
19
23
  }
20
24
  setNetworkProxy(networkProxy) {
@@ -53,6 +57,9 @@ export class SmartCertManager {
53
57
  challengeHandlers: [http01Handler]
54
58
  });
55
59
  await this.smartAcme.start();
60
+ // Add challenge route once at initialization
61
+ console.log('Adding ACME challenge route during initialization');
62
+ await this.addChallengeRoute();
56
63
  }
57
64
  // Provision certificates for all routes
58
65
  await this.provisionAllCertificates();
@@ -65,23 +72,35 @@ export class SmartCertManager {
65
72
  async provisionAllCertificates() {
66
73
  const certRoutes = this.routes.filter(r => r.action.tls?.mode === 'terminate' ||
67
74
  r.action.tls?.mode === 'terminate-and-reencrypt');
68
- for (const route of certRoutes) {
69
- try {
70
- await this.provisionCertificate(route);
71
- }
72
- catch (error) {
73
- console.error(`Failed to provision certificate for route ${route.name}: ${error}`);
75
+ // Set provisioning flag to prevent concurrent operations
76
+ this.isProvisioning = true;
77
+ try {
78
+ for (const route of certRoutes) {
79
+ try {
80
+ await this.provisionCertificate(route, true); // Allow concurrent since we're managing it here
81
+ }
82
+ catch (error) {
83
+ console.error(`Failed to provision certificate for route ${route.name}: ${error}`);
84
+ }
74
85
  }
75
86
  }
87
+ finally {
88
+ this.isProvisioning = false;
89
+ }
76
90
  }
77
91
  /**
78
92
  * Provision certificate for a single route
79
93
  */
80
- async provisionCertificate(route) {
94
+ async provisionCertificate(route, allowConcurrent = false) {
81
95
  const tls = route.action.tls;
82
96
  if (!tls || (tls.mode !== 'terminate' && tls.mode !== 'terminate-and-reencrypt')) {
83
97
  return;
84
98
  }
99
+ // Check if provisioning is already in progress (prevent concurrent provisioning)
100
+ if (!allowConcurrent && this.isProvisioning) {
101
+ console.log(`Certificate provisioning already in progress, skipping ${route.name}`);
102
+ return;
103
+ }
85
104
  const domains = this.extractDomainsFromRoute(route);
86
105
  if (domains.length === 0) {
87
106
  console.warn(`Route ${route.name} has TLS termination but no domains`);
@@ -124,42 +143,30 @@ export class SmartCertManager {
124
143
  console.log(`Requesting ACME certificate for ${domains.join(', ')} (renew ${renewThreshold} days before expiry)`);
125
144
  this.updateCertStatus(routeName, 'pending', 'acme');
126
145
  try {
127
- // Add challenge route before requesting certificate
128
- await this.addChallengeRoute();
129
- try {
130
- // Use smartacme to get certificate
131
- const cert = await this.smartAcme.getCertificateForDomain(primaryDomain);
132
- // SmartAcme's Cert object has these properties:
133
- // - publicKey: The certificate PEM string
134
- // - privateKey: The private key PEM string
135
- // - csr: Certificate signing request
136
- // - validUntil: Timestamp in milliseconds
137
- // - domainName: The domain name
138
- const certData = {
139
- cert: cert.publicKey,
140
- key: cert.privateKey,
141
- ca: cert.publicKey, // Use same as cert for now
142
- expiryDate: new Date(cert.validUntil),
143
- issueDate: new Date(cert.created)
144
- };
145
- await this.certStore.saveCertificate(routeName, certData);
146
- await this.applyCertificate(primaryDomain, certData);
147
- this.updateCertStatus(routeName, 'valid', 'acme', certData);
148
- console.log(`Successfully provisioned ACME certificate for ${primaryDomain}`);
149
- }
150
- catch (error) {
151
- console.error(`Failed to provision ACME certificate for ${primaryDomain}: ${error}`);
152
- this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message);
153
- throw error;
154
- }
155
- finally {
156
- // Always remove challenge route after provisioning
157
- await this.removeChallengeRoute();
158
- }
146
+ // Challenge route should already be active from initialization
147
+ // No need to add it for each certificate
148
+ // Use smartacme to get certificate
149
+ const cert = await this.smartAcme.getCertificateForDomain(primaryDomain);
150
+ // SmartAcme's Cert object has these properties:
151
+ // - publicKey: The certificate PEM string
152
+ // - privateKey: The private key PEM string
153
+ // - csr: Certificate signing request
154
+ // - validUntil: Timestamp in milliseconds
155
+ // - domainName: The domain name
156
+ const certData = {
157
+ cert: cert.publicKey,
158
+ key: cert.privateKey,
159
+ ca: cert.publicKey, // Use same as cert for now
160
+ expiryDate: new Date(cert.validUntil),
161
+ issueDate: new Date(cert.created)
162
+ };
163
+ await this.certStore.saveCertificate(routeName, certData);
164
+ await this.applyCertificate(primaryDomain, certData);
165
+ this.updateCertStatus(routeName, 'valid', 'acme', certData);
166
+ console.log(`Successfully provisioned ACME certificate for ${primaryDomain}`);
159
167
  }
160
168
  catch (error) {
161
- // Handle outer try-catch from adding challenge route
162
- console.error(`Failed to setup ACME challenge for ${primaryDomain}: ${error}`);
169
+ console.error(`Failed to provision ACME certificate for ${primaryDomain}: ${error}`);
163
170
  this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message);
164
171
  throw error;
165
172
  }
@@ -251,6 +258,10 @@ export class SmartCertManager {
251
258
  * Add challenge route to SmartProxy
252
259
  */
253
260
  async addChallengeRoute() {
261
+ if (this.challengeRouteActive) {
262
+ console.log('Challenge route already active, skipping');
263
+ return;
264
+ }
254
265
  if (!this.updateRoutesCallback) {
255
266
  throw new Error('No route update callback set');
256
267
  }
@@ -258,18 +269,43 @@ export class SmartCertManager {
258
269
  throw new Error('Challenge route not initialized');
259
270
  }
260
271
  const challengeRoute = this.challengeRoute;
261
- const updatedRoutes = [...this.routes, challengeRoute];
262
- await this.updateRoutesCallback(updatedRoutes);
272
+ try {
273
+ const updatedRoutes = [...this.routes, challengeRoute];
274
+ await this.updateRoutesCallback(updatedRoutes);
275
+ this.challengeRouteActive = true;
276
+ console.log('ACME challenge route successfully added');
277
+ }
278
+ catch (error) {
279
+ console.error('Failed to add challenge route:', error);
280
+ if (error.code === 'EADDRINUSE') {
281
+ throw new Error(`Port ${this.globalAcmeDefaults?.port || 80} is already in use for ACME challenges`);
282
+ }
283
+ throw error;
284
+ }
263
285
  }
264
286
  /**
265
287
  * Remove challenge route from SmartProxy
266
288
  */
267
289
  async removeChallengeRoute() {
290
+ if (!this.challengeRouteActive) {
291
+ console.log('Challenge route not active, skipping removal');
292
+ return;
293
+ }
268
294
  if (!this.updateRoutesCallback) {
269
295
  return;
270
296
  }
271
- const filteredRoutes = this.routes.filter(r => r.name !== 'acme-challenge');
272
- await this.updateRoutesCallback(filteredRoutes);
297
+ try {
298
+ const filteredRoutes = this.routes.filter(r => r.name !== 'acme-challenge');
299
+ await this.updateRoutesCallback(filteredRoutes);
300
+ this.challengeRouteActive = false;
301
+ console.log('ACME challenge route successfully removed');
302
+ }
303
+ catch (error) {
304
+ console.error('Failed to remove challenge route:', error);
305
+ // Reset the flag even on error to avoid getting stuck
306
+ this.challengeRouteActive = false;
307
+ throw error;
308
+ }
273
309
  }
274
310
  /**
275
311
  * Start renewal timer
@@ -401,13 +437,17 @@ export class SmartCertManager {
401
437
  clearInterval(this.renewalTimer);
402
438
  this.renewalTimer = null;
403
439
  }
440
+ // Always remove challenge route on shutdown
441
+ if (this.challengeRoute) {
442
+ console.log('Removing ACME challenge route during shutdown');
443
+ await this.removeChallengeRoute();
444
+ }
404
445
  if (this.smartAcme) {
405
446
  await this.smartAcme.stop();
406
447
  }
407
- // Remove any active challenge routes
448
+ // Clear any pending challenges
408
449
  if (this.pendingChallenges.size > 0) {
409
450
  this.pendingChallenges.clear();
410
- await this.removeChallengeRoute();
411
451
  }
412
452
  }
413
453
  /**
@@ -417,4 +457,4 @@ export class SmartCertManager {
417
457
  return this.acmeOptions;
418
458
  }
419
459
  }
420
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2VydGlmaWNhdGUtbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3Byb3hpZXMvc21hcnQtcHJveHkvY2VydGlmaWNhdGUtbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUd6RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFtQjVDLE1BQU0sT0FBTyxnQkFBZ0I7SUFpQjNCLFlBQ1UsTUFBc0IsRUFDdEIsVUFBa0IsU0FBUyxFQUMzQixXQUlQO1FBTk8sV0FBTSxHQUFOLE1BQU0sQ0FBZ0I7UUFDdEIsWUFBTyxHQUFQLE9BQU8sQ0FBb0I7UUFDM0IsZ0JBQVcsR0FBWCxXQUFXLENBSWxCO1FBdEJLLGNBQVMsR0FBdUMsSUFBSSxDQUFDO1FBQ3JELGlCQUFZLEdBQXdCLElBQUksQ0FBQztRQUN6QyxpQkFBWSxHQUEwQixJQUFJLENBQUM7UUFDM0Msc0JBQWlCLEdBQXdCLElBQUksR0FBRyxFQUFFLENBQUM7UUFDbkQsbUJBQWMsR0FBd0IsSUFBSSxDQUFDO1FBRW5ELHlDQUF5QztRQUNqQyxlQUFVLEdBQTZCLElBQUksR0FBRyxFQUFFLENBQUM7UUFFekQsb0RBQW9EO1FBQzVDLHVCQUFrQixHQUF3QixJQUFJLENBQUM7UUFjckQsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRU0sZUFBZSxDQUFDLFlBQTBCO1FBQy9DLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO0lBQ25DLENBQUM7SUFFRDs7T0FFRztJQUNJLHFCQUFxQixDQUFDLFFBQXNCO1FBQ2pELElBQUksQ0FBQyxrQkFBa0IsR0FBRyxRQUFRLENBQUM7SUFDckMsQ0FBQztJQUVEOztPQUVHO0lB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
460
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2VydGlmaWNhdGUtbWFuYWdlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL3Byb3hpZXMvc21hcnQtcHJveHkvY2VydGlmaWNhdGUtbWFuYWdlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sRUFBRSxZQUFZLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUd6RCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFtQjVDLE1BQU0sT0FBTyxnQkFBZ0I7SUF1QjNCLFlBQ1UsTUFBc0IsRUFDdEIsVUFBa0IsU0FBUyxFQUMzQixXQUlQO1FBTk8sV0FBTSxHQUFOLE1BQU0sQ0FBZ0I7UUFDdEIsWUFBTyxHQUFQLE9BQU8sQ0FBb0I7UUFDM0IsZ0JBQVcsR0FBWCxXQUFXLENBSWxCO1FBNUJLLGNBQVMsR0FBdUMsSUFBSSxDQUFDO1FBQ3JELGlCQUFZLEdBQXdCLElBQUksQ0FBQztRQUN6QyxpQkFBWSxHQUEwQixJQUFJLENBQUM7UUFDM0Msc0JBQWlCLEdBQXdCLElBQUksR0FBRyxFQUFFLENBQUM7UUFDbkQsbUJBQWMsR0FBd0IsSUFBSSxDQUFDO1FBRW5ELHlDQUF5QztRQUNqQyxlQUFVLEdBQTZCLElBQUksR0FBRyxFQUFFLENBQUM7UUFFekQsb0RBQW9EO1FBQzVDLHVCQUFrQixHQUF3QixJQUFJLENBQUM7UUFLdkQsdURBQXVEO1FBQy9DLHlCQUFvQixHQUFZLEtBQUssQ0FBQztRQUU5QywrQ0FBK0M7UUFDdkMsbUJBQWMsR0FBWSxLQUFLLENBQUM7UUFXdEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxJQUFJLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRU0sZUFBZSxDQUFDLFlBQTBCO1FBQy9DLElBQUksQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDO0lBQ25DLENBQUM7SUFFRDs7T0FFRztJQUNJLHFCQUFxQixDQUFDLFFBQXNCO1FBQ2pELElBQUksQ0FBQyxrQkFBa0IsR0FBRyxRQUFRLENBQUM7SUFDckMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksdUJBQXVCLENBQUMsUUFBbUQ7UUFDaEYsSUFBSSxDQUFDLG9CQUFvQixHQUFHLFFBQVEsQ0FBQztJQUN2QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsVUFBVTtRQUNyQixtREFBbUQ7UUFDbkQsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLFVBQVUsRUFBRSxDQUFDO1FBRWxDLGtEQUFrRDtRQUNsRCxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUN6QyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRSxXQUFXLEtBQUssTUFBTSxDQUNyQyxDQUFDO1FBRUYsSUFBSSxhQUFhLElBQUksSUFBSSxDQUFDLFdBQVcsRUFBRSxLQUFLLEVBQUUsQ0FBQztZQUM3QyxtQ0FBbUM7WUFDbkMsTUFBTSxhQUFhLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxtQkFBbUIsRUFBRSxDQUFDO1lBRTNFLHdEQUF3RDtZQUN4RCxJQUFJLENBQUMscUJBQXFCLENBQUMsYUFBYSxDQUFDLENBQUM7WUFFMUMsZ0ZBQWdGO1lBQ2hGLElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxPQUFPLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQztnQkFDL0MsWUFBWSxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSztnQkFDcEMsV0FBVyxFQUFFLElBQUksQ0FBQyxXQUFXLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxZQUFZLENBQUMsQ0FBQyxDQUFDLGFBQWE7Z0JBQzFFLFdBQVcsRUFBRSxJQUFJLE9BQU8sQ0FBQyxTQUFTLENBQUMsWUFBWSxDQUFDLGlCQUFpQixFQUFFO2dCQUNuRSxpQkFBaUIsRUFBRSxDQUFDLGFBQWEsQ0FBQzthQUNuQyxDQUFDLENBQUM7WUFFSCxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLENBQUM7WUFFN0IsNkNBQTZDO1lBQzdDLE9BQU8sQ0FBQyxHQUFHLENBQUMsbURBQW1ELENBQUMsQ0FBQztZQUNqRSxNQUFNLElBQUksQ0FBQyxpQkFBaUIsRUFBRSxDQUFDO1FBQ2pDLENBQUM7UUFFRCx3Q0FBd0M7UUFDeEMsTUFBTSxJQUFJLENBQUMsd0JBQXdCLEVBQUUsQ0FBQztRQUV0QyxzQkFBc0I7UUFDdEIsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7SUFDM0IsQ0FBQztJQUVEOztPQUVHO0lBQ0ssS0FBSyxDQUFDLHdCQUF3QjtRQUNwQyxNQUFNLFVBQVUsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUN4QyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRSxJQUFJLEtBQUssV0FBVztZQUNsQyxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsRUFBRSxJQUFJLEtBQUsseUJBQXlCLENBQ2pELENBQUM7UUFFRix5REFBeUQ7UUFDekQsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUM7UUFFM0IsSUFBSSxDQUFDO1lBQ0gsS0FBSyxNQUFNLEtBQUssSUFBSSxVQUFVLEVBQUUsQ0FBQztnQkFDL0IsSUFBSSxDQUFDO29CQUNILE1BQU0sSUFBSSxDQUFDLG9CQUFvQixDQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDLGdEQUFnRDtnQkFDaEcsQ0FBQztnQkFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO29CQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsNkNBQTZDLEtBQUssQ0FBQyxJQUFJLEtBQUssS0FBSyxFQUFFLENBQUMsQ0FBQztnQkFDckYsQ0FBQztZQUNILENBQUM7UUFDSCxDQUFDO2dCQUFTLENBQUM7WUFDVCxJQUFJLENBQUMsY0FBYyxHQUFHLEtBQUssQ0FBQztRQUM5QixDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLG9CQUFvQixDQUFDLEtBQW1CLEVBQUUsa0JBQTJCLEtBQUs7UUFDckYsTUFBTSxHQUFHLEdBQUcsS0FBSyxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUM7UUFDN0IsSUFBSSxDQUFDLEdBQUcsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLEtBQUssV0FBVyxJQUFJLEdBQUcsQ0FBQyxJQUFJLEtBQUsseUJBQXlCLENBQUMsRUFBRSxDQUFDO1lBQ2pGLE9BQU87UUFDVCxDQUFDO1FBRUQsaUZBQWlGO1FBQ2pGLElBQUksQ0FBQyxlQUFlLElBQUksSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1lBQzVDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMERBQTBELEtBQUssQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFDO1lBQ3BGLE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLHVCQUF1QixDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQ3BELElBQUksT0FBTyxDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUN6QixPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsS0FBSyxDQUFDLElBQUkscUNBQXFDLENBQUMsQ0FBQztZQUN2RSxPQUFPO1FBQ1QsQ0FBQztRQUVELE1BQU0sYUFBYSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUVqQyxJQUFJLEdBQUcsQ0FBQyxXQUFXLEtBQUssTUFBTSxFQUFFLENBQUM7WUFDL0IsbUJBQW1CO1lBQ25CLE1BQU0sSUFBSSxDQUFDLHdCQUF3QixDQUFDLEtBQUssRUFBRSxPQUFPLENBQUMsQ0FBQztRQUN0RCxDQUFDO2FBQU0sSUFBSSxPQUFPLEdBQUcsQ0FBQyxXQUFXLEtBQUssUUFBUSxFQUFFLENBQUM7WUFDL0MscUJBQXFCO1lBQ3JCLE1BQU0sSUFBSSxDQUFDLDBCQUEwQixDQUFDLEtBQUssRUFBRSxhQUFhLEVBQUUsR0FBRyxDQUFDLFdBQVcsQ0FBQyxDQUFDO1FBQy9FLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsd0JBQXdCLENBQ3BDLEtBQW1CLEVBQ25CLE9BQWlCO1FBRWpCLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLENBQUM7WUFDcEIsTUFBTSxJQUFJLEtBQUssQ0FDYiw0RUFBNEU7Z0JBQzVFLHdFQUF3RTtnQkFDeEUsNENBQTRDO2dCQUM1Qyw2Q0FBNkMsQ0FDOUMsQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLGFBQWEsR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDakMsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLElBQUksSUFBSSxhQUFhLENBQUM7UUFFOUMsK0NBQStDO1FBQy9DLE1BQU0sWUFBWSxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxjQUFjLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDcEUsSUFBSSxZQUFZLElBQUksSUFBSSxDQUFDLGtCQUFrQixDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7WUFDMUQsT0FBTyxDQUFDLEdBQUcsQ0FBQyx3Q0FBd0MsYUFBYSxFQUFFLENBQUMsQ0FBQztZQUNyRSxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxhQUFhLEVBQUUsWUFBWSxDQUFDLENBQUM7WUFDekQsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLFlBQVksQ0FBQyxDQUFDO1lBQ2hFLE9BQU87UUFDVCxDQUFDO1FBRUQsK0RBQStEO1FBQy9ELE1BQU0sY0FBYyxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsR0FBRyxFQUFFLElBQUksRUFBRSxlQUFlO1lBQ3pDLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxrQkFBa0I7WUFDM0MsRUFBRSxDQUFDO1FBRXhCLE9BQU8sQ0FBQyxHQUFHLENBQUMsbUNBQW1DLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFdBQVcsY0FBYyxzQkFBc0IsQ0FBQyxDQUFDO1FBQ2xILElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLEVBQUUsU0FBUyxFQUFFLE1BQU0sQ0FBQyxDQUFDO1FBRXBELElBQUksQ0FBQztZQUNILCtEQUErRDtZQUMvRCx5Q0FBeUM7WUFFekMsbUNBQW1DO1lBQ25DLE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyx1QkFBdUIsQ0FBQyxhQUFhLENBQUMsQ0FBQztZQUV6RSxnREFBZ0Q7WUFDaEQsNENBQTRDO1lBQzVDLDJDQUEyQztZQUMzQyxxQ0FBcUM7WUFDckMsMENBQTBDO1lBQzFDLGdDQUFnQztZQUNoQyxNQUFNLFFBQVEsR0FBcUI7Z0JBQ2pDLElBQUksRUFBRSxJQUFJLENBQUMsU0FBUztnQkFDcEIsR0FBRyxFQUFFLElBQUksQ0FBQyxVQUFVO2dCQUNwQixFQUFFLEVBQUUsSUFBSSxDQUFDLFNBQVMsRUFBRSwyQkFBMkI7Z0JBQy9DLFVBQVUsRUFBRSxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDO2dCQUNyQyxTQUFTLEVBQUUsSUFBSSxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQzthQUNsQyxDQUFDO1lBRUYsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLGVBQWUsQ0FBQyxTQUFTLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDMUQsTUFBTSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsYUFBYSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQ3JELElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FBQztZQUU1RCxPQUFPLENBQUMsR0FBRyxDQUFDLGlEQUFpRCxhQUFhLEVBQUUsQ0FBQyxDQUFDO1FBQ2hGLENBQUM7UUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1lBQ2YsT0FBTyxDQUFDLEtBQUssQ0FBQyw0Q0FBNEMsYUFBYSxLQUFLLEtBQUssRUFBRSxDQUFDLENBQUM7WUFDckYsSUFBSSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLFNBQVMsRUFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDNUUsTUFBTSxLQUFLLENBQUM7UUFDZCxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ssS0FBSyxDQUFDLDBCQUEwQixDQUN0QyxLQUFtQixFQUNuQixNQUFjLEVBQ2QsVUFBOEU7UUFFOUUsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLElBQUksSUFBSSxNQUFNLENBQUM7UUFFdkMsSUFBSSxDQUFDO1lBQ0gsSUFBSSxHQUFHLEdBQVcsVUFBVSxDQUFDLEdBQUcsQ0FBQztZQUNqQyxJQUFJLElBQUksR0FBVyxVQUFVLENBQUMsSUFBSSxDQUFDO1lBRW5DLHdDQUF3QztZQUN4QyxJQUFJLFVBQVUsQ0FBQyxPQUFPLEVBQUUsQ0FBQztnQkFDdkIsTUFBTSxPQUFPLEdBQUcsTUFBTSxPQUFPLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDO2dCQUNuRixHQUFHLEdBQUcsT0FBTyxDQUFDLFFBQVEsQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNwQyxDQUFDO1lBQ0QsSUFBSSxVQUFVLENBQUMsUUFBUSxFQUFFLENBQUM7Z0JBQ3hCLE1BQU0sUUFBUSxHQUFHLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsWUFBWSxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsQ0FBQztnQkFDckYsSUFBSSxHQUFHLFFBQVEsQ0FBQyxRQUFRLENBQUMsUUFBUSxFQUFFLENBQUM7WUFDdEMsQ0FBQztZQUVELGlDQUFpQztZQUNqQyw2REFBNkQ7WUFDN0QsdURBQXVEO1lBQ3ZELE1BQU0sUUFBUSxHQUFHLEVBQUUsT0FBTyxFQUFFLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLEVBQUUsU0FBUyxFQUFFLElBQUksSUFBSSxFQUFFLEVBQUUsQ0FBQztZQUVyRyxNQUFNLFFBQVEsR0FBcUI7Z0JBQ2pDLElBQUk7Z0JBQ0osR0FBRztnQkFDSCxVQUFVLEVBQUUsUUFBUSxDQUFDLE9BQU87Z0JBQzVCLFNBQVMsRUFBRSxRQUFRLENBQUMsU0FBUzthQUM5QixDQUFDO1lBRUYsZ0NBQWdDO1lBQ2hDLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxlQUFlLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxDQUFDO1lBQzFELE1BQU0sSUFBSSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sRUFBRSxRQUFRLENBQUMsQ0FBQztZQUM5QyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFFOUQsT0FBTyxDQUFDLEdBQUcsQ0FBQyw4Q0FBOEMsTUFBTSxFQUFFLENBQUMsQ0FBQztRQUN0RSxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsOENBQThDLE1BQU0sS0FBSyxLQUFLLEVBQUUsQ0FBQyxDQUFDO1lBQ2hGLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLEVBQUUsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQzlFLE1BQU0sS0FBSyxDQUFDO1FBQ2QsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNLLEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFjLEVBQUUsUUFBMEI7UUFDdkUsSUFBSSxDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsQ0FBQztZQUN2QixPQUFPLENBQUMsSUFBSSxDQUFDLGdEQUFnRCxDQUFDLENBQUM7WUFDL0QsT0FBTztRQUNULENBQUM7UUFFRCxvQ0FBb0M7UUFDcEMsSUFBSSxDQUFDLFlBQVksQ0FBQyxpQkFBaUIsQ0FBQyxNQUFNLEVBQUUsUUFBUSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUM7UUFFekUsOENBQThDO1FBQzlDLElBQUksTUFBTSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUNyRCxNQUFNLEtBQUssR0FBRyxNQUFNLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ2hDLElBQUksS0FBSyxDQUFDLE1BQU0sSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDdEIsTUFBTSxjQUFjLEdBQUcsS0FBSyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7Z0JBQ3hELElBQUksQ0FBQyxZQUFZLENBQUMsaUJBQWlCLENBQUMsY0FBYyxFQUFFLFFBQVEsQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ25GLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ssdUJBQXVCLENBQUMsS0FBbUI7UUFDakQsSUFBSSxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDekIsT0FBTyxFQUFFLENBQUM7UUFDWixDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQztZQUNoRCxDQUFDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxPQUFPO1lBQ3JCLENBQUMsQ0FBQyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFFMUIsb0NBQW9DO1FBQ3BDLE9BQU8sT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUN4QixDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDO1lBQ2hCLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7WUFDaEIsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FDaEIsQ0FBQztJQUNKLENBQUM7SUFFRDs7T0FFRztJQUNLLGtCQUFrQixDQUFDLElBQXNCO1FBQy9DLE1BQU0sR0FBRyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7UUFFdkIsb0VBQW9FO1FBQ3BFLE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixFQUFFLGtCQUFrQixJQUFJLEVBQUUsQ0FBQztRQUM3RSxNQUFNLGVBQWUsR0FBRyxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLEdBQUcsa0JBQWtCLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsSUFBSSxDQUFDLENBQUM7UUFFM0YsT0FBTyxJQUFJLENBQUMsVUFBVSxHQUFHLGVBQWUsQ0FBQztJQUMzQyxDQUFDO0lBR0Q7O09BRUc7SUFDSyxLQUFLLENBQUMsaUJBQWlCO1FBQzdCLElBQUksSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7WUFDOUIsT0FBTyxDQUFDLEdBQUcsQ0FBQywwQ0FBMEMsQ0FBQyxDQUFDO1lBQ3hELE9BQU87UUFDVCxDQUFDO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO1lBQy9CLE1BQU0sSUFBSSxLQUFLLENBQUMsOEJBQThCLENBQUMsQ0FBQztRQUNsRCxDQUFDO1FBRUQsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztZQUN6QixNQUFNLElBQUksS0FBSyxDQUFDLGlDQUFpQyxDQUFDLENBQUM7UUFDckQsQ0FBQztRQUNELE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUM7UUFFM0MsSUFBSSxDQUFDO1lBQ0gsTUFBTSxhQUFhLEdBQUcsQ0FBQyxHQUFHLElBQUksQ0FBQyxNQUFNLEVBQUUsY0FBYyxDQUFDLENBQUM7WUFDdkQsTUFBTSxJQUFJLENBQUMsb0JBQW9CLENBQUMsYUFBYSxDQUFDLENBQUM7WUFDL0MsSUFBSSxDQUFDLG9CQUFvQixHQUFHLElBQUksQ0FBQztZQUNqQyxPQUFPLENBQUMsR0FBRyxDQUFDLHlDQUF5QyxDQUFDLENBQUM7UUFDekQsQ0FBQztRQUFDLE9BQU8sS0FBSyxFQUFFLENBQUM7WUFDZixPQUFPLENBQUMsS0FBSyxDQUFDLGdDQUFnQyxFQUFFLEtBQUssQ0FBQyxDQUFDO1lBQ3ZELElBQUssS0FBYSxDQUFDLElBQUksS0FBSyxZQUFZLEVBQUUsQ0FBQztnQkFDekMsTUFBTSxJQUFJLEtBQUssQ0FBQyxRQUFRLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxJQUFJLElBQUksRUFBRSx3Q0FBd0MsQ0FBQyxDQUFDO1lBQ3ZHLENBQUM7WUFDRCxNQUFNLEtBQUssQ0FBQztRQUNkLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMsb0JBQW9CO1FBQ2hDLElBQUksQ0FBQyxJQUFJLENBQUMsb0JBQW9CLEVBQUUsQ0FBQztZQUMvQixPQUFPLENBQUMsR0FBRyxDQUFDLDhDQUE4QyxDQUFDLENBQUM7WUFDNUQsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLENBQUMsSUFBSSxDQUFDLG9CQUFvQixFQUFFLENBQUM7WUFDL0IsT0FBTztRQUNULENBQUM7UUFFRCxJQUFJLENBQUM7WUFDSCxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssZ0JBQWdCLENBQUMsQ0FBQztZQUM1RSxNQUFNLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxjQUFjLENBQUMsQ0FBQztZQUNoRCxJQUFJLENBQUMsb0JBQW9CLEdBQUcsS0FBSyxDQUFDO1lBQ2xDLE9BQU8sQ0FBQyxHQUFHLENBQUMsMkNBQTJDLENBQUMsQ0FBQztRQUMzRCxDQUFDO1FBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQztZQUNmLE9BQU8sQ0FBQyxLQUFLLENBQUMsbUNBQW1DLEVBQUUsS0FBSyxDQUFDLENBQUM7WUFDMUQsc0RBQXNEO1lBQ3RELElBQUksQ0FBQyxvQkFBb0IsR0FBRyxLQUFLLENBQUM7WUFDbEMsTUFBTSxLQUFLLENBQUM7UUFDZCxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ssaUJBQWlCO1FBQ3ZCLG9DQUFvQztRQUNwQyxJQUFJLENBQUMsWUFBWSxHQUFHLFdBQVcsQ0FBQyxHQUFHLEVBQUU7WUFDbkMsSUFBSSxDQUFDLHlCQUF5QixFQUFFLENBQUM7UUFDbkMsQ0FBQyxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDO1FBRXhCLDZCQUE2QjtRQUM3QixJQUFJLENBQUMseUJBQXlCLEVBQUUsQ0FBQztJQUNuQyxDQUFDO0lBRUQ7O09BRUc7SUFDSyxLQUFLLENBQUMseUJBQXlCO1FBQ3JDLEtBQUssTUFBTSxLQUFLLElBQUksSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2hDLElBQUksS0FBSyxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsV0FBVyxLQUFLLE1BQU0sRUFBRSxDQUFDO2dCQUM3QyxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsSUFBSSxJQUFJLElBQUksQ0FBQyx1QkFBdUIsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDdkUsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxTQUFTLENBQUMsQ0FBQztnQkFFNUQsSUFBSSxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztvQkFDM0MsT0FBTyxDQUFDLEdBQUcsQ0FBQyxtQkFBbUIsU0FBUyxnQkFBZ0IsQ0FBQyxDQUFDO29CQUMxRCxJQUFJLENBQUM7d0JBQ0gsTUFBTSxJQUFJLENBQUMsb0JBQW9CLENBQUMsS0FBSyxDQUFDLENBQUM7b0JBQ3pDLENBQUM7b0JBQUMsT0FBTyxLQUFLLEVBQUUsQ0FBQzt3QkFDZixPQUFPLENBQUMsS0FBSyxDQUFDLG1DQUFtQyxTQUFTLEtBQUssS0FBSyxFQUFFLENBQUMsQ0FBQztvQkFDMUUsQ0FBQztnQkFDSCxDQUFDO1lBQ0gsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSyxnQkFBZ0IsQ0FDdEIsU0FBaUIsRUFDakIsTUFBNkIsRUFDN0IsTUFBNkIsRUFDN0IsUUFBMkIsRUFDM0IsS0FBYztRQUVkLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLFNBQVMsRUFBRTtZQUM3QixNQUFNLEVBQUUsU0FBUztZQUNqQixNQUFNO1lBQ04sTUFBTTtZQUNOLFVBQVUsRUFBRSxRQUFRLEVBQUUsVUFBVTtZQUNoQyxTQUFTLEVBQUUsUUFBUSxFQUFFLFNBQVM7WUFDOUIsS0FBSztTQUNOLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNJLG9CQUFvQixDQUFDLFNBQWlCO1FBQzNDLE9BQU8sSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDeEMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLGdCQUFnQixDQUFDLFNBQWlCO1FBQzdDLE1BQU0sS0FBSyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLElBQUksS0FBSyxTQUFTLENBQUMsQ0FBQztRQUMxRCxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDWCxNQUFNLElBQUksS0FBSyxDQUFDLFNBQVMsU0FBUyxZQUFZLENBQUMsQ0FBQztRQUNsRCxDQUFDO1FBRUQsK0NBQStDO1FBQy9DLE1BQU0sSUFBSSxDQUFDLFNBQVMsQ0FBQyxpQkFBaUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUNsRCxNQUFNLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUN6QyxDQUFDO0lBRUQ7O09BRUc7SUFDSyxxQkFBcUIsQ0FBQyxhQUE2RDtRQUN6Rix5REFBeUQ7UUFDekQsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixFQUFFLElBQUksSUFBSSxFQUFFLENBQUM7UUFFMUQseUVBQXlFO1FBQ3pFLE1BQU0sY0FBYyxHQUFpQjtZQUNuQyxJQUFJLEVBQUUsZ0JBQWdCO1lBQ3RCLFFBQVEsRUFBRSxJQUFJLEVBQUcsZ0JBQWdCO1lBQ2pDLEtBQUssRUFBRTtnQkFDTCxLQUFLLEVBQUUsYUFBYTtnQkFDcEIsSUFBSSxFQUFFLCtCQUErQjthQUN0QztZQUNELE1BQU0sRUFBRTtnQkFDTixJQUFJLEVBQUUsUUFBUTtnQkFDZCxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxFQUFFO29CQUN6QixrQ0FBa0M7b0JBQ2xDLE1BQU0sS0FBSyxHQUFHLE9BQU8sQ0FBQyxJQUFJLEVBQUUsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLEdBQUcsRUFBRSxDQUFDO29CQUM3QyxJQUFJLENBQUMsS0FBSyxFQUFFLENBQUM7d0JBQ1gsT0FBTyxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLFdBQVcsRUFBRSxDQUFDO29CQUM1QyxDQUFDO29CQUVELHFEQUFxRDtvQkFDckQsTUFBTSxPQUFPLEdBQUc7d0JBQ2QsR0FBRyxFQUFFLE9BQU8sQ0FBQyxJQUFJO3dCQUNqQixNQUFNLEVBQUUsS0FBSzt3QkFDYixPQUFPLEVBQUUsT0FBTyxDQUFDLE9BQU8sSUFBSSxFQUFFO3FCQUMvQixDQUFDO29CQUVGLElBQUksWUFBWSxHQUFRLElBQUksQ0FBQztvQkFDN0IsTUFBTSxPQUFPLEdBQUc7d0JBQ2QsVUFBVSxFQUFFLEdBQUc7d0JBQ2YsU0FBUyxFQUFFLENBQUMsSUFBWSxFQUFFLEtBQWEsRUFBRSxFQUFFLEdBQUUsQ0FBQzt3QkFDOUMsR0FBRyxFQUFFLENBQUMsSUFBUyxFQUFFLEVBQUU7NEJBQ2pCLFlBQVksR0FBRyxJQUFJLENBQUM7d0JBQ3RCLENBQUM7cUJBQ0YsQ0FBQztvQkFFRiwwQkFBMEI7b0JBQzFCLE1BQU0sT0FBTyxHQUFHLE1BQU0sSUFBSSxPQUFPLENBQVUsQ0FBQyxPQUFPLEVBQUUsRUFBRTt3QkFDckQsYUFBYSxDQUFDLGFBQWEsQ0FBQyxPQUFjLEVBQUUsT0FBYyxFQUFFLEdBQUcsRUFBRTs0QkFDL0QsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO3dCQUNqQixDQUFDLENBQUMsQ0FBQzt3QkFDSCw4QkFBOEI7d0JBQzlCLFVBQVUsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUUsR0FBRyxDQUFDLENBQUM7b0JBQ3ZDLENBQUMsQ0FBQyxDQUFDO29CQUVILElBQUksT0FBTyxJQUFJLFlBQVksRUFBRSxDQUFDO3dCQUM1QixPQUFPOzRCQUNMLE1BQU0sRUFBRSxPQUFPLENBQUMsVUFBVTs0QkFDMUIsT0FBTyxFQUFFLEVBQUUsY0FBYyxFQUFFLFlBQVksRUFBRTs0QkFDekMsSUFBSSxFQUFFLFlBQVk7eUJBQ25CLENBQUM7b0JBQ0osQ0FBQzt5QkFBTSxDQUFDO3dCQUNOLE9BQU8sRUFBRSxNQUFNLEVBQUUsR0FBRyxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUUsQ0FBQztvQkFDNUMsQ0FBQztnQkFDSCxDQUFDO2FBQ0Y7U0FDRixDQUFDO1FBRUYsa0RBQWtEO1FBQ2xELElBQUksQ0FBQyxjQUFjLEdBQUcsY0FBYyxDQUFDO0lBQ3ZDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxJQUFJO1FBQ2YsSUFBSSxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7WUFDdEIsYUFBYSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQztZQUNqQyxJQUFJLENBQUMsWUFBWSxHQUFHLElBQUksQ0FBQztRQUMzQixDQUFDO1FBRUQsNENBQTRDO1FBQzVDLElBQUksSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1lBQ3hCLE9BQU8sQ0FBQyxHQUFHLENBQUMsK0NBQStDLENBQUMsQ0FBQztZQUM3RCxNQUFNLElBQUksQ0FBQyxvQkFBb0IsRUFBRSxDQUFDO1FBQ3BDLENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUNuQixNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLENBQUM7UUFDOUIsQ0FBQztRQUVELCtCQUErQjtRQUMvQixJQUFJLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDcEMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEtBQUssRUFBRSxDQUFDO1FBQ2pDLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxjQUFjO1FBQ25CLE9BQU8sSUFBSSxDQUFDLFdBQVcsQ0FBQztJQUMxQixDQUFDO0NBQ0YifQ==
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "19.2.3",
3
+ "version": "19.2.4",
4
4
  "private": false,
5
5
  "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.",
6
6
  "main": "dist_ts/index.js",
package/readme.plan.md CHANGED
@@ -2,100 +2,136 @@
2
2
 
3
3
  cat /home/philkunz/.claude/CLAUDE.md
4
4
 
5
- ## Critical Bug Fix: Missing Route Update Callback in updateRoutes Method
5
+ ## Critical Bug Fix: Port 80 EADDRINUSE with ACME Challenge Routes
6
6
 
7
7
  ### Problem Statement
8
- SmartProxy v19.2.2 has a bug where the ACME certificate manager loses its route update callback when routes are dynamically updated. This causes the error "No route update callback set" and prevents automatic certificate acquisition.
8
+ SmartProxy encounters an "EADDRINUSE" error on port 80 when provisioning multiple ACME certificates. The issue occurs because the certificate manager adds and removes the challenge route for each certificate individually, causing race conditions when multiple certificates are provisioned concurrently.
9
9
 
10
10
  ### Root Cause
11
- When `updateRoutes()` creates a new SmartCertManager instance, it fails to set the route update callback that's required for ACME challenges. This callback is properly set in `initializeCertificateManager()` but is missing from the route update flow.
11
+ The `SmartCertManager` class adds the ACME challenge route (port 80) before provisioning each certificate and removes it afterward. When multiple certificates are provisioned:
12
+ 1. Each provisioning cycle adds its own challenge route
13
+ 2. This triggers `updateRoutes()` which calls `PortManager.updatePorts()`
14
+ 3. Port 80 is repeatedly added/removed, causing binding conflicts
12
15
 
13
16
  ### Implementation Plan
14
17
 
15
- #### Phase 1: Fix the Bug
16
- 1. **Update the updateRoutes method** in `/mnt/data/lossless/push.rocks/smartproxy/ts/proxies/smart-proxy/smart-proxy.ts`
17
- - [ ] Add the missing callback setup before initializing the new certificate manager
18
- - [ ] Ensure the callback is set after creating the new SmartCertManager instance
19
-
20
- #### Phase 2: Create Tests
21
- 2. **Write comprehensive tests** for the route update functionality
22
- - [ ] Create test file: `test/test.route-update-callback.node.ts`
23
- - [ ] Test that callback is preserved when routes are updated
24
- - [ ] Test that ACME challenges work after route updates
25
- - [ ] Test edge cases (multiple updates, no cert manager, etc.)
26
-
27
- #### Phase 3: Enhance Documentation
28
- 3. **Update documentation** to clarify the route update behavior
29
- - [ ] Add section to certificate-management.md about dynamic route updates
30
- - [ ] Document the callback requirement for ACME challenges
31
- - [ ] Include example of proper route update implementation
32
-
33
- #### Phase 4: Prevent Future Regressions
34
- 4. **Refactor for better maintainability**
35
- - [ ] Consider extracting certificate manager setup to a shared method
36
- - [ ] Add code comments explaining the callback requirement
37
- - [ ] Consider making the callback setup more explicit in the API
18
+ #### Phase 1: Refactor Challenge Route Lifecycle
19
+ 1. **Modify challenge route handling** in `SmartCertManager`
20
+ - [ ] Add challenge route once during initialization if ACME is configured
21
+ - [ ] Keep challenge route active throughout entire certificate provisioning
22
+ - [ ] Remove challenge route only after all certificates are provisioned
23
+ - [ ] Add concurrency control to prevent multiple simultaneous route updates
24
+
25
+ #### Phase 2: Update Certificate Provisioning Flow
26
+ 2. **Refactor certificate provisioning methods**
27
+ - [ ] Separate challenge route management from individual certificate provisioning
28
+ - [ ] Update `provisionAcmeCertificate()` to not add/remove challenge routes
29
+ - [ ] Modify `provisionAllCertificates()` to handle challenge route lifecycle
30
+ - [ ] Add error handling for challenge route initialization failures
31
+
32
+ #### Phase 3: Implement Concurrency Controls
33
+ 3. **Add synchronization mechanisms**
34
+ - [ ] Implement mutex/lock for challenge route operations
35
+ - [ ] Ensure certificate provisioning is properly serialized
36
+ - [ ] Add safeguards against duplicate challenge routes
37
+ - [ ] Handle edge cases (shutdown during provisioning, renewal conflicts)
38
+
39
+ #### Phase 4: Enhance Error Handling
40
+ 4. **Improve error handling and recovery**
41
+ - [ ] Add specific error types for port conflicts
42
+ - [ ] Implement retry logic for transient port binding issues
43
+ - [ ] Add detailed logging for challenge route lifecycle
44
+ - [ ] Ensure proper cleanup on errors
45
+
46
+ #### Phase 5: Create Comprehensive Tests
47
+ 5. **Write tests for challenge route management**
48
+ - [ ] Test concurrent certificate provisioning
49
+ - [ ] Test challenge route persistence during provisioning
50
+ - [ ] Test error scenarios (port already in use)
51
+ - [ ] Test cleanup after provisioning
52
+ - [ ] Test renewal scenarios with existing challenge routes
53
+
54
+ #### Phase 6: Update Documentation
55
+ 6. **Document the new behavior**
56
+ - [ ] Update certificate management documentation
57
+ - [ ] Add troubleshooting guide for port conflicts
58
+ - [ ] Document the challenge route lifecycle
59
+ - [ ] Include examples of proper ACME configuration
38
60
 
39
61
  ### Technical Details
40
62
 
41
63
  #### Specific Code Changes
42
- 1. In `updateRoutes()` method (around line 535), add:
64
+
65
+ 1. In `SmartCertManager.initialize()`:
66
+ ```typescript
67
+ // Add challenge route once at initialization
68
+ if (hasAcmeRoutes && this.acmeOptions?.email) {
69
+ await this.addChallengeRoute();
70
+ }
71
+ ```
72
+
73
+ 2. Modify `provisionAcmeCertificate()`:
43
74
  ```typescript
44
- // Set route update callback for ACME challenges
45
- this.certManager.setUpdateRoutesCallback(async (routes) => {
46
- await this.updateRoutes(routes);
47
- });
75
+ // Remove these lines:
76
+ // await this.addChallengeRoute();
77
+ // await this.removeChallengeRoute();
78
+ ```
79
+
80
+ 3. Update `stop()` method:
81
+ ```typescript
82
+ // Always remove challenge route on shutdown
83
+ if (this.challengeRoute) {
84
+ await this.removeChallengeRoute();
85
+ }
48
86
  ```
49
87
 
50
- 2. Consider refactoring the certificate manager setup into a helper method to avoid duplication:
88
+ 4. Add concurrency control:
51
89
  ```typescript
52
- private async setupCertificateManager(
53
- routes: IRouteConfig[],
54
- certStore: string,
55
- acmeOptions?: any
56
- ): Promise<SmartCertManager> {
57
- const certManager = new SmartCertManager(routes, certStore, acmeOptions);
58
-
59
- // Always set up the route update callback
60
- certManager.setUpdateRoutesCallback(async (routes) => {
61
- await this.updateRoutes(routes);
90
+ private challengeRouteLock = new AsyncLock();
91
+
92
+ private async manageChallengeRoute(operation: 'add' | 'remove'): Promise<void> {
93
+ await this.challengeRouteLock.acquire('challenge-route', async () => {
94
+ if (operation === 'add') {
95
+ await this.addChallengeRoute();
96
+ } else {
97
+ await this.removeChallengeRoute();
98
+ }
62
99
  });
63
-
64
- if (this.networkProxyBridge.getNetworkProxy()) {
65
- certManager.setNetworkProxy(this.networkProxyBridge.getNetworkProxy());
66
- }
67
-
68
- return certManager;
69
100
  }
70
101
  ```
71
102
 
72
103
  ### Success Criteria
73
- - [x] ACME certificate acquisition works after route updates
74
- - [x] No "No route update callback set" errors occur
104
+ - [x] No EADDRINUSE errors when provisioning multiple certificates
105
+ - [x] Challenge route remains active during entire provisioning cycle
106
+ - [x] Port 80 is only bound once per SmartProxy instance
107
+ - [x] Proper cleanup on shutdown or error
75
108
  - [x] All tests pass
76
109
  - [x] Documentation clearly explains the behavior
77
- - [x] Code is more maintainable and less prone to regression
78
110
 
79
111
  ### Implementation Summary
80
112
 
81
- The bug has been successfully fixed through the following steps:
113
+ The port 80 EADDRINUSE issue has been successfully fixed through the following changes:
82
114
 
83
- 1. **Bug Fix Applied**: Added the missing `setUpdateRoutesCallback` call in the `updateRoutes` method
84
- 2. **Tests Created**: Comprehensive test suite validates the fix and prevents regression
85
- 3. **Documentation Updated**: Added section on dynamic route updates to the certificate management guide
86
- 4. **Code Refactored**: Extracted certificate manager creation into a helper method for better maintainability
115
+ 1. **Challenge Route Lifecycle**: Modified to add challenge route once during initialization and keep it active throughout certificate provisioning
116
+ 2. **Concurrency Control**: Added flags to prevent concurrent provisioning and duplicate challenge route operations
117
+ 3. **Error Handling**: Enhanced error messages for port conflicts and proper cleanup on errors
118
+ 4. **Tests**: Created comprehensive test suite for challenge route lifecycle scenarios
119
+ 5. **Documentation**: Updated certificate management guide with troubleshooting section for port conflicts
87
120
 
88
- The fix ensures that when routes are dynamically updated, the certificate manager maintains its ability to update routes for ACME challenges, preventing the "No route update callback set" error.
121
+ The fix ensures that port 80 is only bound once, preventing EADDRINUSE errors during concurrent certificate provisioning operations.
89
122
 
90
123
  ### Timeline
91
- - Phase 1: Immediate fix (30 minutes)
92
- - Phase 2: Test creation (1 hour)
93
- - Phase 3: Documentation (30 minutes)
94
- - Phase 4: Refactoring (1 hour)
124
+ - Phase 1: 2 hours (Challenge route lifecycle)
125
+ - Phase 2: 1 hour (Provisioning flow)
126
+ - Phase 3: 2 hours (Concurrency controls)
127
+ - Phase 4: 1 hour (Error handling)
128
+ - Phase 5: 2 hours (Testing)
129
+ - Phase 6: 1 hour (Documentation)
95
130
 
96
- Total estimated time: 3 hours
131
+ Total estimated time: 9 hours
97
132
 
98
133
  ### Notes
99
- - This is a critical bug that affects production use of SmartProxy
100
- - The fix is straightforward but requires careful testing
101
- - Consider backporting to v19.2.x branch if maintaining multiple versions
134
+ - This is a critical bug affecting ACME certificate provisioning
135
+ - The fix requires careful handling of concurrent operations
136
+ - Backward compatibility must be maintained
137
+ - Consider impact on renewal operations and edge cases
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '19.2.3',
6
+ version: '19.2.4',
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
  }
@@ -38,6 +38,12 @@ export class SmartCertManager {
38
38
  // Callback to update SmartProxy routes for challenges
39
39
  private updateRoutesCallback?: (routes: IRouteConfig[]) => Promise<void>;
40
40
 
41
+ // Flag to track if challenge route is currently active
42
+ private challengeRouteActive: boolean = false;
43
+
44
+ // Flag to track if provisioning is in progress
45
+ private isProvisioning: boolean = false;
46
+
41
47
  constructor(
42
48
  private routes: IRouteConfig[],
43
49
  private certDir: string = './certs',
@@ -96,6 +102,10 @@ export class SmartCertManager {
96
102
  });
97
103
 
98
104
  await this.smartAcme.start();
105
+
106
+ // Add challenge route once at initialization
107
+ console.log('Adding ACME challenge route during initialization');
108
+ await this.addChallengeRoute();
99
109
  }
100
110
 
101
111
  // Provision certificates for all routes
@@ -114,24 +124,37 @@ export class SmartCertManager {
114
124
  r.action.tls?.mode === 'terminate-and-reencrypt'
115
125
  );
116
126
 
117
- for (const route of certRoutes) {
118
- try {
119
- await this.provisionCertificate(route);
120
- } catch (error) {
121
- console.error(`Failed to provision certificate for route ${route.name}: ${error}`);
127
+ // Set provisioning flag to prevent concurrent operations
128
+ this.isProvisioning = true;
129
+
130
+ try {
131
+ for (const route of certRoutes) {
132
+ try {
133
+ await this.provisionCertificate(route, true); // Allow concurrent since we're managing it here
134
+ } catch (error) {
135
+ console.error(`Failed to provision certificate for route ${route.name}: ${error}`);
136
+ }
122
137
  }
138
+ } finally {
139
+ this.isProvisioning = false;
123
140
  }
124
141
  }
125
142
 
126
143
  /**
127
144
  * Provision certificate for a single route
128
145
  */
129
- public async provisionCertificate(route: IRouteConfig): Promise<void> {
146
+ public async provisionCertificate(route: IRouteConfig, allowConcurrent: boolean = false): Promise<void> {
130
147
  const tls = route.action.tls;
131
148
  if (!tls || (tls.mode !== 'terminate' && tls.mode !== 'terminate-and-reencrypt')) {
132
149
  return;
133
150
  }
134
151
 
152
+ // Check if provisioning is already in progress (prevent concurrent provisioning)
153
+ if (!allowConcurrent && this.isProvisioning) {
154
+ console.log(`Certificate provisioning already in progress, skipping ${route.name}`);
155
+ return;
156
+ }
157
+
135
158
  const domains = this.extractDomainsFromRoute(route);
136
159
  if (domains.length === 0) {
137
160
  console.warn(`Route ${route.name} has TLS termination but no domains`);
@@ -186,13 +209,12 @@ export class SmartCertManager {
186
209
  this.updateCertStatus(routeName, 'pending', 'acme');
187
210
 
188
211
  try {
189
- // Add challenge route before requesting certificate
190
- await this.addChallengeRoute();
191
-
192
- try {
193
- // Use smartacme to get certificate
194
- const cert = await this.smartAcme.getCertificateForDomain(primaryDomain);
212
+ // Challenge route should already be active from initialization
213
+ // No need to add it for each certificate
195
214
 
215
+ // Use smartacme to get certificate
216
+ const cert = await this.smartAcme.getCertificateForDomain(primaryDomain);
217
+
196
218
  // SmartAcme's Cert object has these properties:
197
219
  // - publicKey: The certificate PEM string
198
220
  // - privateKey: The private key PEM string
@@ -211,18 +233,9 @@ export class SmartCertManager {
211
233
  await this.applyCertificate(primaryDomain, certData);
212
234
  this.updateCertStatus(routeName, 'valid', 'acme', certData);
213
235
 
214
- console.log(`Successfully provisioned ACME certificate for ${primaryDomain}`);
215
- } catch (error) {
216
- console.error(`Failed to provision ACME certificate for ${primaryDomain}: ${error}`);
217
- this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message);
218
- throw error;
219
- } finally {
220
- // Always remove challenge route after provisioning
221
- await this.removeChallengeRoute();
222
- }
236
+ console.log(`Successfully provisioned ACME certificate for ${primaryDomain}`);
223
237
  } catch (error) {
224
- // Handle outer try-catch from adding challenge route
225
- console.error(`Failed to setup ACME challenge for ${primaryDomain}: ${error}`);
238
+ console.error(`Failed to provision ACME certificate for ${primaryDomain}: ${error}`);
226
239
  this.updateCertStatus(routeName, 'error', 'acme', undefined, error.message);
227
240
  throw error;
228
241
  }
@@ -337,6 +350,11 @@ export class SmartCertManager {
337
350
  * Add challenge route to SmartProxy
338
351
  */
339
352
  private async addChallengeRoute(): Promise<void> {
353
+ if (this.challengeRouteActive) {
354
+ console.log('Challenge route already active, skipping');
355
+ return;
356
+ }
357
+
340
358
  if (!this.updateRoutesCallback) {
341
359
  throw new Error('No route update callback set');
342
360
  }
@@ -346,20 +364,44 @@ export class SmartCertManager {
346
364
  }
347
365
  const challengeRoute = this.challengeRoute;
348
366
 
349
- const updatedRoutes = [...this.routes, challengeRoute];
350
- await this.updateRoutesCallback(updatedRoutes);
367
+ try {
368
+ const updatedRoutes = [...this.routes, challengeRoute];
369
+ await this.updateRoutesCallback(updatedRoutes);
370
+ this.challengeRouteActive = true;
371
+ console.log('ACME challenge route successfully added');
372
+ } catch (error) {
373
+ console.error('Failed to add challenge route:', error);
374
+ if ((error as any).code === 'EADDRINUSE') {
375
+ throw new Error(`Port ${this.globalAcmeDefaults?.port || 80} is already in use for ACME challenges`);
376
+ }
377
+ throw error;
378
+ }
351
379
  }
352
380
 
353
381
  /**
354
382
  * Remove challenge route from SmartProxy
355
383
  */
356
384
  private async removeChallengeRoute(): Promise<void> {
385
+ if (!this.challengeRouteActive) {
386
+ console.log('Challenge route not active, skipping removal');
387
+ return;
388
+ }
389
+
357
390
  if (!this.updateRoutesCallback) {
358
391
  return;
359
392
  }
360
393
 
361
- const filteredRoutes = this.routes.filter(r => r.name !== 'acme-challenge');
362
- await this.updateRoutesCallback(filteredRoutes);
394
+ try {
395
+ const filteredRoutes = this.routes.filter(r => r.name !== 'acme-challenge');
396
+ await this.updateRoutesCallback(filteredRoutes);
397
+ this.challengeRouteActive = false;
398
+ console.log('ACME challenge route successfully removed');
399
+ } catch (error) {
400
+ console.error('Failed to remove challenge route:', error);
401
+ // Reset the flag even on error to avoid getting stuck
402
+ this.challengeRouteActive = false;
403
+ throw error;
404
+ }
363
405
  }
364
406
 
365
407
  /**
@@ -512,14 +554,19 @@ export class SmartCertManager {
512
554
  this.renewalTimer = null;
513
555
  }
514
556
 
557
+ // Always remove challenge route on shutdown
558
+ if (this.challengeRoute) {
559
+ console.log('Removing ACME challenge route during shutdown');
560
+ await this.removeChallengeRoute();
561
+ }
562
+
515
563
  if (this.smartAcme) {
516
564
  await this.smartAcme.stop();
517
565
  }
518
566
 
519
- // Remove any active challenge routes
567
+ // Clear any pending challenges
520
568
  if (this.pendingChallenges.size > 0) {
521
569
  this.pendingChallenges.clear();
522
- await this.removeChallengeRoute();
523
570
  }
524
571
  }
525
572