update
This commit is contained in:
		| @@ -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'; | ||||
|   | ||||
| @@ -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); | ||||
|   } | ||||
| } | ||||
| @@ -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 | ||||
|    */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user