@serve.zone/dcrouter 5.4.6 → 6.0.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.
- package/dist_serve/bundle.js +23 -12
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.cert-provision-scheduler.d.ts +60 -0
- package/dist_ts/classes.cert-provision-scheduler.js +141 -0
- package/dist_ts/classes.dcrouter.d.ts +8 -2
- package/dist_ts/classes.dcrouter.js +107 -43
- package/dist_ts/classes.storage-cert-manager.d.ts +18 -0
- package/dist_ts/classes.storage-cert-manager.js +43 -0
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +12 -1
- package/dist_ts/opsserver/handlers/certificate.handler.js +126 -22
- package/dist_ts_interfaces/requests/certificate.d.ts +18 -2
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.js +4 -4
- package/dist_ts_web/elements/ops-view-certificates.d.ts +2 -1
- package/dist_ts_web/elements/ops-view-certificates.js +46 -19
- package/package.json +3 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.cert-provision-scheduler.ts +176 -0
- package/ts/classes.dcrouter.ts +111 -44
- package/ts/classes.storage-cert-manager.ts +46 -0
- package/ts/opsserver/handlers/certificate.handler.ts +146 -21
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +4 -4
- package/ts_web/elements/ops-view-certificates.ts +43 -18
|
@@ -129,13 +129,13 @@ let OpsViewCertificates = (() => {
|
|
|
129
129
|
color: ${cssManager.bdTheme('#374151', '#d1d5db')};
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
-
.
|
|
132
|
+
.routePills {
|
|
133
133
|
display: flex;
|
|
134
134
|
flex-wrap: wrap;
|
|
135
135
|
gap: 4px;
|
|
136
136
|
}
|
|
137
137
|
|
|
138
|
-
.
|
|
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
|
-
|
|
249
|
-
|
|
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.
|
|
254
|
-
? html `<span class="
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
315
|
+
name: 'Copy Domain',
|
|
303
316
|
iconName: 'copy',
|
|
304
317
|
action: async () => {
|
|
305
|
-
await navigator.clipboard.writeText(cert.
|
|
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
|
|
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
|
-
|
|
335
|
+
renderRoutePills(routeNames) {
|
|
323
336
|
const maxShow = 3;
|
|
324
|
-
const visible =
|
|
325
|
-
const remaining =
|
|
337
|
+
const visible = routeNames.slice(0, maxShow);
|
|
338
|
+
const remaining = routeNames.length - maxShow;
|
|
326
339
|
return html `
|
|
327
|
-
<span class="
|
|
328
|
-
${visible.map((
|
|
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,
|
|
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": "
|
|
4
|
+
"version": "6.0.0",
|
|
5
5
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -42,14 +42,14 @@
|
|
|
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.
|
|
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.
|
|
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/ts/00_commitinfo_data.ts
CHANGED
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
* - Serial stagger queue with configurable delay between provisions
|
|
15
|
+
*/
|
|
16
|
+
export class CertProvisionScheduler {
|
|
17
|
+
private storageManager: StorageManager;
|
|
18
|
+
private staggerDelayMs: number;
|
|
19
|
+
private maxBackoffHours: number;
|
|
20
|
+
|
|
21
|
+
// In-memory serial queue
|
|
22
|
+
private queue: Array<{
|
|
23
|
+
domain: string;
|
|
24
|
+
fn: () => Promise<any>;
|
|
25
|
+
resolve: (value: any) => void;
|
|
26
|
+
reject: (err: any) => void;
|
|
27
|
+
}> = [];
|
|
28
|
+
private processing = false;
|
|
29
|
+
|
|
30
|
+
// In-memory backoff cache (mirrors storage for fast lookups)
|
|
31
|
+
private backoffCache = new Map<string, IBackoffEntry>();
|
|
32
|
+
|
|
33
|
+
constructor(
|
|
34
|
+
storageManager: StorageManager,
|
|
35
|
+
options?: { staggerDelayMs?: number; maxBackoffHours?: number }
|
|
36
|
+
) {
|
|
37
|
+
this.storageManager = storageManager;
|
|
38
|
+
this.staggerDelayMs = options?.staggerDelayMs ?? 3000;
|
|
39
|
+
this.maxBackoffHours = options?.maxBackoffHours ?? 24;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Storage key for a domain's backoff entry
|
|
44
|
+
*/
|
|
45
|
+
private backoffKey(domain: string): string {
|
|
46
|
+
const clean = domain.replace(/\*/g, '_wildcard_').replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
47
|
+
return `/cert-backoff/${clean}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Load backoff entry from storage (with in-memory cache)
|
|
52
|
+
*/
|
|
53
|
+
private async loadBackoff(domain: string): Promise<IBackoffEntry | null> {
|
|
54
|
+
const cached = this.backoffCache.get(domain);
|
|
55
|
+
if (cached) return cached;
|
|
56
|
+
|
|
57
|
+
const entry = await this.storageManager.getJSON<IBackoffEntry>(this.backoffKey(domain));
|
|
58
|
+
if (entry) {
|
|
59
|
+
this.backoffCache.set(domain, entry);
|
|
60
|
+
}
|
|
61
|
+
return entry;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Save backoff entry to both cache and storage
|
|
66
|
+
*/
|
|
67
|
+
private async saveBackoff(domain: string, entry: IBackoffEntry): Promise<void> {
|
|
68
|
+
this.backoffCache.set(domain, entry);
|
|
69
|
+
await this.storageManager.setJSON(this.backoffKey(domain), entry);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check if a domain is currently in backoff
|
|
74
|
+
*/
|
|
75
|
+
async isInBackoff(domain: string): Promise<boolean> {
|
|
76
|
+
const entry = await this.loadBackoff(domain);
|
|
77
|
+
if (!entry) return false;
|
|
78
|
+
|
|
79
|
+
const retryAfter = new Date(entry.retryAfter);
|
|
80
|
+
return retryAfter.getTime() > Date.now();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Record a provisioning failure for a domain.
|
|
85
|
+
* Sets exponential backoff: min(failures^2 * 1h, maxBackoffHours)
|
|
86
|
+
*/
|
|
87
|
+
async recordFailure(domain: string, error?: string): Promise<void> {
|
|
88
|
+
const existing = await this.loadBackoff(domain);
|
|
89
|
+
const failures = (existing?.failures ?? 0) + 1;
|
|
90
|
+
|
|
91
|
+
// Exponential backoff: failures^2 hours, capped
|
|
92
|
+
const backoffHours = Math.min(failures * failures, this.maxBackoffHours);
|
|
93
|
+
const retryAfter = new Date(Date.now() + backoffHours * 60 * 60 * 1000);
|
|
94
|
+
|
|
95
|
+
const entry: IBackoffEntry = {
|
|
96
|
+
failures,
|
|
97
|
+
lastFailure: new Date().toISOString(),
|
|
98
|
+
retryAfter: retryAfter.toISOString(),
|
|
99
|
+
lastError: error,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
await this.saveBackoff(domain, entry);
|
|
103
|
+
logger.log('warn', `Cert backoff for ${domain}: ${failures} failures, retry after ${retryAfter.toISOString()}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Clear backoff for a domain (on success or manual override)
|
|
108
|
+
*/
|
|
109
|
+
async clearBackoff(domain: string): Promise<void> {
|
|
110
|
+
this.backoffCache.delete(domain);
|
|
111
|
+
try {
|
|
112
|
+
await this.storageManager.delete(this.backoffKey(domain));
|
|
113
|
+
} catch {
|
|
114
|
+
// Ignore delete errors (key may not exist)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get backoff info for UI display
|
|
120
|
+
*/
|
|
121
|
+
async getBackoffInfo(domain: string): Promise<{
|
|
122
|
+
failures: number;
|
|
123
|
+
retryAfter?: string;
|
|
124
|
+
lastError?: string;
|
|
125
|
+
} | null> {
|
|
126
|
+
const entry = await this.loadBackoff(domain);
|
|
127
|
+
if (!entry) return null;
|
|
128
|
+
|
|
129
|
+
// Only return if still in backoff
|
|
130
|
+
const retryAfter = new Date(entry.retryAfter);
|
|
131
|
+
if (retryAfter.getTime() <= Date.now()) return null;
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
failures: entry.failures,
|
|
135
|
+
retryAfter: entry.retryAfter,
|
|
136
|
+
lastError: entry.lastError,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Enqueue a provision operation for serial execution with stagger delay.
|
|
142
|
+
* Returns the result of the provision function.
|
|
143
|
+
*/
|
|
144
|
+
enqueueProvision<T>(domain: string, fn: () => Promise<T>): Promise<T> {
|
|
145
|
+
return new Promise<T>((resolve, reject) => {
|
|
146
|
+
this.queue.push({ domain, fn, resolve, reject });
|
|
147
|
+
this.processQueue();
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Process the stagger queue serially
|
|
153
|
+
*/
|
|
154
|
+
private async processQueue(): Promise<void> {
|
|
155
|
+
if (this.processing) return;
|
|
156
|
+
this.processing = true;
|
|
157
|
+
|
|
158
|
+
while (this.queue.length > 0) {
|
|
159
|
+
const item = this.queue.shift()!;
|
|
160
|
+
try {
|
|
161
|
+
logger.log('info', `Processing cert provision for ${item.domain}`);
|
|
162
|
+
const result = await item.fn();
|
|
163
|
+
item.resolve(result);
|
|
164
|
+
} catch (err) {
|
|
165
|
+
item.reject(err);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Stagger delay between provisions
|
|
169
|
+
if (this.queue.length > 0) {
|
|
170
|
+
await new Promise<void>((r) => setTimeout(r, this.staggerDelayMs));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
this.processing = false;
|
|
175
|
+
}
|
|
176
|
+
}
|
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
import { logger } from './logger.js';
|
|
14
14
|
// Import storage manager
|
|
15
15
|
import { StorageManager, type IStorageConfig } from './storage/index.js';
|
|
16
|
+
import { StorageBackedCertManager } from './classes.storage-cert-manager.js';
|
|
17
|
+
import { CertProvisionScheduler } from './classes.cert-provision-scheduler.js';
|
|
16
18
|
// Import cache system
|
|
17
19
|
import { CacheDb, CacheCleaner, type ICacheDbOptions } from './cache/index.js';
|
|
18
20
|
|
|
@@ -183,16 +185,19 @@ export class DcRouter {
|
|
|
183
185
|
public cacheDb?: CacheDb;
|
|
184
186
|
public cacheCleaner?: CacheCleaner;
|
|
185
187
|
|
|
186
|
-
// Certificate status tracking from SmartProxy events
|
|
188
|
+
// Certificate status tracking from SmartProxy events (keyed by domain)
|
|
187
189
|
public certificateStatusMap = new Map<string, {
|
|
188
190
|
status: 'valid' | 'failed';
|
|
189
|
-
|
|
191
|
+
routeNames: string[];
|
|
190
192
|
expiryDate?: string;
|
|
191
193
|
issuedAt?: string;
|
|
192
194
|
source?: string;
|
|
193
195
|
error?: string;
|
|
194
196
|
}>();
|
|
195
197
|
|
|
198
|
+
// Certificate provisioning scheduler with backoff + stagger
|
|
199
|
+
public certProvisionScheduler?: CertProvisionScheduler;
|
|
200
|
+
|
|
196
201
|
// TypedRouter for API endpoints
|
|
197
202
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
198
203
|
|
|
@@ -204,7 +209,14 @@ export class DcRouter {
|
|
|
204
209
|
this.options = {
|
|
205
210
|
...optionsArg
|
|
206
211
|
};
|
|
207
|
-
|
|
212
|
+
|
|
213
|
+
// Default storage to filesystem if not configured
|
|
214
|
+
if (!this.options.storage) {
|
|
215
|
+
this.options.storage = {
|
|
216
|
+
fsPath: plugins.path.join(paths.dcrouterHomeDir, 'storage'),
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
208
220
|
// Initialize storage manager
|
|
209
221
|
this.storageManager = new StorageManager(this.options.storage);
|
|
210
222
|
}
|
|
@@ -437,38 +449,77 @@ export class DcRouter {
|
|
|
437
449
|
const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
|
|
438
450
|
...this.options.smartProxyConfig,
|
|
439
451
|
routes,
|
|
440
|
-
acme: acmeConfig
|
|
452
|
+
acme: acmeConfig,
|
|
453
|
+
certStore: {
|
|
454
|
+
loadAll: async () => {
|
|
455
|
+
const keys = await this.storageManager.list('/proxy-certs/');
|
|
456
|
+
const certs: Array<{ domain: string; publicKey: string; privateKey: string; ca?: string }> = [];
|
|
457
|
+
for (const key of keys) {
|
|
458
|
+
const data = await this.storageManager.getJSON(key);
|
|
459
|
+
if (data) certs.push(data);
|
|
460
|
+
}
|
|
461
|
+
return certs;
|
|
462
|
+
},
|
|
463
|
+
save: async (domain: string, publicKey: string, privateKey: string, ca?: string) => {
|
|
464
|
+
await this.storageManager.setJSON(`/proxy-certs/${domain}`, {
|
|
465
|
+
domain, publicKey, privateKey, ca,
|
|
466
|
+
});
|
|
467
|
+
},
|
|
468
|
+
remove: async (domain: string) => {
|
|
469
|
+
await this.storageManager.delete(`/proxy-certs/${domain}`);
|
|
470
|
+
},
|
|
471
|
+
},
|
|
441
472
|
};
|
|
442
473
|
|
|
474
|
+
// Initialize cert provision scheduler
|
|
475
|
+
this.certProvisionScheduler = new CertProvisionScheduler(this.storageManager);
|
|
476
|
+
|
|
443
477
|
// If we have DNS challenge handlers, create SmartAcme and wire to certProvisionFunction
|
|
444
478
|
if (challengeHandlers.length > 0) {
|
|
445
479
|
this.smartAcme = new plugins.smartacme.SmartAcme({
|
|
446
480
|
accountEmail: acmeConfig?.accountEmail || this.options.tls?.contactEmail || 'admin@example.com',
|
|
447
|
-
certManager: new
|
|
481
|
+
certManager: new StorageBackedCertManager(this.storageManager),
|
|
448
482
|
environment: 'production',
|
|
449
483
|
challengeHandlers: challengeHandlers,
|
|
450
484
|
challengePriority: ['dns-01'],
|
|
451
485
|
});
|
|
452
486
|
await this.smartAcme.start();
|
|
453
487
|
|
|
488
|
+
const scheduler = this.certProvisionScheduler;
|
|
454
489
|
smartProxyConfig.certProvisionFunction = async (domain, eventComms) => {
|
|
490
|
+
// Check backoff before attempting provision
|
|
491
|
+
if (await scheduler.isInBackoff(domain)) {
|
|
492
|
+
const info = await scheduler.getBackoffInfo(domain);
|
|
493
|
+
const msg = `Domain ${domain} is in backoff (${info?.failures} failures), retry after ${info?.retryAfter}`;
|
|
494
|
+
eventComms.warn(msg);
|
|
495
|
+
throw new Error(msg);
|
|
496
|
+
}
|
|
497
|
+
|
|
455
498
|
try {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
499
|
+
const result = await scheduler.enqueueProvision(domain, async () => {
|
|
500
|
+
eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
|
|
501
|
+
eventComms.setSource('smartacme-dns-01');
|
|
502
|
+
const cert = await this.smartAcme.getCertificateForDomain(domain);
|
|
503
|
+
if (cert.validUntil) {
|
|
504
|
+
eventComms.setExpiryDate(new Date(cert.validUntil));
|
|
505
|
+
}
|
|
506
|
+
return {
|
|
507
|
+
id: cert.id,
|
|
508
|
+
domainName: cert.domainName,
|
|
509
|
+
created: cert.created,
|
|
510
|
+
validUntil: cert.validUntil,
|
|
511
|
+
privateKey: cert.privateKey,
|
|
512
|
+
publicKey: cert.publicKey,
|
|
513
|
+
csr: cert.csr,
|
|
514
|
+
};
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
// Success — clear any backoff
|
|
518
|
+
await scheduler.clearBackoff(domain);
|
|
519
|
+
return result;
|
|
471
520
|
} catch (err) {
|
|
521
|
+
// Record failure for backoff tracking
|
|
522
|
+
await scheduler.recordFailure(domain, err.message);
|
|
472
523
|
eventComms.warn(`SmartAcme DNS-01 failed for ${domain}: ${err.message}, falling back to http-01`);
|
|
473
524
|
return 'http01';
|
|
474
525
|
}
|
|
@@ -492,39 +543,34 @@ export class DcRouter {
|
|
|
492
543
|
});
|
|
493
544
|
|
|
494
545
|
// Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
|
|
546
|
+
// Events are keyed by domain for domain-centric certificate tracking
|
|
495
547
|
this.smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
|
|
496
548
|
console.log(`[DcRouter] Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
});
|
|
504
|
-
}
|
|
549
|
+
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
550
|
+
this.certificateStatusMap.set(event.domain, {
|
|
551
|
+
status: 'valid', routeNames,
|
|
552
|
+
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
|
|
553
|
+
source: event.source,
|
|
554
|
+
});
|
|
505
555
|
});
|
|
506
556
|
|
|
507
557
|
this.smartProxy.on('certificate-renewed', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
|
|
508
558
|
console.log(`[DcRouter] Certificate renewed for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
});
|
|
516
|
-
}
|
|
559
|
+
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
560
|
+
this.certificateStatusMap.set(event.domain, {
|
|
561
|
+
status: 'valid', routeNames,
|
|
562
|
+
expiryDate: event.expiryDate, issuedAt: new Date().toISOString(),
|
|
563
|
+
source: event.source,
|
|
564
|
+
});
|
|
517
565
|
});
|
|
518
566
|
|
|
519
567
|
this.smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
|
|
520
568
|
console.error(`[DcRouter] Certificate failed for ${event.domain} (${event.source}):`, event.error);
|
|
521
|
-
const
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
});
|
|
527
|
-
}
|
|
569
|
+
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
570
|
+
this.certificateStatusMap.set(event.domain, {
|
|
571
|
+
status: 'failed', routeNames, error: event.error,
|
|
572
|
+
source: event.source,
|
|
573
|
+
});
|
|
528
574
|
});
|
|
529
575
|
|
|
530
576
|
// Start SmartProxy
|
|
@@ -697,7 +743,7 @@ export class DcRouter {
|
|
|
697
743
|
}
|
|
698
744
|
|
|
699
745
|
/**
|
|
700
|
-
* Find the route name that matches a given domain
|
|
746
|
+
* Find the first route name that matches a given domain
|
|
701
747
|
*/
|
|
702
748
|
private findRouteNameForDomain(domain: string): string | undefined {
|
|
703
749
|
if (!this.smartProxy) return undefined;
|
|
@@ -713,6 +759,27 @@ export class DcRouter {
|
|
|
713
759
|
return undefined;
|
|
714
760
|
}
|
|
715
761
|
|
|
762
|
+
/**
|
|
763
|
+
* Find ALL route names that match a given domain
|
|
764
|
+
*/
|
|
765
|
+
public findRouteNamesForDomain(domain: string): string[] {
|
|
766
|
+
if (!this.smartProxy) return [];
|
|
767
|
+
const names: string[] = [];
|
|
768
|
+
for (const route of this.smartProxy.routeManager.getRoutes()) {
|
|
769
|
+
if (!route.match.domains || !route.name) continue;
|
|
770
|
+
const routeDomains = Array.isArray(route.match.domains)
|
|
771
|
+
? route.match.domains
|
|
772
|
+
: [route.match.domains];
|
|
773
|
+
for (const pattern of routeDomains) {
|
|
774
|
+
if (this.isDomainMatch(domain, pattern)) {
|
|
775
|
+
names.push(route.name);
|
|
776
|
+
break; // This route already matched, no need to check other patterns
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return names;
|
|
781
|
+
}
|
|
782
|
+
|
|
716
783
|
public async stop() {
|
|
717
784
|
console.log('Stopping DcRouter services...');
|
|
718
785
|
|