update
This commit is contained in:
parent
a59ebd6202
commit
09aadc702e
@ -30,6 +30,7 @@
|
|||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartrequest": "^2.1.0",
|
"@push.rocks/smartrequest": "^2.1.0",
|
||||||
"@push.rocks/smartstring": "^4.0.15",
|
"@push.rocks/smartstring": "^4.0.15",
|
||||||
|
"@push.rocks/taskbuffer": "^3.1.7",
|
||||||
"@tsclass/tsclass": "^9.1.0",
|
"@tsclass/tsclass": "^9.1.0",
|
||||||
"@types/minimatch": "^5.1.2",
|
"@types/minimatch": "^5.1.2",
|
||||||
"@types/ws": "^8.18.1",
|
"@types/ws": "^8.18.1",
|
||||||
|
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@ -29,6 +29,9 @@ importers:
|
|||||||
'@push.rocks/smartstring':
|
'@push.rocks/smartstring':
|
||||||
specifier: ^4.0.15
|
specifier: ^4.0.15
|
||||||
version: 4.0.15
|
version: 4.0.15
|
||||||
|
'@push.rocks/taskbuffer':
|
||||||
|
specifier: ^3.1.7
|
||||||
|
version: 3.1.7
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.1.0
|
specifier: ^9.1.0
|
||||||
version: 9.1.0
|
version: 9.1.0
|
||||||
@ -6301,7 +6304,6 @@ snapshots:
|
|||||||
- '@aws-sdk/credential-providers'
|
- '@aws-sdk/credential-providers'
|
||||||
- '@mongodb-js/zstd'
|
- '@mongodb-js/zstd'
|
||||||
- '@nuxt/kit'
|
- '@nuxt/kit'
|
||||||
- aws-crt
|
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- encoding
|
- encoding
|
||||||
- gcp-metadata
|
- gcp-metadata
|
||||||
@ -6920,7 +6922,7 @@ snapshots:
|
|||||||
'@push.rocks/smartdelay': 3.0.5
|
'@push.rocks/smartdelay': 3.0.5
|
||||||
'@push.rocks/smartlog': 3.0.7
|
'@push.rocks/smartlog': 3.0.7
|
||||||
'@push.rocks/smartpromise': 4.2.3
|
'@push.rocks/smartpromise': 4.2.3
|
||||||
'@push.rocks/smartrx': 3.0.7
|
'@push.rocks/smartrx': 3.0.10
|
||||||
'@push.rocks/smarttime': 4.1.1
|
'@push.rocks/smarttime': 4.1.1
|
||||||
'@push.rocks/smartunique': 3.0.9
|
'@push.rocks/smartunique': 3.0.9
|
||||||
|
|
||||||
|
@ -1,31 +1,26 @@
|
|||||||
## Plan: Integrate @push.rocks/smartacme into Port80Handler
|
## Plan: Centralize Certificate Renewal for all certificates
|
||||||
|
|
||||||
- [x] read the complete README of @push.rocks/smartacme and understand the API.
|
- [ ] Remove renewal logic from Port80Handler
|
||||||
- [x] Add imports to ts/plugins.ts:
|
- Delete `startRenewalTimer()` and `checkForRenewals()` methods
|
||||||
- import * as smartacme from '@push.rocks/smartacme';
|
- Remove `renewThresholdDays` and `renewCheckIntervalHours` options from `IPort80HandlerOptions`
|
||||||
- export { smartacme };
|
- [ ] Expose certificate status from Port80Handler
|
||||||
- [x] In Port80Handler.start():
|
- Ensure `getDomainCertificateStatus()` returns `{certObtained, expiryDate}` for each domain
|
||||||
- Instantiate SmartAcme and use the in memory certmanager.
|
- [ ] Add renewal settings to SmartProxy
|
||||||
- use the DisklessHttp01Handler implemented in classes.port80handler.ts
|
- Extend `port80HandlerConfig` to include `renewThresholdDays` and `renewCheckIntervalHours`
|
||||||
- Call `await smartAcme.start()` before binding HTTP server.
|
- [ ] Implement renewal scheduler in SmartProxy using taskbuffer
|
||||||
- [x] Replace old ACME flow in `obtainCertificate()` to use `await smartAcme.getCertificateForDomain(domain)` and process returned cert object. Remove old code.
|
- Add dependency on `@push.rocks/taskbuffer` and import `{ Task, TaskManager }` in `SmartProxy`
|
||||||
- [x] Update `handleRequest()` to let DisklessHttp01Handler serve challenges.
|
- Add `performRenewals()` to iterate domains and trigger renewals where `daysRemaining <= renewThresholdDays`
|
||||||
- [x] Remove legacy methods: `getAcmeClient()`, `handleAcmeChallenge()`, `processAuthorizations()`, and related token bookkeeping in domainInfo.
|
- Instantiate a `TaskManager` and define a `Task` that wraps `performRenewals()`
|
||||||
|
- Use `taskManager.addAndScheduleTask(task, cronExpr)` to schedule renewals, building `cronExpr` from `renewCheckIntervalHours` (e.g. `0 0 */${renewCheckIntervalHours} * * *`)
|
||||||
## Plan: Certificate Provider Hook & Observable Emission
|
- Call `taskManager.start()` in `SmartProxy.start()`
|
||||||
|
- [ ] Clean shutdown handling
|
||||||
- [x] Extend IPortProxySettings (ts/smartproxy/classes.pp.interfaces.ts):
|
- Call `taskManager.stop()` in `SmartProxy.stop()` alongside other cleanup
|
||||||
- Define type ISmartProxyCertProvisionObject = tsclass.network.ICert | 'http01'`.
|
- [ ] Throttling and safety
|
||||||
- Add optional `certProvider?: (domain: string) => Promise<ISmartProxyCertProvisionObject>`.
|
- Skip domains already in `obtainingInProgress`
|
||||||
- [x] Enhance SmartProxy (ts/smartproxy/classes.smartproxy.ts):
|
- Optionally batch or stagger renewal calls for large domain sets
|
||||||
- Import `EventEmitter` and change class signature to `export class SmartProxy extends EventEmitter`.
|
- [ ] Tests
|
||||||
- Call `super()` in constructor.
|
- Unit test `performRenewals()`, mocking `getDomainCertificateStatus()` to simulate expiring certificates
|
||||||
- In `initializePort80Handler` and `updateDomainConfigs`, for each non-wildcard domain:
|
- Integration test using an in-memory `Port80Handler` to verify that scheduled renewals invoke `obtainCertificate()` correctly
|
||||||
- Invoke `certProvider(domain)` if provided, defaulting to `'http01'`.
|
- [ ] Documentation
|
||||||
- If result is `'http01'`, register domain with `Port80Handler` for ACME challenges.
|
- Update `readme.plan.md` (this section)
|
||||||
- If static cert returned, bypass `Port80Handler`, apply via `NetworkProxyBridge`
|
- Update `README.md` and code comments to document new renewal settings and workflow
|
||||||
- Subscribe to `Port80HandlerEvents.CERTIFICATE_ISSUED` and `CERTIFICATE_RENEWED` and re-emit on `SmartProxy` as `'certificate'` events (include `domain`, `publicKey`, `privateKey`, `expiryDate`, `source: 'http01'`, `isRenewal` flag).
|
|
||||||
- [x] Extend NetworkProxyBridge (ts/smartproxy/classes.pp.networkproxybridge.ts):
|
|
||||||
- Add public method `applyExternalCertificate(data: ICertificateData): void` to forward static certs into `NetworkProxy`.
|
|
||||||
- [ ] Define `SmartProxy` `'certificate'` event interface in TypeScript and update documentation.
|
|
||||||
- [ ] Update README with usage examples showing `certProvider` callback and listening for `'certificate'` events.
|
|
||||||
|
45
test/test.smartproxy.renewals.node.ts
Normal file
45
test/test.smartproxy.renewals.node.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
import { SmartProxy } from '../ts/smartproxy/classes.smartproxy.js';
|
||||||
|
|
||||||
|
tap.test('performRenewals only renews domains below threshold', async () => {
|
||||||
|
// Set up SmartProxy instance without real servers
|
||||||
|
const proxy = new SmartProxy({
|
||||||
|
fromPort: 0,
|
||||||
|
toPort: 0,
|
||||||
|
domainConfigs: [],
|
||||||
|
sniEnabled: false,
|
||||||
|
defaultAllowedIPs: [],
|
||||||
|
globalPortRanges: []
|
||||||
|
});
|
||||||
|
// Stub port80Handler status and renewal
|
||||||
|
const statuses = new Map<string, any>();
|
||||||
|
const now = new Date();
|
||||||
|
statuses.set('expiring.com', {
|
||||||
|
certObtained: true,
|
||||||
|
expiryDate: new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000),
|
||||||
|
obtainingInProgress: false
|
||||||
|
});
|
||||||
|
statuses.set('ok.com', {
|
||||||
|
certObtained: true,
|
||||||
|
expiryDate: new Date(now.getTime() + 100 * 24 * 60 * 60 * 1000),
|
||||||
|
obtainingInProgress: false
|
||||||
|
});
|
||||||
|
const renewed: string[] = [];
|
||||||
|
// Inject fake handler
|
||||||
|
(proxy as any).port80Handler = {
|
||||||
|
getDomainCertificateStatus: () => statuses,
|
||||||
|
renewCertificate: async (domain: string) => { renewed.push(domain); }
|
||||||
|
};
|
||||||
|
// Configure threshold
|
||||||
|
proxy.settings.port80HandlerConfig.enabled = true;
|
||||||
|
proxy.settings.port80HandlerConfig.autoRenew = true;
|
||||||
|
proxy.settings.port80HandlerConfig.renewThresholdDays = 10;
|
||||||
|
|
||||||
|
// Execute renewals
|
||||||
|
await (proxy as any).performRenewals();
|
||||||
|
|
||||||
|
// Only the expiring.com domain should be renewed
|
||||||
|
expect(renewed).toEqual(['expiring.com']);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@ -7,7 +7,6 @@ import * as tls from 'tls';
|
|||||||
import * as url from 'url';
|
import * as url from 'url';
|
||||||
import * as http2 from 'http2';
|
import * as http2 from 'http2';
|
||||||
|
|
||||||
|
|
||||||
export { EventEmitter, http, https, net, tls, url, http2 };
|
export { EventEmitter, http, https, net, tls, url, http2 };
|
||||||
|
|
||||||
// tsclass scope
|
// tsclass scope
|
||||||
@ -25,7 +24,19 @@ import * as smartstring from '@push.rocks/smartstring';
|
|||||||
import * as smartacme from '@push.rocks/smartacme';
|
import * as smartacme from '@push.rocks/smartacme';
|
||||||
import * as smartacmePlugins from '@push.rocks/smartacme/dist_ts/smartacme.plugins.js';
|
import * as smartacmePlugins from '@push.rocks/smartacme/dist_ts/smartacme.plugins.js';
|
||||||
import * as smartacmeHandlers from '@push.rocks/smartacme/dist_ts/handlers/index.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
|
// third party scope
|
||||||
import prettyMs from 'pretty-ms';
|
import prettyMs from 'pretty-ms';
|
||||||
|
@ -85,9 +85,7 @@ interface IPort80HandlerOptions {
|
|||||||
port?: number;
|
port?: number;
|
||||||
contactEmail?: string;
|
contactEmail?: string;
|
||||||
useProduction?: boolean;
|
useProduction?: boolean;
|
||||||
renewThresholdDays?: number;
|
|
||||||
httpsRedirectPort?: number;
|
httpsRedirectPort?: number;
|
||||||
renewCheckIntervalHours?: number;
|
|
||||||
enabled?: boolean; // Whether ACME is enabled at all
|
enabled?: boolean; // Whether ACME is enabled at all
|
||||||
autoRenew?: boolean; // Whether to automatically renew certificates
|
autoRenew?: boolean; // Whether to automatically renew certificates
|
||||||
certificateStore?: string; // Directory to store certificates
|
certificateStore?: string; // Directory to store certificates
|
||||||
@ -146,7 +144,8 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
// SmartAcme instance for certificate management
|
// SmartAcme instance for certificate management
|
||||||
private smartAcme: plugins.smartacme.SmartAcme | null = null;
|
private smartAcme: plugins.smartacme.SmartAcme | null = null;
|
||||||
private server: plugins.http.Server | 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 isShuttingDown: boolean = false;
|
||||||
private options: Required<IPort80HandlerOptions>;
|
private options: Required<IPort80HandlerOptions>;
|
||||||
|
|
||||||
@ -163,9 +162,7 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
port: options.port ?? 80,
|
port: options.port ?? 80,
|
||||||
contactEmail: options.contactEmail ?? 'admin@example.com',
|
contactEmail: options.contactEmail ?? 'admin@example.com',
|
||||||
useProduction: options.useProduction ?? false, // Safer default: staging
|
useProduction: options.useProduction ?? false, // Safer default: staging
|
||||||
renewThresholdDays: options.renewThresholdDays ?? 10, // Changed to 10 days as per requirements
|
|
||||||
httpsRedirectPort: options.httpsRedirectPort ?? 443,
|
httpsRedirectPort: options.httpsRedirectPort ?? 443,
|
||||||
renewCheckIntervalHours: options.renewCheckIntervalHours ?? 24,
|
|
||||||
enabled: options.enabled ?? true, // Enable by default
|
enabled: options.enabled ?? true, // Enable by default
|
||||||
autoRenew: options.autoRenew ?? true, // Auto-renew by default
|
autoRenew: options.autoRenew ?? true, // Auto-renew by default
|
||||||
certificateStore: options.certificateStore ?? './certs', // Default store location
|
certificateStore: options.certificateStore ?? './certs', // Default store location
|
||||||
@ -223,7 +220,6 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
|
|
||||||
this.server.listen(this.options.port, () => {
|
this.server.listen(this.options.port, () => {
|
||||||
console.log(`Port80Handler is listening on port ${this.options.port}`);
|
console.log(`Port80Handler is listening on port ${this.options.port}`);
|
||||||
this.startRenewalTimer();
|
|
||||||
this.emit(Port80HandlerEvents.MANAGER_STARTED, this.options.port);
|
this.emit(Port80HandlerEvents.MANAGER_STARTED, this.options.port);
|
||||||
|
|
||||||
// Start certificate process for domains with acmeMaintenance enabled
|
// Start certificate process for domains with acmeMaintenance enabled
|
||||||
@ -260,11 +256,6 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
|
|
||||||
this.isShuttingDown = true;
|
this.isShuttingDown = true;
|
||||||
|
|
||||||
// Stop the renewal timer
|
|
||||||
if (this.renewalTimer) {
|
|
||||||
clearInterval(this.renewalTimer);
|
|
||||||
this.renewalTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
if (this.server) {
|
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
|
* Extract expiry date from certificate using a more robust approach
|
||||||
@ -1041,4 +949,16 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
public getConfig(): Required<IPort80HandlerOptions> {
|
public getConfig(): Required<IPort80HandlerOptions> {
|
||||||
return { ...this.options };
|
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
|
// Port80Handler for ACME certificate management
|
||||||
private port80Handler: Port80Handler | null = null;
|
private port80Handler: Port80Handler | null = null;
|
||||||
|
// Renewal scheduler for certificates
|
||||||
|
private renewManager?: plugins.taskbuffer.TaskManager;
|
||||||
|
|
||||||
constructor(settingsArg: IPortProxySettings) {
|
constructor(settingsArg: IPortProxySettings) {
|
||||||
super();
|
super();
|
||||||
@ -157,9 +159,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
port: config.port,
|
port: config.port,
|
||||||
contactEmail: config.contactEmail,
|
contactEmail: config.contactEmail,
|
||||||
useProduction: config.useProduction,
|
useProduction: config.useProduction,
|
||||||
renewThresholdDays: config.renewThresholdDays,
|
|
||||||
httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort,
|
httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort,
|
||||||
renewCheckIntervalHours: config.renewCheckIntervalHours,
|
|
||||||
enabled: config.enabled,
|
enabled: config.enabled,
|
||||||
autoRenew: config.autoRenew,
|
autoRenew: config.autoRenew,
|
||||||
certificateStore: config.certificateStore,
|
certificateStore: config.certificateStore,
|
||||||
@ -258,6 +258,21 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
// Start Port80Handler
|
// Start Port80Handler
|
||||||
await this.port80Handler.start();
|
await this.port80Handler.start();
|
||||||
console.log(`Port80Handler started on port ${config.port}`);
|
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) {
|
} catch (err) {
|
||||||
console.log(`Error initializing Port80Handler: ${err}`);
|
console.log(`Error initializing Port80Handler: ${err}`);
|
||||||
}
|
}
|
||||||
@ -403,6 +418,11 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
public async stop() {
|
public async stop() {
|
||||||
console.log('PortProxy shutting down...');
|
console.log('PortProxy shutting down...');
|
||||||
this.isShuttingDown = true;
|
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
|
// Stop the Port80Handler if running
|
||||||
if (this.port80Handler) {
|
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
|
* Request a certificate for a specific domain
|
||||||
*/
|
*/
|
||||||
|
Loading…
x
Reference in New Issue
Block a user