Compare commits

..

6 Commits

Author SHA1 Message Date
4e32745a8f v6.2.2
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-16 01:58:39 +00:00
121573de2f fix(certs): Populate certificate status for cert-store-loaded certificates after SmartProxy startup and check proxy-certs in opsserver certificate handler 2026-02-16 01:58:39 +00:00
cd957526e2 v6.2.1
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-16 00:56:41 +00:00
7aa5f07731 fix(smartacme,storage): Respect wildcard domain requests when retrieving certificates and treat empty/whitespace storage values as null in getJSON 2026-02-16 00:56:41 +00:00
5b6f7b30c3 v6.2.0
Some checks failed
Docker (tags) / security (push) Failing after 1s
Docker (tags) / test (push) Has been skipped
Docker (tags) / release (push) Has been skipped
Docker (tags) / metadata (push) Has been skipped
2026-02-16 00:26:35 +00:00
18cc21a49e feat(ts_web): add Certificate Management documentation and ops-view-certificates reference 2026-02-16 00:26:35 +00:00
9 changed files with 91 additions and 15 deletions

View File

@@ -1,5 +1,26 @@
# Changelog # Changelog
## 2026-02-16 - 6.2.2 - fix(certs)
Populate certificate status for cert-store-loaded certificates after SmartProxy startup and check proxy-certs in opsserver certificate handler
- Track domains loaded from storageManager '/proxy-certs/' and populate certificateStatusMap with status, routeNames, expiryDate and issuedAt (when available) after SmartProxy starts
- Opsserver certificate handler now falls back to '/proxy-certs/{domain}' if '/certs/{cleanDomain}' is missing and marks cert-store-only entries as valid with issuer 'cert-store'
- Bump @push.rocks/smartproxy dependency from ^25.3.1 to ^25.4.0
## 2026-02-16 - 6.2.1 - fix(smartacme,storage)
Respect wildcard domain requests when retrieving certificates and treat empty/whitespace storage values as null in getJSON
- Pass includeWildcard flag to smartAcme.getCertificateForDomain to avoid incorrectly including/excluding wildcard certificates based on whether the requested domain itself is a wildcard
- Detect wildcard domains via domain.startsWith('*.') and set includeWildcard to false for wildcard requests
- Treat empty or whitespace-only stored values as null in StorageManager.getJSON to avoid parsing empty strings as JSON and potential errors
## 2026-02-16 - 6.2.0 - feat(ts_web)
add Certificate Management documentation and ops-view-certificates reference
- Adds a new 'Certificate Management' section to ts_web/readme.md describing domain-centric overview, certificate sources (ACME/provision/static), expiry monitoring, per-domain backoff, and one-click reprovisioning
- Adds ops-view-certificates.ts entry to the ops UI file list
- Documents new route mapping '/certificates' in the readme navigation
## 2026-02-16 - 6.1.0 - feat(certs) ## 2026-02-16 - 6.1.0 - feat(certs)
integrate smartacme v9 for ACME certificate provisioning and add certificate management features, docs, dashboard views, API endpoints, and per-domain backoff scheduler integrate smartacme v9 for ACME certificate provisioning and add certificate management features, docs, dashboard views, API endpoints, and per-domain backoff scheduler

View File

@@ -1,7 +1,7 @@
{ {
"name": "@serve.zone/dcrouter", "name": "@serve.zone/dcrouter",
"private": false, "private": false,
"version": "6.1.0", "version": "6.2.2",
"description": "A multifaceted routing service handling mail and SMS delivery functions.", "description": "A multifaceted routing service handling mail and SMS delivery functions.",
"type": "module", "type": "module",
"exports": { "exports": {
@@ -49,7 +49,7 @@
"@push.rocks/smartnetwork": "^4.4.0", "@push.rocks/smartnetwork": "^4.4.0",
"@push.rocks/smartpath": "^6.0.0", "@push.rocks/smartpath": "^6.0.0",
"@push.rocks/smartpromise": "^4.2.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartproxy": "^25.3.1", "@push.rocks/smartproxy": "^25.4.0",
"@push.rocks/smartradius": "^1.1.1", "@push.rocks/smartradius": "^1.1.1",
"@push.rocks/smartrequest": "^5.0.1", "@push.rocks/smartrequest": "^5.0.1",
"@push.rocks/smartrx": "^3.0.10", "@push.rocks/smartrx": "^3.0.10",

10
pnpm-lock.yaml generated
View File

@@ -75,8 +75,8 @@ importers:
specifier: ^4.2.3 specifier: ^4.2.3
version: 4.2.3 version: 4.2.3
'@push.rocks/smartproxy': '@push.rocks/smartproxy':
specifier: ^25.3.1 specifier: ^25.4.0
version: 25.3.1 version: 25.4.0
'@push.rocks/smartradius': '@push.rocks/smartradius':
specifier: ^1.1.1 specifier: ^1.1.1
version: 1.1.1 version: 1.1.1
@@ -1031,8 +1031,8 @@ packages:
'@push.rocks/smartpromise@4.2.3': '@push.rocks/smartpromise@4.2.3':
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==} resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
'@push.rocks/smartproxy@25.3.1': '@push.rocks/smartproxy@25.4.0':
resolution: {integrity: sha512-kGJGpx3KBUz+qWU2L9B2gbZoUbQEG2BFe6ZzK0b68Y32nHoSIMjol14hzc3sRgW1p/loWy+Gj+5j0KuVytKWmA==} resolution: {integrity: sha512-aU7ySk/2llRs6hcIGrl4gjuXsJOmLuVv952ys4H1yZyZSdPx7G8m5gJl6RxB5Rp8GzM2YaKUvCp00apcGcnEfw==}
'@push.rocks/smartpuppeteer@2.0.5': '@push.rocks/smartpuppeteer@2.0.5':
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==} resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
@@ -6369,7 +6369,7 @@ snapshots:
'@push.rocks/smartpromise@4.2.3': {} '@push.rocks/smartpromise@4.2.3': {}
'@push.rocks/smartproxy@25.3.1': '@push.rocks/smartproxy@25.4.0':
dependencies: dependencies:
'@push.rocks/smartcrypto': 2.0.4 '@push.rocks/smartcrypto': 2.0.4
'@push.rocks/smartlog': 3.1.11 '@push.rocks/smartlog': 3.1.11

View File

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

View File

@@ -444,7 +444,10 @@ export class DcRouter {
// If we have routes or need a basic SmartProxy instance, create it // If we have routes or need a basic SmartProxy instance, create it
if (routes.length > 0 || this.options.smartProxyConfig) { if (routes.length > 0 || this.options.smartProxyConfig) {
console.log('Setting up SmartProxy with combined configuration'); console.log('Setting up SmartProxy with combined configuration');
// Track domains loaded from cert store so we can populate certificateStatusMap after start
const loadedCertDomains: string[] = [];
// Create SmartProxy configuration // Create SmartProxy configuration
const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = { const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
...this.options.smartProxyConfig, ...this.options.smartProxyConfig,
@@ -456,7 +459,10 @@ export class DcRouter {
const certs: Array<{ domain: string; publicKey: string; privateKey: string; ca?: string }> = []; const certs: Array<{ domain: string; publicKey: string; privateKey: string; ca?: string }> = [];
for (const key of keys) { for (const key of keys) {
const data = await this.storageManager.getJSON(key); const data = await this.storageManager.getJSON(key);
if (data) certs.push(data); if (data) {
certs.push(data);
loadedCertDomains.push(data.domain);
}
} }
return certs; return certs;
}, },
@@ -499,7 +505,10 @@ export class DcRouter {
// smartacme v9 handles concurrency, per-domain dedup, and rate limiting internally // smartacme v9 handles concurrency, per-domain dedup, and rate limiting internally
eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`); eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
eventComms.setSource('smartacme-dns-01'); eventComms.setSource('smartacme-dns-01');
const cert = await this.smartAcme.getCertificateForDomain(domain); const isWildcardDomain = domain.startsWith('*.');
const cert = await this.smartAcme.getCertificateForDomain(domain, {
includeWildcard: !isWildcardDomain,
});
if (cert.validUntil) { if (cert.validUntil) {
eventComms.setExpiryDate(new Date(cert.validUntil)); eventComms.setExpiryDate(new Date(cert.validUntil));
} }
@@ -576,7 +585,36 @@ export class DcRouter {
console.log('[DcRouter] Starting SmartProxy...'); console.log('[DcRouter] Starting SmartProxy...');
await this.smartProxy.start(); await this.smartProxy.start();
console.log('[DcRouter] SmartProxy started successfully'); console.log('[DcRouter] SmartProxy started successfully');
// Populate certificateStatusMap for certs loaded from store at startup
for (const domain of loadedCertDomains) {
if (!this.certificateStatusMap.has(domain)) {
const routeNames = this.findRouteNamesForDomain(domain);
let expiryDate: string | undefined;
let issuedAt: string | undefined;
try {
const cleanDomain = domain.replace(/^\*\.?/, '');
const certMeta = await this.storageManager.getJSON(`/certs/${cleanDomain}`);
if (certMeta?.validUntil) {
expiryDate = new Date(certMeta.validUntil).toISOString();
}
if (certMeta?.created) {
issuedAt = new Date(certMeta.created).toISOString();
}
} catch { /* no metadata available */ }
this.certificateStatusMap.set(domain, {
status: 'valid',
routeNames,
expiryDate,
issuedAt,
source: 'cert-store',
});
}
}
if (loadedCertDomains.length > 0) {
console.log(`[DcRouter] Populated certificate status for ${loadedCertDomains.length} store-loaded domain(s)`);
}
console.log(`SmartProxy started with ${routes.length} routes`); console.log(`SmartProxy started with ${routes.length} routes`);
} }
} }

View File

@@ -156,13 +156,21 @@ export class CertificateHandler {
// Check persisted cert data from StorageManager // Check persisted cert data from StorageManager
if (status === 'unknown') { if (status === 'unknown') {
const cleanDomain = domain.replace(/^\*\.?/, ''); const cleanDomain = domain.replace(/^\*\.?/, '');
const certData = await dcRouter.storageManager.getJSON(`/certs/${cleanDomain}`); let certData = await dcRouter.storageManager.getJSON(`/certs/${cleanDomain}`);
if (!certData) {
// Also check certStore path (proxy-certs)
certData = await dcRouter.storageManager.getJSON(`/proxy-certs/${domain}`);
}
if (certData?.validUntil) { if (certData?.validUntil) {
expiryDate = new Date(certData.validUntil).toISOString(); expiryDate = new Date(certData.validUntil).toISOString();
if (certData.created) { if (certData.created) {
issuedAt = new Date(certData.created).toISOString(); issuedAt = new Date(certData.created).toISOString();
} }
issuer = 'smartacme-dns-01'; issuer = 'smartacme-dns-01';
} else if (certData) {
// certStore has the cert (no expiry metadata) — it's loaded and serving
status = 'valid';
issuer = 'cert-store';
} }
} }

View File

@@ -378,7 +378,7 @@ export class StorageManager {
*/ */
async getJSON<T = any>(key: string): Promise<T | null> { async getJSON<T = any>(key: string): Promise<T | null> {
const value = await this.get(key); const value = await this.get(key);
if (value === null) { if (value === null || value.trim() === '') {
return null; return null;
} }

View File

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

View File

@@ -34,6 +34,13 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- **Security** — Security incidents from email processing - **Security** — Security incidents from email processing
- Bounce record management and suppression list controls - Bounce record management and suppression list controls
### 🔐 Certificate Management
- Domain-centric certificate overview with status indicators
- Certificate source tracking (ACME, provision function, static)
- Expiry date monitoring and alerts
- Per-domain backoff status for failed provisions
- One-click reprovisioning per domain
### 📜 Log Viewer ### 📜 Log Viewer
- Real-time log streaming - Real-time log streaming
- Filter by log level (error, warning, info, debug) - Filter by log level (error, warning, info, debug)
@@ -77,6 +84,7 @@ ts_web/
├── ops-view-overview.ts # Overview statistics ├── ops-view-overview.ts # Overview statistics
├── ops-view-network.ts # Network monitoring ├── ops-view-network.ts # Network monitoring
├── ops-view-emails.ts # Email queue management ├── ops-view-emails.ts # Email queue management
├── ops-view-certificates.ts # Certificate overview & reprovisioning
├── ops-view-logs.ts # Log viewer ├── ops-view-logs.ts # Log viewer
├── ops-view-config.ts # Configuration display ├── ops-view-config.ts # Configuration display
├── ops-view-security.ts # Security dashboard ├── ops-view-security.ts # Security dashboard
@@ -132,6 +140,7 @@ removeFromSuppressionAction(email) // Remove from suppression list
/emails/sent → Sent emails /emails/sent → Sent emails
/emails/failed → Failed emails /emails/failed → Failed emails
/emails/security → Security incidents /emails/security → Security incidents
/certificates → Certificate management
/logs → Log viewer /logs → Log viewer
/configuration → System configuration /configuration → System configuration
/security → Security dashboard /security → Security dashboard