feat(certificates): add force renew option for domain certificate reprovisioning

This commit is contained in:
2026-04-03 19:08:46 +00:00
parent 4a6913d4bb
commit 6112e4e884
9 changed files with 30 additions and 19 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## 2026-04-03 - 12.8.0 - feat(certificates)
add force renew option for domain certificate reprovisioning
- pass an optional forceRenew flag through certificate reprovision requests from the UI to the ops handler
- use smartacme forceRenew support and return renewal-specific success messages
- update the SmartAcme dependency to version ^9.4.0
## 2026-04-03 - 12.7.0 - feat(opsserver) ## 2026-04-03 - 12.7.0 - feat(opsserver)
add RADIUS and VPN metrics to combined ops stats and overview dashboards, and stream live log buffer entries in follow mode add RADIUS and VPN metrics to combined ops stats and overview dashboards, and stream live log buffer entries in follow mode

View File

@@ -40,7 +40,7 @@
"@push.rocks/lik": "^6.4.0", "@push.rocks/lik": "^6.4.0",
"@push.rocks/projectinfo": "^5.1.0", "@push.rocks/projectinfo": "^5.1.0",
"@push.rocks/qenv": "^6.1.3", "@push.rocks/qenv": "^6.1.3",
"@push.rocks/smartacme": "^9.3.1", "@push.rocks/smartacme": "^9.4.0",
"@push.rocks/smartdata": "^7.1.3", "@push.rocks/smartdata": "^7.1.3",
"@push.rocks/smartdb": "^2.1.1", "@push.rocks/smartdb": "^2.1.1",
"@push.rocks/smartdns": "^7.9.0", "@push.rocks/smartdns": "^7.9.0",

10
pnpm-lock.yaml generated
View File

@@ -39,8 +39,8 @@ importers:
specifier: ^6.1.3 specifier: ^6.1.3
version: 6.1.3 version: 6.1.3
'@push.rocks/smartacme': '@push.rocks/smartacme':
specifier: ^9.3.1 specifier: ^9.4.0
version: 9.3.1(socks@2.8.7) version: 9.4.0(socks@2.8.7)
'@push.rocks/smartdata': '@push.rocks/smartdata':
specifier: ^7.1.3 specifier: ^7.1.3
version: 7.1.3(socks@2.8.7) version: 7.1.3(socks@2.8.7)
@@ -1108,8 +1108,8 @@ packages:
'@push.rocks/qenv@6.1.3': '@push.rocks/qenv@6.1.3':
resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==} resolution: {integrity: sha512-+z2hsAU/7CIgpYLFqvda8cn9rUBMHqLdQLjsFfRn5jPoD7dJ5rFlpkbhfM4Ws8mHMniwWaxGKo+q/YBhtzRBLg==}
'@push.rocks/smartacme@9.3.1': '@push.rocks/smartacme@9.4.0':
resolution: {integrity: sha512-Cl1DVQ+rfpaYkk6VVm/KYVeUYzWfXzSfTXybHfCZ5SuiACuTVHZ6jK8TouELaV1RgrdYnIp0MrbiY2Kqi8ayAw==} resolution: {integrity: sha512-mSqsI859mHI9fCZxLfayzPf/WvukDFzVHOh02vXq3ujxbb5M+ArMnXe0MmC2egR9GeXmQTm3DTENaETX5ffMtw==}
'@push.rocks/smartarchive@4.2.4': '@push.rocks/smartarchive@4.2.4':
resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==} resolution: {integrity: sha512-uiqVAXPxmr8G5rv3uZvZFMOCt8l7cZC3nzvsy4YQqKf/VkPhKIEX+b7LkAeNlxPSYUiBQUkNRoawg9+5BaMcHg==}
@@ -5960,7 +5960,7 @@ snapshots:
'@push.rocks/smartlog': 3.2.1 '@push.rocks/smartlog': 3.2.1
'@push.rocks/smartpath': 6.0.0 '@push.rocks/smartpath': 6.0.0
'@push.rocks/smartacme@9.3.1(socks@2.8.7)': '@push.rocks/smartacme@9.4.0(socks@2.8.7)':
dependencies: dependencies:
'@apiclient.xyz/cloudflare': 7.1.0 '@apiclient.xyz/cloudflare': 7.1.0
'@peculiar/x509': 2.0.0 '@peculiar/x509': 2.0.0

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '12.7.0', version: '12.8.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -43,7 +43,7 @@ export class CertificateHandler {
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>( new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_ReprovisionCertificateDomain>(
'reprovisionCertificateDomain', 'reprovisionCertificateDomain',
async (dataArg) => { async (dataArg) => {
return this.reprovisionCertificateDomain(dataArg.domain); return this.reprovisionCertificateDomain(dataArg.domain, dataArg.forceRenew);
} }
) )
); );
@@ -318,7 +318,7 @@ export class CertificateHandler {
/** /**
* Domain-based reprovisioning — clears backoff first, then triggers provision * Domain-based reprovisioning — clears backoff first, then triggers provision
*/ */
private async reprovisionCertificateDomain(domain: string): Promise<{ success: boolean; message?: string }> { private async reprovisionCertificateDomain(domain: string, forceRenew?: boolean): Promise<{ success: boolean; message?: string }> {
const dcRouter = this.opsServerRef.dcRouterRef; const dcRouter = this.opsServerRef.dcRouterRef;
const smartProxy = dcRouter.smartProxy; const smartProxy = dcRouter.smartProxy;
@@ -337,8 +337,8 @@ export class CertificateHandler {
// Try to provision via SmartAcme directly // Try to provision via SmartAcme directly
if (dcRouter.smartAcme) { if (dcRouter.smartAcme) {
try { try {
await dcRouter.smartAcme.getCertificateForDomain(domain); await dcRouter.smartAcme.getCertificateForDomain(domain, { forceRenew: forceRenew ?? false });
return { success: true, message: `Certificate reprovisioning triggered for domain '${domain}'` }; return { success: true, message: forceRenew ? `Certificate force-renewed for domain '${domain}'` : `Certificate reprovisioning triggered for domain '${domain}'` };
} catch (err: unknown) { } catch (err: unknown) {
return { success: false, message: (err as Error).message || `Failed to reprovision certificate for ${domain}` }; return { success: false, message: (err as Error).message || `Failed to reprovision certificate for ${domain}` };
} }

View File

@@ -68,6 +68,7 @@ export interface IReq_ReprovisionCertificateDomain extends plugins.typedrequestI
request: { request: {
identity: authInterfaces.IIdentity; identity: authInterfaces.IIdentity;
domain: string; domain: string;
forceRenew?: boolean;
}; };
response: { response: {
success: boolean; success: boolean;

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@serve.zone/dcrouter', name: '@serve.zone/dcrouter',
version: '12.7.0', version: '12.8.0',
description: 'A multifaceted routing service handling mail and SMS delivery functions.' description: 'A multifaceted routing service handling mail and SMS delivery functions.'
} }

View File

@@ -605,8 +605,8 @@ export const fetchCertificateOverviewAction = certificateStatePart.createAction(
} }
}); });
export const reprovisionCertificateAction = certificateStatePart.createAction<string>( export const reprovisionCertificateAction = certificateStatePart.createAction<{ domain: string; forceRenew?: boolean }>(
async (statePartArg, domain, actionContext): Promise<ICertificateState> => { async (statePartArg, dataArg, actionContext): Promise<ICertificateState> => {
const context = getActionContext(); const context = getActionContext();
const currentState = statePartArg.getState()!; const currentState = statePartArg.getState()!;
@@ -617,7 +617,8 @@ export const reprovisionCertificateAction = certificateStatePart.createAction<st
await request.fire({ await request.fire({
identity: context.identity!, identity: context.identity!,
domain, domain: dataArg.domain,
forceRenew: dataArg.forceRenew,
}); });
// Re-fetch overview after reprovisioning // Re-fetch overview after reprovisioning

View File

@@ -312,14 +312,16 @@ export class OpsViewCertificates extends DeesElement {
return; return;
} }
const doReprovision = async () => { const doReprovision = async (forceRenew = false) => {
await appstate.certificateStatePart.dispatchAction( await appstate.certificateStatePart.dispatchAction(
appstate.reprovisionCertificateAction, appstate.reprovisionCertificateAction,
cert.domain, { domain: cert.domain, forceRenew },
); );
const { DeesToast } = await import('@design.estate/dees-catalog'); const { DeesToast } = await import('@design.estate/dees-catalog');
DeesToast.show({ DeesToast.show({
message: `Reprovisioning triggered for ${cert.domain}`, message: forceRenew
? `Force renewal triggered for ${cert.domain}`
: `Reprovisioning triggered for ${cert.domain}`,
type: 'success', type: 'success',
duration: 3000, duration: 3000,
}); });
@@ -336,7 +338,7 @@ export class OpsViewCertificates extends DeesElement {
name: 'Force Renew', name: 'Force Renew',
action: async (modalArg: any) => { action: async (modalArg: any) => {
await modalArg.destroy(); await modalArg.destroy();
await doReprovision(); await doReprovision(true);
}, },
}, },
], ],