@serve.zone/dcrouter 13.17.5 → 13.17.8
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 +1 -1
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +1 -1
- package/dist_ts/classes.dcrouter.js +12 -11
- package/dist_ts/config/classes.route-config-manager.d.ts +7 -2
- package/dist_ts/config/classes.route-config-manager.js +64 -38
- package/dist_ts/dns/manager.dns.js +3 -3
- package/dist_ts/monitoring/classes.metricsmanager.d.ts +1 -1
- package/dist_ts/opsserver/handlers/certificate.handler.js +6 -9
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/package.json +6 -6
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +9 -9
- package/ts/config/classes.route-config-manager.ts +80 -36
- package/ts/dns/manager.dns.ts +2 -2
- package/ts/opsserver/handlers/certificate.handler.ts +4 -5
- package/ts_web/00_commitinfo_data.ts +1 -1
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -315,7 +315,8 @@ export class DcRouter {
|
|
|
315
315
|
// Seed routes assembled during setupSmartProxy, passed to RouteConfigManager for DB seeding
|
|
316
316
|
private seedConfigRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
|
317
317
|
private seedEmailRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
|
318
|
-
|
|
318
|
+
// Runtime-only DoH routes. These carry live socket handlers and must never be persisted.
|
|
319
|
+
private runtimeDnsRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
|
319
320
|
|
|
320
321
|
// Environment access
|
|
321
322
|
private qenv = new plugins.qenv.Qenv('./', '.nogit/');
|
|
@@ -580,13 +581,13 @@ export class DcRouter {
|
|
|
580
581
|
this.tunnelManager.syncAllowedEdges();
|
|
581
582
|
}
|
|
582
583
|
},
|
|
584
|
+
() => this.runtimeDnsRoutes,
|
|
583
585
|
);
|
|
584
586
|
this.apiTokenManager = new ApiTokenManager();
|
|
585
587
|
await this.apiTokenManager.initialize();
|
|
586
588
|
await this.routeConfigManager.initialize(
|
|
587
589
|
this.seedConfigRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
588
590
|
this.seedEmailRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
589
|
-
this.seedDnsRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
590
591
|
);
|
|
591
592
|
await this.targetProfileManager.normalizeAllRouteRefs();
|
|
592
593
|
|
|
@@ -892,7 +893,7 @@ export class DcRouter {
|
|
|
892
893
|
this.smartProxy = undefined;
|
|
893
894
|
}
|
|
894
895
|
|
|
895
|
-
// Assemble seed routes from constructor config — these will be seeded into DB
|
|
896
|
+
// Assemble serializable seed routes from constructor config — these will be seeded into DB
|
|
896
897
|
// by RouteConfigManager.initialize() when the ConfigManagers service starts.
|
|
897
898
|
this.seedConfigRoutes = (this.options.smartProxyConfig?.routes || []) as plugins.smartproxy.IRouteConfig[];
|
|
898
899
|
logger.log('info', `Found ${this.seedConfigRoutes.length} routes in config`);
|
|
@@ -903,17 +904,17 @@ export class DcRouter {
|
|
|
903
904
|
logger.log('debug', 'Email routes generated', { routes: JSON.stringify(this.seedEmailRoutes) });
|
|
904
905
|
}
|
|
905
906
|
|
|
906
|
-
this.
|
|
907
|
+
this.runtimeDnsRoutes = [];
|
|
907
908
|
if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0) {
|
|
908
|
-
this.
|
|
909
|
-
logger.log('debug', `DNS routes for nameservers ${this.options.dnsNsDomains.join(', ')}`, { routes: JSON.stringify(this.
|
|
909
|
+
this.runtimeDnsRoutes = this.generateDnsRoutes();
|
|
910
|
+
logger.log('debug', `DNS routes for nameservers ${this.options.dnsNsDomains.join(', ')}`, { routes: JSON.stringify(this.runtimeDnsRoutes) });
|
|
910
911
|
}
|
|
911
912
|
|
|
912
913
|
// Combined routes for SmartProxy bootstrap (before DB routes are loaded)
|
|
913
914
|
let routes: plugins.smartproxy.IRouteConfig[] = [
|
|
914
915
|
...this.seedConfigRoutes,
|
|
915
916
|
...this.seedEmailRoutes,
|
|
916
|
-
...this.
|
|
917
|
+
...this.runtimeDnsRoutes,
|
|
917
918
|
];
|
|
918
919
|
|
|
919
920
|
// Build the ACME options for SmartProxy from the DB-backed AcmeConfigManager.
|
|
@@ -1463,7 +1464,6 @@ export class DcRouter {
|
|
|
1463
1464
|
await this.routeConfigManager.initialize(
|
|
1464
1465
|
this.seedConfigRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
1465
1466
|
this.seedEmailRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
1466
|
-
this.seedDnsRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
1467
1467
|
);
|
|
1468
1468
|
}
|
|
1469
1469
|
|
|
@@ -2185,7 +2185,7 @@ export class DcRouter {
|
|
|
2185
2185
|
// Pass current bootstrap routes so the manager can derive edge ports initially.
|
|
2186
2186
|
// Once RouteConfigManager applies the full DB set, the onRoutesApplied callback
|
|
2187
2187
|
// will push the complete merged routes here.
|
|
2188
|
-
const bootstrapRoutes = [...this.seedConfigRoutes, ...this.seedEmailRoutes, ...this.
|
|
2188
|
+
const bootstrapRoutes = [...this.seedConfigRoutes, ...this.seedEmailRoutes, ...this.runtimeDnsRoutes];
|
|
2189
2189
|
this.remoteIngressManager.setRoutes(bootstrapRoutes as any[]);
|
|
2190
2190
|
|
|
2191
2191
|
// If ConfigManagers finished before us, re-apply routes
|
|
@@ -55,6 +55,7 @@ export class RouteConfigManager {
|
|
|
55
55
|
private getVpnClientIpsForRoute?: (route: IDcRouterRouteConfig, routeId?: string) => TIpAllowEntry[],
|
|
56
56
|
private referenceResolver?: ReferenceResolver,
|
|
57
57
|
private onRoutesApplied?: (routes: plugins.smartproxy.IRouteConfig[]) => void,
|
|
58
|
+
private getRuntimeRoutes?: () => plugins.smartproxy.IRouteConfig[],
|
|
58
59
|
) {}
|
|
59
60
|
|
|
60
61
|
/** Expose routes map for reference resolution lookups. */
|
|
@@ -63,7 +64,8 @@ export class RouteConfigManager {
|
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
66
|
-
* Load persisted routes, seed config/email/dns routes,
|
|
67
|
+
* Load persisted routes, seed serializable config/email/dns routes,
|
|
68
|
+
* compute warnings, and apply the combined DB-backed + runtime route set to SmartProxy.
|
|
67
69
|
*/
|
|
68
70
|
public async initialize(
|
|
69
71
|
configRoutes: IDcRouterRouteConfig[] = [],
|
|
@@ -284,23 +286,40 @@ export class RouteConfigManager {
|
|
|
284
286
|
|
|
285
287
|
private async loadRoutes(): Promise<void> {
|
|
286
288
|
const docs = await RouteDoc.findAll();
|
|
289
|
+
let prunedRuntimeRoutes = 0;
|
|
290
|
+
|
|
287
291
|
for (const doc of docs) {
|
|
288
|
-
if (doc.id)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
292
|
+
if (!doc.id) continue;
|
|
293
|
+
|
|
294
|
+
const storedRoute: IRoute = {
|
|
295
|
+
id: doc.id,
|
|
296
|
+
route: doc.route,
|
|
297
|
+
enabled: doc.enabled,
|
|
298
|
+
createdAt: doc.createdAt,
|
|
299
|
+
updatedAt: doc.updatedAt,
|
|
300
|
+
createdBy: doc.createdBy,
|
|
301
|
+
origin: doc.origin || 'api',
|
|
302
|
+
metadata: doc.metadata,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
if (this.isPersistedRuntimeRoute(storedRoute)) {
|
|
306
|
+
await doc.delete();
|
|
307
|
+
prunedRuntimeRoutes++;
|
|
308
|
+
logger.log(
|
|
309
|
+
'warn',
|
|
310
|
+
`Removed persisted runtime-only route '${storedRoute.route.name || storedRoute.id}' (${storedRoute.id}) from RouteDoc`,
|
|
311
|
+
);
|
|
312
|
+
continue;
|
|
299
313
|
}
|
|
314
|
+
|
|
315
|
+
this.routes.set(doc.id, storedRoute);
|
|
300
316
|
}
|
|
301
317
|
if (this.routes.size > 0) {
|
|
302
318
|
logger.log('info', `Loaded ${this.routes.size} route(s) from database`);
|
|
303
319
|
}
|
|
320
|
+
if (prunedRuntimeRoutes > 0) {
|
|
321
|
+
logger.log('info', `Pruned ${prunedRuntimeRoutes} persisted runtime-only route(s) from RouteDoc`);
|
|
322
|
+
}
|
|
304
323
|
}
|
|
305
324
|
|
|
306
325
|
private async persistRoute(stored: IRoute): Promise<void> {
|
|
@@ -389,36 +408,18 @@ export class RouteConfigManager {
|
|
|
389
408
|
|
|
390
409
|
const enabledRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
|
391
410
|
|
|
392
|
-
const http3Config = this.getHttp3Config?.();
|
|
393
|
-
const vpnCallback = this.getVpnClientIpsForRoute;
|
|
394
|
-
|
|
395
|
-
// Helper: inject VPN security into a vpnOnly route
|
|
396
|
-
const injectVpn = (route: plugins.smartproxy.IRouteConfig, routeId?: string): plugins.smartproxy.IRouteConfig => {
|
|
397
|
-
if (!vpnCallback) return route;
|
|
398
|
-
const dcRoute = route as IDcRouterRouteConfig;
|
|
399
|
-
if (!dcRoute.vpnOnly) return route;
|
|
400
|
-
const vpnEntries = vpnCallback(dcRoute, routeId);
|
|
401
|
-
const existingEntries = route.security?.ipAllowList || [];
|
|
402
|
-
return {
|
|
403
|
-
...route,
|
|
404
|
-
security: {
|
|
405
|
-
...route.security,
|
|
406
|
-
ipAllowList: [...existingEntries, ...vpnEntries],
|
|
407
|
-
},
|
|
408
|
-
};
|
|
409
|
-
};
|
|
410
|
-
|
|
411
411
|
// Add all enabled routes with HTTP/3 and VPN augmentation
|
|
412
412
|
for (const route of this.routes.values()) {
|
|
413
413
|
if (route.enabled) {
|
|
414
|
-
|
|
415
|
-
if (http3Config?.enabled !== false) {
|
|
416
|
-
r = augmentRouteWithHttp3(r, { enabled: true, ...http3Config });
|
|
417
|
-
}
|
|
418
|
-
enabledRoutes.push(injectVpn(r, route.id));
|
|
414
|
+
enabledRoutes.push(this.prepareRouteForApply(route.route, route.id));
|
|
419
415
|
}
|
|
420
416
|
}
|
|
421
417
|
|
|
418
|
+
const runtimeRoutes = this.getRuntimeRoutes?.() || [];
|
|
419
|
+
for (const route of runtimeRoutes) {
|
|
420
|
+
enabledRoutes.push(this.prepareRouteForApply(route));
|
|
421
|
+
}
|
|
422
|
+
|
|
422
423
|
await smartProxy.updateRoutes(enabledRoutes);
|
|
423
424
|
|
|
424
425
|
// Notify listeners (e.g. RemoteIngressManager) of the route set
|
|
@@ -429,4 +430,47 @@ export class RouteConfigManager {
|
|
|
429
430
|
logger.log('info', `Applied ${enabledRoutes.length} routes to SmartProxy (${this.routes.size} total)`);
|
|
430
431
|
});
|
|
431
432
|
}
|
|
433
|
+
|
|
434
|
+
private prepareRouteForApply(
|
|
435
|
+
route: plugins.smartproxy.IRouteConfig,
|
|
436
|
+
routeId?: string,
|
|
437
|
+
): plugins.smartproxy.IRouteConfig {
|
|
438
|
+
let preparedRoute = route;
|
|
439
|
+
const http3Config = this.getHttp3Config?.();
|
|
440
|
+
|
|
441
|
+
if (http3Config?.enabled !== false) {
|
|
442
|
+
preparedRoute = augmentRouteWithHttp3(preparedRoute, { enabled: true, ...http3Config });
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return this.injectVpnSecurity(preparedRoute, routeId);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private injectVpnSecurity(
|
|
449
|
+
route: plugins.smartproxy.IRouteConfig,
|
|
450
|
+
routeId?: string,
|
|
451
|
+
): plugins.smartproxy.IRouteConfig {
|
|
452
|
+
const vpnCallback = this.getVpnClientIpsForRoute;
|
|
453
|
+
if (!vpnCallback) return route;
|
|
454
|
+
|
|
455
|
+
const dcRoute = route as IDcRouterRouteConfig;
|
|
456
|
+
if (!dcRoute.vpnOnly) return route;
|
|
457
|
+
|
|
458
|
+
const vpnEntries = vpnCallback(dcRoute, routeId);
|
|
459
|
+
const existingEntries = route.security?.ipAllowList || [];
|
|
460
|
+
return {
|
|
461
|
+
...route,
|
|
462
|
+
security: {
|
|
463
|
+
...route.security,
|
|
464
|
+
ipAllowList: [...existingEntries, ...vpnEntries],
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
private isPersistedRuntimeRoute(storedRoute: IRoute): boolean {
|
|
470
|
+
const routeName = storedRoute.route.name || '';
|
|
471
|
+
const actionType = storedRoute.route.action?.type;
|
|
472
|
+
|
|
473
|
+
return (routeName.startsWith('dns-over-https-') && actionType === 'socket-handler')
|
|
474
|
+
|| (storedRoute.origin === 'dns' && actionType === 'socket-handler');
|
|
475
|
+
}
|
|
432
476
|
}
|
package/ts/dns/manager.dns.ts
CHANGED
|
@@ -97,8 +97,8 @@ export class DnsManager {
|
|
|
97
97
|
if (hasLegacyConfig) {
|
|
98
98
|
logger.log(
|
|
99
99
|
'warn',
|
|
100
|
-
'DnsManager: DB has DomainDoc entries — ignoring legacy dnsScopes/dnsRecords
|
|
101
|
-
'
|
|
100
|
+
'DnsManager: DB has DomainDoc entries — ignoring legacy dnsScopes/dnsRecords constructor config. ' +
|
|
101
|
+
'dnsNsDomains is still required for nameserver and DoH bootstrap unless that moves into DB-backed config.',
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
return;
|
|
@@ -198,12 +198,11 @@ export class CertificateHandler {
|
|
|
198
198
|
try {
|
|
199
199
|
const rustStatus = await smartProxy.getCertificateStatus(info.routeNames[0]);
|
|
200
200
|
if (rustStatus) {
|
|
201
|
-
if (rustStatus.
|
|
202
|
-
|
|
203
|
-
if (rustStatus.issuedAt) issuedAt = rustStatus.issuedAt;
|
|
204
|
-
if (rustStatus.status === 'valid' || rustStatus.status === 'expired') {
|
|
205
|
-
status = rustStatus.status;
|
|
201
|
+
if (rustStatus.expiresAt > 0) {
|
|
202
|
+
expiryDate = new Date(rustStatus.expiresAt).toISOString();
|
|
206
203
|
}
|
|
204
|
+
if (rustStatus.source) issuer = rustStatus.source;
|
|
205
|
+
status = rustStatus.isValid ? 'valid' : 'expired';
|
|
207
206
|
}
|
|
208
207
|
} catch {
|
|
209
208
|
// Rust bridge may not support this command yet — ignore
|