@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.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.17.5',
6
+ version: '13.17.8',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }
@@ -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
- private seedDnsRoutes: plugins.smartproxy.IRouteConfig[] = [];
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.seedDnsRoutes = [];
907
+ this.runtimeDnsRoutes = [];
907
908
  if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0) {
908
- this.seedDnsRoutes = this.generateDnsRoutes();
909
- logger.log('debug', `DNS routes for nameservers ${this.options.dnsNsDomains.join(', ')}`, { routes: JSON.stringify(this.seedDnsRoutes) });
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.seedDnsRoutes,
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.seedDnsRoutes];
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, compute warnings, apply to SmartProxy.
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
- this.routes.set(doc.id, {
290
- id: doc.id,
291
- route: doc.route,
292
- enabled: doc.enabled,
293
- createdAt: doc.createdAt,
294
- updatedAt: doc.updatedAt,
295
- createdBy: doc.createdBy,
296
- origin: doc.origin || 'api',
297
- metadata: doc.metadata,
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
- let r = route.route;
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
  }
@@ -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/dnsNsDomains constructor config. ' +
101
- 'Manage DNS via the Domains UI instead.',
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.expiryDate) expiryDate = rustStatus.expiryDate;
202
- if (rustStatus.issuer) issuer = rustStatus.issuer;
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
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@serve.zone/dcrouter',
6
- version: '13.17.5',
6
+ version: '13.17.8',
7
7
  description: 'A multifaceted routing service handling mail and SMS delivery functions.'
8
8
  }