This commit is contained in:
2025-05-20 16:01:32 +00:00
parent b2584fffb1
commit 6512551f02
5 changed files with 183 additions and 1749 deletions

View File

@ -401,9 +401,13 @@ export class SmartCertManager {
/**
* Add challenge route to SmartProxy
*
* This method adds a special route for ACME HTTP-01 challenges, which typically uses port 80.
* Since we may already be listening on port 80 for regular routes, we need to be
* careful about how we add this route to avoid binding conflicts.
*/
private async addChallengeRoute(): Promise<void> {
// Check with state manager first
// Check with state manager first - avoid duplication
if (this.acmeStateManager && this.acmeStateManager.isChallengeRouteActive()) {
try {
logger.log('info', 'Challenge route already active in global state, skipping', { component: 'certificate-manager' });
@ -437,6 +441,7 @@ export class SmartCertManager {
const challengePort = this.globalAcmeDefaults?.port || 80;
// Check if any existing routes are already using this port
// This helps us determine if we need to create a new binding or can reuse existing one
const portInUseByRoutes = this.routes.some(route => {
const routePorts = Array.isArray(route.match.ports) ? route.match.ports : [route.match.ports];
return routePorts.some(p => {
@ -450,36 +455,37 @@ export class SmartCertManager {
return false;
});
});
if (portInUseByRoutes) {
logger.log('info', `Port ${challengePort} is already used by another route, merging ACME challenge route`, {
port: challengePort,
component: 'certificate-manager'
});
}
// Add the challenge route
const challengeRoute = this.challengeRoute;
// If the port is already in use by other routes in this SmartProxy instance,
// we can safely add the ACME challenge route without trying to bind to the port again
try {
// Check if we're already listening on the challenge port
const isPortAlreadyBound = portInUseByRoutes;
if (isPortAlreadyBound) {
// Log whether port is already in use by other routes
if (portInUseByRoutes) {
try {
logger.log('info', `Port ${challengePort} is already bound by SmartProxy, adding ACME challenge route without rebinding`, {
logger.log('info', `Port ${challengePort} is already used by another route, merging ACME challenge route`, {
port: challengePort,
component: 'certificate-manager'
component: 'certificate-manager'
});
} catch (error) {
// Silently handle logging errors
console.log(`[INFO] Port ${challengePort} is already bound by SmartProxy, adding ACME challenge route without rebinding`);
console.log(`[INFO] Port ${challengePort} is already used by another route, merging ACME challenge route`);
}
} else {
try {
logger.log('info', `Adding new ACME challenge route on port ${challengePort}`, {
port: challengePort,
component: 'certificate-manager'
});
} catch (error) {
// Silently handle logging errors
console.log(`[INFO] Adding new ACME challenge route on port ${challengePort}`);
}
}
// Add the challenge route to the existing routes
const challengeRoute = this.challengeRoute;
const updatedRoutes = [...this.routes, challengeRoute];
// With the re-ordering of start(), port binding should already be done
// This updateRoutes call should just add the route without binding again
await this.updateRoutesCallback(updatedRoutes);
this.challengeRouteActive = true;
@ -495,28 +501,47 @@ export class SmartCertManager {
console.log('[INFO] ACME challenge route successfully added');
}
} catch (error) {
// Handle specific EADDRINUSE errors differently based on whether it's an internal conflict
// Enhanced error handling based on error type
if ((error as any).code === 'EADDRINUSE') {
try {
logger.log('error', `Failed to add challenge route on port ${challengePort}: ${error.message}`, {
error: (error as Error).message,
logger.log('warn', `Challenge port ${challengePort} is unavailable - it's already in use by another process. Consider configuring a different ACME port.`, {
port: challengePort,
error: (error as Error).message,
component: 'certificate-manager'
});
} catch (logError) {
// Silently handle logging errors
console.log(`[WARN] Challenge port ${challengePort} is unavailable - it's already in use by another process. Consider configuring a different ACME port.`);
}
// Provide a more informative and actionable error message
throw new Error(
`ACME HTTP-01 challenge port ${challengePort} is already in use by another process. ` +
`Please configure a different port using the acme.port setting (e.g., 8080).`
);
} else if (error.message && error.message.includes('EADDRINUSE')) {
// Some Node.js versions embed the error code in the message rather than the code property
try {
logger.log('warn', `Port ${challengePort} conflict detected: ${error.message}`, {
port: challengePort,
component: 'certificate-manager'
});
} catch (logError) {
// Silently handle logging errors
console.log(`[ERROR] Failed to add challenge route on port ${challengePort}: ${error.message}`);
console.log(`[WARN] Port ${challengePort} conflict detected: ${error.message}`);
}
// Provide a more informative error message
// More detailed error message with suggestions
throw new Error(
`Port ${challengePort} is already in use. ` +
`If it's in use by an external process, configure a different port in the ACME settings. ` +
`If it's in use by SmartProxy, there may be a route configuration issue.`
`ACME HTTP challenge port ${challengePort} conflict detected. ` +
`To resolve this issue, try one of these approaches:\n` +
`1. Configure a different port in ACME settings (acme.port)\n` +
`2. Add a regular route that uses port ${challengePort} before initializing the certificate manager\n` +
`3. Stop any other services that might be using port ${challengePort}`
);
}
// Log and rethrow other errors
// Log and rethrow other types of errors
try {
logger.log('error', `Failed to add challenge route: ${(error as Error).message}`, {
error: (error as Error).message,