BREAKING CHANGE(acme): Refactor ACME configuration and certificate provisioning by replacing legacy port80HandlerConfig with unified acme options and updating CertProvisioner event subscriptions
This commit is contained in:
parent
edd8ca8d70
commit
878e76ab23
@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-05-02 - 9.0.0 - BREAKING CHANGE(acme)
|
||||||
|
Refactor ACME configuration and certificate provisioning by replacing legacy port80HandlerConfig with unified acme options and updating CertProvisioner event subscriptions
|
||||||
|
|
||||||
|
- Remove deprecated port80HandlerConfig references and merge configuration into a single acme options schema
|
||||||
|
- Use buildPort80Handler factory for consistent Port80Handler instantiation
|
||||||
|
- Integrate subscribeToPort80Handler utility in CertProvisioner and NetworkProxyBridge for event management
|
||||||
|
- Update types in common modules and IPortProxySettings to reflect unified acme configurations
|
||||||
|
- Adjust documentation (readme.plan.md and code-level comments) to reflect the new refactored flow
|
||||||
|
|
||||||
## 2025-05-02 - 8.0.0 - BREAKING CHANGE(certProvisioner)
|
## 2025-05-02 - 8.0.0 - BREAKING CHANGE(certProvisioner)
|
||||||
Refactor: Introduce unified CertProvisioner to centralize certificate provisioning and renewal; remove legacy ACME config from Port80Handler and update SmartProxy to delegate certificate lifecycle management.
|
Refactor: Introduce unified CertProvisioner to centralize certificate provisioning and renewal; remove legacy ACME config from Port80Handler and update SmartProxy to delegate certificate lifecycle management.
|
||||||
|
|
||||||
|
@ -1,47 +1,29 @@
|
|||||||
## Refactor: Introduce a Unified CertProvisioner for Certificate Lifecycle
|
# Project Simplification Plan
|
||||||
|
|
||||||
- [x] Ensure Port80Handler is challenge-only:
|
This document outlines a roadmap to simplify and refactor the SmartProxy & NetworkProxy codebase for better maintainability, reduced duplication, and clearer configuration.
|
||||||
- Remove any internal scheduling and deprecated ACME flows (`getAcmeClient`, `processAuthorizations`, `handleAcmeChallenge`) from Port80Handler.
|
|
||||||
- Remove legacy ACME options (`renewThresholdDays`, `renewCheckIntervalHours`, `mongoDescriptor`, etc.) from `IPort80HandlerOptions`.
|
|
||||||
- Retain only methods for HTTP-01 challenge and direct renewals (`obtainCertificate`, `renewCertificate`, `getDomainCertificateStatus`).
|
|
||||||
- [x] Clean up deprecated `acme` configuration:
|
|
||||||
- Remove the `acme` property from `IPortProxySettings` and all legacy references in code.
|
|
||||||
|
|
||||||
- [x] Implement `CertProvisioner` component:
|
## Goals
|
||||||
- [x] Create class `ts/smartproxy/classes.pp.certprovisioner.ts`.
|
- Eliminate duplicate code and shared types
|
||||||
- [x] Constructor accepts:
|
- Unify certificate management flow across components
|
||||||
* `domainConfigs: IDomainConfig[]`
|
- Simplify configuration schemas and option handling
|
||||||
* `port80Handler: Port80Handler`
|
- Centralize plugin imports and module interfaces
|
||||||
* `networkProxyBridge: NetworkProxyBridge`
|
- Strengthen type safety and linting
|
||||||
* optional `certProvider: (domain) => Promise<ICert | 'http01'>`
|
- Improve test coverage and CI integration
|
||||||
* `renewThresholdDays`, `renewCheckIntervalHours`, `autoRenew` settings.
|
|
||||||
- Responsibilities:
|
|
||||||
* Initial provisioning: static vs HTTP-01.
|
|
||||||
* Subscribe to Port80Handler events (CERTIFICATE_ISSUED/RENEWED) and to static cert updates.
|
|
||||||
* Re-emit unified `'certificate'` events to SmartProxy.
|
|
||||||
* Central scheduling of renewals via `@push.rocks/taskbuffer`.
|
|
||||||
|
|
||||||
- [x] Refactor SmartProxy:
|
## Plan
|
||||||
- [x] Remove existing scheduling / renewal logic.
|
- [x] Extract all shared interfaces and types (e.g., certificate, proxy, domain configs) into a common `ts/common` module
|
||||||
- [x] Instantiate `CertProvisioner` in `start()`, delegate cert workflows entirely.
|
- [x] Consolidate ACME/Port80Handler logic:
|
||||||
- [x] Forward CertProvisioner events to SmartProxy’s `'certificate'` listener.
|
- [x] Merge standalone Port80Handler into a single certificate service
|
||||||
|
- [x] Remove duplicate ACME setup in SmartProxy and NetworkProxy
|
||||||
|
- [ ] Unify configuration options:
|
||||||
|
- [x] Merge `INetworkProxyOptions.acme`, `IPort80HandlerOptions`, and `port80HandlerConfig` into one schema
|
||||||
|
- [ ] Deprecate old option names and provide clear upgrade path
|
||||||
|
- [ ] Centralize plugin imports in `ts/plugins.ts` and update all modules to use it
|
||||||
|
- [ ] Remove legacy or unused code paths (e.g., old HTTP/2 fallback logic if obsolete)
|
||||||
|
- [ ] Enhance and expand test coverage:
|
||||||
|
- Add unit tests for certificate issuance, renewal, and error handling
|
||||||
|
- Add integration tests for HTTP challenge routing and request forwarding
|
||||||
|
- [ ] Update main README.md with architecture overview and configuration guide
|
||||||
|
- [ ] Review and prune external dependencies no longer needed
|
||||||
|
|
||||||
- [x] CertProvisioner lifecycle methods:
|
Once these steps are complete, the project will be cleaner, easier to understand, and simpler to extend.
|
||||||
- [x] `start()`: provision all domains, start scheduler.
|
|
||||||
- [x] `stop()`: stop scheduler.
|
|
||||||
- [x] `requestCertificate(domain)`: on-demand provisioning.
|
|
||||||
|
|
||||||
- [x] Handle static certificate auto-refresh:
|
|
||||||
- [x] In the renewal scheduler, for domains with static certs, re-call `certProvider(domain)` near expiry.
|
|
||||||
- [x] Apply returned cert via `networkProxyBridge.applyExternalCertificate()`.
|
|
||||||
|
|
||||||
- [ ] Tests:
|
|
||||||
- Unit tests for `CertProvisioner`, mocking Port80Handler and `certProvider`:
|
|
||||||
* Validate initial provisioning and dynamic/static flows.
|
|
||||||
* Validate scheduling triggers correct renewals.
|
|
||||||
- Integration tests:
|
|
||||||
* Use actual in-memory Port80Handler with short intervals to verify renewals and event emission.
|
|
||||||
|
|
||||||
- [ ] Documentation:
|
|
||||||
- Add code-level TS doc for `CertProvisioner` API (options, methods, events).
|
|
||||||
- Update root `README.md` and architecture diagrams to show `CertProvisioner` role.
|
|
@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartproxy',
|
name: '@push.rocks/smartproxy',
|
||||||
version: '8.0.0',
|
version: '9.0.0',
|
||||||
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
|
description: 'A powerful proxy package that effectively handles high traffic, with features such as SSL/TLS support, port proxying, WebSocket handling, dynamic routing with authentication options, and automatic ACME certificate management.'
|
||||||
}
|
}
|
||||||
|
23
ts/common/acmeFactory.ts
Normal file
23
ts/common/acmeFactory.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type { IAcmeOptions } from './types.js';
|
||||||
|
import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create a Port80Handler with common setup.
|
||||||
|
* Ensures the certificate store directory exists and instantiates the handler.
|
||||||
|
* @param options Port80Handler configuration options
|
||||||
|
* @returns A new Port80Handler instance
|
||||||
|
*/
|
||||||
|
export function buildPort80Handler(
|
||||||
|
options: IAcmeOptions
|
||||||
|
): Port80Handler {
|
||||||
|
if (options.certificateStore) {
|
||||||
|
const certStorePath = path.resolve(options.certificateStore);
|
||||||
|
if (!fs.existsSync(certStorePath)) {
|
||||||
|
fs.mkdirSync(certStorePath, { recursive: true });
|
||||||
|
console.log(`Created certificate store directory: ${certStorePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Port80Handler(options);
|
||||||
|
}
|
34
ts/common/eventUtils.ts
Normal file
34
ts/common/eventUtils.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import type { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||||
|
import { Port80HandlerEvents } from './types.js';
|
||||||
|
import type { ICertificateData, ICertificateFailure, ICertificateExpiring } from './types.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribers callback definitions for Port80Handler events
|
||||||
|
*/
|
||||||
|
export interface Port80HandlerSubscribers {
|
||||||
|
onCertificateIssued?: (data: ICertificateData) => void;
|
||||||
|
onCertificateRenewed?: (data: ICertificateData) => void;
|
||||||
|
onCertificateFailed?: (data: ICertificateFailure) => void;
|
||||||
|
onCertificateExpiring?: (data: ICertificateExpiring) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes to Port80Handler events based on provided callbacks
|
||||||
|
*/
|
||||||
|
export function subscribeToPort80Handler(
|
||||||
|
handler: Port80Handler,
|
||||||
|
subscribers: Port80HandlerSubscribers
|
||||||
|
): void {
|
||||||
|
if (subscribers.onCertificateIssued) {
|
||||||
|
handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, subscribers.onCertificateIssued);
|
||||||
|
}
|
||||||
|
if (subscribers.onCertificateRenewed) {
|
||||||
|
handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, subscribers.onCertificateRenewed);
|
||||||
|
}
|
||||||
|
if (subscribers.onCertificateFailed) {
|
||||||
|
handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, subscribers.onCertificateFailed);
|
||||||
|
}
|
||||||
|
if (subscribers.onCertificateExpiring) {
|
||||||
|
handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, subscribers.onCertificateExpiring);
|
||||||
|
}
|
||||||
|
}
|
89
ts/common/types.ts
Normal file
89
ts/common/types.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/**
|
||||||
|
* Shared types for certificate management and domain options
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain forwarding configuration
|
||||||
|
*/
|
||||||
|
export interface IForwardConfig {
|
||||||
|
ip: string;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Domain configuration options
|
||||||
|
*/
|
||||||
|
export interface IDomainOptions {
|
||||||
|
domainName: string;
|
||||||
|
sslRedirect: boolean; // if true redirects the request to port 443
|
||||||
|
acmeMaintenance: boolean; // tries to always have a valid cert for this domain
|
||||||
|
forward?: IForwardConfig; // forwards all http requests to that target
|
||||||
|
acmeForward?: IForwardConfig; // forwards letsencrypt requests to this config
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certificate data that can be emitted via events or set from outside
|
||||||
|
*/
|
||||||
|
export interface ICertificateData {
|
||||||
|
domain: string;
|
||||||
|
certificate: string;
|
||||||
|
privateKey: string;
|
||||||
|
expiryDate: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Events emitted by the Port80Handler
|
||||||
|
*/
|
||||||
|
export enum Port80HandlerEvents {
|
||||||
|
CERTIFICATE_ISSUED = 'certificate-issued',
|
||||||
|
CERTIFICATE_RENEWED = 'certificate-renewed',
|
||||||
|
CERTIFICATE_FAILED = 'certificate-failed',
|
||||||
|
CERTIFICATE_EXPIRING = 'certificate-expiring',
|
||||||
|
MANAGER_STARTED = 'manager-started',
|
||||||
|
MANAGER_STOPPED = 'manager-stopped',
|
||||||
|
REQUEST_FORWARDED = 'request-forwarded',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certificate failure payload type
|
||||||
|
*/
|
||||||
|
export interface ICertificateFailure {
|
||||||
|
domain: string;
|
||||||
|
error: string;
|
||||||
|
isRenewal: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Certificate expiry payload type
|
||||||
|
*/
|
||||||
|
export interface ICertificateExpiring {
|
||||||
|
domain: string;
|
||||||
|
expiryDate: Date;
|
||||||
|
daysRemaining: number;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Forwarding configuration for specific domains in ACME setup
|
||||||
|
*/
|
||||||
|
export interface IDomainForwardConfig {
|
||||||
|
domain: string;
|
||||||
|
forwardConfig?: IForwardConfig;
|
||||||
|
acmeForwardConfig?: IForwardConfig;
|
||||||
|
sslRedirect?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unified ACME configuration options used across proxies and handlers
|
||||||
|
*/
|
||||||
|
export interface IAcmeOptions {
|
||||||
|
enabled?: boolean; // Whether ACME is enabled
|
||||||
|
port?: number; // Port to listen on for ACME challenges (default: 80)
|
||||||
|
contactEmail?: string; // Email for Let's Encrypt account
|
||||||
|
useProduction?: boolean; // Use production environment (default: staging)
|
||||||
|
httpsRedirectPort?: number; // Port to redirect HTTP requests to HTTPS (default: 443)
|
||||||
|
renewThresholdDays?: number; // Days before expiry to renew certificates
|
||||||
|
renewCheckIntervalHours?: number; // How often to check for renewals (in hours)
|
||||||
|
autoRenew?: boolean; // Whether to automatically renew certificates
|
||||||
|
certificateStore?: string; // Directory to store certificates
|
||||||
|
skipConfiguredCerts?: boolean; // Skip domains with existing certificates
|
||||||
|
domainForwards?: IDomainForwardConfig[]; // Domain-specific forwarding configs
|
||||||
|
}
|
@ -3,7 +3,11 @@ import * as fs from 'fs';
|
|||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { type INetworkProxyOptions, type ICertificateEntry, type ILogger, createLogger } from './classes.np.types.js';
|
import { type INetworkProxyOptions, type ICertificateEntry, type ILogger, createLogger } from './classes.np.types.js';
|
||||||
import { Port80Handler, Port80HandlerEvents, type IDomainOptions } from '../port80handler/classes.port80handler.js';
|
import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||||
|
import { Port80HandlerEvents } from '../common/types.js';
|
||||||
|
import { buildPort80Handler } from '../common/acmeFactory.js';
|
||||||
|
import { subscribeToPort80Handler } from '../common/eventUtils.js';
|
||||||
|
import type { IDomainOptions } from '../common/types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages SSL certificates for NetworkProxy including ACME integration
|
* Manages SSL certificates for NetworkProxy including ACME integration
|
||||||
@ -101,12 +105,14 @@ export class CertificateManager {
|
|||||||
this.port80Handler = handler;
|
this.port80Handler = handler;
|
||||||
this.externalPort80Handler = true;
|
this.externalPort80Handler = true;
|
||||||
|
|
||||||
// Register event handlers
|
// Subscribe to Port80Handler events
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateIssued.bind(this));
|
subscribeToPort80Handler(this.port80Handler, {
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateIssued.bind(this));
|
onCertificateIssued: this.handleCertificateIssued.bind(this),
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, this.handleCertificateFailed.bind(this));
|
onCertificateRenewed: this.handleCertificateIssued.bind(this),
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (data) => {
|
onCertificateFailed: this.handleCertificateFailed.bind(this),
|
||||||
|
onCertificateExpiring: (data) => {
|
||||||
this.logger.info(`Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
|
this.logger.info(`Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.logger.info('External Port80Handler connected to CertificateManager');
|
this.logger.info('External Port80Handler connected to CertificateManager');
|
||||||
@ -348,8 +354,8 @@ export class CertificateManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create certificate manager
|
// Build and configure Port80Handler
|
||||||
this.port80Handler = new Port80Handler({
|
this.port80Handler = buildPort80Handler({
|
||||||
port: this.options.acme.port,
|
port: this.options.acme.port,
|
||||||
contactEmail: this.options.acme.contactEmail,
|
contactEmail: this.options.acme.contactEmail,
|
||||||
useProduction: this.options.acme.useProduction,
|
useProduction: this.options.acme.useProduction,
|
||||||
@ -358,13 +364,14 @@ export class CertificateManager {
|
|||||||
certificateStore: this.options.acme.certificateStore,
|
certificateStore: this.options.acme.certificateStore,
|
||||||
skipConfiguredCerts: this.options.acme.skipConfiguredCerts
|
skipConfiguredCerts: this.options.acme.skipConfiguredCerts
|
||||||
});
|
});
|
||||||
|
// Subscribe to Port80Handler events
|
||||||
// Register event handlers
|
subscribeToPort80Handler(this.port80Handler, {
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateIssued.bind(this));
|
onCertificateIssued: this.handleCertificateIssued.bind(this),
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateIssued.bind(this));
|
onCertificateRenewed: this.handleCertificateIssued.bind(this),
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_FAILED, this.handleCertificateFailed.bind(this));
|
onCertificateFailed: this.handleCertificateFailed.bind(this),
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_EXPIRING, (data) => {
|
onCertificateExpiring: (data) => {
|
||||||
this.logger.info(`Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
|
this.logger.info(`Certificate for ${data.domain} expires in ${data.daysRemaining} days`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start the handler
|
// Start the handler
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration options for NetworkProxy
|
||||||
|
*/
|
||||||
|
import type { IAcmeOptions } from '../common/types.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration options for NetworkProxy
|
* Configuration options for NetworkProxy
|
||||||
*/
|
*/
|
||||||
@ -24,16 +29,7 @@ export interface INetworkProxyOptions {
|
|||||||
backendProtocol?: 'http1' | 'http2';
|
backendProtocol?: 'http1' | 'http2';
|
||||||
|
|
||||||
// ACME certificate management options
|
// ACME certificate management options
|
||||||
acme?: {
|
acme?: IAcmeOptions;
|
||||||
enabled?: boolean; // Whether to enable automatic certificate management
|
|
||||||
port?: number; // Port to listen on for ACME challenges (default: 80)
|
|
||||||
contactEmail?: string; // Email for Let's Encrypt account
|
|
||||||
useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
|
|
||||||
renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
|
|
||||||
autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
|
|
||||||
certificateStore?: string; // Directory to store certificates (default: ./certs)
|
|
||||||
skipConfiguredCerts?: boolean; // Skip domains that already have certificates configured
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,14 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { IncomingMessage, ServerResponse } from 'http';
|
import { IncomingMessage, ServerResponse } from 'http';
|
||||||
|
import { Port80HandlerEvents } from '../common/types.js';
|
||||||
|
import type {
|
||||||
|
IForwardConfig,
|
||||||
|
IDomainOptions,
|
||||||
|
ICertificateData,
|
||||||
|
ICertificateFailure,
|
||||||
|
ICertificateExpiring,
|
||||||
|
IAcmeOptions
|
||||||
|
} from '../common/types.js';
|
||||||
// (fs and path I/O moved to CertProvisioner)
|
// (fs and path I/O moved to CertProvisioner)
|
||||||
// ACME HTTP-01 challenge handler storing tokens in memory (diskless)
|
// ACME HTTP-01 challenge handler storing tokens in memory (diskless)
|
||||||
class DisklessHttp01Handler {
|
class DisklessHttp01Handler {
|
||||||
@ -45,24 +54,6 @@ export class ServerError extends Port80HandlerError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain forwarding configuration
|
|
||||||
*/
|
|
||||||
export interface IForwardConfig {
|
|
||||||
ip: string;
|
|
||||||
port: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Domain configuration options
|
|
||||||
*/
|
|
||||||
export interface IDomainOptions {
|
|
||||||
domainName: string;
|
|
||||||
sslRedirect: boolean; // if true redirects the request to port 443
|
|
||||||
acmeMaintenance: boolean; // tries to always have a valid cert for this domain
|
|
||||||
forward?: IForwardConfig; // forwards all http requests to that target
|
|
||||||
acmeForward?: IForwardConfig; // forwards letsencrypt requests to this config
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a domain configuration with certificate status information
|
* Represents a domain configuration with certificate status information
|
||||||
@ -80,55 +71,8 @@ interface IDomainCertificate {
|
|||||||
/**
|
/**
|
||||||
* Configuration options for the Port80Handler
|
* Configuration options for the Port80Handler
|
||||||
*/
|
*/
|
||||||
interface IPort80HandlerOptions {
|
// Port80Handler options moved to common types
|
||||||
port?: number;
|
|
||||||
contactEmail?: string;
|
|
||||||
useProduction?: boolean;
|
|
||||||
httpsRedirectPort?: number;
|
|
||||||
enabled?: boolean; // Whether ACME is enabled at all
|
|
||||||
// (Persistence moved to CertProvisioner)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Certificate data that can be emitted via events or set from outside
|
|
||||||
*/
|
|
||||||
export interface ICertificateData {
|
|
||||||
domain: string;
|
|
||||||
certificate: string;
|
|
||||||
privateKey: string;
|
|
||||||
expiryDate: Date;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Events emitted by the Port80Handler
|
|
||||||
*/
|
|
||||||
export enum Port80HandlerEvents {
|
|
||||||
CERTIFICATE_ISSUED = 'certificate-issued',
|
|
||||||
CERTIFICATE_RENEWED = 'certificate-renewed',
|
|
||||||
CERTIFICATE_FAILED = 'certificate-failed',
|
|
||||||
CERTIFICATE_EXPIRING = 'certificate-expiring',
|
|
||||||
MANAGER_STARTED = 'manager-started',
|
|
||||||
MANAGER_STOPPED = 'manager-stopped',
|
|
||||||
REQUEST_FORWARDED = 'request-forwarded',
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Certificate failure payload type
|
|
||||||
*/
|
|
||||||
export interface ICertificateFailure {
|
|
||||||
domain: string;
|
|
||||||
error: string;
|
|
||||||
isRenewal: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Certificate expiry payload type
|
|
||||||
*/
|
|
||||||
export interface ICertificateExpiring {
|
|
||||||
domain: string;
|
|
||||||
expiryDate: Date;
|
|
||||||
daysRemaining: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Port80Handler with ACME certificate management and request forwarding capabilities
|
* Port80Handler with ACME certificate management and request forwarding capabilities
|
||||||
@ -144,13 +88,13 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
// Renewal scheduling is handled externally by SmartProxy
|
// Renewal scheduling is handled externally by SmartProxy
|
||||||
// (Removed internal renewal timer)
|
// (Removed internal renewal timer)
|
||||||
private isShuttingDown: boolean = false;
|
private isShuttingDown: boolean = false;
|
||||||
private options: Required<IPort80HandlerOptions>;
|
private options: Required<IAcmeOptions>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new Port80Handler
|
* Creates a new Port80Handler
|
||||||
* @param options Configuration options
|
* @param options Configuration options
|
||||||
*/
|
*/
|
||||||
constructor(options: IPort80HandlerOptions = {}) {
|
constructor(options: IAcmeOptions = {}) {
|
||||||
super();
|
super();
|
||||||
this.domainCertificates = new Map<string, IDomainCertificate>();
|
this.domainCertificates = new Map<string, IDomainCertificate>();
|
||||||
|
|
||||||
@ -160,7 +104,13 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
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
|
||||||
httpsRedirectPort: options.httpsRedirectPort ?? 443,
|
httpsRedirectPort: options.httpsRedirectPort ?? 443,
|
||||||
enabled: options.enabled ?? true // Enable by default
|
enabled: options.enabled ?? true, // Enable by default
|
||||||
|
certificateStore: options.certificateStore ?? './certs',
|
||||||
|
skipConfiguredCerts: options.skipConfiguredCerts ?? false,
|
||||||
|
renewThresholdDays: options.renewThresholdDays ?? 30,
|
||||||
|
renewCheckIntervalHours: options.renewCheckIntervalHours ?? 24,
|
||||||
|
autoRenew: options.autoRenew ?? true,
|
||||||
|
domainForwards: options.domainForwards ?? []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -810,7 +760,7 @@ export class Port80Handler extends plugins.EventEmitter {
|
|||||||
* Gets configuration details
|
* Gets configuration details
|
||||||
* @returns Current configuration
|
* @returns Current configuration
|
||||||
*/
|
*/
|
||||||
public getConfig(): Required<IPort80HandlerOptions> {
|
public getConfig(): Required<IAcmeOptions> {
|
||||||
return { ...this.options };
|
return { ...this.options };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import type { IDomainConfig, ISmartProxyCertProvisionObject } from './classes.pp.interfaces.js';
|
import type { IDomainConfig, ISmartProxyCertProvisionObject } from './classes.pp.interfaces.js';
|
||||||
import { Port80Handler, Port80HandlerEvents, type ICertificateData } from '../port80handler/classes.port80handler.js';
|
import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||||
|
import { Port80HandlerEvents } from '../common/types.js';
|
||||||
|
import { subscribeToPort80Handler } from '../common/eventUtils.js';
|
||||||
|
import type { ICertificateData } from '../common/types.js';
|
||||||
import type { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
|
import type { NetworkProxyBridge } from './classes.pp.networkproxybridge.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -56,11 +59,13 @@ export class CertProvisioner extends plugins.EventEmitter {
|
|||||||
*/
|
*/
|
||||||
public async start(): Promise<void> {
|
public async start(): Promise<void> {
|
||||||
// Subscribe to Port80Handler certificate events
|
// Subscribe to Port80Handler certificate events
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, (data: ICertificateData) => {
|
subscribeToPort80Handler(this.port80Handler, {
|
||||||
|
onCertificateIssued: (data: ICertificateData) => {
|
||||||
this.emit('certificate', { ...data, source: 'http01', isRenewal: false });
|
this.emit('certificate', { ...data, source: 'http01', isRenewal: false });
|
||||||
});
|
},
|
||||||
this.port80Handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, (data: ICertificateData) => {
|
onCertificateRenewed: (data: ICertificateData) => {
|
||||||
this.emit('certificate', { ...data, source: 'http01', isRenewal: true });
|
this.emit('certificate', { ...data, source: 'http01', isRenewal: true });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply external forwarding for ACME challenges (e.g. Synology)
|
// Apply external forwarding for ACME challenges (e.g. Synology)
|
||||||
|
@ -21,6 +21,7 @@ export interface IDomainConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Port proxy settings including global allowed port ranges */
|
/** Port proxy settings including global allowed port ranges */
|
||||||
|
import type { IAcmeOptions } from '../common/types.js';
|
||||||
export interface IPortProxySettings {
|
export interface IPortProxySettings {
|
||||||
fromPort: number;
|
fromPort: number;
|
||||||
toPort: number;
|
toPort: number;
|
||||||
@ -83,31 +84,8 @@ export interface IPortProxySettings {
|
|||||||
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
|
useNetworkProxy?: number[]; // Array of ports to forward to NetworkProxy
|
||||||
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
|
networkProxyPort?: number; // Port where NetworkProxy is listening (default: 8443)
|
||||||
|
|
||||||
// Port80Handler configuration (replaces ACME configuration)
|
// ACME configuration options for SmartProxy
|
||||||
port80HandlerConfig?: {
|
acme?: IAcmeOptions;
|
||||||
enabled?: boolean; // Whether to enable automatic certificate management
|
|
||||||
port?: number; // Port to listen on for ACME challenges (default: 80)
|
|
||||||
contactEmail?: string; // Email for Let's Encrypt account
|
|
||||||
useProduction?: boolean; // Whether to use Let's Encrypt production (default: false for staging)
|
|
||||||
renewThresholdDays?: number; // Days before expiry to renew certificates (default: 30)
|
|
||||||
autoRenew?: boolean; // Whether to automatically renew certificates (default: true)
|
|
||||||
certificateStore?: string; // Directory to store certificates (default: ./certs)
|
|
||||||
skipConfiguredCerts?: boolean; // Skip domains that already have certificates
|
|
||||||
httpsRedirectPort?: number; // Port to redirect HTTP requests to HTTPS (default: 443)
|
|
||||||
renewCheckIntervalHours?: number; // How often to check for renewals (default: 24)
|
|
||||||
// Domain-specific forwarding configurations
|
|
||||||
domainForwards?: Array<{
|
|
||||||
domain: string;
|
|
||||||
forwardConfig?: {
|
|
||||||
ip: string;
|
|
||||||
port: number;
|
|
||||||
};
|
|
||||||
acmeForwardConfig?: {
|
|
||||||
ip: string;
|
|
||||||
port: number;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges,
|
* Optional certificate provider callback. Return 'http01' to use HTTP-01 challenges,
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import { NetworkProxy } from '../networkproxy/classes.np.networkproxy.js';
|
import { NetworkProxy } from '../networkproxy/classes.np.networkproxy.js';
|
||||||
import { Port80Handler, Port80HandlerEvents, type ICertificateData } from '../port80handler/classes.port80handler.js';
|
import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||||
|
import { Port80HandlerEvents } from '../common/types.js';
|
||||||
|
import { subscribeToPort80Handler } from '../common/eventUtils.js';
|
||||||
|
import type { ICertificateData } from '../common/types.js';
|
||||||
import type { IConnectionRecord, IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';
|
import type { IConnectionRecord, IPortProxySettings, IDomainConfig } from './classes.pp.interfaces.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,9 +21,11 @@ export class NetworkProxyBridge {
|
|||||||
public setPort80Handler(handler: Port80Handler): void {
|
public setPort80Handler(handler: Port80Handler): void {
|
||||||
this.port80Handler = handler;
|
this.port80Handler = handler;
|
||||||
|
|
||||||
// Register for certificate events
|
// Subscribe to certificate events
|
||||||
handler.on(Port80HandlerEvents.CERTIFICATE_ISSUED, this.handleCertificateEvent.bind(this));
|
subscribeToPort80Handler(handler, {
|
||||||
handler.on(Port80HandlerEvents.CERTIFICATE_RENEWED, this.handleCertificateEvent.bind(this));
|
onCertificateIssued: this.handleCertificateEvent.bind(this),
|
||||||
|
onCertificateRenewed: this.handleCertificateEvent.bind(this)
|
||||||
|
});
|
||||||
|
|
||||||
// If NetworkProxy is already initialized, connect it with Port80Handler
|
// If NetworkProxy is already initialized, connect it with Port80Handler
|
||||||
if (this.networkProxy) {
|
if (this.networkProxy) {
|
||||||
@ -284,7 +289,7 @@ export class NetworkProxyBridge {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Log ACME-eligible domains
|
// Log ACME-eligible domains
|
||||||
const acmeEnabled = !!this.settings.port80HandlerConfig?.enabled;
|
const acmeEnabled = !!this.settings.acme?.enabled;
|
||||||
if (acmeEnabled) {
|
if (acmeEnabled) {
|
||||||
const acmeEligibleDomains = proxyConfigs
|
const acmeEligibleDomains = proxyConfigs
|
||||||
.filter((config) => !config.hostName.includes('*')) // Exclude wildcards
|
.filter((config) => !config.hostName.includes('*')) // Exclude wildcards
|
||||||
@ -345,7 +350,7 @@ export class NetworkProxyBridge {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.settings.port80HandlerConfig?.enabled) {
|
if (!this.settings.acme?.enabled) {
|
||||||
console.log('Cannot request certificate - ACME is not enabled');
|
console.log('Cannot request certificate - ACME is not enabled');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,8 @@ import { PortRangeManager } from './classes.pp.portrangemanager.js';
|
|||||||
import { ConnectionHandler } from './classes.pp.connectionhandler.js';
|
import { ConnectionHandler } from './classes.pp.connectionhandler.js';
|
||||||
import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
import { Port80Handler } from '../port80handler/classes.port80handler.js';
|
||||||
import { CertProvisioner } from './classes.pp.certprovisioner.js';
|
import { CertProvisioner } from './classes.pp.certprovisioner.js';
|
||||||
import type { ICertificateData } from '../port80handler/classes.port80handler.js';
|
import type { ICertificateData } from '../common/types.js';
|
||||||
import * as path from 'path';
|
import { buildPort80Handler } from '../common/acmeFactory.js';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SmartProxy - Main class that coordinates all components
|
* SmartProxy - Main class that coordinates all components
|
||||||
@ -67,13 +66,13 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
keepAliveInactivityMultiplier: settingsArg.keepAliveInactivityMultiplier || 6,
|
||||||
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
|
extendedKeepAliveLifetime: settingsArg.extendedKeepAliveLifetime || 7 * 24 * 60 * 60 * 1000,
|
||||||
networkProxyPort: settingsArg.networkProxyPort || 8443,
|
networkProxyPort: settingsArg.networkProxyPort || 8443,
|
||||||
port80HandlerConfig: settingsArg.port80HandlerConfig || {},
|
acme: settingsArg.acme || {},
|
||||||
globalPortRanges: settingsArg.globalPortRanges || [],
|
globalPortRanges: settingsArg.globalPortRanges || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set default port80HandlerConfig if not provided
|
// Set default ACME options if not provided
|
||||||
if (!this.settings.port80HandlerConfig || Object.keys(this.settings.port80HandlerConfig).length === 0) {
|
if (!this.settings.acme || Object.keys(this.settings.acme).length === 0) {
|
||||||
this.settings.port80HandlerConfig = {
|
this.settings.acme = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
port: 80,
|
port: 80,
|
||||||
contactEmail: 'admin@example.com',
|
contactEmail: 'admin@example.com',
|
||||||
@ -83,7 +82,8 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
certificateStore: './certs',
|
certificateStore: './certs',
|
||||||
skipConfiguredCerts: false,
|
skipConfiguredCerts: false,
|
||||||
httpsRedirectPort: this.settings.fromPort,
|
httpsRedirectPort: this.settings.fromPort,
|
||||||
renewCheckIntervalHours: 24
|
renewCheckIntervalHours: 24,
|
||||||
|
domainForwards: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,40 +122,20 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
* Initialize the Port80Handler for ACME certificate management
|
* Initialize the Port80Handler for ACME certificate management
|
||||||
*/
|
*/
|
||||||
private async initializePort80Handler(): Promise<void> {
|
private async initializePort80Handler(): Promise<void> {
|
||||||
const config = this.settings.port80HandlerConfig;
|
const config = this.settings.acme!;
|
||||||
|
if (!config.enabled) {
|
||||||
if (!config || !config.enabled) {
|
console.log('ACME is disabled in configuration');
|
||||||
console.log('Port80Handler is disabled in configuration');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Ensure the certificate store directory exists
|
// Build and start the Port80Handler
|
||||||
if (config.certificateStore) {
|
this.port80Handler = buildPort80Handler({
|
||||||
const certStorePath = path.resolve(config.certificateStore);
|
...config,
|
||||||
if (!fs.existsSync(certStorePath)) {
|
httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort
|
||||||
fs.mkdirSync(certStorePath, { recursive: true });
|
|
||||||
console.log(`Created certificate store directory: ${certStorePath}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create Port80Handler with options from config
|
|
||||||
this.port80Handler = new Port80Handler({
|
|
||||||
port: config.port,
|
|
||||||
contactEmail: config.contactEmail,
|
|
||||||
useProduction: config.useProduction,
|
|
||||||
httpsRedirectPort: config.httpsRedirectPort || this.settings.fromPort,
|
|
||||||
enabled: config.enabled,
|
|
||||||
certificateStore: config.certificateStore,
|
|
||||||
skipConfiguredCerts: config.skipConfiguredCerts
|
|
||||||
});
|
});
|
||||||
|
// Share Port80Handler with NetworkProxyBridge before start
|
||||||
|
|
||||||
|
|
||||||
// Share Port80Handler with NetworkProxyBridge
|
|
||||||
this.networkProxyBridge.setPort80Handler(this.port80Handler);
|
this.networkProxyBridge.setPort80Handler(this.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}`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -177,20 +157,20 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
await this.initializePort80Handler();
|
await this.initializePort80Handler();
|
||||||
// Initialize CertProvisioner for unified certificate workflows
|
// Initialize CertProvisioner for unified certificate workflows
|
||||||
if (this.port80Handler) {
|
if (this.port80Handler) {
|
||||||
|
const acme = this.settings.acme!;
|
||||||
this.certProvisioner = new CertProvisioner(
|
this.certProvisioner = new CertProvisioner(
|
||||||
this.settings.domainConfigs,
|
this.settings.domainConfigs,
|
||||||
this.port80Handler,
|
this.port80Handler,
|
||||||
this.networkProxyBridge,
|
this.networkProxyBridge,
|
||||||
this.settings.certProvider,
|
this.settings.certProvider,
|
||||||
this.settings.port80HandlerConfig?.renewThresholdDays || 30,
|
acme.renewThresholdDays!,
|
||||||
this.settings.port80HandlerConfig?.renewCheckIntervalHours || 24,
|
acme.renewCheckIntervalHours!,
|
||||||
this.settings.port80HandlerConfig?.autoRenew !== false,
|
acme.autoRenew!,
|
||||||
// External ACME forwarding for specific domains
|
acme.domainForwards?.map(f => ({
|
||||||
this.settings.port80HandlerConfig?.domainForwards?.map(f => ({
|
|
||||||
domain: f.domain,
|
domain: f.domain,
|
||||||
forwardConfig: f.forwardConfig,
|
forwardConfig: f.forwardConfig,
|
||||||
acmeForwardConfig: f.acmeForwardConfig,
|
acmeForwardConfig: f.acmeForwardConfig,
|
||||||
sslRedirect: false
|
sslRedirect: f.sslRedirect || false
|
||||||
})) || []
|
})) || []
|
||||||
);
|
);
|
||||||
this.certProvisioner.on('certificate', (certData) => {
|
this.certProvisioner.on('certificate', (certData) => {
|
||||||
@ -405,7 +385,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If Port80Handler is running, provision certificates per new domain
|
// If Port80Handler is running, provision certificates per new domain
|
||||||
if (this.port80Handler && this.settings.port80HandlerConfig?.enabled) {
|
if (this.port80Handler && this.settings.acme?.enabled) {
|
||||||
for (const domainConfig of newDomainConfigs) {
|
for (const domainConfig of newDomainConfigs) {
|
||||||
for (const domain of domainConfig.domains) {
|
for (const domain of domainConfig.domains) {
|
||||||
if (domain.includes('*')) continue;
|
if (domain.includes('*')) continue;
|
||||||
@ -441,72 +421,6 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the Port80Handler configuration
|
|
||||||
*/
|
|
||||||
public async updatePort80HandlerConfig(config: IPortProxySettings['port80HandlerConfig']): Promise<void> {
|
|
||||||
if (!config) return;
|
|
||||||
|
|
||||||
console.log('Updating Port80Handler configuration');
|
|
||||||
|
|
||||||
// Update the settings
|
|
||||||
this.settings.port80HandlerConfig = {
|
|
||||||
...this.settings.port80HandlerConfig,
|
|
||||||
...config
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if we need to restart Port80Handler
|
|
||||||
let needsRestart = false;
|
|
||||||
|
|
||||||
// Restart if enabled state changed
|
|
||||||
if (this.port80Handler && config.enabled === false) {
|
|
||||||
needsRestart = true;
|
|
||||||
} else if (!this.port80Handler && config.enabled === true) {
|
|
||||||
needsRestart = true;
|
|
||||||
} else if (this.port80Handler && (
|
|
||||||
config.port !== undefined ||
|
|
||||||
config.contactEmail !== undefined ||
|
|
||||||
config.useProduction !== undefined ||
|
|
||||||
config.renewThresholdDays !== undefined ||
|
|
||||||
config.renewCheckIntervalHours !== undefined
|
|
||||||
)) {
|
|
||||||
// Restart if critical settings changed
|
|
||||||
needsRestart = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsRestart) {
|
|
||||||
// Stop if running
|
|
||||||
if (this.port80Handler) {
|
|
||||||
try {
|
|
||||||
await this.port80Handler.stop();
|
|
||||||
this.port80Handler = null;
|
|
||||||
console.log('Stopped Port80Handler for configuration update');
|
|
||||||
} catch (err) {
|
|
||||||
console.log(`Error stopping Port80Handler: ${err}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start with new config if enabled
|
|
||||||
if (this.settings.port80HandlerConfig.enabled) {
|
|
||||||
await this.initializePort80Handler();
|
|
||||||
console.log('Restarted Port80Handler with new configuration');
|
|
||||||
}
|
|
||||||
} else if (this.port80Handler) {
|
|
||||||
// Just update domain forwards if they changed
|
|
||||||
if (config.domainForwards) {
|
|
||||||
for (const forward of config.domainForwards) {
|
|
||||||
this.port80Handler.addDomain({
|
|
||||||
domainName: forward.domain,
|
|
||||||
sslRedirect: true,
|
|
||||||
acmeMaintenance: true,
|
|
||||||
forward: forward.forwardConfig,
|
|
||||||
acmeForward: forward.acmeForwardConfig
|
|
||||||
});
|
|
||||||
}
|
|
||||||
console.log('Updated domain forwards in Port80Handler');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform scheduled renewals for managed domains
|
* Perform scheduled renewals for managed domains
|
||||||
@ -514,7 +428,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
private async performRenewals(): Promise<void> {
|
private async performRenewals(): Promise<void> {
|
||||||
if (!this.port80Handler) return;
|
if (!this.port80Handler) return;
|
||||||
const statuses = this.port80Handler.getDomainCertificateStatus();
|
const statuses = this.port80Handler.getDomainCertificateStatus();
|
||||||
const threshold = this.settings.port80HandlerConfig.renewThresholdDays ?? 30;
|
const threshold = this.settings.acme?.renewThresholdDays ?? 30;
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
for (const [domain, status] of statuses.entries()) {
|
for (const [domain, status] of statuses.entries()) {
|
||||||
if (!status.certObtained || status.obtainingInProgress || !status.expiryDate) continue;
|
if (!status.certObtained || status.obtainingInProgress || !status.expiryDate) continue;
|
||||||
@ -621,7 +535,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
networkProxyConnections,
|
networkProxyConnections,
|
||||||
terminationStats,
|
terminationStats,
|
||||||
acmeEnabled: !!this.port80Handler,
|
acmeEnabled: !!this.port80Handler,
|
||||||
port80HandlerPort: this.port80Handler ? this.settings.port80HandlerConfig?.port : null
|
port80HandlerPort: this.port80Handler ? this.settings.acme?.port : null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,7 +586,7 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
status: 'valid',
|
status: 'valid',
|
||||||
expiryDate: expiryDate.toISOString(),
|
expiryDate: expiryDate.toISOString(),
|
||||||
daysRemaining,
|
daysRemaining,
|
||||||
renewalNeeded: daysRemaining <= this.settings.port80HandlerConfig.renewThresholdDays
|
renewalNeeded: daysRemaining <= (this.settings.acme?.renewThresholdDays ?? 0)
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
certificateStatus[domain] = {
|
certificateStatus[domain] = {
|
||||||
@ -682,11 +596,12 @@ export class SmartProxy extends plugins.EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const acme = this.settings.acme!;
|
||||||
return {
|
return {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
port: this.settings.port80HandlerConfig.port,
|
port: acme.port!,
|
||||||
useProduction: this.settings.port80HandlerConfig.useProduction,
|
useProduction: acme.useProduction!,
|
||||||
autoRenew: this.settings.port80HandlerConfig.autoRenew,
|
autoRenew: acme.autoRenew!,
|
||||||
certificates: certificateStatus
|
certificates: certificateStatus
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user