@serve.zone/dcrouter 5.5.0 → 6.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -129,13 +129,13 @@ let OpsViewCertificates = (() => {
129
129
  color: ${cssManager.bdTheme('#374151', '#d1d5db')};
130
130
  }
131
131
 
132
- .domainPills {
132
+ .routePills {
133
133
  display: flex;
134
134
  flex-wrap: wrap;
135
135
  gap: 4px;
136
136
  }
137
137
 
138
- .domainPill {
138
+ .routePill {
139
139
  display: inline-flex;
140
140
  align-items: center;
141
141
  padding: 2px 8px;
@@ -160,6 +160,17 @@ let OpsViewCertificates = (() => {
160
160
  white-space: nowrap;
161
161
  }
162
162
 
163
+ .backoffIndicator {
164
+ display: inline-flex;
165
+ align-items: center;
166
+ gap: 4px;
167
+ font-size: 11px;
168
+ color: ${cssManager.bdTheme('#9a3412', '#fb923c')};
169
+ padding: 2px 6px;
170
+ border-radius: 4px;
171
+ background: ${cssManager.bdTheme('#fff7ed', '#431407')};
172
+ }
173
+
163
174
  .expiryInfo {
164
175
  font-size: 12px;
165
176
  }
@@ -245,14 +256,16 @@ let OpsViewCertificates = (() => {
245
256
  <dees-table
246
257
  .data=${this.certState.certificates}
247
258
  .displayFunction=${(cert) => ({
248
- Route: cert.routeName,
249
- Domains: this.renderDomainPills(cert.domains),
259
+ Domain: cert.domain,
260
+ Routes: this.renderRoutePills(cert.routeNames),
250
261
  Status: this.renderStatusBadge(cert.status),
251
262
  Source: this.renderSourceBadge(cert.source),
252
263
  Expires: this.renderExpiry(cert.expiryDate),
253
- Error: cert.error
254
- ? html `<span class="errorText" title="${cert.error}">${cert.error}</span>`
255
- : '',
264
+ Error: cert.backoffInfo
265
+ ? html `<span class="backoffIndicator">${cert.backoffInfo.failures} failures, retry ${this.formatRetryTime(cert.backoffInfo.retryAfter)}</span>`
266
+ : cert.error
267
+ ? html `<span class="errorText" title="${cert.error}">${cert.error}</span>`
268
+ : '',
256
269
  })}
257
270
  .dataActions=${[
258
271
  {
@@ -270,10 +283,10 @@ let OpsViewCertificates = (() => {
270
283
  });
271
284
  return;
272
285
  }
273
- await appstate.certificateStatePart.dispatchAction(appstate.reprovisionCertificateAction, cert.routeName);
286
+ await appstate.certificateStatePart.dispatchAction(appstate.reprovisionCertificateAction, cert.domain);
274
287
  const { DeesToast } = await import('@design.estate/dees-catalog');
275
288
  DeesToast.show({
276
- message: `Reprovisioning triggered for ${cert.routeName}`,
289
+ message: `Reprovisioning triggered for ${cert.domain}`,
277
290
  type: 'success',
278
291
  duration: 3000,
279
292
  });
@@ -287,7 +300,7 @@ let OpsViewCertificates = (() => {
287
300
  const cert = actionData.item;
288
301
  const { DeesModal } = await import('@design.estate/dees-catalog');
289
302
  await DeesModal.createAndShow({
290
- heading: `Certificate: ${cert.routeName}`,
303
+ heading: `Certificate: ${cert.domain}`,
291
304
  content: html `
292
305
  <div style="padding: 20px;">
293
306
  <dees-dataview-codebox
@@ -299,10 +312,10 @@ let OpsViewCertificates = (() => {
299
312
  `,
300
313
  menuOptions: [
301
314
  {
302
- name: 'Copy Route Name',
315
+ name: 'Copy Domain',
303
316
  iconName: 'copy',
304
317
  action: async () => {
305
- await navigator.clipboard.writeText(cert.routeName);
318
+ await navigator.clipboard.writeText(cert.domain);
306
319
  },
307
320
  },
308
321
  ],
@@ -311,7 +324,7 @@ let OpsViewCertificates = (() => {
311
324
  },
312
325
  ]}
313
326
  heading1="Certificate Status"
314
- heading2="TLS certificates across all routes"
327
+ heading2="TLS certificates by domain"
315
328
  searchable
316
329
  .pagination=${true}
317
330
  .paginationSize=${50}
@@ -319,13 +332,13 @@ let OpsViewCertificates = (() => {
319
332
  ></dees-table>
320
333
  `;
321
334
  }
322
- renderDomainPills(domains) {
335
+ renderRoutePills(routeNames) {
323
336
  const maxShow = 3;
324
- const visible = domains.slice(0, maxShow);
325
- const remaining = domains.length - maxShow;
337
+ const visible = routeNames.slice(0, maxShow);
338
+ const remaining = routeNames.length - maxShow;
326
339
  return html `
327
- <span class="domainPills">
328
- ${visible.map((d) => html `<span class="domainPill">${d}</span>`)}
340
+ <span class="routePills">
341
+ ${visible.map((r) => html `<span class="routePill">${r}</span>`)}
329
342
  ${remaining > 0 ? html `<span class="moreCount">+${remaining} more</span>` : ''}
330
343
  </span>
331
344
  `;
@@ -369,6 +382,20 @@ let OpsViewCertificates = (() => {
369
382
  </span>
370
383
  `;
371
384
  }
385
+ formatRetryTime(retryAfter) {
386
+ if (!retryAfter)
387
+ return 'soon';
388
+ const retryDate = new Date(retryAfter);
389
+ const now = new Date();
390
+ const diffMs = retryDate.getTime() - now.getTime();
391
+ if (diffMs <= 0)
392
+ return 'now';
393
+ const diffMin = Math.ceil(diffMs / 60000);
394
+ if (diffMin < 60)
395
+ return `in ${diffMin}m`;
396
+ const diffHours = Math.ceil(diffMin / 60);
397
+ return `in ${diffHours}h`;
398
+ }
372
399
  static {
373
400
  __runInitializers(_classThis, _classExtraInitializers);
374
401
  }
@@ -376,4 +403,4 @@ let OpsViewCertificates = (() => {
376
403
  return OpsViewCertificates = _classThis;
377
404
  })();
378
405
  export { OpsViewCertificates };
379
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctY2VydGlmaWNhdGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViL2VsZW1lbnRzL29wcy12aWV3LWNlcnRpZmljYXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssVUFBVSxNQUFNLG1DQUFtQyxDQUFDO0FBQ2hFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFTakQsbUJBQW1COzRCQUQvQixhQUFhLENBQUMsdUJBQXVCLENBQUM7Ozs7c0JBQ0UsV0FBVzs7OzttQ0FBbkIsU0FBUSxXQUFXOzs7O3FDQUNqRCxLQUFLLEVBQUU7WUFDUixnTEFBUyxTQUFTLDZCQUFULFNBQVMsNkZBQXdFO1lBRjVGLDZLQTZVQzs7OztRQTNVQywrRUFBaUQsUUFBUSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsRUFBRSxFQUFDO1FBQTFGLElBQVMsU0FBUywrQ0FBd0U7UUFBMUYsSUFBUyxTQUFTLHFEQUF3RTtRQUUxRjtZQUNFLEtBQUssRUFBRSxDQUFDOztZQUNSLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ3JFLElBQUksQ0FBQyxTQUFTLEdBQUcsUUFBUSxDQUFDO1lBQzVCLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDaEM7UUFFRCxLQUFL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
406
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctY2VydGlmaWNhdGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViL2VsZW1lbnRzL29wcy12aWV3LWNlcnRpZmljYXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssVUFBVSxNQUFNLG1DQUFtQyxDQUFDO0FBQ2hFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFTakQsbUJBQW1COzRCQUQvQixhQUFhLENBQUMsdUJBQXVCLENBQUM7Ozs7c0JBQ0UsV0FBVzs7OzttQ0FBbkIsU0FBUSxXQUFXOzs7O3FDQUNqRCxLQUFLLEVBQUU7WUFDUixnTEFBUyxTQUFTLDZCQUFULFNBQVMsNkZBQXdFO1lBRjVGLDZLQXNXQzs7OztRQXBXQywrRUFBaUQsUUFBUSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsRUFBRSxFQUFDO1FBQTFGLElBQVMsU0FBUywrQ0FBd0U7UUFBMUYsSUFBUyxTQUFTLHFEQUF3RTtRQUUxRjtZQUNFLEtBQUssRUFBRSxDQUFDOztZQUNSLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ3JFLElBQUksQ0FBQyxTQUFTLEdBQUcsUUFBUSxDQUFDO1lBQzVCLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDaEM7UUFFRCxLQUFL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
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@serve.zone/dcrouter",
3
3
  "private": false,
4
- "version": "5.5.0",
4
+ "version": "6.2.0",
5
5
  "description": "A multifaceted routing service handling mail and SMS delivery functions.",
6
6
  "type": "module",
7
7
  "exports": {
@@ -36,20 +36,20 @@
36
36
  "@design.estate/dees-element": "^2.1.6",
37
37
  "@push.rocks/projectinfo": "^5.0.2",
38
38
  "@push.rocks/qenv": "^6.1.3",
39
- "@push.rocks/smartacme": "^8.0.0",
39
+ "@push.rocks/smartacme": "^9.0.0",
40
40
  "@push.rocks/smartdata": "^7.0.15",
41
41
  "@push.rocks/smartdns": "^7.8.1",
42
42
  "@push.rocks/smartfile": "^13.1.2",
43
43
  "@push.rocks/smartguard": "^3.1.0",
44
44
  "@push.rocks/smartjwt": "^2.2.1",
45
- "@push.rocks/smartlog": "^3.1.10",
45
+ "@push.rocks/smartlog": "^3.1.11",
46
46
  "@push.rocks/smartmetrics": "^2.0.10",
47
47
  "@push.rocks/smartmongo": "^5.1.0",
48
48
  "@push.rocks/smartmta": "^5.2.2",
49
49
  "@push.rocks/smartnetwork": "^4.4.0",
50
50
  "@push.rocks/smartpath": "^6.0.0",
51
51
  "@push.rocks/smartpromise": "^4.2.3",
52
- "@push.rocks/smartproxy": "^25.3.0",
52
+ "@push.rocks/smartproxy": "^25.3.1",
53
53
  "@push.rocks/smartradius": "^1.1.1",
54
54
  "@push.rocks/smartrequest": "^5.0.1",
55
55
  "@push.rocks/smartrx": "^3.0.10",
package/readme.md CHANGED
@@ -21,6 +21,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
21
21
  - [Email System](#email-system)
22
22
  - [DNS Server](#dns-server)
23
23
  - [RADIUS Server](#radius-server)
24
+ - [Certificate Management](#certificate-management)
24
25
  - [Storage & Caching](#storage--caching)
25
26
  - [Security Features](#security-features)
26
27
  - [OpsServer Dashboard](#opsserver-dashboard)
@@ -46,7 +47,9 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
46
47
  - **Hierarchical rate limiting** — global, per-domain, per-sender
47
48
 
48
49
  ### 🔒 Enterprise Security
49
- - **Automatic TLS certificates** via ACME with Cloudflare DNS-01 challenges
50
+ - **Automatic TLS certificates** via ACME (smartacme v9) with Cloudflare DNS-01 challenges
51
+ - **Smart certificate scheduling** — per-domain deduplication, controlled parallelism, and account rate limiting handled automatically
52
+ - **Per-domain exponential backoff** — failed provisioning attempts are tracked and backed off to avoid hammering ACME servers
50
53
  - **IP reputation checking** with caching and configurable thresholds
51
54
  - **Content scanning** for spam, viruses, and malicious attachments
52
55
  - **Security event logging** with structured audit trails
@@ -73,7 +76,8 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
73
76
  ### 🖥️ OpsServer Dashboard
74
77
  - **Web-based management interface** with real-time monitoring
75
78
  - **JWT authentication** with session persistence
76
- - **Live views** for connections, email queues, DNS queries, RADIUS sessions, and security events
79
+ - **Live views** for connections, email queues, DNS queries, RADIUS sessions, certificates, and security events
80
+ - **Domain-centric certificate overview** with backoff status and one-click reprovisioning
77
81
  - **Read-only configuration display** — DcRouter is configured through code
78
82
 
79
83
  ## Installation
@@ -250,7 +254,7 @@ graph TB
250
254
  ES[smartmta Email Server<br/><i>TypeScript + Rust</i>]
251
255
  DS[SmartDNS Server<br/><i>Rust-powered</i>]
252
256
  RS[SmartRadius Server]
253
- CM[Certificate Manager]
257
+ CM[Certificate Manager<br/><i>smartacme v9</i>]
254
258
  OS[OpsServer Dashboard]
255
259
  MM[Metrics Manager]
256
260
  SM[Storage Manager]
@@ -297,6 +301,7 @@ graph TB
297
301
  | **SmartProxy** | `@push.rocks/smartproxy` | High-performance HTTP/HTTPS and TCP/SNI proxy with route-based config (Rust engine) |
298
302
  | **UnifiedEmailServer** | `@push.rocks/smartmta` | Full SMTP server with pattern-based routing, DKIM, queue management (TypeScript + Rust) |
299
303
  | **DNS Server** | `@push.rocks/smartdns` | Authoritative DNS with dynamic records and DKIM TXT auto-generation (Rust engine) |
304
+ | **SmartAcme** | `@push.rocks/smartacme` | ACME certificate management with per-domain dedup, concurrency control, and rate limiting |
300
305
  | **RADIUS Server** | `@push.rocks/smartradius` | Network authentication with MAB, VLAN assignment, and accounting |
301
306
  | **OpsServer** | `@api.global/typedserver` | Web dashboard + TypedRequest API for monitoring and management |
302
307
  | **MetricsManager** | `@push.rocks/smartmetrics` | Real-time metrics collection (CPU, memory, email, DNS, security) |
@@ -308,8 +313,8 @@ graph TB
308
313
  DcRouter acts purely as an **orchestrator** — it doesn't implement protocols itself. Instead, it wires together best-in-class packages for each protocol:
309
314
 
310
315
  1. **On `start()`**: DcRouter initializes OpsServer (port 3000), then spins up SmartProxy, smartmta, SmartDNS, and SmartRadius based on which configs are provided.
311
- 2. **During operation**: Each service handles its own protocol independently. SmartProxy uses a Rust-powered engine for maximum throughput. smartmta uses a hybrid TypeScript + Rust architecture for reliable email delivery.
312
- 3. **On `stop()`**: All services are gracefully shut down in reverse order.
316
+ 2. **During operation**: Each service handles its own protocol independently. SmartProxy uses a Rust-powered engine for maximum throughput. smartmta uses a hybrid TypeScript + Rust architecture for reliable email delivery. SmartAcme v9 handles all certificate operations with built-in concurrency control and rate limiting.
317
+ 3. **On `stop()`**: All services are gracefully shut down in parallel, including cleanup of HTTP agents and DNS clients.
313
318
 
314
319
  ### Rust-Powered Architecture
315
320
 
@@ -584,15 +589,6 @@ match: { sizeRange: { min: 1000, max: 5000000 }, hasAttachments: true }
584
589
  match: { subject: /invoice|receipt/i }
585
590
  ```
586
591
 
587
- ### Socket-Handler Mode 🔌
588
-
589
- When `useSocketHandler: true` is set, SmartProxy passes sockets directly to the email server — no internal port binding, lower latency, and fewer open ports:
590
-
591
- ```
592
- Traditional: External Port → SmartProxy → Internal Port → Email Server
593
- Socket Mode: External Port → SmartProxy → (direct socket) → Email Server
594
- ```
595
-
596
592
  ### Email Security Stack
597
593
 
598
594
  - **DKIM** — Automatic key generation, signing, and rotation for all domains
@@ -705,6 +701,73 @@ RADIUS is fully manageable at runtime via the OpsServer API:
705
701
  - Session monitoring and forced disconnects
706
702
  - Accounting summaries and statistics
707
703
 
704
+ ## Certificate Management
705
+
706
+ DcRouter uses [`@push.rocks/smartacme`](https://code.foss.global/push.rocks/smartacme) v9 for ACME certificate provisioning. smartacme v9 brings significant improvements over previous versions:
707
+
708
+ ### How It Works
709
+
710
+ When a `dnsChallenge` is configured (e.g. with a Cloudflare API key), DcRouter creates a SmartAcme instance that handles DNS-01 challenges for automatic certificate provisioning. SmartProxy calls the `certProvisionFunction` whenever a route needs a TLS certificate, and SmartAcme takes care of the rest.
711
+
712
+ ```typescript
713
+ const router = new DcRouter({
714
+ smartProxyConfig: {
715
+ routes: [
716
+ {
717
+ name: 'secure-app',
718
+ match: { domains: ['app.example.com'], ports: [443] },
719
+ action: {
720
+ type: 'forward',
721
+ targets: [{ host: '192.168.1.10', port: 8080 }],
722
+ tls: { mode: 'terminate', certificate: 'auto' } // ← triggers ACME provisioning
723
+ }
724
+ }
725
+ ],
726
+ acme: { email: 'admin@example.com', enabled: true, useProduction: true }
727
+ },
728
+ tls: { contactEmail: 'admin@example.com' },
729
+ dnsChallenge: { cloudflareApiKey: process.env.CLOUDFLARE_API_KEY }
730
+ });
731
+ ```
732
+
733
+ ### smartacme v9 Features
734
+
735
+ | Feature | Description |
736
+ |---------|-------------|
737
+ | **Per-domain deduplication** | Concurrent requests for the same domain share a single ACME operation |
738
+ | **Global concurrency cap** | Default 5 parallel ACME operations to prevent overload |
739
+ | **Account rate limiting** | Sliding window (250 orders / 3 hours) to stay within ACME provider limits |
740
+ | **Structured errors** | `AcmeError` with `isRetryable`, `isRateLimited`, `retryAfter` fields |
741
+ | **Clean shutdown** | `stop()` properly destroys HTTP agents and DNS clients |
742
+
743
+ ### Per-Domain Backoff
744
+
745
+ DcRouter's `CertProvisionScheduler` adds **per-domain exponential backoff** on top of smartacme's built-in protections. If a DNS-01 challenge fails for a domain:
746
+
747
+ 1. The failure is recorded (persisted to storage)
748
+ 2. The domain enters backoff: `min(failures² × 1 hour, 24 hours)`
749
+ 3. Subsequent requests for that domain are rejected until the backoff expires
750
+ 4. On success, the backoff is cleared
751
+
752
+ This prevents hammering ACME servers for domains with persistent issues (e.g. missing DNS delegation).
753
+
754
+ ### Fallback to HTTP-01
755
+
756
+ If DNS-01 fails, the `certProvisionFunction` returns `'http01'` to tell SmartProxy to fall back to HTTP-01 challenge validation. This provides a safety net for domains where DNS-01 isn't viable.
757
+
758
+ ### Certificate Storage
759
+
760
+ Certificates are persisted via the `StorageBackedCertManager` which uses DcRouter's `StorageManager`. This means certs survive restarts and don't need to be re-provisioned unless they expire.
761
+
762
+ ### Dashboard
763
+
764
+ The OpsServer includes a **Certificates** view showing:
765
+ - All domains with their certificate status (valid, expiring, expired, failed)
766
+ - Certificate source (ACME, provision function, static)
767
+ - Expiry dates and issuer information
768
+ - Backoff status for failed domains
769
+ - One-click reprovisioning per domain
770
+
708
771
  ## Storage & Caching
709
772
 
710
773
  ### StorageManager
@@ -725,7 +788,7 @@ storage: {
725
788
  // Simply omit the storage config
726
789
  ```
727
790
 
728
- Used for: DKIM keys, email routes, bounce/suppression lists, IP reputation data, domain configs.
791
+ Used for: TLS certificates, DKIM keys, email routes, bounce/suppression lists, IP reputation data, domain configs, cert backoff state.
729
792
 
730
793
  ### Cache Database
731
794
 
@@ -811,6 +874,7 @@ The OpsServer provides a web-based management interface served on port 3000. It'
811
874
  | 📊 **Overview** | Real-time server stats, CPU/memory, connection counts, email throughput |
812
875
  | 🌐 **Network** | Active connections, top IPs, throughput rates, SmartProxy metrics |
813
876
  | 📧 **Email** | Queue monitoring (queued/sent/failed), bounce records, security incidents |
877
+ | 🔐 **Certificates** | Domain-centric certificate overview, status, backoff info, reprovisioning |
814
878
  | 📜 **Logs** | Real-time log viewer with level filtering and search |
815
879
  | ⚙️ **Configuration** | Read-only view of current system configuration |
816
880
  | 🛡️ **Security** | IP reputation, rate limit status, blocked connections |
@@ -838,6 +902,11 @@ All management is done via TypedRequest over HTTP POST to `/typedrequest`:
838
902
  'getBounceRecords' // Bounce records
839
903
  'removeFromSuppressionList' // Unsuppress an address
840
904
 
905
+ // Certificates
906
+ 'getCertificateOverview' // Domain-centric certificate status
907
+ 'reprovisionCertificate' // Reprovision by route name (legacy)
908
+ 'reprovisionCertificateDomain' // Reprovision by domain (preferred)
909
+
841
910
  // Configuration (read-only)
842
911
  'getConfiguration' // Current system config
843
912
 
@@ -884,6 +953,7 @@ const router = new DcRouter(options: IDcRouterOptions);
884
953
  |----------|------|-------------|
885
954
  | `options` | `IDcRouterOptions` | Current configuration |
886
955
  | `smartProxy` | `SmartProxy` | SmartProxy instance |
956
+ | `smartAcme` | `SmartAcme` | SmartAcme v9 certificate manager instance |
887
957
  | `emailServer` | `UnifiedEmailServer` | Email server instance (from smartmta) |
888
958
  | `dnsServer` | `DnsServer` | DNS server instance |
889
959
  | `radiusServer` | `RadiusServer` | RADIUS server instance |
@@ -891,6 +961,8 @@ const router = new DcRouter(options: IDcRouterOptions);
891
961
  | `opsServer` | `OpsServer` | OpsServer/dashboard instance |
892
962
  | `metricsManager` | `MetricsManager` | Metrics collector |
893
963
  | `cacheDb` | `CacheDb` | Cache database instance |
964
+ | `certProvisionScheduler` | `CertProvisionScheduler` | Per-domain backoff scheduler for cert provisioning |
965
+ | `certificateStatusMap` | `Map<string, ...>` | Domain-keyed certificate status from SmartProxy events |
894
966
 
895
967
  ### Re-exported Types
896
968
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '5.5.0',
6
+ version: '6.2.0',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -0,0 +1,130 @@
1
+ import { logger } from './logger.js';
2
+ import type { StorageManager } from './storage/index.js';
3
+
4
+ interface IBackoffEntry {
5
+ failures: number;
6
+ lastFailure: string; // ISO string
7
+ retryAfter: string; // ISO string
8
+ lastError?: string;
9
+ }
10
+
11
+ /**
12
+ * Manages certificate provisioning scheduling with:
13
+ * - Per-domain exponential backoff persisted in StorageManager
14
+ *
15
+ * Note: Serial stagger queue was removed — smartacme v9 handles
16
+ * concurrency, per-domain dedup, and rate limiting internally.
17
+ */
18
+ export class CertProvisionScheduler {
19
+ private storageManager: StorageManager;
20
+ private maxBackoffHours: number;
21
+
22
+ // In-memory backoff cache (mirrors storage for fast lookups)
23
+ private backoffCache = new Map<string, IBackoffEntry>();
24
+
25
+ constructor(
26
+ storageManager: StorageManager,
27
+ options?: { maxBackoffHours?: number }
28
+ ) {
29
+ this.storageManager = storageManager;
30
+ this.maxBackoffHours = options?.maxBackoffHours ?? 24;
31
+ }
32
+
33
+ /**
34
+ * Storage key for a domain's backoff entry
35
+ */
36
+ private backoffKey(domain: string): string {
37
+ const clean = domain.replace(/\*/g, '_wildcard_').replace(/[^a-zA-Z0-9._-]/g, '_');
38
+ return `/cert-backoff/${clean}`;
39
+ }
40
+
41
+ /**
42
+ * Load backoff entry from storage (with in-memory cache)
43
+ */
44
+ private async loadBackoff(domain: string): Promise<IBackoffEntry | null> {
45
+ const cached = this.backoffCache.get(domain);
46
+ if (cached) return cached;
47
+
48
+ const entry = await this.storageManager.getJSON<IBackoffEntry>(this.backoffKey(domain));
49
+ if (entry) {
50
+ this.backoffCache.set(domain, entry);
51
+ }
52
+ return entry;
53
+ }
54
+
55
+ /**
56
+ * Save backoff entry to both cache and storage
57
+ */
58
+ private async saveBackoff(domain: string, entry: IBackoffEntry): Promise<void> {
59
+ this.backoffCache.set(domain, entry);
60
+ await this.storageManager.setJSON(this.backoffKey(domain), entry);
61
+ }
62
+
63
+ /**
64
+ * Check if a domain is currently in backoff
65
+ */
66
+ async isInBackoff(domain: string): Promise<boolean> {
67
+ const entry = await this.loadBackoff(domain);
68
+ if (!entry) return false;
69
+
70
+ const retryAfter = new Date(entry.retryAfter);
71
+ return retryAfter.getTime() > Date.now();
72
+ }
73
+
74
+ /**
75
+ * Record a provisioning failure for a domain.
76
+ * Sets exponential backoff: min(failures^2 * 1h, maxBackoffHours)
77
+ */
78
+ async recordFailure(domain: string, error?: string): Promise<void> {
79
+ const existing = await this.loadBackoff(domain);
80
+ const failures = (existing?.failures ?? 0) + 1;
81
+
82
+ // Exponential backoff: failures^2 hours, capped
83
+ const backoffHours = Math.min(failures * failures, this.maxBackoffHours);
84
+ const retryAfter = new Date(Date.now() + backoffHours * 60 * 60 * 1000);
85
+
86
+ const entry: IBackoffEntry = {
87
+ failures,
88
+ lastFailure: new Date().toISOString(),
89
+ retryAfter: retryAfter.toISOString(),
90
+ lastError: error,
91
+ };
92
+
93
+ await this.saveBackoff(domain, entry);
94
+ logger.log('warn', `Cert backoff for ${domain}: ${failures} failures, retry after ${retryAfter.toISOString()}`);
95
+ }
96
+
97
+ /**
98
+ * Clear backoff for a domain (on success or manual override)
99
+ */
100
+ async clearBackoff(domain: string): Promise<void> {
101
+ this.backoffCache.delete(domain);
102
+ try {
103
+ await this.storageManager.delete(this.backoffKey(domain));
104
+ } catch {
105
+ // Ignore delete errors (key may not exist)
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Get backoff info for UI display
111
+ */
112
+ async getBackoffInfo(domain: string): Promise<{
113
+ failures: number;
114
+ retryAfter?: string;
115
+ lastError?: string;
116
+ } | null> {
117
+ const entry = await this.loadBackoff(domain);
118
+ if (!entry) return null;
119
+
120
+ // Only return if still in backoff
121
+ const retryAfter = new Date(entry.retryAfter);
122
+ if (retryAfter.getTime() <= Date.now()) return null;
123
+
124
+ return {
125
+ failures: entry.failures,
126
+ retryAfter: entry.retryAfter,
127
+ lastError: entry.lastError,
128
+ };
129
+ }
130
+ }