@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.
- 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 +49 -0
- package/dist_ts/classes.cert-provision-scheduler.js +104 -0
- package/dist_ts/classes.dcrouter.d.ts +8 -2
- package/dist_ts/classes.dcrouter.js +63 -27
- package/dist_ts/opsserver/handlers/certificate.handler.d.ts +12 -1
- package/dist_ts/opsserver/handlers/certificate.handler.js +119 -35
- 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 +4 -4
- package/readme.md +87 -15
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.cert-provision-scheduler.ts +130 -0
- package/ts/classes.dcrouter.ts +66 -27
- package/ts/opsserver/handlers/certificate.handler.ts +137 -33
- 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
- package/ts_web/readme.md +9 -0
|
@@ -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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoib3BzLXZpZXctY2VydGlmaWNhdGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViL2VsZW1lbnRzL29wcy12aWV3LWNlcnRpZmljYXRlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0FBQUEsT0FBTyxFQUNMLFdBQVcsRUFDWCxJQUFJLEVBQ0osYUFBYSxFQUViLEdBQUcsRUFDSCxLQUFLLEVBQ0wsVUFBVSxHQUNYLE1BQU0sNkJBQTZCLENBQUM7QUFDckMsT0FBTyxLQUFLLFFBQVEsTUFBTSxnQkFBZ0IsQ0FBQztBQUMzQyxPQUFPLEtBQUssVUFBVSxNQUFNLG1DQUFtQyxDQUFDO0FBQ2hFLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUM5QyxPQUFPLEVBQW1CLE1BQU0sNkJBQTZCLENBQUM7SUFTakQsbUJBQW1COzRCQUQvQixhQUFhLENBQUMsdUJBQXVCLENBQUM7Ozs7c0JBQ0UsV0FBVzs7OzttQ0FBbkIsU0FBUSxXQUFXOzs7O3FDQUNqRCxLQUFLLEVBQUU7WUFDUixnTEFBUyxTQUFTLDZCQUFULFNBQVMsNkZBQXdFO1lBRjVGLDZLQXNXQzs7OztRQXBXQywrRUFBaUQsUUFBUSxDQUFDLG9CQUFvQixDQUFDLFFBQVEsRUFBRSxFQUFDO1FBQTFGLElBQVMsU0FBUywrQ0FBd0U7UUFBMUYsSUFBUyxTQUFTLHFEQUF3RTtRQUUxRjtZQUNFLEtBQUssRUFBRSxDQUFDOztZQUNSLE1BQU0sR0FBRyxHQUFHLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsU0FBUyxDQUFDLENBQUMsUUFBUSxFQUFFLEVBQUU7Z0JBQ3JFLElBQUksQ0FBQyxTQUFTLEdBQUcsUUFBUSxDQUFDO1lBQzVCLENBQUMsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7U0FDaEM7UUFFRCxLQUFLLENBQUMsaUJBQWlCO1lBQ3JCLE1BQU0sS0FBSyxDQUFDLGlCQUFpQixFQUFFLENBQUM7WUFDaEMsTUFBTSxRQUFRLENBQUMsb0JBQW9CLENBQUMsY0FBYyxDQUFDLFFBQVEsQ0FBQyw4QkFBOEIsRUFBRSxJQUFJLENBQUMsQ0FBQztRQUNwRyxDQUFDO1FBRU0sTUFBTSxDQUFDLE1BQU0sR0FBRztZQUNyQixVQUFVLENBQUMsYUFBYTtZQUN4QixXQUFXO1lBQ1gsR0FBRyxDQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O3NCQW1CZSxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUJBQzdDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7OztzQkFJbkMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDO2lCQUM3QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7O3NCQUtuQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUJBQzdDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7OztzQkFJbkMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDO2lCQUM3QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7c0JBSW5DLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQztpQkFDN0MsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7Ozs7Ozs7O3NCQVVuQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUJBQzdDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7O3NCQWVuQyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7aUJBQzdDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7aUJBS3hDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7O2lCQU14QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7Ozs7Ozs7OztpQkFZeEMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOzs7c0JBR25DLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7Ozs7Ozs7O2lCQVM3QyxVQUFVLENBQUMsT0FBTyxDQUFDLFNBQVMsRUFBRSxTQUFTLENBQUM7Ozs7aUJBSXhDLFVBQVUsQ0FBQyxPQUFPLENBQUMsU0FBUyxFQUFFLFNBQVMsQ0FBQzs7OztpQkFJeEMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDOztLQUVwRDtTQUNGLENBQUM7UUFFSyxNQUFNO1lBQ1gsTUFBTSxFQUFFLE9BQU8sRUFBRSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUM7WUFFbkMsT0FBTyxJQUFJLENBQUE7Ozs7VUFJTCxJQUFJLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxDQUFDO1VBQzlCLElBQUksQ0FBQyxzQkFBc0IsRUFBRTs7S0FFbEMsQ0FBQztRQUNKLENBQUM7UUFFTyxnQkFBZ0IsQ0FBQyxPQUE4QztZQUNyRSxNQUFNLEtBQUssR0FBaUI7Z0JBQzFCO29CQUNFLEVBQUUsRUFBRSxPQUFPO29CQUNYLEtBQUssRUFBRSxvQkFBb0I7b0JBQzNCLEtBQUssRUFBRSxPQUFPLENBQUMsS0FBSztvQkFDcEIsSUFBSSxFQUFFLFFBQVE7b0JBQ2QsSUFBSSxFQUFFLGNBQWM7b0JBQ3BCLEtBQUssRUFBRSxTQUFTO2lCQUNqQjtnQkFDRDtvQkFDRSxFQUFFLEVBQUUsT0FBTztvQkFDWCxLQUFLLEVBQUUsT0FBTztvQkFDZCxLQUFLLEVBQUUsT0FBTyxDQUFDLEtBQUs7b0JBQ3BCLElBQUksRUFBRSxRQUFRO29CQUNkLElBQUksRUFBRSxPQUFPO29CQUNiLEtBQUssRUFBRSxTQUFTO2lCQUNqQjtnQkFDRDtvQkFDRSxFQUFFLEVBQUUsVUFBVTtvQkFDZCxLQUFLLEVBQUUsZUFBZTtvQkFDdEIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxRQUFRO29CQUN2QixJQUFJLEVBQUUsUUFBUTtvQkFDZCxJQUFJLEVBQUUsT0FBTztvQkFDYixLQUFLLEVBQUUsU0FBUztpQkFDakI7Z0JBQ0Q7b0JBQ0UsRUFBRSxFQUFFLFVBQVU7b0JBQ2QsS0FBSyxFQUFFLGtCQUFrQjtvQkFDekIsS0FBSyxFQUFFLE9BQU8sQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDLE9BQU87b0JBQ3ZDLElBQUksRUFBRSxRQUFRO29CQUNkLElBQUksRUFBRSxxQkFBcUI7b0JBQzNCLEtBQUssRUFBRSxTQUFTO2lCQUNqQjthQUNGLENBQUM7WUFFRixPQUFPLElBQUksQ0FBQTs7aUJBRUUsS0FBSzt3QkFDRSxHQUFHO3VCQUNKO2dCQUNiO29CQUNFLElBQUksRUFBRSxTQUFTO29CQUNmLFFBQVEsRUFBRSxjQUFjO29CQUN4QixNQUFNLEVBQUUsS0FBSyxJQUFJLEVBQUU7d0JBQ2pCLE1BQU0sUUFBUSxDQUFDLG9CQUFvQixDQUFDLGNBQWMsQ0FDaEQsUUFBUSxDQUFDLDhCQUE4QixFQUN2QyxJQUFJLENBQ0wsQ0FBQztvQkFDSixDQUFDO2lCQUNGO2FBQ0Y7O0tBRUosQ0FBQztRQUNKLENBQUM7UUFFTyxzQkFBc0I7WUFDNUIsT0FBTyxJQUFJLENBQUE7O2dCQUVDLElBQUksQ0FBQyxTQUFTLENBQUMsWUFBWTsyQkFDaEIsQ0FBQyxJQUEwQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO2dCQUNsRSxNQUFNLEVBQUUsSUFBSSxDQUFDLE1BQU07Z0JBQ25CLE1BQU0sRUFBRSxJQUFJLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQztnQkFDOUMsTUFBTSxFQUFFLElBQUksQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDO2dCQUMzQyxNQUFNLEVBQUUsSUFBSSxDQUFDLGlCQUFpQixDQUFDLElBQUksQ0FBQyxNQUFNLENBQUM7Z0JBQzNDLE9BQU8sRUFBRSxJQUFJLENBQUMsWUFBWSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUM7Z0JBQzNDLEtBQUssRUFBRSxJQUFJLENBQUMsV0FBVztvQkFDckIsQ0FBQyxDQUFDLElBQUksQ0FBQSxrQ0FBa0MsSUFBSSxDQUFDLFdBQVcsQ0FBQyxRQUFRLG9CQUFvQixJQUFJLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxXQUFXLENBQUMsVUFBVSxDQUFDLFNBQVM7b0JBQy9JLENBQUMsQ0FBQyxJQUFJLENBQUMsS0FBSzt3QkFDVixDQUFDLENBQUMsSUFBSSxDQUFBLGtDQUFrQyxJQUFJLENBQUMsS0FBSyxLQUFLLElBQUksQ0FBQyxLQUFLLFNBQVM7d0JBQzFFLENBQUMsQ0FBQyxFQUFFO2FBQ1QsQ0FBQzt1QkFDYTtnQkFDYjtvQkFDRSxJQUFJLEVBQUUsYUFBYTtvQkFDbkIsUUFBUSxFQUFFLGNBQWM7b0JBQ3hCLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQztvQkFDZixVQUFVLEVBQUUsS0FBSyxFQUFFLFVBQTBELEVBQUUsRUFBRTt3QkFDL0UsTUFBTSxJQUFJLEdBQUcsVUFBVSxDQUFDLElBQUksQ0FBQzt3QkFDN0IsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQzs0QkFDekIsTUFBTSxFQUFFLFNBQVMsRUFBRSxHQUFHLE1BQU0sTUFBTSxDQUFDLDZCQUE2QixDQUFDLENBQUM7NEJBQ2xFLFNBQVMsQ0FBQyxJQUFJLENBQUM7Z0NBQ2IsT0FBTyxFQUFFLDBEQUEwRDtnQ0FDbkUsSUFBSSxFQUFFLFNBQVM7Z0NBQ2YsUUFBUSxFQUFFLElBQUk7NkJBQ2YsQ0FBQyxDQUFDOzRCQUNILE9BQU87d0JBQ1QsQ0FBQzt3QkFDRCxNQUFNLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxjQUFjLENBQ2hELFFBQVEsQ0FBQyw0QkFBNEIsRUFDckMsSUFBSSxDQUFDLE1BQU0sQ0FDWixDQUFDO3dCQUNGLE1BQU0sRUFBRSxTQUFTLEVBQUUsR0FBRyxNQUFNLE1BQU0sQ0FBQyw2QkFBNkIsQ0FBQyxDQUFDO3dCQUNsRSxTQUFTLENBQUMsSUFBSSxDQUFDOzRCQUNiLE9BQU8sRUFBRSxnQ0FBZ0MsSUFBSSxDQUFDLE1BQU0sRUFBRTs0QkFDdEQsSUFBSSxFQUFFLFNBQVM7NEJBQ2YsUUFBUSxFQUFFLElBQUk7eUJBQ2YsQ0FBQyxDQUFDO29CQUNMLENBQUM7aUJBQ0Y7Z0JBQ0Q7b0JBQ0UsSUFBSSxFQUFFLGNBQWM7b0JBQ3BCLFFBQVEsRUFBRSxpQkFBaUI7b0JBQzNCLElBQUksRUFBRSxDQUFDLGFBQWEsRUFBRSxhQUFhLENBQUM7b0JBQ3BDLFVBQVUsRUFBRSxLQUFLLEVBQUUsVUFBMEQsRUFBRSxFQUFFO3dCQUMvRSxNQUFNLElBQUksR0FBRyxVQUFVLENBQUMsSUFBSSxDQUFDO3dCQUM3QixNQUFNLEVBQUUsU0FBUyxFQUFFLEdBQUcsTUFBTSxNQUFNLENBQUMsNkJBQTZCLENBQUMsQ0FBQzt3QkFDbEUsTUFBTSxTQUFTLENBQUMsYUFBYSxDQUFDOzRCQUM1QixPQUFPLEVBQUUsZ0JBQWdCLElBQUksQ0FBQyxNQUFNLEVBQUU7NEJBQ3RDLE9BQU8sRUFBRSxJQUFJLENBQUE7OztpQ0FHSSxxQkFBcUI7O3VDQUVmLElBQUksQ0FBQyxTQUFTLENBQUMsSUFBSSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7OztpQkFHbkQ7NEJBQ0QsV0FBVyxFQUFFO2dDQUNYO29DQUNFLElBQUksRUFBRSxhQUFhO29DQUNuQixRQUFRLEVBQUUsTUFBTTtvQ0FDaEIsTUFBTSxFQUFFLEtBQUssSUFBSSxFQUFFO3dDQUNqQixNQUFNLFNBQVMsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztvQ0FDbkQsQ0FBQztpQ0FDRjs2QkFDRjt5QkFDRixDQUFDLENBQUM7b0JBQ0wsQ0FBQztpQkFDRjthQUNGOzs7O3NCQUlhLElBQUk7MEJBQ0EsRUFBRTs7O0tBR3ZCLENBQUM7UUFDSixDQUFDO1FBRU8sZ0JBQWdCLENBQUMsVUFBb0I7WUFDM0MsTUFBTSxPQUFPLEdBQUcsQ0FBQyxDQUFDO1lBQ2xCLE1BQU0sT0FBTyxHQUFHLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxFQUFFLE9BQU8sQ0FBQyxDQUFDO1lBQzdDLE1BQU0sU0FBUyxHQUFHLFVBQVUsQ0FBQyxNQUFNLEdBQUcsT0FBTyxDQUFDO1lBRTlDLE9BQU8sSUFBSSxDQUFBOztVQUVMLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQSwyQkFBMkIsQ0FBQyxTQUFTLENBQUM7VUFDN0QsU0FBUyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFBLDRCQUE0QixTQUFTLGNBQWMsQ0FBQyxDQUFDLENBQUMsRUFBRTs7S0FFakYsQ0FBQztRQUNKLENBQUM7UUFFTyxpQkFBaUIsQ0FBQyxNQUE4QztZQUN0RSxPQUFPLElBQUksQ0FBQSw0QkFBNEIsTUFBTSxLQUFLLE1BQU0sU0FBUyxDQUFDO1FBQ3BFLENBQUM7UUFFTyxpQkFBaUIsQ0FBQyxNQUE4QztZQUN0RSxNQUFNLE1BQU0sR0FBMkI7Z0JBQ3JDLElBQUksRUFBRSxNQUFNO2dCQUNaLG9CQUFvQixFQUFFLFFBQVE7Z0JBQzlCLE1BQU0sRUFBRSxRQUFRO2dCQUNoQixJQUFJLEVBQUUsTUFBTTthQUNiLENBQUM7WUFDRixPQUFPLElBQUksQ0FBQSw2QkFBNkIsTUFBTSxDQUFDLE1BQU0sQ0FBQyxJQUFJLE1BQU0sU0FBUyxDQUFDO1FBQzVFLENBQUM7UUFFTyxZQUFZLENBQUMsVUFBbUI7WUFDdEMsSUFBSSxDQUFDLFVBQVUsRUFBRSxDQUFDO2dCQUNoQixPQUFPLElBQUksQ0FBQSx1QkFBdUIsVUFBVSxDQUFDLE9BQU8sQ0FBQyxTQUFTLEVBQUUsU0FBUyxDQUFDLGFBQWEsQ0FBQztZQUMxRixDQUFDO1lBRUQsTUFBTSxNQUFNLEdBQUcsSUFBSSxJQUFJLENBQUMsVUFBVSxDQUFDLENBQUM7WUFDcEMsTUFBTSxHQUFHLEdBQUcsSUFBSSxJQUFJLEVBQUUsQ0FBQztZQUN2QixNQUFNLFFBQVEsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxHQUFHLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQyxHQUFHLENBQUMsSUFBSSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQztZQUV2RixNQUFNLE9BQU8sR0FBRyxNQUFNLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztZQUM1QyxJQUFJLFNBQVMsR0FBRyxFQUFFLENBQUM7WUFDbkIsSUFBSSxRQUFRLEdBQUcsRUFBRSxDQUFDO1lBRWxCLElBQUksUUFBUSxHQUFHLENBQUMsRUFBRSxDQUFDO2dCQUNqQixTQUFTLEdBQUcsUUFBUSxDQUFDO2dCQUNyQixRQUFRLEdBQUcsV0FBVyxDQUFDO1lBQ3pCLENBQUM7aUJBQU0sSUFBSSxRQUFRLEdBQUcsRUFBRSxFQUFFLENBQUM7Z0JBQ3pCLFNBQVMsR0FBRyxNQUFNLENBQUM7Z0JBQ25CLFFBQVEsR0FBRyxJQUFJLFFBQVEsU0FBUyxDQUFDO1lBQ25DLENBQUM7aUJBQU0sQ0FBQztnQkFDTixRQUFRLEdBQUcsSUFBSSxRQUFRLFNBQVMsQ0FBQztZQUNuQyxDQUFDO1lBRUQsT0FBTyxJQUFJLENBQUE7O1VBRUwsT0FBTywwQkFBMEIsU0FBUyxLQUFLLFFBQVE7O0tBRTVELENBQUM7UUFDSixDQUFDO1FBRU8sZUFBZSxDQUFDLFVBQW1CO1lBQ3pDLElBQUksQ0FBQyxVQUFVO2dCQUFFLE9BQU8sTUFBTSxDQUFDO1lBQy9CLE1BQU0sU0FBUyxHQUFHLElBQUksSUFBSSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1lBQ3ZDLE1BQU0sR0FBRyxHQUFHLElBQUksSUFBSSxFQUFFLENBQUM7WUFDdkIsTUFBTSxNQUFNLEdBQUcsU0FBUyxDQUFDLE9BQU8sRUFBRSxHQUFHLEdBQUcsQ0FBQyxPQUFPLEVBQUUsQ0FBQztZQUNuRCxJQUFJLE1BQU0sSUFBSSxDQUFDO2dCQUFFLE9BQU8sS0FBSyxDQUFDO1lBQzlCLE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsTUFBTSxHQUFHLEtBQUssQ0FBQyxDQUFDO1lBQzFDLElBQUksT0FBTyxHQUFHLEVBQUU7Z0JBQUUsT0FBTyxNQUFNLE9BQU8sR0FBRyxDQUFDO1lBQzFDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQyxDQUFDO1lBQzFDLE9BQU8sTUFBTSxTQUFTLEdBQUcsQ0FBQztRQUM1QixDQUFDOztZQXJXVSx1REFBbUI7Ozs7O1NBQW5CLG1CQUFtQiJ9
|
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.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": "^
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
package/ts/00_commitinfo_data.ts
CHANGED
|
@@ -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
|
+
}
|