fix(acme): Fix port 80 ACME management and challenge route concurrency issues by deduplicating port listeners, preserving challenge route state across certificate manager recreations, and adding mutex locks to route updates.
This commit is contained in:
@@ -3,6 +3,7 @@ import { NetworkProxy } from '../network-proxy/index.js';
|
||||
import type { IRouteConfig, IRouteTls } from './models/route-types.js';
|
||||
import type { IAcmeOptions } from './models/interfaces.js';
|
||||
import { CertStore } from './cert-store.js';
|
||||
import type { AcmeStateManager } from './acme-state-manager.js';
|
||||
|
||||
export interface ICertStatus {
|
||||
domain: string;
|
||||
@@ -44,6 +45,9 @@ export class SmartCertManager {
|
||||
// Flag to track if provisioning is in progress
|
||||
private isProvisioning: boolean = false;
|
||||
|
||||
// ACME state manager reference
|
||||
private acmeStateManager: AcmeStateManager | null = null;
|
||||
|
||||
constructor(
|
||||
private routes: IRouteConfig[],
|
||||
private certDir: string = './certs',
|
||||
@@ -51,15 +55,39 @@ export class SmartCertManager {
|
||||
email?: string;
|
||||
useProduction?: boolean;
|
||||
port?: number;
|
||||
},
|
||||
private initialState?: {
|
||||
challengeRouteActive?: boolean;
|
||||
}
|
||||
) {
|
||||
this.certStore = new CertStore(certDir);
|
||||
|
||||
// Apply initial state if provided
|
||||
if (initialState) {
|
||||
this.challengeRouteActive = initialState.challengeRouteActive || false;
|
||||
}
|
||||
}
|
||||
|
||||
public setNetworkProxy(networkProxy: NetworkProxy): void {
|
||||
this.networkProxy = networkProxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current state of the certificate manager
|
||||
*/
|
||||
public getState(): { challengeRouteActive: boolean } {
|
||||
return {
|
||||
challengeRouteActive: this.challengeRouteActive
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the ACME state manager
|
||||
*/
|
||||
public setAcmeStateManager(stateManager: AcmeStateManager): void {
|
||||
this.acmeStateManager = stateManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set global ACME defaults from top-level configuration
|
||||
*/
|
||||
@@ -103,9 +131,13 @@ export class SmartCertManager {
|
||||
|
||||
await this.smartAcme.start();
|
||||
|
||||
// Add challenge route once at initialization
|
||||
console.log('Adding ACME challenge route during initialization');
|
||||
await this.addChallengeRoute();
|
||||
// Add challenge route once at initialization if not already active
|
||||
if (!this.challengeRouteActive) {
|
||||
console.log('Adding ACME challenge route during initialization');
|
||||
await this.addChallengeRoute();
|
||||
} else {
|
||||
console.log('Challenge route already active from previous instance');
|
||||
}
|
||||
}
|
||||
|
||||
// Provision certificates for all routes
|
||||
@@ -350,8 +382,15 @@ export class SmartCertManager {
|
||||
* Add challenge route to SmartProxy
|
||||
*/
|
||||
private async addChallengeRoute(): Promise<void> {
|
||||
// Check with state manager first
|
||||
if (this.acmeStateManager && this.acmeStateManager.isChallengeRouteActive()) {
|
||||
console.log('Challenge route already active in global state, skipping');
|
||||
this.challengeRouteActive = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.challengeRouteActive) {
|
||||
console.log('Challenge route already active, skipping');
|
||||
console.log('Challenge route already active locally, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -368,6 +407,12 @@ export class SmartCertManager {
|
||||
const updatedRoutes = [...this.routes, challengeRoute];
|
||||
await this.updateRoutesCallback(updatedRoutes);
|
||||
this.challengeRouteActive = true;
|
||||
|
||||
// Register with state manager
|
||||
if (this.acmeStateManager) {
|
||||
this.acmeStateManager.addChallengeRoute(challengeRoute);
|
||||
}
|
||||
|
||||
console.log('ACME challenge route successfully added');
|
||||
} catch (error) {
|
||||
console.error('Failed to add challenge route:', error);
|
||||
@@ -395,6 +440,12 @@ export class SmartCertManager {
|
||||
const filteredRoutes = this.routes.filter(r => r.name !== 'acme-challenge');
|
||||
await this.updateRoutesCallback(filteredRoutes);
|
||||
this.challengeRouteActive = false;
|
||||
|
||||
// Remove from state manager
|
||||
if (this.acmeStateManager) {
|
||||
this.acmeStateManager.removeChallengeRoute('acme-challenge');
|
||||
}
|
||||
|
||||
console.log('ACME challenge route successfully removed');
|
||||
} catch (error) {
|
||||
console.error('Failed to remove challenge route:', error);
|
||||
|
Reference in New Issue
Block a user