@serve.zone/dcrouter 11.23.5 → 12.1.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 +1052 -939
- package/dist_ts/00_commitinfo_data.js +2 -2
- package/dist_ts/classes.cert-provision-scheduler.d.ts +6 -8
- package/dist_ts/classes.cert-provision-scheduler.js +37 -17
- package/dist_ts/classes.dcrouter.d.ts +26 -29
- package/dist_ts/classes.dcrouter.js +101 -91
- package/dist_ts/classes.storage-cert-manager.d.ts +3 -6
- package/dist_ts/classes.storage-cert-manager.js +35 -25
- package/dist_ts/config/classes.api-token-manager.d.ts +1 -3
- package/dist_ts/config/classes.api-token-manager.js +45 -15
- package/dist_ts/config/classes.route-config-manager.d.ts +1 -3
- package/dist_ts/config/classes.route-config-manager.js +62 -24
- package/dist_ts/{cache → db}/classes.cache.cleaner.d.ts +3 -3
- package/dist_ts/db/classes.cache.cleaner.js +130 -0
- package/dist_ts/{cache → db}/classes.cached.document.js +1 -1
- package/dist_ts/db/classes.dcrouter-db.d.ts +70 -0
- package/dist_ts/db/classes.dcrouter-db.js +146 -0
- package/dist_ts/db/documents/classes.accounting-session.doc.d.ts +32 -0
- package/dist_ts/db/documents/classes.accounting-session.doc.js +214 -0
- package/dist_ts/db/documents/classes.acme-cert.doc.d.ts +13 -0
- package/dist_ts/db/documents/classes.acme-cert.doc.js +109 -0
- package/dist_ts/db/documents/classes.api-token.doc.d.ts +18 -0
- package/dist_ts/db/documents/classes.api-token.doc.js +127 -0
- package/dist_ts/{cache → db}/documents/classes.cached.email.js +3 -3
- package/dist_ts/{cache → db}/documents/classes.cached.ip.reputation.js +3 -3
- package/dist_ts/db/documents/classes.cert-backoff.doc.d.ts +11 -0
- package/dist_ts/db/documents/classes.cert-backoff.doc.js +97 -0
- package/dist_ts/db/documents/classes.proxy-cert.doc.d.ts +12 -0
- package/dist_ts/db/documents/classes.proxy-cert.doc.js +103 -0
- package/dist_ts/db/documents/classes.remote-ingress-edge.doc.d.ts +17 -0
- package/dist_ts/db/documents/classes.remote-ingress-edge.doc.js +130 -0
- package/dist_ts/db/documents/classes.route-override.doc.d.ts +10 -0
- package/dist_ts/db/documents/classes.route-override.doc.js +91 -0
- package/dist_ts/db/documents/classes.stored-route.doc.d.ts +12 -0
- package/dist_ts/db/documents/classes.stored-route.doc.js +103 -0
- package/dist_ts/db/documents/classes.vlan-mappings.doc.d.ts +15 -0
- package/dist_ts/db/documents/classes.vlan-mappings.doc.js +77 -0
- package/dist_ts/db/documents/classes.vpn-client.doc.d.ts +26 -0
- package/dist_ts/db/documents/classes.vpn-client.doc.js +184 -0
- package/dist_ts/db/documents/classes.vpn-server-keys.doc.d.ts +10 -0
- package/dist_ts/db/documents/classes.vpn-server-keys.doc.js +94 -0
- package/dist_ts/db/documents/index.d.ts +13 -0
- package/dist_ts/db/documents/index.js +20 -0
- package/dist_ts/{cache → db}/index.d.ts +1 -1
- package/dist_ts/db/index.js +9 -0
- package/dist_ts/opsserver/handlers/certificate.handler.js +66 -66
- package/dist_ts/opsserver/handlers/config.handler.js +14 -15
- package/dist_ts/opsserver/handlers/vpn.handler.js +35 -1
- package/dist_ts/paths.d.ts +0 -1
- package/dist_ts/paths.js +1 -2
- package/dist_ts/radius/classes.accounting.manager.d.ts +4 -12
- package/dist_ts/radius/classes.accounting.manager.js +80 -93
- package/dist_ts/radius/classes.radius.server.d.ts +1 -3
- package/dist_ts/radius/classes.radius.server.js +4 -6
- package/dist_ts/radius/classes.vlan.manager.d.ts +3 -7
- package/dist_ts/radius/classes.vlan.manager.js +21 -28
- package/dist_ts/radius/index.d.ts +1 -1
- package/dist_ts/radius/index.js +1 -1
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +3 -5
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +41 -21
- package/dist_ts/security/classes.ipreputationchecker.d.ts +6 -21
- package/dist_ts/security/classes.ipreputationchecker.js +59 -138
- package/dist_ts/vpn/classes.vpn-manager.d.ts +37 -22
- package/dist_ts/vpn/classes.vpn-manager.js +161 -51
- package/dist_ts_interfaces/data/vpn.d.ts +8 -0
- package/dist_ts_interfaces/requests/vpn.d.ts +16 -0
- package/dist_ts_oci_container/index.js +4 -4
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/dist_ts_web/appstate.d.ts +16 -0
- package/dist_ts_web/appstate.js +17 -1
- package/dist_ts_web/elements/ops-view-vpn.js +155 -3
- package/package.json +3 -3
- package/readme.storage.md +55 -91
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.cert-provision-scheduler.ts +35 -17
- package/ts/classes.dcrouter.ts +129 -125
- package/ts/classes.storage-cert-manager.ts +34 -22
- package/ts/config/classes.api-token-manager.ts +42 -11
- package/ts/config/classes.route-config-manager.ts +56 -21
- package/ts/{cache → db}/classes.cache.cleaner.ts +6 -6
- package/ts/db/classes.dcrouter-db.ts +179 -0
- package/ts/db/documents/classes.accounting-session.doc.ts +106 -0
- package/ts/db/documents/classes.acme-cert.doc.ts +41 -0
- package/ts/db/documents/classes.api-token.doc.ts +56 -0
- package/ts/{cache → db}/documents/classes.cached.email.ts +2 -2
- package/ts/{cache → db}/documents/classes.cached.ip.reputation.ts +2 -2
- package/ts/db/documents/classes.cert-backoff.doc.ts +35 -0
- package/ts/db/documents/classes.proxy-cert.doc.ts +38 -0
- package/ts/db/documents/classes.remote-ingress-edge.doc.ts +54 -0
- package/ts/db/documents/classes.route-override.doc.ts +32 -0
- package/ts/db/documents/classes.stored-route.doc.ts +38 -0
- package/ts/db/documents/classes.vlan-mappings.doc.ts +32 -0
- package/ts/db/documents/classes.vpn-client.doc.ts +81 -0
- package/ts/db/documents/classes.vpn-server-keys.doc.ts +31 -0
- package/ts/db/documents/index.ts +24 -0
- package/ts/{cache → db}/index.ts +6 -2
- package/ts/opsserver/handlers/certificate.handler.ts +67 -65
- package/ts/opsserver/handlers/config.handler.ts +13 -14
- package/ts/opsserver/handlers/vpn.handler.ts +37 -0
- package/ts/paths.ts +0 -1
- package/ts/radius/classes.accounting.manager.ts +81 -103
- package/ts/radius/classes.radius.server.ts +3 -6
- package/ts/radius/classes.vlan.manager.ts +20 -32
- package/ts/radius/index.ts +1 -1
- package/ts/remoteingress/classes.remoteingress-manager.ts +40 -22
- package/ts/security/classes.ipreputationchecker.ts +103 -196
- package/ts/vpn/classes.vpn-manager.ts +187 -81
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +32 -0
- package/ts_web/elements/ops-view-vpn.ts +153 -2
- package/dist_ts/cache/classes.cache.cleaner.js +0 -130
- package/dist_ts/cache/classes.cachedb.d.ts +0 -60
- package/dist_ts/cache/classes.cachedb.js +0 -126
- package/dist_ts/cache/documents/index.d.ts +0 -2
- package/dist_ts/cache/documents/index.js +0 -3
- package/dist_ts/cache/index.js +0 -7
- package/dist_ts/storage/classes.storagemanager.d.ts +0 -83
- package/dist_ts/storage/classes.storagemanager.js +0 -348
- package/dist_ts/storage/index.d.ts +0 -1
- package/dist_ts/storage/index.js +0 -3
- package/ts/cache/classes.cachedb.ts +0 -155
- package/ts/cache/documents/index.ts +0 -2
- package/ts/storage/classes.storagemanager.ts +0 -404
- package/ts/storage/index.ts +0 -2
- /package/dist_ts/{cache → db}/classes.cached.document.d.ts +0 -0
- /package/dist_ts/{cache → db}/documents/classes.cached.email.d.ts +0 -0
- /package/dist_ts/{cache → db}/documents/classes.cached.ip.reputation.d.ts +0 -0
- /package/ts/{cache → db}/classes.cached.document.ts +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logger } from './logger.js';
|
|
2
|
-
import
|
|
2
|
+
import { CertBackoffDoc } from './db/index.js';
|
|
3
3
|
|
|
4
4
|
interface IBackoffEntry {
|
|
5
5
|
failures: number;
|
|
@@ -10,54 +10,68 @@ interface IBackoffEntry {
|
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Manages certificate provisioning scheduling with:
|
|
13
|
-
* - Per-domain exponential backoff persisted
|
|
13
|
+
* - Per-domain exponential backoff persisted via CertBackoffDoc
|
|
14
14
|
*
|
|
15
15
|
* Note: Serial stagger queue was removed — smartacme v9 handles
|
|
16
16
|
* concurrency, per-domain dedup, and rate limiting internally.
|
|
17
17
|
*/
|
|
18
18
|
export class CertProvisionScheduler {
|
|
19
|
-
private storageManager: StorageManager;
|
|
20
19
|
private maxBackoffHours: number;
|
|
21
20
|
|
|
22
21
|
// In-memory backoff cache (mirrors storage for fast lookups)
|
|
23
22
|
private backoffCache = new Map<string, IBackoffEntry>();
|
|
24
23
|
|
|
25
24
|
constructor(
|
|
26
|
-
storageManager: StorageManager,
|
|
27
25
|
options?: { maxBackoffHours?: number }
|
|
28
26
|
) {
|
|
29
|
-
this.storageManager = storageManager;
|
|
30
27
|
this.maxBackoffHours = options?.maxBackoffHours ?? 24;
|
|
31
28
|
}
|
|
32
29
|
|
|
33
30
|
/**
|
|
34
|
-
*
|
|
31
|
+
* Sanitized domain key for storage lookups
|
|
35
32
|
*/
|
|
36
|
-
private
|
|
37
|
-
|
|
38
|
-
return `/cert-backoff/${clean}`;
|
|
33
|
+
private sanitizeDomain(domain: string): string {
|
|
34
|
+
return domain.replace(/\*/g, '_wildcard_').replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
/**
|
|
42
|
-
* Load backoff entry from
|
|
38
|
+
* Load backoff entry from database (with in-memory cache)
|
|
43
39
|
*/
|
|
44
40
|
private async loadBackoff(domain: string): Promise<IBackoffEntry | null> {
|
|
45
41
|
const cached = this.backoffCache.get(domain);
|
|
46
42
|
if (cached) return cached;
|
|
47
43
|
|
|
48
|
-
const
|
|
49
|
-
|
|
44
|
+
const sanitized = this.sanitizeDomain(domain);
|
|
45
|
+
const doc = await CertBackoffDoc.findByDomain(sanitized);
|
|
46
|
+
if (doc) {
|
|
47
|
+
const entry: IBackoffEntry = {
|
|
48
|
+
failures: doc.failures,
|
|
49
|
+
lastFailure: doc.lastFailure,
|
|
50
|
+
retryAfter: doc.retryAfter,
|
|
51
|
+
lastError: doc.lastError,
|
|
52
|
+
};
|
|
50
53
|
this.backoffCache.set(domain, entry);
|
|
54
|
+
return entry;
|
|
51
55
|
}
|
|
52
|
-
return
|
|
56
|
+
return null;
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
/**
|
|
56
|
-
* Save backoff entry to both cache and
|
|
60
|
+
* Save backoff entry to both cache and database
|
|
57
61
|
*/
|
|
58
62
|
private async saveBackoff(domain: string, entry: IBackoffEntry): Promise<void> {
|
|
59
63
|
this.backoffCache.set(domain, entry);
|
|
60
|
-
|
|
64
|
+
const sanitized = this.sanitizeDomain(domain);
|
|
65
|
+
let doc = await CertBackoffDoc.findByDomain(sanitized);
|
|
66
|
+
if (!doc) {
|
|
67
|
+
doc = new CertBackoffDoc();
|
|
68
|
+
doc.domain = sanitized;
|
|
69
|
+
}
|
|
70
|
+
doc.failures = entry.failures;
|
|
71
|
+
doc.lastFailure = entry.lastFailure;
|
|
72
|
+
doc.retryAfter = entry.retryAfter;
|
|
73
|
+
doc.lastError = entry.lastError || '';
|
|
74
|
+
await doc.save();
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
/**
|
|
@@ -107,9 +121,13 @@ export class CertProvisionScheduler {
|
|
|
107
121
|
async clearBackoff(domain: string): Promise<void> {
|
|
108
122
|
this.backoffCache.delete(domain);
|
|
109
123
|
try {
|
|
110
|
-
|
|
124
|
+
const sanitized = this.sanitizeDomain(domain);
|
|
125
|
+
const doc = await CertBackoffDoc.findByDomain(sanitized);
|
|
126
|
+
if (doc) {
|
|
127
|
+
await doc.delete();
|
|
128
|
+
}
|
|
111
129
|
} catch {
|
|
112
|
-
// Ignore delete errors (
|
|
130
|
+
// Ignore delete errors (doc may not exist)
|
|
113
131
|
}
|
|
114
132
|
}
|
|
115
133
|
|
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -11,12 +11,10 @@ import {
|
|
|
11
11
|
type IEmailDomainConfig,
|
|
12
12
|
} from '@push.rocks/smartmta';
|
|
13
13
|
import { logger } from './logger.js';
|
|
14
|
-
// Import storage manager
|
|
15
|
-
import { StorageManager, type IStorageConfig } from './storage/index.js';
|
|
16
14
|
import { StorageBackedCertManager } from './classes.storage-cert-manager.js';
|
|
17
15
|
import { CertProvisionScheduler } from './classes.cert-provision-scheduler.js';
|
|
18
|
-
// Import
|
|
19
|
-
import {
|
|
16
|
+
// Import unified database
|
|
17
|
+
import { DcRouterDb, type IDcRouterDbConfig, CacheCleaner, ProxyCertDoc, AcmeCertDoc } from './db/index.js';
|
|
20
18
|
|
|
21
19
|
import { OpsServer } from './opsserver/index.js';
|
|
22
20
|
import { MetricsManager } from './monitoring/index.js';
|
|
@@ -122,37 +120,23 @@ export interface IDcRouterOptions {
|
|
|
122
120
|
/** Other DNS providers can be added here */
|
|
123
121
|
};
|
|
124
122
|
|
|
125
|
-
/** Storage configuration */
|
|
126
|
-
storage?: IStorageConfig;
|
|
127
|
-
|
|
128
123
|
/**
|
|
129
|
-
*
|
|
130
|
-
*
|
|
124
|
+
* Unified database configuration.
|
|
125
|
+
* All persistent data (config, certs, VPN, cache, etc.) is stored via smartdata.
|
|
126
|
+
* If mongoDbUrl is provided, connects to external MongoDB.
|
|
127
|
+
* Otherwise, starts an embedded LocalSmartDb automatically.
|
|
131
128
|
*/
|
|
132
|
-
|
|
133
|
-
/** Enable
|
|
129
|
+
dbConfig?: {
|
|
130
|
+
/** Enable database (default: true). Set to false in tests to skip DB startup. */
|
|
134
131
|
enabled?: boolean;
|
|
135
|
-
/**
|
|
132
|
+
/** External MongoDB connection URL. If absent, uses embedded LocalSmartDb. */
|
|
133
|
+
mongoDbUrl?: string;
|
|
134
|
+
/** Storage path for embedded database data (default: ~/.serve.zone/dcrouter/tsmdb) */
|
|
136
135
|
storagePath?: string;
|
|
137
136
|
/** Database name (default: dcrouter) */
|
|
138
137
|
dbName?: string;
|
|
139
|
-
/**
|
|
140
|
-
defaultTTLDays?: number;
|
|
141
|
-
/** Cleanup interval in hours (default: 1) */
|
|
138
|
+
/** Cache cleanup interval in hours (default: 1) */
|
|
142
139
|
cleanupIntervalHours?: number;
|
|
143
|
-
/** TTL configuration per data type (in days) */
|
|
144
|
-
ttlConfig?: {
|
|
145
|
-
/** Email cache TTL (default: 30 days) */
|
|
146
|
-
emails?: number;
|
|
147
|
-
/** IP reputation cache TTL (default: 1 day) */
|
|
148
|
-
ipReputation?: number;
|
|
149
|
-
/** Bounce records TTL (default: 30 days) */
|
|
150
|
-
bounces?: number;
|
|
151
|
-
/** DKIM keys TTL (default: 90 days) */
|
|
152
|
-
dkimKeys?: number;
|
|
153
|
-
/** Suppression list TTL (default: 30 days, can be permanent) */
|
|
154
|
-
suppression?: number;
|
|
155
|
-
};
|
|
156
140
|
};
|
|
157
141
|
|
|
158
142
|
/**
|
|
@@ -221,6 +205,17 @@ export interface IDcRouterOptions {
|
|
|
221
205
|
allowList?: string[];
|
|
222
206
|
blockList?: string[];
|
|
223
207
|
};
|
|
208
|
+
/** Forwarding mode: 'socket' (default, userspace NAT), 'bridge' (L2 bridge to host LAN),
|
|
209
|
+
* or 'hybrid' (socket default, bridge for clients with useHostIp=true) */
|
|
210
|
+
forwardingMode?: 'socket' | 'bridge' | 'hybrid';
|
|
211
|
+
/** LAN subnet CIDR for bridge mode (e.g., '192.168.1.0/24') */
|
|
212
|
+
bridgeLanSubnet?: string;
|
|
213
|
+
/** Physical network interface for bridge mode (auto-detected if omitted) */
|
|
214
|
+
bridgePhysicalInterface?: string;
|
|
215
|
+
/** Start of VPN client IP range in LAN subnet (host offset, default: 200) */
|
|
216
|
+
bridgeIpRangeStart?: number;
|
|
217
|
+
/** End of VPN client IP range in LAN subnet (host offset, default: 250) */
|
|
218
|
+
bridgeIpRangeEnd?: number;
|
|
224
219
|
};
|
|
225
220
|
}
|
|
226
221
|
|
|
@@ -248,12 +243,20 @@ export class DcRouter {
|
|
|
248
243
|
public dnsServer?: plugins.smartdns.dnsServerMod.DnsServer;
|
|
249
244
|
public emailServer?: UnifiedEmailServer;
|
|
250
245
|
public radiusServer?: RadiusServer;
|
|
251
|
-
public storageManager: StorageManager;
|
|
252
246
|
public opsServer!: OpsServer;
|
|
253
247
|
public metricsManager?: MetricsManager;
|
|
254
248
|
|
|
255
|
-
//
|
|
256
|
-
public
|
|
249
|
+
// Compatibility shim for smartmta's DkimManager which calls dcRouter.storageManager.set()
|
|
250
|
+
public storageManager: any = {
|
|
251
|
+
get: async (_key: string) => null,
|
|
252
|
+
set: async (_key: string, _value: string) => {
|
|
253
|
+
// DKIM keys from smartmta — logged but not yet migrated to smartdata
|
|
254
|
+
logger.log('debug', `storageManager.set() called (compat shim) for key: ${_key}`);
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Unified database (smartdata + LocalSmartDb or external MongoDB)
|
|
259
|
+
public dcRouterDb?: DcRouterDb;
|
|
257
260
|
public cacheCleaner?: CacheCleaner;
|
|
258
261
|
|
|
259
262
|
// Remote Ingress
|
|
@@ -312,16 +315,6 @@ export class DcRouter {
|
|
|
312
315
|
// Resolve all data paths from baseDir
|
|
313
316
|
this.resolvedPaths = paths.resolvePaths(this.options.baseDir);
|
|
314
317
|
|
|
315
|
-
// Default storage to filesystem if not configured
|
|
316
|
-
if (!this.options.storage) {
|
|
317
|
-
this.options.storage = {
|
|
318
|
-
fsPath: this.resolvedPaths.defaultStoragePath,
|
|
319
|
-
};
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Initialize storage manager
|
|
323
|
-
this.storageManager = new StorageManager(this.options.storage);
|
|
324
|
-
|
|
325
318
|
// Initialize service manager and register all services
|
|
326
319
|
this.serviceManager = new plugins.taskbuffer.ServiceManager({
|
|
327
320
|
name: 'dcrouter',
|
|
@@ -350,23 +343,23 @@ export class DcRouter {
|
|
|
350
343
|
.withRetry({ maxRetries: 0 }),
|
|
351
344
|
);
|
|
352
345
|
|
|
353
|
-
//
|
|
354
|
-
if (this.options.
|
|
346
|
+
// DcRouterDb: optional, no dependencies — unified database for all persistence
|
|
347
|
+
if (this.options.dbConfig?.enabled !== false) {
|
|
355
348
|
this.serviceManager.addService(
|
|
356
|
-
new plugins.taskbuffer.Service('
|
|
349
|
+
new plugins.taskbuffer.Service('DcRouterDb')
|
|
357
350
|
.optional()
|
|
358
351
|
.withStart(async () => {
|
|
359
|
-
await this.
|
|
352
|
+
await this.setupDcRouterDb();
|
|
360
353
|
})
|
|
361
354
|
.withStop(async () => {
|
|
362
355
|
if (this.cacheCleaner) {
|
|
363
356
|
this.cacheCleaner.stop();
|
|
364
357
|
this.cacheCleaner = undefined;
|
|
365
358
|
}
|
|
366
|
-
if (this.
|
|
367
|
-
await this.
|
|
368
|
-
|
|
369
|
-
this.
|
|
359
|
+
if (this.dcRouterDb) {
|
|
360
|
+
await this.dcRouterDb.stop();
|
|
361
|
+
DcRouterDb.resetInstance();
|
|
362
|
+
this.dcRouterDb = undefined;
|
|
370
363
|
}
|
|
371
364
|
})
|
|
372
365
|
.withRetry({ maxRetries: 2, baseDelayMs: 1000, maxDelayMs: 5000 }),
|
|
@@ -391,10 +384,10 @@ export class DcRouter {
|
|
|
391
384
|
.withRetry({ maxRetries: 1, baseDelayMs: 1000 }),
|
|
392
385
|
);
|
|
393
386
|
|
|
394
|
-
// SmartProxy: critical, depends on
|
|
387
|
+
// SmartProxy: critical, depends on DcRouterDb (if enabled)
|
|
395
388
|
const smartProxyDeps: string[] = [];
|
|
396
|
-
if (this.options.
|
|
397
|
-
smartProxyDeps.push('
|
|
389
|
+
if (this.options.dbConfig?.enabled !== false) {
|
|
390
|
+
smartProxyDeps.push('DcRouterDb');
|
|
398
391
|
}
|
|
399
392
|
this.serviceManager.addService(
|
|
400
393
|
new plugins.taskbuffer.Service('SmartProxy')
|
|
@@ -455,36 +448,38 @@ export class DcRouter {
|
|
|
455
448
|
);
|
|
456
449
|
}
|
|
457
450
|
|
|
458
|
-
// ConfigManagers: optional, depends on SmartProxy
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
451
|
+
// ConfigManagers: optional, depends on SmartProxy + DcRouterDb
|
|
452
|
+
// Requires DcRouterDb to be enabled (document classes need the database)
|
|
453
|
+
if (this.options.dbConfig?.enabled !== false) {
|
|
454
|
+
this.serviceManager.addService(
|
|
455
|
+
new plugins.taskbuffer.Service('ConfigManagers')
|
|
456
|
+
.optional()
|
|
457
|
+
.dependsOn('SmartProxy', 'DcRouterDb')
|
|
458
|
+
.withStart(async () => {
|
|
459
|
+
this.routeConfigManager = new RouteConfigManager(
|
|
460
|
+
() => this.getConstructorRoutes(),
|
|
461
|
+
() => this.smartProxy,
|
|
462
|
+
() => this.options.http3,
|
|
463
|
+
this.options.vpnConfig?.enabled
|
|
464
|
+
? (tags?: string[]) => {
|
|
465
|
+
if (tags?.length && this.vpnManager) {
|
|
466
|
+
return this.vpnManager.getClientIpsForServerDefinedTags(tags);
|
|
467
|
+
}
|
|
468
|
+
return [this.options.vpnConfig?.subnet || '10.8.0.0/24'];
|
|
473
469
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
);
|
|
470
|
+
: undefined,
|
|
471
|
+
);
|
|
472
|
+
this.apiTokenManager = new ApiTokenManager();
|
|
473
|
+
await this.apiTokenManager.initialize();
|
|
474
|
+
await this.routeConfigManager.initialize();
|
|
475
|
+
})
|
|
476
|
+
.withStop(async () => {
|
|
477
|
+
this.routeConfigManager = undefined;
|
|
478
|
+
this.apiTokenManager = undefined;
|
|
479
|
+
})
|
|
480
|
+
.withRetry({ maxRetries: 2, baseDelayMs: 1000 }),
|
|
481
|
+
);
|
|
482
|
+
}
|
|
488
483
|
|
|
489
484
|
// Email Server: optional, depends on SmartProxy
|
|
490
485
|
if (this.options.emailConfig) {
|
|
@@ -695,14 +690,9 @@ export class DcRouter {
|
|
|
695
690
|
logger.log('info', `Remote Ingress: tunnel port=${this.options.remoteIngressConfig.tunnelPort || 8443}, edges=${edgeCount} registered/${connectedCount} connected`);
|
|
696
691
|
}
|
|
697
692
|
|
|
698
|
-
//
|
|
699
|
-
if (this.
|
|
700
|
-
logger.log('info', `
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Cache database summary
|
|
704
|
-
if (this.cacheDb) {
|
|
705
|
-
logger.log('info', `Cache Database: storage=${this.cacheDb.getStoragePath()}, db=${this.cacheDb.getDbName()}, cleaner=${this.cacheCleaner?.isActive() ? 'active' : 'inactive'} (${(this.options.cacheConfig?.cleanupIntervalHours || 1)}h interval)`);
|
|
693
|
+
// Database summary
|
|
694
|
+
if (this.dcRouterDb) {
|
|
695
|
+
logger.log('info', `Database: ${this.dcRouterDb.isEmbedded() ? 'embedded' : 'external'}, db=${this.dcRouterDb.getDbName()}, cleaner=${this.cacheCleaner?.isActive() ? 'active' : 'inactive'} (${(this.options.dbConfig?.cleanupIntervalHours || 1)}h interval)`);
|
|
706
696
|
}
|
|
707
697
|
|
|
708
698
|
// Service status summary from ServiceManager
|
|
@@ -723,31 +713,32 @@ export class DcRouter {
|
|
|
723
713
|
}
|
|
724
714
|
|
|
725
715
|
/**
|
|
726
|
-
* Set up the
|
|
716
|
+
* Set up the unified database (smartdata + LocalSmartDb or external MongoDB)
|
|
727
717
|
*/
|
|
728
|
-
private async
|
|
729
|
-
logger.log('info', 'Setting up
|
|
718
|
+
private async setupDcRouterDb(): Promise<void> {
|
|
719
|
+
logger.log('info', 'Setting up DcRouterDb...');
|
|
730
720
|
|
|
731
|
-
const
|
|
721
|
+
const dbConfig = this.options.dbConfig || {};
|
|
732
722
|
|
|
733
|
-
// Initialize
|
|
734
|
-
this.
|
|
735
|
-
|
|
736
|
-
|
|
723
|
+
// Initialize DcRouterDb singleton
|
|
724
|
+
this.dcRouterDb = DcRouterDb.getInstance({
|
|
725
|
+
mongoDbUrl: dbConfig.mongoDbUrl,
|
|
726
|
+
storagePath: dbConfig.storagePath || this.resolvedPaths.defaultTsmDbPath,
|
|
727
|
+
dbName: dbConfig.dbName || 'dcrouter',
|
|
737
728
|
debug: false,
|
|
738
729
|
});
|
|
739
730
|
|
|
740
|
-
await this.
|
|
731
|
+
await this.dcRouterDb.start();
|
|
741
732
|
|
|
742
|
-
// Start the cache cleaner
|
|
743
|
-
const cleanupIntervalMs = (
|
|
744
|
-
this.cacheCleaner = new CacheCleaner(this.
|
|
733
|
+
// Start the cache cleaner for TTL-based document cleanup
|
|
734
|
+
const cleanupIntervalMs = (dbConfig.cleanupIntervalHours || 1) * 60 * 60 * 1000;
|
|
735
|
+
this.cacheCleaner = new CacheCleaner(this.dcRouterDb, {
|
|
745
736
|
intervalMs: cleanupIntervalMs,
|
|
746
737
|
verbose: false,
|
|
747
738
|
});
|
|
748
739
|
this.cacheCleaner.start();
|
|
749
740
|
|
|
750
|
-
logger.log('info', `
|
|
741
|
+
logger.log('info', `DcRouterDb ready (${this.dcRouterDb.isEmbedded() ? 'embedded' : 'external'})`);
|
|
751
742
|
}
|
|
752
743
|
|
|
753
744
|
/**
|
|
@@ -850,14 +841,11 @@ export class DcRouter {
|
|
|
850
841
|
acme: acmeConfig,
|
|
851
842
|
certStore: {
|
|
852
843
|
loadAll: async () => {
|
|
853
|
-
const
|
|
844
|
+
const docs = await ProxyCertDoc.findAll();
|
|
854
845
|
const certs: Array<{ domain: string; publicKey: string; privateKey: string; ca?: string }> = [];
|
|
855
|
-
for (const
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
certs.push(data);
|
|
859
|
-
loadedCertEntries.push({ domain: data.domain, publicKey: data.publicKey, validUntil: data.validUntil, validFrom: data.validFrom });
|
|
860
|
-
}
|
|
846
|
+
for (const doc of docs) {
|
|
847
|
+
certs.push({ domain: doc.domain, publicKey: doc.publicKey, privateKey: doc.privateKey, ca: doc.ca });
|
|
848
|
+
loadedCertEntries.push({ domain: doc.domain, publicKey: doc.publicKey, validUntil: doc.validUntil, validFrom: doc.validFrom });
|
|
861
849
|
}
|
|
862
850
|
return certs;
|
|
863
851
|
},
|
|
@@ -869,18 +857,29 @@ export class DcRouter {
|
|
|
869
857
|
validUntil = new Date(x509.validTo).getTime();
|
|
870
858
|
validFrom = new Date(x509.validFrom).getTime();
|
|
871
859
|
} catch { /* PEM parsing failed */ }
|
|
872
|
-
await
|
|
873
|
-
|
|
874
|
-
|
|
860
|
+
let doc = await ProxyCertDoc.findByDomain(domain);
|
|
861
|
+
if (!doc) {
|
|
862
|
+
doc = new ProxyCertDoc();
|
|
863
|
+
doc.domain = domain;
|
|
864
|
+
}
|
|
865
|
+
doc.publicKey = publicKey;
|
|
866
|
+
doc.privateKey = privateKey;
|
|
867
|
+
doc.ca = ca || '';
|
|
868
|
+
doc.validUntil = validUntil || 0;
|
|
869
|
+
doc.validFrom = validFrom || 0;
|
|
870
|
+
await doc.save();
|
|
875
871
|
},
|
|
876
872
|
remove: async (domain: string) => {
|
|
877
|
-
await
|
|
873
|
+
const doc = await ProxyCertDoc.findByDomain(domain);
|
|
874
|
+
if (doc) {
|
|
875
|
+
await doc.delete();
|
|
876
|
+
}
|
|
878
877
|
},
|
|
879
878
|
},
|
|
880
879
|
};
|
|
881
880
|
|
|
882
881
|
// Initialize cert provision scheduler
|
|
883
|
-
this.certProvisionScheduler = new CertProvisionScheduler(
|
|
882
|
+
this.certProvisionScheduler = new CertProvisionScheduler();
|
|
884
883
|
|
|
885
884
|
// If we have DNS challenge handlers, create SmartAcme instance and wire certProvisionFunction
|
|
886
885
|
// Note: SmartAcme.start() is NOT called here — it runs as a separate optional service
|
|
@@ -895,7 +894,7 @@ export class DcRouter {
|
|
|
895
894
|
}
|
|
896
895
|
this.smartAcme = new plugins.smartacme.SmartAcme({
|
|
897
896
|
accountEmail: acmeConfig?.accountEmail || this.options.tls?.contactEmail || 'admin@example.com',
|
|
898
|
-
certManager: new StorageBackedCertManager(
|
|
897
|
+
certManager: new StorageBackedCertManager(),
|
|
899
898
|
environment: 'production',
|
|
900
899
|
challengeHandlers: challengeHandlers,
|
|
901
900
|
challengePriority: ['dns-01'],
|
|
@@ -1037,16 +1036,16 @@ export class DcRouter {
|
|
|
1037
1036
|
issuedAt = new Date(entry.validFrom).toISOString();
|
|
1038
1037
|
}
|
|
1039
1038
|
|
|
1040
|
-
// Try SmartAcme
|
|
1039
|
+
// Try SmartAcme AcmeCertDoc metadata as secondary source
|
|
1041
1040
|
if (!expiryDate) {
|
|
1042
1041
|
try {
|
|
1043
1042
|
const cleanDomain = entry.domain.replace(/^\*\.?/, '');
|
|
1044
|
-
const
|
|
1045
|
-
if (
|
|
1046
|
-
expiryDate = new Date(
|
|
1043
|
+
const certDoc = await AcmeCertDoc.findByDomain(cleanDomain);
|
|
1044
|
+
if (certDoc?.validUntil) {
|
|
1045
|
+
expiryDate = new Date(certDoc.validUntil).toISOString();
|
|
1047
1046
|
}
|
|
1048
|
-
if (
|
|
1049
|
-
issuedAt = new Date(
|
|
1047
|
+
if (certDoc?.created && !issuedAt) {
|
|
1048
|
+
issuedAt = new Date(certDoc.created).toISOString();
|
|
1050
1049
|
}
|
|
1051
1050
|
} catch { /* no metadata available */ }
|
|
1052
1051
|
}
|
|
@@ -2030,7 +2029,7 @@ export class DcRouter {
|
|
|
2030
2029
|
logger.log('info', 'Setting up Remote Ingress hub...');
|
|
2031
2030
|
|
|
2032
2031
|
// Initialize the edge registration manager
|
|
2033
|
-
this.remoteIngressManager = new RemoteIngressManager(
|
|
2032
|
+
this.remoteIngressManager = new RemoteIngressManager();
|
|
2034
2033
|
await this.remoteIngressManager.initialize();
|
|
2035
2034
|
|
|
2036
2035
|
// Pass current routes so the manager can derive edge ports from remoteIngress-tagged routes
|
|
@@ -2056,7 +2055,7 @@ export class DcRouter {
|
|
|
2056
2055
|
// Priority 2: Existing cert from SmartProxy cert store for hubDomain
|
|
2057
2056
|
if (!tlsConfig && riCfg.hubDomain) {
|
|
2058
2057
|
try {
|
|
2059
|
-
const stored = await
|
|
2058
|
+
const stored = await ProxyCertDoc.findByDomain(riCfg.hubDomain);
|
|
2060
2059
|
if (stored?.publicKey && stored?.privateKey) {
|
|
2061
2060
|
tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
|
|
2062
2061
|
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${riCfg.hubDomain}`);
|
|
@@ -2090,13 +2089,18 @@ export class DcRouter {
|
|
|
2090
2089
|
|
|
2091
2090
|
logger.log('info', 'Setting up VPN server...');
|
|
2092
2091
|
|
|
2093
|
-
this.vpnManager = new VpnManager(
|
|
2092
|
+
this.vpnManager = new VpnManager({
|
|
2094
2093
|
subnet: this.options.vpnConfig.subnet,
|
|
2095
2094
|
wgListenPort: this.options.vpnConfig.wgListenPort,
|
|
2096
2095
|
dns: this.options.vpnConfig.dns,
|
|
2097
2096
|
serverEndpoint: this.options.vpnConfig.serverEndpoint,
|
|
2098
2097
|
initialClients: this.options.vpnConfig.clients,
|
|
2099
2098
|
destinationPolicy: this.options.vpnConfig.destinationPolicy,
|
|
2099
|
+
forwardingMode: this.options.vpnConfig.forwardingMode,
|
|
2100
|
+
bridgeLanSubnet: this.options.vpnConfig.bridgeLanSubnet,
|
|
2101
|
+
bridgePhysicalInterface: this.options.vpnConfig.bridgePhysicalInterface,
|
|
2102
|
+
bridgeIpRangeStart: this.options.vpnConfig.bridgeIpRangeStart,
|
|
2103
|
+
bridgeIpRangeEnd: this.options.vpnConfig.bridgeIpRangeEnd,
|
|
2100
2104
|
onClientChanged: () => {
|
|
2101
2105
|
// Re-apply routes so tag-based ipAllowLists get updated
|
|
2102
2106
|
this.routeConfigManager?.applyRoutes();
|
|
@@ -2180,7 +2184,7 @@ export class DcRouter {
|
|
|
2180
2184
|
|
|
2181
2185
|
logger.log('info', 'Setting up RADIUS server...');
|
|
2182
2186
|
|
|
2183
|
-
this.radiusServer = new RadiusServer(this.options.radiusConfig
|
|
2187
|
+
this.radiusServer = new RadiusServer(this.options.radiusConfig);
|
|
2184
2188
|
await this.radiusServer.start();
|
|
2185
2189
|
|
|
2186
2190
|
logger.log('info', `RADIUS server started on ports ${this.options.radiusConfig.authPort || 1812} (auth) and ${this.options.radiusConfig.acctPort || 1813} (acct)`);
|
|
@@ -1,46 +1,58 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import {
|
|
2
|
+
import { AcmeCertDoc } from './db/index.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* ICertManager implementation backed by
|
|
6
|
-
* Persists SmartAcme certificates
|
|
5
|
+
* ICertManager implementation backed by smartdata document classes.
|
|
6
|
+
* Persists SmartAcme certificates via AcmeCertDoc so they
|
|
7
7
|
* survive process restarts without re-hitting ACME.
|
|
8
8
|
*/
|
|
9
9
|
export class StorageBackedCertManager implements plugins.smartacme.ICertManager {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
constructor(private storageManager: StorageManager) {}
|
|
10
|
+
constructor() {}
|
|
13
11
|
|
|
14
12
|
async init(): Promise<void> {}
|
|
15
13
|
|
|
16
14
|
async retrieveCertificate(domainName: string): Promise<plugins.smartacme.Cert | null> {
|
|
17
|
-
const
|
|
18
|
-
if (!
|
|
19
|
-
return new plugins.smartacme.Cert(
|
|
15
|
+
const doc = await AcmeCertDoc.findByDomain(domainName);
|
|
16
|
+
if (!doc) return null;
|
|
17
|
+
return new plugins.smartacme.Cert({
|
|
18
|
+
id: doc.id,
|
|
19
|
+
domainName: doc.domainName,
|
|
20
|
+
created: doc.created,
|
|
21
|
+
privateKey: doc.privateKey,
|
|
22
|
+
publicKey: doc.publicKey,
|
|
23
|
+
csr: doc.csr,
|
|
24
|
+
validUntil: doc.validUntil,
|
|
25
|
+
});
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
async storeCertificate(cert: plugins.smartacme.Cert): Promise<void> {
|
|
23
|
-
await
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
let doc = await AcmeCertDoc.findByDomain(cert.domainName);
|
|
30
|
+
if (!doc) {
|
|
31
|
+
doc = new AcmeCertDoc();
|
|
32
|
+
doc.domainName = cert.domainName;
|
|
33
|
+
}
|
|
34
|
+
doc.id = cert.id;
|
|
35
|
+
doc.created = cert.created;
|
|
36
|
+
doc.privateKey = cert.privateKey;
|
|
37
|
+
doc.publicKey = cert.publicKey;
|
|
38
|
+
doc.csr = cert.csr;
|
|
39
|
+
doc.validUntil = cert.validUntil;
|
|
40
|
+
await doc.save();
|
|
32
41
|
}
|
|
33
42
|
|
|
34
43
|
async deleteCertificate(domainName: string): Promise<void> {
|
|
35
|
-
await
|
|
44
|
+
const doc = await AcmeCertDoc.findByDomain(domainName);
|
|
45
|
+
if (doc) {
|
|
46
|
+
await doc.delete();
|
|
47
|
+
}
|
|
36
48
|
}
|
|
37
49
|
|
|
38
50
|
async close(): Promise<void> {}
|
|
39
51
|
|
|
40
52
|
async wipe(): Promise<void> {
|
|
41
|
-
const
|
|
42
|
-
for (const
|
|
43
|
-
await
|
|
53
|
+
const docs = await AcmeCertDoc.findAll();
|
|
54
|
+
for (const doc of docs) {
|
|
55
|
+
await doc.delete();
|
|
44
56
|
}
|
|
45
57
|
}
|
|
46
58
|
}
|