@serve.zone/dcrouter 11.10.4 → 11.11.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.
Files changed (82) hide show
  1. package/dist_serve/bundle.js +5102 -5102
  2. package/dist_ts/00_commitinfo_data.js +1 -1
  3. package/dist_ts/cache/classes.cache.cleaner.js +1 -1
  4. package/dist_ts/cache/classes.cached.document.js +1 -1
  5. package/dist_ts/cache/classes.cachedb.d.ts +5 -5
  6. package/dist_ts/cache/classes.cachedb.js +14 -14
  7. package/dist_ts/cache/documents/classes.cached.email.js +1 -1
  8. package/dist_ts/cache/documents/classes.cached.ip.reputation.js +1 -1
  9. package/dist_ts/classes.dcrouter.d.ts +5 -0
  10. package/dist_ts/classes.dcrouter.js +50 -4
  11. package/dist_ts/config/classes.route-config-manager.d.ts +1 -1
  12. package/dist_ts/config/validator.js +1 -1
  13. package/dist_ts/errors/base.errors.js +2 -2
  14. package/dist_ts/monitoring/classes.metricsmanager.d.ts +6 -1
  15. package/dist_ts/monitoring/classes.metricsmanager.js +1 -1
  16. package/dist_ts/opsserver/classes.opsserver.js +2 -2
  17. package/dist_ts/opsserver/handlers/admin.handler.js +1 -1
  18. package/dist_ts/opsserver/handlers/certificate.handler.js +1 -1
  19. package/dist_ts/opsserver/handlers/radius.handler.js +1 -1
  20. package/dist_ts/opsserver/handlers/stats.handler.js +1 -1
  21. package/dist_ts/plugins.d.ts +3 -3
  22. package/dist_ts/plugins.js +5 -5
  23. package/dist_ts/radius/classes.accounting.manager.js +1 -1
  24. package/dist_ts/radius/classes.radius.server.js +1 -1
  25. package/dist_ts/radius/classes.vlan.manager.js +1 -1
  26. package/dist_ts/security/classes.contentscanner.js +3 -3
  27. package/dist_ts/security/classes.ipreputationchecker.js +4 -4
  28. package/dist_ts/security/classes.securitylogger.js +5 -3
  29. package/dist_ts/sms/classes.smsservice.js +2 -2
  30. package/dist_ts/storage/classes.storagemanager.d.ts +1 -1
  31. package/dist_ts/storage/classes.storagemanager.js +2 -4
  32. package/dist_ts_oci_container/index.js +22 -1
  33. package/dist_ts_web/00_commitinfo_data.js +1 -1
  34. package/dist_ts_web/appstate.js +9 -6
  35. package/dist_ts_web/elements/ops-dashboard.js +3 -2
  36. package/dist_ts_web/elements/ops-view-certificates.js +1 -1
  37. package/dist_ts_web/elements/ops-view-config.js +1 -1
  38. package/dist_ts_web/elements/ops-view-emails.js +1 -1
  39. package/dist_ts_web/elements/ops-view-network.js +1 -1
  40. package/dist_ts_web/elements/ops-view-remoteingress.js +1 -1
  41. package/dist_ts_web/router.js +1 -1
  42. package/license +21 -0
  43. package/package.json +18 -18
  44. package/readme.hints.md +1 -1
  45. package/readme.md +42 -21
  46. package/ts/00_commitinfo_data.ts +1 -1
  47. package/ts/cache/classes.cache.cleaner.ts +10 -10
  48. package/ts/cache/classes.cached.document.ts +1 -1
  49. package/ts/cache/classes.cachedb.ts +18 -18
  50. package/ts/cache/documents/classes.cached.email.ts +14 -14
  51. package/ts/cache/documents/classes.cached.ip.reputation.ts +10 -10
  52. package/ts/classes.dcrouter.ts +77 -32
  53. package/ts/config/validator.ts +5 -5
  54. package/ts/errors/base.errors.ts +1 -1
  55. package/ts/monitoring/classes.metricsmanager.ts +3 -3
  56. package/ts/opsserver/classes.opsserver.ts +13 -13
  57. package/ts/opsserver/handlers/admin.handler.ts +1 -1
  58. package/ts/opsserver/handlers/certificate.handler.ts +6 -6
  59. package/ts/opsserver/handlers/radius.handler.ts +4 -4
  60. package/ts/opsserver/handlers/stats.handler.ts +1 -1
  61. package/ts/plugins.ts +4 -4
  62. package/ts/radius/classes.accounting.manager.ts +10 -10
  63. package/ts/radius/classes.radius.server.ts +2 -2
  64. package/ts/radius/classes.vlan.manager.ts +5 -5
  65. package/ts/readme.md +1 -1
  66. package/ts/security/classes.contentscanner.ts +12 -12
  67. package/ts/security/classes.ipreputationchecker.ts +26 -26
  68. package/ts/security/classes.securitylogger.ts +6 -4
  69. package/ts/sms/classes.smsservice.ts +3 -3
  70. package/ts/storage/classes.storagemanager.ts +23 -25
  71. package/ts_apiclient/readme.md +1 -1
  72. package/ts_web/00_commitinfo_data.ts +1 -1
  73. package/ts_web/appstate.ts +136 -133
  74. package/ts_web/elements/ops-dashboard.ts +15 -14
  75. package/ts_web/elements/ops-view-certificates.ts +10 -10
  76. package/ts_web/elements/ops-view-config.ts +8 -8
  77. package/ts_web/elements/ops-view-emails.ts +2 -2
  78. package/ts_web/elements/ops-view-network.ts +2 -2
  79. package/ts_web/elements/ops-view-remoteingress.ts +6 -6
  80. package/ts_web/readme.md +1 -1
  81. package/ts_web/router.ts +3 -3
  82. /package/{npmextra.json → .smartconfig.json} +0 -0
@@ -15,16 +15,16 @@ export interface ICacheDbOptions {
15
15
  }
16
16
 
17
17
  /**
18
- * CacheDb - Wrapper around LocalTsmDb and smartdata
18
+ * CacheDb - Wrapper around LocalSmartDb and smartdata
19
19
  *
20
20
  * Provides persistent caching using smartdata as the ORM layer
21
- * and LocalTsmDb as the embedded database engine.
21
+ * and LocalSmartDb as the embedded database engine.
22
22
  */
23
23
  export class CacheDb {
24
24
  private static instance: CacheDb | null = null;
25
25
 
26
- private localTsmDb: plugins.smartmongo.LocalTsmDb;
27
- private smartdataDb: plugins.smartdata.SmartdataDb;
26
+ private localSmartDb!: plugins.smartdb.LocalSmartDb;
27
+ private smartdataDb!: plugins.smartdata.SmartdataDb;
28
28
  private options: Required<ICacheDbOptions>;
29
29
  private isStarted: boolean = false;
30
30
 
@@ -55,8 +55,8 @@ export class CacheDb {
55
55
 
56
56
  /**
57
57
  * Start the cache database
58
- * - Initializes LocalTsmDb with file persistence
59
- * - Connects smartdata to the LocalTsmDb via Unix socket
58
+ * - Initializes LocalSmartDb with file persistence
59
+ * - Connects smartdata to the LocalSmartDb via Unix socket
60
60
  */
61
61
  public async start(): Promise<void> {
62
62
  if (this.isStarted) {
@@ -68,16 +68,16 @@ export class CacheDb {
68
68
  // Ensure storage directory exists
69
69
  await plugins.fsUtils.ensureDir(this.options.storagePath);
70
70
 
71
- // Create LocalTsmDb instance
72
- this.localTsmDb = new plugins.smartmongo.LocalTsmDb({
71
+ // Create LocalSmartDb instance
72
+ this.localSmartDb = new plugins.smartdb.LocalSmartDb({
73
73
  folderPath: this.options.storagePath,
74
74
  });
75
75
 
76
- // Start LocalTsmDb and get connection info
77
- const connectionInfo = await this.localTsmDb.start();
76
+ // Start LocalSmartDb and get connection info
77
+ const connectionInfo = await this.localSmartDb.start();
78
78
 
79
79
  if (this.options.debug) {
80
- logger.log('debug', `LocalTsmDb started with URI: ${connectionInfo.connectionUri}`);
80
+ logger.log('debug', `LocalSmartDb started with URI: ${connectionInfo.connectionUri}`);
81
81
  }
82
82
 
83
83
  // Initialize smartdata with the connection URI
@@ -89,8 +89,8 @@ export class CacheDb {
89
89
 
90
90
  this.isStarted = true;
91
91
  logger.log('info', `CacheDb started at ${this.options.storagePath}`);
92
- } catch (error) {
93
- logger.log('error', `Failed to start CacheDb: ${error.message}`);
92
+ } catch (error: unknown) {
93
+ logger.log('error', `Failed to start CacheDb: ${(error as Error).message}`);
94
94
  throw error;
95
95
  }
96
96
  }
@@ -109,15 +109,15 @@ export class CacheDb {
109
109
  await this.smartdataDb.close();
110
110
  }
111
111
 
112
- // Stop LocalTsmDb
113
- if (this.localTsmDb) {
114
- await this.localTsmDb.stop();
112
+ // Stop LocalSmartDb
113
+ if (this.localSmartDb) {
114
+ await this.localSmartDb.stop();
115
115
  }
116
116
 
117
117
  this.isStarted = false;
118
118
  logger.log('info', 'CacheDb stopped');
119
- } catch (error) {
120
- logger.log('error', `Error stopping CacheDb: ${error.message}`);
119
+ } catch (error: unknown) {
120
+ logger.log('error', `Error stopping CacheDb: ${(error as Error).message}`);
121
121
  throw error;
122
122
  }
123
123
  }
@@ -35,55 +35,55 @@ export class CachedEmail extends CachedDocument<CachedEmail> {
35
35
  */
36
36
  @plugins.smartdata.unI()
37
37
  @plugins.smartdata.svDb()
38
- public id: string;
38
+ public id!: string;
39
39
 
40
40
  /**
41
41
  * Email message ID (RFC 822 Message-ID header)
42
42
  */
43
43
  @plugins.smartdata.svDb()
44
- public messageId: string;
44
+ public messageId!: string;
45
45
 
46
46
  /**
47
47
  * Sender email address (envelope from)
48
48
  */
49
49
  @plugins.smartdata.svDb()
50
- public from: string;
50
+ public from!: string;
51
51
 
52
52
  /**
53
53
  * Recipient email addresses
54
54
  */
55
55
  @plugins.smartdata.svDb()
56
- public to: string[];
56
+ public to!: string[];
57
57
 
58
58
  /**
59
59
  * CC recipients
60
60
  */
61
61
  @plugins.smartdata.svDb()
62
- public cc: string[];
62
+ public cc!: string[];
63
63
 
64
64
  /**
65
65
  * BCC recipients
66
66
  */
67
67
  @plugins.smartdata.svDb()
68
- public bcc: string[];
68
+ public bcc!: string[];
69
69
 
70
70
  /**
71
71
  * Email subject
72
72
  */
73
73
  @plugins.smartdata.svDb()
74
- public subject: string;
74
+ public subject!: string;
75
75
 
76
76
  /**
77
77
  * Raw RFC822 email content
78
78
  */
79
79
  @plugins.smartdata.svDb()
80
- public rawContent: string;
80
+ public rawContent!: string;
81
81
 
82
82
  /**
83
83
  * Current status of the email
84
84
  */
85
85
  @plugins.smartdata.svDb()
86
- public status: TCachedEmailStatus;
86
+ public status!: TCachedEmailStatus;
87
87
 
88
88
  /**
89
89
  * Number of delivery attempts
@@ -101,25 +101,25 @@ export class CachedEmail extends CachedDocument<CachedEmail> {
101
101
  * Timestamp for next delivery attempt
102
102
  */
103
103
  @plugins.smartdata.svDb()
104
- public nextAttempt: Date;
104
+ public nextAttempt!: Date;
105
105
 
106
106
  /**
107
107
  * Last error message if delivery failed
108
108
  */
109
109
  @plugins.smartdata.svDb()
110
- public lastError: string;
110
+ public lastError!: string;
111
111
 
112
112
  /**
113
113
  * Timestamp when the email was successfully delivered
114
114
  */
115
115
  @plugins.smartdata.svDb()
116
- public deliveredAt: Date;
116
+ public deliveredAt!: Date;
117
117
 
118
118
  /**
119
119
  * Sender domain (for querying/filtering)
120
120
  */
121
121
  @plugins.smartdata.svDb()
122
- public senderDomain: string;
122
+ public senderDomain!: string;
123
123
 
124
124
  /**
125
125
  * Priority level (higher = more important)
@@ -131,7 +131,7 @@ export class CachedEmail extends CachedDocument<CachedEmail> {
131
131
  * JSON-serialized route data
132
132
  */
133
133
  @plugins.smartdata.svDb()
134
- public routeData: string;
134
+ public routeData!: string;
135
135
 
136
136
  /**
137
137
  * DKIM signature status
@@ -45,61 +45,61 @@ export class CachedIPReputation extends CachedDocument<CachedIPReputation> {
45
45
  */
46
46
  @plugins.smartdata.unI()
47
47
  @plugins.smartdata.svDb()
48
- public ipAddress: string;
48
+ public ipAddress!: string;
49
49
 
50
50
  /**
51
51
  * Reputation score (0-100, higher = better)
52
52
  */
53
53
  @plugins.smartdata.svDb()
54
- public score: number;
54
+ public score!: number;
55
55
 
56
56
  /**
57
57
  * Whether the IP is flagged as spam source
58
58
  */
59
59
  @plugins.smartdata.svDb()
60
- public isSpam: boolean;
60
+ public isSpam!: boolean;
61
61
 
62
62
  /**
63
63
  * Whether the IP is a known proxy
64
64
  */
65
65
  @plugins.smartdata.svDb()
66
- public isProxy: boolean;
66
+ public isProxy!: boolean;
67
67
 
68
68
  /**
69
69
  * Whether the IP is a Tor exit node
70
70
  */
71
71
  @plugins.smartdata.svDb()
72
- public isTor: boolean;
72
+ public isTor!: boolean;
73
73
 
74
74
  /**
75
75
  * Whether the IP is a VPN endpoint
76
76
  */
77
77
  @plugins.smartdata.svDb()
78
- public isVPN: boolean;
78
+ public isVPN!: boolean;
79
79
 
80
80
  /**
81
81
  * Country code (ISO 3166-1 alpha-2)
82
82
  */
83
83
  @plugins.smartdata.svDb()
84
- public country: string;
84
+ public country!: string;
85
85
 
86
86
  /**
87
87
  * Autonomous System Number
88
88
  */
89
89
  @plugins.smartdata.svDb()
90
- public asn: string;
90
+ public asn!: string;
91
91
 
92
92
  /**
93
93
  * Organization name
94
94
  */
95
95
  @plugins.smartdata.svDb()
96
- public org: string;
96
+ public org!: string;
97
97
 
98
98
  /**
99
99
  * List of blacklists the IP appears on
100
100
  */
101
101
  @plugins.smartdata.svDb()
102
- public blacklists: string[];
102
+ public blacklists!: string[];
103
103
 
104
104
  /**
105
105
  * Number of times this IP has been checked
@@ -215,7 +215,7 @@ export class DcRouter {
215
215
  public emailServer?: UnifiedEmailServer;
216
216
  public radiusServer?: RadiusServer;
217
217
  public storageManager: StorageManager;
218
- public opsServer: OpsServer;
218
+ public opsServer!: OpsServer;
219
219
  public metricsManager?: MetricsManager;
220
220
 
221
221
  // Cache system (smartdata + LocalTsmDb)
@@ -448,7 +448,7 @@ export class DcRouter {
448
448
  }
449
449
 
450
450
  // DNS Server: optional, depends on SmartProxy
451
- if (this.options.dnsNsDomains?.length > 0 && this.options.dnsScopes?.length > 0) {
451
+ if (this.options.dnsNsDomains && this.options.dnsNsDomains.length > 0 && this.options.dnsScopes && this.options.dnsScopes.length > 0) {
452
452
  this.serviceManager.addService(
453
453
  new plugins.taskbuffer.Service('DnsServer')
454
454
  .optional()
@@ -528,10 +528,36 @@ export class DcRouter {
528
528
  }
529
529
 
530
530
  public async start() {
531
+ await this.checkSystemLimits();
531
532
  logger.log('info', 'Starting DcRouter Services');
532
533
  await this.serviceManager.start();
533
534
  this.logStartupSummary();
534
535
  }
536
+
537
+ /**
538
+ * Detect OS-level resource limits and warn if they are too low for production use.
539
+ * This is detection only — no attempts to raise limits.
540
+ */
541
+ private async checkSystemLimits(): Promise<void> {
542
+ try {
543
+ const fs = new plugins.smartfs.SmartFs(new plugins.smartfs.SmartFsProviderNode());
544
+ const limitsContent = await fs.file('/proc/self/limits').encoding('utf8').read() as string;
545
+ const nofileLine = limitsContent.split('\n').find((line: string) => line.startsWith('Max open files'));
546
+ if (nofileLine) {
547
+ const parts = nofileLine.split(/\s{2,}/);
548
+ const softLimit = parseInt(parts[1], 10);
549
+ const hardLimit = parseInt(parts[2], 10);
550
+ if (softLimit < 65536) {
551
+ logger.log('warn', `File descriptor soft limit is ${softLimit} (hard: ${hardLimit}). ` +
552
+ `For production use, set --ulimit nofile=65536:65536 on the container runtime.`);
553
+ } else {
554
+ logger.log('info', `File descriptor limits: soft=${softLimit}, hard=${hardLimit}`);
555
+ }
556
+ }
557
+ } catch {
558
+ // Non-Linux or /proc not available — silently skip
559
+ }
560
+ }
535
561
 
536
562
  /**
537
563
  * Log comprehensive startup summary
@@ -708,9 +734,28 @@ export class DcRouter {
708
734
  // Track cert entries loaded from cert store so we can populate certificateStatusMap after start
709
735
  const loadedCertEntries: Array<{domain: string; publicKey: string; validUntil?: number; validFrom?: number}> = [];
710
736
 
711
- // Create SmartProxy configuration
737
+ // Create SmartProxy configuration with sensible gateway defaults.
738
+ // User's smartProxyConfig overrides these defaults via spread.
712
739
  const smartProxyConfig: plugins.smartproxy.ISmartProxyOptions = {
740
+ // --- dcrouter gateway defaults ---
741
+ maxConnectionsPerIP: 100,
742
+ connectionRateLimitPerMinute: 600,
743
+ socketTimeout: 120_000,
744
+ inactivityTimeout: 120_000,
745
+ keepAlive: true,
746
+ noDelay: true,
747
+ gracefulShutdownTimeout: 30_000,
748
+ // --- user overrides ---
713
749
  ...this.options.smartProxyConfig,
750
+ // --- deep-merge defaults.security so user can override maxConnections ---
751
+ defaults: {
752
+ ...this.options.smartProxyConfig?.defaults,
753
+ security: {
754
+ maxConnections: 50_000,
755
+ ...this.options.smartProxyConfig?.defaults?.security,
756
+ },
757
+ },
758
+ // --- always set by dcrouter (after spread) ---
714
759
  routes,
715
760
  acme: acmeConfig,
716
761
  certStore: {
@@ -787,7 +832,7 @@ export class DcRouter {
787
832
  eventComms.log(`Attempting DNS-01 via SmartAcme for ${domain}`);
788
833
  eventComms.setSource('smartacme-dns-01');
789
834
  const isWildcardDomain = domain.startsWith('*.');
790
- const cert = await this.smartAcme.getCertificateForDomain(domain, {
835
+ const cert = await this.smartAcme!.getCertificateForDomain(domain, {
791
836
  includeWildcard: !isWildcardDomain,
792
837
  });
793
838
  if (cert.validUntil) {
@@ -806,10 +851,10 @@ export class DcRouter {
806
851
  // Success — clear any backoff
807
852
  await scheduler.clearBackoff(domain);
808
853
  return result;
809
- } catch (err) {
854
+ } catch (err: unknown) {
810
855
  // Record failure for backoff tracking
811
- await scheduler.recordFailure(domain, err.message);
812
- eventComms.warn(`SmartAcme DNS-01 failed for ${domain}: ${err.message}, falling back to http-01`);
856
+ await scheduler.recordFailure(domain, (err as Error).message);
857
+ eventComms.warn(`SmartAcme DNS-01 failed for ${domain}: ${(err as Error).message}, falling back to http-01`);
813
858
  return 'http01';
814
859
  }
815
860
  };
@@ -1248,21 +1293,21 @@ export class DcRouter {
1248
1293
  // Wire delivery events to MetricsManager and logger
1249
1294
  if (this.metricsManager && this.emailServer.deliverySystem) {
1250
1295
  this.emailServer.deliverySystem.on('deliveryStart', (item: any) => {
1251
- this.metricsManager.trackEmailReceived(item?.from);
1296
+ this.metricsManager!.trackEmailReceived(item?.from);
1252
1297
  logger.log('info', `Email delivery started: ${item?.from} → ${item?.to}`, { zone: 'email' });
1253
1298
  });
1254
1299
  this.emailServer.deliverySystem.on('deliverySuccess', (item: any) => {
1255
- this.metricsManager.trackEmailSent(item?.to);
1300
+ this.metricsManager!.trackEmailSent(item?.to);
1256
1301
  logger.log('info', `Email delivered to ${item?.to}`, { zone: 'email' });
1257
1302
  });
1258
1303
  this.emailServer.deliverySystem.on('deliveryFailed', (item: any, error: any) => {
1259
- this.metricsManager.trackEmailFailed(item?.to, error?.message);
1304
+ this.metricsManager!.trackEmailFailed(item?.to, error?.message);
1260
1305
  logger.log('warn', `Email delivery failed to ${item?.to}: ${error?.message}`, { zone: 'email' });
1261
1306
  });
1262
1307
  }
1263
1308
  if (this.metricsManager && this.emailServer) {
1264
1309
  this.emailServer.on('bounceProcessed', () => {
1265
- this.metricsManager.trackEmailBounced();
1310
+ this.metricsManager!.trackEmailBounced();
1266
1311
  logger.log('warn', 'Email bounce processed', { zone: 'email' });
1267
1312
  });
1268
1313
  }
@@ -1305,12 +1350,12 @@ export class DcRouter {
1305
1350
  }
1306
1351
 
1307
1352
  logger.log('info', 'All unified email components stopped');
1308
- } catch (error) {
1309
- logger.log('error', `Error stopping unified email components: ${error.message}`);
1353
+ } catch (error: unknown) {
1354
+ logger.log('error', `Error stopping unified email components: ${(error as Error).message}`);
1310
1355
  throw error;
1311
1356
  }
1312
1357
  }
1313
-
1358
+
1314
1359
  /**
1315
1360
  * Update domain rules for email routing
1316
1361
  * @param rules New domain rules to apply
@@ -1468,7 +1513,7 @@ export class DcRouter {
1468
1513
  this.dnsServer.on('query', (event: plugins.smartdns.dnsServerMod.IDnsQueryCompletedEvent) => {
1469
1514
  // Metrics tracking
1470
1515
  for (const question of event.questions) {
1471
- this.metricsManager.trackDnsQuery(
1516
+ this.metricsManager?.trackDnsQuery(
1472
1517
  question.type,
1473
1518
  question.name,
1474
1519
  false,
@@ -1553,8 +1598,8 @@ export class DcRouter {
1553
1598
  // Use the built-in socket handler from smartdns
1554
1599
  // This handles HTTP/2, DoH protocol, etc.
1555
1600
  await (this.dnsServer as any).handleHttpsSocket(socket);
1556
- } catch (error) {
1557
- logger.log('error', `DNS socket handler error: ${error.message}`);
1601
+ } catch (error: unknown) {
1602
+ logger.log('error', `DNS socket handler error: ${(error as Error).message}`);
1558
1603
  if (!socket.destroyed) {
1559
1604
  socket.destroy();
1560
1605
  }
@@ -1695,14 +1740,14 @@ export class DcRouter {
1695
1740
  } else {
1696
1741
  logger.log('warn', `Invalid DKIM record structure in ${file}`);
1697
1742
  }
1698
- } catch (error) {
1699
- logger.log('error', `Failed to load DKIM record from ${file}: ${error.message}`);
1743
+ } catch (error: unknown) {
1744
+ logger.log('error', `Failed to load DKIM record from ${file}: ${(error as Error).message}`);
1700
1745
  }
1701
1746
  }
1702
- } catch (error) {
1703
- logger.log('error', `Failed to load DKIM records: ${error.message}`);
1747
+ } catch (error: unknown) {
1748
+ logger.log('error', `Failed to load DKIM records: ${(error as Error).message}`);
1704
1749
  }
1705
-
1750
+
1706
1751
  return records;
1707
1752
  }
1708
1753
 
@@ -1734,11 +1779,11 @@ export class DcRouter {
1734
1779
  // This ensures keys are ready even if DNS mode changes later
1735
1780
  await dkimCreator.handleDKIMKeysForDomain(domainConfig.domain);
1736
1781
  logger.log('info', `DKIM keys initialized for ${domainConfig.domain}`);
1737
- } catch (error) {
1738
- logger.log('error', `Failed to initialize DKIM for ${domainConfig.domain}: ${error.message}`);
1782
+ } catch (error: unknown) {
1783
+ logger.log('error', `Failed to initialize DKIM for ${domainConfig.domain}: ${(error as Error).message}`);
1739
1784
  }
1740
1785
  }
1741
-
1786
+
1742
1787
  logger.log('info', 'DKIM initialization complete');
1743
1788
  }
1744
1789
 
@@ -1779,10 +1824,10 @@ export class DcRouter {
1779
1824
  } else {
1780
1825
  logger.log('warn', 'Could not auto-discover public IPv4 address');
1781
1826
  }
1782
- } catch (error) {
1783
- logger.log('error', `Failed to auto-discover public IP: ${error.message}`);
1827
+ } catch (error: unknown) {
1828
+ logger.log('error', `Failed to auto-discover public IP: ${(error as Error).message}`);
1784
1829
  }
1785
-
1830
+
1786
1831
  if (!publicIp) {
1787
1832
  logger.log('warn', 'No public IP available. Nameserver A records require either proxyIps, publicIp, or successful auto-discovery.');
1788
1833
  }
@@ -1876,8 +1921,8 @@ export class DcRouter {
1876
1921
  }
1877
1922
 
1878
1923
  return null;
1879
- } catch (error) {
1880
- logger.log('warn', `Failed to detect public IP: ${error.message}`);
1924
+ } catch (error: unknown) {
1925
+ logger.log('warn', `Failed to detect public IP: ${(error as Error).message}`);
1881
1926
  return null;
1882
1927
  }
1883
1928
  }
@@ -1911,8 +1956,8 @@ export class DcRouter {
1911
1956
  const keyPem = plugins.fs.readFileSync(riCfg.tls.keyPath, 'utf8');
1912
1957
  tlsConfig = { certPem, keyPem };
1913
1958
  logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
1914
- } catch (err) {
1915
- logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${err.message}`);
1959
+ } catch (err: unknown) {
1960
+ logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${(err as Error).message}`);
1916
1961
  }
1917
1962
  }
1918
1963
 
@@ -170,7 +170,7 @@ export class ConfigValidator {
170
170
  } else if (rules.items.schema && itemType === 'object') {
171
171
  const itemResult = this.validate(value[i], rules.items.schema);
172
172
  if (!itemResult.valid) {
173
- errors.push(...itemResult.errors.map(err => `${key}[${i}].${err}`));
173
+ errors.push(...itemResult.errors!.map(err => `${key}[${i}].${err}`));
174
174
  }
175
175
  }
176
176
  }
@@ -181,7 +181,7 @@ export class ConfigValidator {
181
181
  if (rules.schema) {
182
182
  const nestedResult = this.validate(value, rules.schema);
183
183
  if (!nestedResult.valid) {
184
- errors.push(...nestedResult.errors.map(err => `${key}.${err}`));
184
+ errors.push(...nestedResult.errors!.map(err => `${key}.${err}`));
185
185
  }
186
186
  validatedConfig[key] = nestedResult.config;
187
187
  }
@@ -233,8 +233,8 @@ export class ConfigValidator {
233
233
 
234
234
  // Apply defaults to array items
235
235
  if (result[key] && rules.type === 'array' && rules.items && rules.items.schema) {
236
- result[key] = result[key].map(item =>
237
- typeof item === 'object' ? this.applyDefaults(item, rules.items.schema) : item
236
+ result[key] = result[key].map(item =>
237
+ typeof item === 'object' ? this.applyDefaults(item, rules.items!.schema!) : item
238
238
  );
239
239
  }
240
240
  }
@@ -255,7 +255,7 @@ export class ConfigValidator {
255
255
 
256
256
  if (!result.valid) {
257
257
  throw new ValidationError(
258
- `Configuration validation failed: ${result.errors.join(', ')}`,
258
+ `Configuration validation failed: ${result.errors!.join(', ')}`,
259
259
  'CONFIG_VALIDATION_ERROR',
260
260
  { data: { errors: result.errors } }
261
261
  );
@@ -227,7 +227,7 @@ export class PlatformError extends Error {
227
227
  const { retry } = this.context;
228
228
  if (!retry) return false;
229
229
 
230
- return retry.currentRetry < retry.maxRetries;
230
+ return (retry.currentRetry ?? 0) < (retry.maxRetries ?? 0);
231
231
  }
232
232
 
233
233
  /**
@@ -296,11 +296,11 @@ export class MetricsManager {
296
296
  const proxyMetrics = this.dcRouter.smartProxy ? this.dcRouter.smartProxy.getMetrics() : null;
297
297
 
298
298
  if (!proxyMetrics) {
299
- return [];
299
+ return [] as Array<{ type: string; count: number; source: string; lastActivity: Date }>;
300
300
  }
301
-
301
+
302
302
  const connectionsByRoute = proxyMetrics.connections.byRoute();
303
- const connectionInfo = [];
303
+ const connectionInfo: Array<{ type: string; count: number; source: string; lastActivity: Date }> = [];
304
304
 
305
305
  for (const [routeName, count] of connectionsByRoute) {
306
306
  connectionInfo.push({
@@ -7,7 +7,7 @@ import { requireValidIdentity, requireAdminIdentity } from './helpers/guards.js'
7
7
 
8
8
  export class OpsServer {
9
9
  public dcRouterRef: DcRouter;
10
- public server: plugins.typedserver.utilityservers.UtilityWebsiteServer;
10
+ public server!: plugins.typedserver.utilityservers.UtilityWebsiteServer;
11
11
 
12
12
  // Main TypedRouter — unauthenticated endpoints (login/logout/verify) and own-auth handlers
13
13
  public typedrouter = new plugins.typedrequest.TypedRouter();
@@ -17,17 +17,17 @@ export class OpsServer {
17
17
  public adminRouter = new plugins.typedrequest.TypedRouter<{ request: { identity: interfaces.data.IIdentity } }>();
18
18
 
19
19
  // Handler instances
20
- public adminHandler: handlers.AdminHandler;
21
- private configHandler: handlers.ConfigHandler;
22
- private logsHandler: handlers.LogsHandler;
23
- private securityHandler: handlers.SecurityHandler;
24
- private statsHandler: handlers.StatsHandler;
25
- private radiusHandler: handlers.RadiusHandler;
26
- private emailOpsHandler: handlers.EmailOpsHandler;
27
- private certificateHandler: handlers.CertificateHandler;
28
- private remoteIngressHandler: handlers.RemoteIngressHandler;
29
- private routeManagementHandler: handlers.RouteManagementHandler;
30
- private apiTokenHandler: handlers.ApiTokenHandler;
20
+ public adminHandler!: handlers.AdminHandler;
21
+ private configHandler!: handlers.ConfigHandler;
22
+ private logsHandler!: handlers.LogsHandler;
23
+ private securityHandler!: handlers.SecurityHandler;
24
+ private statsHandler!: handlers.StatsHandler;
25
+ private radiusHandler!: handlers.RadiusHandler;
26
+ private emailOpsHandler!: handlers.EmailOpsHandler;
27
+ private certificateHandler!: handlers.CertificateHandler;
28
+ private remoteIngressHandler!: handlers.RemoteIngressHandler;
29
+ private routeManagementHandler!: handlers.RouteManagementHandler;
30
+ private apiTokenHandler!: handlers.ApiTokenHandler;
31
31
 
32
32
  constructor(dcRouterRefArg: DcRouter) {
33
33
  this.dcRouterRef = dcRouterRefArg;
@@ -39,7 +39,7 @@ export class OpsServer {
39
39
  public async start() {
40
40
  this.server = new plugins.typedserver.utilityservers.UtilityWebsiteServer({
41
41
  domain: 'localhost',
42
- feedMetadata: null,
42
+ feedMetadata: undefined,
43
43
  serveDir: paths.distServe,
44
44
  });
45
45
 
@@ -12,7 +12,7 @@ export class AdminHandler {
12
12
  public typedrouter = new plugins.typedrequest.TypedRouter();
13
13
 
14
14
  // JWT instance
15
- public smartjwtInstance: plugins.smartjwt.SmartJwt<IJwtData>;
15
+ public smartjwtInstance!: plugins.smartjwt.SmartJwt<IJwtData>;
16
16
 
17
17
  // Simple in-memory user storage (in production, use proper database)
18
18
  private users = new Map<string, {
@@ -311,8 +311,8 @@ export class CertificateHandler {
311
311
  }
312
312
  }
313
313
  return { success: true, message: `Certificate reprovisioning triggered for route '${routeName}'` };
314
- } catch (err) {
315
- return { success: false, message: err.message || 'Failed to reprovision certificate' };
314
+ } catch (err: unknown) {
315
+ return { success: false, message: (err as Error).message || 'Failed to reprovision certificate' };
316
316
  }
317
317
  }
318
318
 
@@ -340,8 +340,8 @@ export class CertificateHandler {
340
340
  try {
341
341
  await dcRouter.smartAcme.getCertificateForDomain(domain);
342
342
  return { success: true, message: `Certificate reprovisioning triggered for domain '${domain}'` };
343
- } catch (err) {
344
- return { success: false, message: err.message || `Failed to reprovision certificate for ${domain}` };
343
+ } catch (err: unknown) {
344
+ return { success: false, message: (err as Error).message || `Failed to reprovision certificate for ${domain}` };
345
345
  }
346
346
  }
347
347
 
@@ -351,8 +351,8 @@ export class CertificateHandler {
351
351
  try {
352
352
  await smartProxy.provisionCertificate(routeNames[0]);
353
353
  return { success: true, message: `Certificate reprovisioning triggered for domain '${domain}' via route '${routeNames[0]}'` };
354
- } catch (err) {
355
- return { success: false, message: err.message || `Failed to reprovision certificate for ${domain}` };
354
+ } catch (err: unknown) {
355
+ return { success: false, message: (err as Error).message || `Failed to reprovision certificate for ${domain}` };
356
356
  }
357
357
  }
358
358
 
@@ -52,8 +52,8 @@ export class RadiusHandler {
52
52
  try {
53
53
  await radiusServer.addClient(dataArg.client);
54
54
  return { success: true };
55
- } catch (error) {
56
- return { success: false, message: error.message };
55
+ } catch (error: unknown) {
56
+ return { success: false, message: (error as Error).message };
57
57
  }
58
58
  }
59
59
  )
@@ -144,8 +144,8 @@ export class RadiusHandler {
144
144
  updatedAt: mapping.updatedAt,
145
145
  },
146
146
  };
147
- } catch (error) {
148
- return { success: false, message: error.message };
147
+ } catch (error: unknown) {
148
+ return { success: false, message: (error as Error).message };
149
149
  }
150
150
  }
151
151
  )