feat(acme): add DB-backed ACME configuration management and OpsServer certificate settings UI

This commit is contained in:
2026-04-08 13:12:20 +00:00
parent 4fbe01823b
commit c224028495
18 changed files with 793 additions and 33 deletions

View File

@@ -0,0 +1,25 @@
/**
* ACME configuration for automated TLS certificate issuance via Let's Encrypt.
*
* Persisted as a singleton `AcmeConfigDoc` in the DcRouterDb. Replaces the
* legacy constructor fields `tls.contactEmail` / `smartProxyConfig.acme.*`
* which are now seed-only (used once on first boot if the DB is empty).
*
* Managed via the OpsServer UI at **Domains > Certificates > Settings**.
*/
export interface IAcmeConfig {
/** Contact email used for Let's Encrypt account registration. */
accountEmail: string;
/** Whether ACME is enabled. If false, no certs are issued via ACME. */
enabled: boolean;
/** True = Let's Encrypt production, false = staging. */
useProduction: boolean;
/** Whether to automatically renew certs before expiry. */
autoRenew: boolean;
/** Renew when a cert has fewer than this many days of validity left. */
renewThresholdDays: number;
/** Unix ms timestamp of last config change. */
updatedAt: number;
/** Who last updated the config (userId or 'seed' / 'system'). */
updatedBy: string;
}

View File

@@ -6,4 +6,5 @@ export * from './target-profile.js';
export * from './vpn.js';
export * from './dns-provider.js';
export * from './domain.js';
export * from './dns-record.js';
export * from './dns-record.js';
export * from './acme-config.js';

View File

@@ -17,7 +17,8 @@ export type TApiTokenScope =
| 'targets:read' | 'targets:write'
| 'dns-providers:read' | 'dns-providers:write'
| 'domains:read' | 'domains:write'
| 'dns-records:read' | 'dns-records:write';
| 'dns-records:read' | 'dns-records:write'
| 'acme-config:read' | 'acme-config:write';
// ============================================================================
// Source Profile Types (source-side: who can access)

View File

@@ -0,0 +1,54 @@
import * as plugins from '../plugins.js';
import type * as authInterfaces from '../data/auth.js';
import type { IAcmeConfig } from '../data/acme-config.js';
// ============================================================================
// ACME Config Endpoints
// ============================================================================
/**
* Get the current ACME configuration. Returns null if no config has been
* set yet (neither from DB nor seeded from the constructor).
*/
export interface IReq_GetAcmeConfig extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_GetAcmeConfig
> {
method: 'getAcmeConfig';
request: {
identity?: authInterfaces.IIdentity;
apiToken?: string;
};
response: {
config: IAcmeConfig | null;
};
}
/**
* Update the ACME configuration (upsert). All fields are required on first
* create, optional on subsequent updates (partial update).
*
* NOTE: Most fields take effect on the next dcrouter restart — SmartAcme is
* instantiated once at startup. `renewThresholdDays` applies immediately to
* the next renewal check.
*/
export interface IReq_UpdateAcmeConfig extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_UpdateAcmeConfig
> {
method: 'updateAcmeConfig';
request: {
identity?: authInterfaces.IIdentity;
apiToken?: string;
accountEmail?: string;
enabled?: boolean;
useProduction?: boolean;
autoRenew?: boolean;
renewThresholdDays?: number;
};
response: {
success: boolean;
config?: IAcmeConfig;
message?: string;
};
}

View File

@@ -16,4 +16,5 @@ export * from './network-targets.js';
export * from './users.js';
export * from './dns-providers.js';
export * from './domains.js';
export * from './dns-records.js';
export * from './dns-records.js';
export * from './acme-config.js';