This commit is contained in:
2025-05-01 15:39:20 +00:00
parent a59ebd6202
commit 09aadc702e
7 changed files with 145 additions and 130 deletions

View File

@@ -7,7 +7,6 @@ import * as tls from 'tls';
import * as url from 'url';
import * as http2 from 'http2';
export { EventEmitter, http, https, net, tls, url, http2 };
// tsclass scope
@@ -25,7 +24,19 @@ import * as smartstring from '@push.rocks/smartstring';
import * as smartacme from '@push.rocks/smartacme';
import * as smartacmePlugins from '@push.rocks/smartacme/dist_ts/smartacme.plugins.js';
import * as smartacmeHandlers from '@push.rocks/smartacme/dist_ts/handlers/index.js';
export { lik, smartdelay, smartrequest, smartpromise, smartstring, smartacme, smartacmePlugins, smartacmeHandlers };
import * as taskbuffer from '@push.rocks/taskbuffer';
export {
lik,
smartdelay,
smartrequest,
smartpromise,
smartstring,
smartacme,
smartacmePlugins,
smartacmeHandlers,
taskbuffer,
};
// third party scope
import prettyMs from 'pretty-ms';

View File

@@ -85,9 +85,7 @@ interface IPort80HandlerOptions {
port?: number;
contactEmail?: string;
useProduction?: boolean;
renewThresholdDays?: number;
httpsRedirectPort?: number;
renewCheckIntervalHours?: number;
enabled?: boolean; // Whether ACME is enabled at all
autoRenew?: boolean; // Whether to automatically renew certificates
certificateStore?: string; // Directory to store certificates
@@ -146,7 +144,8 @@ export class Port80Handler extends plugins.EventEmitter {
// SmartAcme instance for certificate management
private smartAcme: plugins.smartacme.SmartAcme | null = null;
private server: plugins.http.Server | null = null;
private renewalTimer: NodeJS.Timeout | null = null;
// Renewal scheduling is handled externally by SmartProxy
// (Removed internal renewal timer)
private isShuttingDown: boolean = false;
private options: Required<IPort80HandlerOptions>;
@@ -163,9 +162,7 @@ export class Port80Handler extends plugins.EventEmitter {
port: options.port ?? 80,
contactEmail: options.contactEmail ?? 'admin@example.com',
useProduction: options.useProduction ?? false, // Safer default: staging
renewThresholdDays: options.renewThresholdDays ?? 10, // Changed to 10 days as per requirements
httpsRedirectPort: options.httpsRedirectPort ?? 443,
renewCheckIntervalHours: options.renewCheckIntervalHours ?? 24,
enabled: options.enabled ?? true, // Enable by default
autoRenew: options.autoRenew ?? true, // Auto-renew by default
certificateStore: options.certificateStore ?? './certs', // Default store location
@@ -223,7 +220,6 @@ export class Port80Handler extends plugins.EventEmitter {
this.server.listen(this.options.port, () => {
console.log(`Port80Handler is listening on port ${this.options.port}`);
this.startRenewalTimer();
this.emit(Port80HandlerEvents.MANAGER_STARTED, this.options.port);
// Start certificate process for domains with acmeMaintenance enabled
@@ -260,11 +256,6 @@ export class Port80Handler extends plugins.EventEmitter {
this.isShuttingDown = true;
// Stop the renewal timer
if (this.renewalTimer) {
clearInterval(this.renewalTimer);
this.renewalTimer = null;
}
return new Promise<void>((resolve) => {
if (this.server) {
@@ -830,89 +821,6 @@ export class Port80Handler extends plugins.EventEmitter {
}
}
/**
* Starts the certificate renewal timer
*/
private startRenewalTimer(): void {
if (this.renewalTimer) {
clearInterval(this.renewalTimer);
}
// Convert hours to milliseconds
const checkInterval = this.options.renewCheckIntervalHours * 60 * 60 * 1000;
this.renewalTimer = setInterval(() => this.checkForRenewals(), checkInterval);
// Prevent the timer from keeping the process alive
if (this.renewalTimer.unref) {
this.renewalTimer.unref();
}
console.log(`Certificate renewal check scheduled every ${this.options.renewCheckIntervalHours} hours`);
}
/**
* Checks for certificates that need renewal
*/
private checkForRenewals(): void {
if (this.isShuttingDown) {
return;
}
// Skip renewal if auto-renewal is disabled
if (this.options.autoRenew === false) {
console.log('Auto-renewal is disabled, skipping certificate renewal check');
return;
}
console.log('Checking for certificates that need renewal...');
const now = new Date();
const renewThresholdMs = this.options.renewThresholdDays * 24 * 60 * 60 * 1000;
for (const [domain, domainInfo] of this.domainCertificates.entries()) {
// Skip glob patterns
if (this.isGlobPattern(domain)) {
continue;
}
// Skip domains with acmeMaintenance disabled
if (!domainInfo.options.acmeMaintenance) {
continue;
}
// Skip domains without certificates or already in renewal
if (!domainInfo.certObtained || domainInfo.obtainingInProgress) {
continue;
}
// Skip domains without expiry dates
if (!domainInfo.expiryDate) {
continue;
}
const timeUntilExpiry = domainInfo.expiryDate.getTime() - now.getTime();
// Check if certificate is near expiry
if (timeUntilExpiry <= renewThresholdMs) {
console.log(`Certificate for ${domain} expires soon, renewing...`);
const daysRemaining = Math.ceil(timeUntilExpiry / (24 * 60 * 60 * 1000));
this.emit(Port80HandlerEvents.CERTIFICATE_EXPIRING, {
domain,
expiryDate: domainInfo.expiryDate,
daysRemaining
} as ICertificateExpiring);
// Start renewal process
this.obtainCertificate(domain, true).catch(err => {
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
console.error(`Error renewing certificate for ${domain}:`, errorMessage);
});
}
}
}
/**
* Extract expiry date from certificate using a more robust approach
@@ -1041,4 +949,16 @@ export class Port80Handler extends plugins.EventEmitter {
public getConfig(): Required<IPort80HandlerOptions> {
return { ...this.options };
}
/**
* Request a certificate renewal for a specific domain.
* @param domain The domain to renew.
*/
public async renewCertificate(domain: string): Promise<void> {
if (!this.domainCertificates.has(domain)) {
throw new Port80HandlerError(`Domain not managed: ${domain}`);
}
// Trigger renewal via ACME
await this.obtainCertificate(domain, true);
}
}

View File

@@ -32,6 +32,8 @@ export class SmartProxy extends plugins.EventEmitter {
// Port80Handler for ACME certificate management
private port80Handler: Port80Handler | null = null;
// Renewal scheduler for certificates
private renewManager?: plugins.taskbuffer.TaskManager;
constructor(settingsArg: IPortProxySettings) {
super();
@@ -157,9 +159,7 @@ export class SmartProxy extends plugins.EventEmitter {
port: config.port,
contactEmail: config.contactEmail,
useProduction: config.useProduction,
renewThresholdDays: config.renewThresholdDays,
httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort,
renewCheckIntervalHours: config.renewCheckIntervalHours,
enabled: config.enabled,
autoRenew: config.autoRenew,
certificateStore: config.certificateStore,
@@ -258,6 +258,21 @@ export class SmartProxy extends plugins.EventEmitter {
// Start Port80Handler
await this.port80Handler.start();
console.log(`Port80Handler started on port ${config.port}`);
// Schedule certificate renewals using taskbuffer
if (config.autoRenew) {
this.renewManager = new plugins.taskbuffer.TaskManager();
const renewTask = new plugins.taskbuffer.Task({
name: 'CertificateRenewals',
taskFunction: async () => {
await (this as any).performRenewals();
}
});
const hours = config.renewCheckIntervalHours!;
const cronExpr = `0 0 */${hours} * * *`;
this.renewManager.addAndScheduleTask(renewTask, cronExpr);
this.renewManager.start();
console.log(`Scheduled certificate renewals every ${hours} hours`);
}
} catch (err) {
console.log(`Error initializing Port80Handler: ${err}`);
}
@@ -403,6 +418,11 @@ export class SmartProxy extends plugins.EventEmitter {
public async stop() {
console.log('PortProxy shutting down...');
this.isShuttingDown = true;
// Stop the certificate renewal scheduler if active
if (this.renewManager) {
this.renewManager.stop();
console.log('Certificate renewal scheduler stopped');
}
// Stop the Port80Handler if running
if (this.port80Handler) {
@@ -572,6 +592,27 @@ export class SmartProxy extends plugins.EventEmitter {
}
}
/**
* Perform scheduled renewals for managed domains
*/
private async performRenewals(): Promise<void> {
if (!this.port80Handler) return;
const statuses = this.port80Handler.getDomainCertificateStatus();
const threshold = this.settings.port80HandlerConfig.renewThresholdDays ?? 30;
const now = new Date();
for (const [domain, status] of statuses.entries()) {
if (!status.certObtained || status.obtainingInProgress || !status.expiryDate) continue;
const msRemaining = status.expiryDate.getTime() - now.getTime();
const daysRemaining = Math.ceil(msRemaining / (24 * 60 * 60 * 1000));
if (daysRemaining <= threshold) {
try {
await this.port80Handler.renewCertificate(domain);
} catch (err) {
console.error(`Error renewing certificate for ${domain}:`, err);
}
}
}
}
/**
* Request a certificate for a specific domain
*/