@serve.zone/dcrouter 11.8.11 → 11.9.1
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 +2 -2
- package/dist_ts/classes.cert-provision-scheduler.d.ts +2 -1
- package/dist_ts/classes.cert-provision-scheduler.js +13 -5
- package/dist_ts/classes.dcrouter.d.ts +8 -0
- package/dist_ts/classes.dcrouter.js +247 -119
- package/dist_ts/index.js +3 -3
- package/dist_ts/opsserver/handlers/stats.handler.js +31 -28
- package/dist_ts/plugins.d.ts +2 -1
- package/dist_ts/plugins.js +3 -2
- package/dist_ts/radius/classes.accounting.manager.d.ts +13 -0
- package/dist_ts/radius/classes.accounting.manager.js +46 -1
- package/dist_ts/radius/classes.radius.server.js +2 -1
- package/dist_ts_web/00_commitinfo_data.js +2 -2
- package/package.json +4 -3
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.cert-provision-scheduler.ts +14 -4
- package/ts/classes.dcrouter.ts +290 -146
- package/ts/index.ts +2 -2
- package/ts/opsserver/handlers/stats.handler.ts +34 -37
- package/ts/plugins.ts +2 -1
- package/ts/radius/classes.accounting.manager.ts +55 -0
- package/ts/radius/classes.radius.server.ts +2 -0
- package/ts_web/00_commitinfo_data.ts +1 -1
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -252,6 +252,11 @@ export class DcRouter {
|
|
|
252
252
|
// Certificate provisioning scheduler with per-domain backoff
|
|
253
253
|
public certProvisionScheduler?: CertProvisionScheduler;
|
|
254
254
|
|
|
255
|
+
// Service lifecycle management
|
|
256
|
+
public serviceManager: plugins.taskbuffer.ServiceManager;
|
|
257
|
+
private serviceSubjectSubscription?: plugins.smartrx.rxjs.Subscription;
|
|
258
|
+
public smartAcmeReady = false;
|
|
259
|
+
|
|
255
260
|
// TypedRouter for API endpoints
|
|
256
261
|
public typedrouter = new plugins.typedrequest.TypedRouter();
|
|
257
262
|
|
|
@@ -279,67 +284,253 @@ export class DcRouter {
|
|
|
279
284
|
|
|
280
285
|
// Initialize storage manager
|
|
281
286
|
this.storageManager = new StorageManager(this.options.storage);
|
|
287
|
+
|
|
288
|
+
// Initialize service manager and register all services
|
|
289
|
+
this.serviceManager = new plugins.taskbuffer.ServiceManager({
|
|
290
|
+
name: 'dcrouter',
|
|
291
|
+
startupTimeoutMs: 120_000,
|
|
292
|
+
shutdownTimeoutMs: 30_000,
|
|
293
|
+
});
|
|
294
|
+
this.registerServices();
|
|
282
295
|
}
|
|
283
296
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
297
|
+
/**
|
|
298
|
+
* Register all dcrouter services with the ServiceManager.
|
|
299
|
+
* Services are started in dependency order, with failure isolation for optional services.
|
|
300
|
+
*/
|
|
301
|
+
private registerServices(): void {
|
|
302
|
+
// OpsServer: critical, no dependencies — provides visibility
|
|
303
|
+
this.serviceManager.addService(
|
|
304
|
+
new plugins.taskbuffer.Service('OpsServer')
|
|
305
|
+
.critical()
|
|
306
|
+
.withStart(async () => {
|
|
307
|
+
this.opsServer = new OpsServer(this);
|
|
308
|
+
await this.opsServer.start();
|
|
309
|
+
})
|
|
310
|
+
.withStop(async () => {
|
|
311
|
+
await this.opsServer?.stop();
|
|
312
|
+
})
|
|
313
|
+
.withRetry({ maxRetries: 0 }),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
// CacheDb: optional, no dependencies
|
|
317
|
+
if (this.options.cacheConfig?.enabled !== false) {
|
|
318
|
+
this.serviceManager.addService(
|
|
319
|
+
new plugins.taskbuffer.Service('CacheDb')
|
|
320
|
+
.optional()
|
|
321
|
+
.withStart(async () => {
|
|
322
|
+
await this.setupCacheDb();
|
|
323
|
+
})
|
|
324
|
+
.withStop(async () => {
|
|
325
|
+
if (this.cacheCleaner) {
|
|
326
|
+
this.cacheCleaner.stop();
|
|
327
|
+
this.cacheCleaner = undefined;
|
|
328
|
+
}
|
|
329
|
+
if (this.cacheDb) {
|
|
330
|
+
await this.cacheDb.stop();
|
|
331
|
+
CacheDb.resetInstance();
|
|
332
|
+
this.cacheDb = undefined;
|
|
333
|
+
}
|
|
334
|
+
})
|
|
335
|
+
.withRetry({ maxRetries: 2, baseDelayMs: 1000, maxDelayMs: 5000 }),
|
|
336
|
+
);
|
|
337
|
+
}
|
|
287
338
|
|
|
288
|
-
|
|
289
|
-
|
|
339
|
+
// MetricsManager: optional, depends on OpsServer
|
|
340
|
+
this.serviceManager.addService(
|
|
341
|
+
new plugins.taskbuffer.Service('MetricsManager')
|
|
342
|
+
.optional()
|
|
343
|
+
.dependsOn('OpsServer')
|
|
344
|
+
.withStart(async () => {
|
|
345
|
+
this.metricsManager = new MetricsManager(this);
|
|
346
|
+
await this.metricsManager.start();
|
|
347
|
+
})
|
|
348
|
+
.withStop(async () => {
|
|
349
|
+
if (this.metricsManager) {
|
|
350
|
+
await this.metricsManager.stop();
|
|
351
|
+
this.metricsManager = undefined;
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
.withRetry({ maxRetries: 1, baseDelayMs: 1000 }),
|
|
355
|
+
);
|
|
290
356
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
357
|
+
// SmartProxy: critical, depends on CacheDb (if enabled)
|
|
358
|
+
const smartProxyDeps: string[] = [];
|
|
359
|
+
if (this.options.cacheConfig?.enabled !== false) {
|
|
360
|
+
smartProxyDeps.push('CacheDb');
|
|
361
|
+
}
|
|
362
|
+
this.serviceManager.addService(
|
|
363
|
+
new plugins.taskbuffer.Service('SmartProxy')
|
|
364
|
+
.critical()
|
|
365
|
+
.dependsOn(...smartProxyDeps)
|
|
366
|
+
.withStart(async () => {
|
|
367
|
+
await this.setupSmartProxy();
|
|
368
|
+
})
|
|
369
|
+
.withStop(async () => {
|
|
370
|
+
if (this.smartProxy) {
|
|
371
|
+
this.smartProxy.removeAllListeners();
|
|
372
|
+
await this.smartProxy.stop();
|
|
373
|
+
this.smartProxy = undefined;
|
|
374
|
+
}
|
|
375
|
+
})
|
|
376
|
+
.withRetry({ maxRetries: 0 }),
|
|
377
|
+
);
|
|
296
378
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
379
|
+
// SmartAcme: optional, depends on SmartProxy — aggressive retry for rate limits
|
|
380
|
+
// Only registered if DNS challenge is configured
|
|
381
|
+
if (this.options.dnsChallenge?.cloudflareApiKey) {
|
|
382
|
+
this.serviceManager.addService(
|
|
383
|
+
new plugins.taskbuffer.Service('SmartAcme')
|
|
384
|
+
.optional()
|
|
385
|
+
.dependsOn('SmartProxy')
|
|
386
|
+
.withStart(async () => {
|
|
387
|
+
if (this.smartAcme) {
|
|
388
|
+
await this.smartAcme.start();
|
|
389
|
+
this.smartAcmeReady = true;
|
|
390
|
+
logger.log('info', 'SmartAcme DNS-01 provider is now ready');
|
|
391
|
+
}
|
|
392
|
+
})
|
|
393
|
+
.withStop(async () => {
|
|
394
|
+
this.smartAcmeReady = false;
|
|
395
|
+
if (this.smartAcme) {
|
|
396
|
+
await this.smartAcme.stop();
|
|
397
|
+
this.smartAcme = undefined;
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
.withRetry({ maxRetries: 20, baseDelayMs: 5000, maxDelayMs: 3_600_000, backoffFactor: 2 }),
|
|
310
401
|
);
|
|
311
|
-
|
|
312
|
-
await this.apiTokenManager.initialize();
|
|
313
|
-
await this.routeConfigManager.initialize();
|
|
402
|
+
}
|
|
314
403
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
404
|
+
// ConfigManagers: optional, depends on SmartProxy
|
|
405
|
+
this.serviceManager.addService(
|
|
406
|
+
new plugins.taskbuffer.Service('ConfigManagers')
|
|
407
|
+
.optional()
|
|
408
|
+
.dependsOn('SmartProxy')
|
|
409
|
+
.withStart(async () => {
|
|
410
|
+
this.routeConfigManager = new RouteConfigManager(
|
|
411
|
+
this.storageManager,
|
|
412
|
+
() => this.getConstructorRoutes(),
|
|
413
|
+
() => this.smartProxy,
|
|
414
|
+
() => this.options.http3,
|
|
415
|
+
);
|
|
416
|
+
this.apiTokenManager = new ApiTokenManager(this.storageManager);
|
|
417
|
+
await this.apiTokenManager.initialize();
|
|
418
|
+
await this.routeConfigManager.initialize();
|
|
419
|
+
})
|
|
420
|
+
.withStop(async () => {
|
|
421
|
+
this.routeConfigManager = undefined;
|
|
422
|
+
this.apiTokenManager = undefined;
|
|
423
|
+
})
|
|
424
|
+
.withRetry({ maxRetries: 2, baseDelayMs: 1000 }),
|
|
425
|
+
);
|
|
325
426
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
427
|
+
// Email Server: optional, depends on SmartProxy
|
|
428
|
+
if (this.options.emailConfig) {
|
|
429
|
+
this.serviceManager.addService(
|
|
430
|
+
new plugins.taskbuffer.Service('EmailServer')
|
|
431
|
+
.optional()
|
|
432
|
+
.dependsOn('SmartProxy')
|
|
433
|
+
.withStart(async () => {
|
|
434
|
+
await this.setupUnifiedEmailHandling();
|
|
435
|
+
})
|
|
436
|
+
.withStop(async () => {
|
|
437
|
+
if (this.emailServer) {
|
|
438
|
+
if ((this.emailServer as any).deliverySystem) {
|
|
439
|
+
(this.emailServer as any).deliverySystem.removeAllListeners();
|
|
440
|
+
}
|
|
441
|
+
this.emailServer.removeAllListeners();
|
|
442
|
+
await this.emailServer.stop();
|
|
443
|
+
this.emailServer = undefined;
|
|
444
|
+
}
|
|
445
|
+
})
|
|
446
|
+
.withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
|
|
447
|
+
);
|
|
448
|
+
}
|
|
330
449
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
450
|
+
// DNS Server: optional, depends on SmartProxy
|
|
451
|
+
if (this.options.dnsNsDomains?.length > 0 && this.options.dnsScopes?.length > 0) {
|
|
452
|
+
this.serviceManager.addService(
|
|
453
|
+
new plugins.taskbuffer.Service('DnsServer')
|
|
454
|
+
.optional()
|
|
455
|
+
.dependsOn('SmartProxy')
|
|
456
|
+
.withStart(async () => {
|
|
457
|
+
await this.setupDnsWithSocketHandler();
|
|
458
|
+
})
|
|
459
|
+
.withStop(async () => {
|
|
460
|
+
// Flush pending DNS batch log
|
|
461
|
+
if (this.dnsBatchTimer) {
|
|
462
|
+
clearTimeout(this.dnsBatchTimer);
|
|
463
|
+
if (this.dnsBatchCount > 0) {
|
|
464
|
+
logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (final flush)`, { zone: 'dns' });
|
|
465
|
+
}
|
|
466
|
+
this.dnsBatchTimer = null;
|
|
467
|
+
this.dnsBatchCount = 0;
|
|
468
|
+
this.dnsLogWindowSecond = 0;
|
|
469
|
+
this.dnsLogWindowCount = 0;
|
|
470
|
+
}
|
|
471
|
+
if (this.dnsServer) {
|
|
472
|
+
this.dnsServer.removeAllListeners();
|
|
473
|
+
await this.dnsServer.stop();
|
|
474
|
+
this.dnsServer = undefined;
|
|
475
|
+
}
|
|
476
|
+
})
|
|
477
|
+
.withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
|
|
478
|
+
);
|
|
479
|
+
}
|
|
335
480
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
481
|
+
// RADIUS Server: optional, no dependency on SmartProxy
|
|
482
|
+
if (this.options.radiusConfig) {
|
|
483
|
+
this.serviceManager.addService(
|
|
484
|
+
new plugins.taskbuffer.Service('RadiusServer')
|
|
485
|
+
.optional()
|
|
486
|
+
.withStart(async () => {
|
|
487
|
+
await this.setupRadiusServer();
|
|
488
|
+
})
|
|
489
|
+
.withStop(async () => {
|
|
490
|
+
if (this.radiusServer) {
|
|
491
|
+
await this.radiusServer.stop();
|
|
492
|
+
this.radiusServer = undefined;
|
|
493
|
+
}
|
|
494
|
+
})
|
|
495
|
+
.withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
|
|
496
|
+
);
|
|
342
497
|
}
|
|
498
|
+
|
|
499
|
+
// Remote Ingress: optional, depends on SmartProxy
|
|
500
|
+
if (this.options.remoteIngressConfig?.enabled) {
|
|
501
|
+
this.serviceManager.addService(
|
|
502
|
+
new plugins.taskbuffer.Service('RemoteIngress')
|
|
503
|
+
.optional()
|
|
504
|
+
.dependsOn('SmartProxy')
|
|
505
|
+
.withStart(async () => {
|
|
506
|
+
await this.setupRemoteIngress();
|
|
507
|
+
})
|
|
508
|
+
.withStop(async () => {
|
|
509
|
+
if (this.tunnelManager) {
|
|
510
|
+
await this.tunnelManager.stop();
|
|
511
|
+
this.tunnelManager = undefined;
|
|
512
|
+
}
|
|
513
|
+
this.remoteIngressManager = undefined;
|
|
514
|
+
})
|
|
515
|
+
.withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Wire up aggregated events for logging
|
|
520
|
+
this.serviceSubjectSubscription = this.serviceManager.serviceSubject.subscribe((event) => {
|
|
521
|
+
const level = event.type === 'failed' ? 'error' : event.type === 'retrying' ? 'warn' : 'info';
|
|
522
|
+
logger.log(level as any, `Service '${event.serviceName}': ${event.type}`, {
|
|
523
|
+
state: event.state,
|
|
524
|
+
...(event.error ? { error: event.error } : {}),
|
|
525
|
+
...(event.attempt ? { attempt: event.attempt } : {}),
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
public async start() {
|
|
531
|
+
logger.log('info', 'Starting DcRouter Services');
|
|
532
|
+
await this.serviceManager.start();
|
|
533
|
+
this.logStartupSummary();
|
|
343
534
|
}
|
|
344
535
|
|
|
345
536
|
/**
|
|
@@ -399,7 +590,21 @@ export class DcRouter {
|
|
|
399
590
|
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)`);
|
|
400
591
|
}
|
|
401
592
|
|
|
402
|
-
|
|
593
|
+
// Service status summary from ServiceManager
|
|
594
|
+
const health = this.serviceManager.getHealth();
|
|
595
|
+
const statuses = health.services;
|
|
596
|
+
const running = statuses.filter(s => s.state === 'running').length;
|
|
597
|
+
const failed = statuses.filter(s => s.state === 'failed').length;
|
|
598
|
+
const retrying = statuses.filter(s => s.state === 'starting' || s.state === 'degraded').length;
|
|
599
|
+
|
|
600
|
+
if (failed > 0) {
|
|
601
|
+
const failedNames = statuses.filter(s => s.state === 'failed').map(s => `${s.name}: ${s.lastError || 'unknown'}`);
|
|
602
|
+
logger.log('warn', `DcRouter started in degraded mode — ${running} running, ${failed} failed: ${failedNames.join('; ')}`);
|
|
603
|
+
} else if (retrying > 0) {
|
|
604
|
+
logger.log('info', `DcRouter started — ${running} running, ${retrying} still initializing`);
|
|
605
|
+
} else {
|
|
606
|
+
logger.log('info', `All ${running} services are running`);
|
|
607
|
+
}
|
|
403
608
|
}
|
|
404
609
|
|
|
405
610
|
/**
|
|
@@ -435,6 +640,13 @@ export class DcRouter {
|
|
|
435
640
|
*/
|
|
436
641
|
private async setupSmartProxy(): Promise<void> {
|
|
437
642
|
logger.log('info', 'Setting up SmartProxy...');
|
|
643
|
+
|
|
644
|
+
// Clean up any existing SmartProxy instance (e.g. from a retry)
|
|
645
|
+
if (this.smartProxy) {
|
|
646
|
+
this.smartProxy.removeAllListeners();
|
|
647
|
+
this.smartProxy = undefined;
|
|
648
|
+
}
|
|
649
|
+
|
|
438
650
|
let routes: plugins.smartproxy.IRouteConfig[] = [];
|
|
439
651
|
let acmeConfig: plugins.smartproxy.IAcmeOptions | undefined;
|
|
440
652
|
|
|
@@ -535,10 +747,13 @@ export class DcRouter {
|
|
|
535
747
|
// Initialize cert provision scheduler
|
|
536
748
|
this.certProvisionScheduler = new CertProvisionScheduler(this.storageManager);
|
|
537
749
|
|
|
538
|
-
// If we have DNS challenge handlers, create SmartAcme and wire
|
|
750
|
+
// If we have DNS challenge handlers, create SmartAcme instance and wire certProvisionFunction
|
|
751
|
+
// Note: SmartAcme.start() is NOT called here — it runs as a separate optional service
|
|
752
|
+
// via the ServiceManager, with aggressive retry for rate-limit resilience.
|
|
539
753
|
if (challengeHandlers.length > 0) {
|
|
540
754
|
// Stop old SmartAcme if it exists (e.g., during updateSmartProxyConfig)
|
|
541
755
|
if (this.smartAcme) {
|
|
756
|
+
this.smartAcmeReady = false;
|
|
542
757
|
await this.smartAcme.stop().catch(err =>
|
|
543
758
|
logger.log('error', 'Error stopping old SmartAcme', { error: String(err) })
|
|
544
759
|
);
|
|
@@ -550,10 +765,15 @@ export class DcRouter {
|
|
|
550
765
|
challengeHandlers: challengeHandlers,
|
|
551
766
|
challengePriority: ['dns-01'],
|
|
552
767
|
});
|
|
553
|
-
await this.smartAcme.start();
|
|
554
768
|
|
|
555
769
|
const scheduler = this.certProvisionScheduler;
|
|
556
770
|
smartProxyConfig.certProvisionFunction = async (domain, eventComms) => {
|
|
771
|
+
// If SmartAcme is not yet ready (still starting or retrying), fall back to HTTP-01
|
|
772
|
+
if (!this.smartAcmeReady) {
|
|
773
|
+
eventComms.warn(`SmartAcme not yet initialized, falling back to http-01 for ${domain}`);
|
|
774
|
+
return 'http01';
|
|
775
|
+
}
|
|
776
|
+
|
|
557
777
|
// Check backoff before attempting provision
|
|
558
778
|
if (await scheduler.isInBackoff(domain)) {
|
|
559
779
|
const info = await scheduler.getBackoffInfo(domain);
|
|
@@ -914,105 +1134,29 @@ export class DcRouter {
|
|
|
914
1134
|
public async stop() {
|
|
915
1135
|
logger.log('info', 'Stopping DcRouter services...');
|
|
916
1136
|
|
|
917
|
-
//
|
|
918
|
-
if (this.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
logger.log('info', `DNS: ${this.dnsBatchCount} queries processed (rate limited, final flush)`, { zone: 'dns' });
|
|
922
|
-
}
|
|
923
|
-
this.dnsBatchTimer = null;
|
|
924
|
-
this.dnsBatchCount = 0;
|
|
925
|
-
this.dnsLogWindowSecond = 0;
|
|
926
|
-
this.dnsLogWindowCount = 0;
|
|
1137
|
+
// Unsubscribe from service events before stopping services
|
|
1138
|
+
if (this.serviceSubjectSubscription) {
|
|
1139
|
+
this.serviceSubjectSubscription.unsubscribe();
|
|
1140
|
+
this.serviceSubjectSubscription = undefined;
|
|
927
1141
|
}
|
|
928
1142
|
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
try {
|
|
932
|
-
// Remove event listeners before stopping services to prevent leaks
|
|
933
|
-
if (this.smartProxy) {
|
|
934
|
-
this.smartProxy.removeAllListeners();
|
|
935
|
-
}
|
|
936
|
-
if (this.emailServer) {
|
|
937
|
-
if ((this.emailServer as any).deliverySystem) {
|
|
938
|
-
(this.emailServer as any).deliverySystem.removeAllListeners();
|
|
939
|
-
}
|
|
940
|
-
this.emailServer.removeAllListeners();
|
|
941
|
-
}
|
|
942
|
-
if (this.dnsServer) {
|
|
943
|
-
this.dnsServer.removeAllListeners();
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// Stop all services in parallel for faster shutdown
|
|
947
|
-
await Promise.all([
|
|
948
|
-
// Stop cache cleaner if running
|
|
949
|
-
this.cacheCleaner ? Promise.resolve(this.cacheCleaner.stop()) : Promise.resolve(),
|
|
950
|
-
|
|
951
|
-
// Stop metrics manager if running
|
|
952
|
-
this.metricsManager ? this.metricsManager.stop().catch(err => logger.log('error', 'Error stopping MetricsManager', { error: String(err) })) : Promise.resolve(),
|
|
953
|
-
|
|
954
|
-
// Stop unified email server if running
|
|
955
|
-
this.emailServer ? this.emailServer.stop().catch(err => logger.log('error', 'Error stopping email server', { error: String(err) })) : Promise.resolve(),
|
|
956
|
-
|
|
957
|
-
// Stop SmartAcme if running
|
|
958
|
-
this.smartAcme ? this.smartAcme.stop().catch(err => logger.log('error', 'Error stopping SmartAcme', { error: String(err) })) : Promise.resolve(),
|
|
959
|
-
|
|
960
|
-
// Stop HTTP SmartProxy if running
|
|
961
|
-
this.smartProxy ? this.smartProxy.stop().catch(err => logger.log('error', 'Error stopping SmartProxy', { error: String(err) })) : Promise.resolve(),
|
|
962
|
-
|
|
963
|
-
// Stop DNS server if running
|
|
964
|
-
this.dnsServer ?
|
|
965
|
-
this.dnsServer.stop().catch(err => logger.log('error', 'Error stopping DNS server', { error: String(err) })) :
|
|
966
|
-
Promise.resolve(),
|
|
967
|
-
|
|
968
|
-
// Stop RADIUS server if running
|
|
969
|
-
this.radiusServer ?
|
|
970
|
-
this.radiusServer.stop().catch(err => logger.log('error', 'Error stopping RADIUS server', { error: String(err) })) :
|
|
971
|
-
Promise.resolve(),
|
|
1143
|
+
// ServiceManager handles reverse-dependency-ordered shutdown
|
|
1144
|
+
await this.serviceManager.stop();
|
|
972
1145
|
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
Promise.resolve()
|
|
977
|
-
]);
|
|
978
|
-
|
|
979
|
-
// Stop cache database after other services (they may need it during shutdown)
|
|
980
|
-
if (this.cacheDb) {
|
|
981
|
-
await this.cacheDb.stop().catch(err => logger.log('error', 'Error stopping CacheDb', { error: String(err) }));
|
|
982
|
-
CacheDb.resetInstance();
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
// Clear backoff cache in cert scheduler
|
|
986
|
-
if (this.certProvisionScheduler) {
|
|
987
|
-
this.certProvisionScheduler.clear();
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
// Allow GC of stopped services by nulling references
|
|
991
|
-
this.smartProxy = undefined;
|
|
992
|
-
this.emailServer = undefined;
|
|
993
|
-
this.dnsServer = undefined;
|
|
994
|
-
this.metricsManager = undefined;
|
|
995
|
-
this.cacheCleaner = undefined;
|
|
996
|
-
this.cacheDb = undefined;
|
|
997
|
-
this.tunnelManager = undefined;
|
|
998
|
-
this.radiusServer = undefined;
|
|
999
|
-
this.smartAcme = undefined;
|
|
1146
|
+
// Clear backoff cache in cert scheduler
|
|
1147
|
+
if (this.certProvisionScheduler) {
|
|
1148
|
+
this.certProvisionScheduler.clear();
|
|
1000
1149
|
this.certProvisionScheduler = undefined;
|
|
1001
|
-
|
|
1002
|
-
this.routeConfigManager = undefined;
|
|
1003
|
-
this.apiTokenManager = undefined;
|
|
1004
|
-
this.certificateStatusMap.clear();
|
|
1150
|
+
}
|
|
1005
1151
|
|
|
1006
|
-
|
|
1007
|
-
SecurityLogger.resetInstance();
|
|
1008
|
-
ContentScanner.resetInstance();
|
|
1009
|
-
IPReputationChecker.resetInstance();
|
|
1152
|
+
this.certificateStatusMap.clear();
|
|
1010
1153
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1154
|
+
// Reset security singletons to allow GC
|
|
1155
|
+
SecurityLogger.resetInstance();
|
|
1156
|
+
ContentScanner.resetInstance();
|
|
1157
|
+
IPReputationChecker.resetInstance();
|
|
1158
|
+
|
|
1159
|
+
logger.log('info', 'All DcRouter services stopped');
|
|
1016
1160
|
}
|
|
1017
1161
|
|
|
1018
1162
|
/**
|
package/ts/index.ts
CHANGED
|
@@ -489,44 +489,41 @@ export class StatsHandler {
|
|
|
489
489
|
message?: string;
|
|
490
490
|
}>;
|
|
491
491
|
}> {
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
services.push({
|
|
524
|
-
name: 'OpsServer',
|
|
525
|
-
status: 'healthy',
|
|
492
|
+
const dcRouter = this.opsServerRef.dcRouterRef;
|
|
493
|
+
const health = dcRouter.serviceManager.getHealth();
|
|
494
|
+
|
|
495
|
+
const services = health.services.map((svc) => {
|
|
496
|
+
let status: 'healthy' | 'degraded' | 'unhealthy';
|
|
497
|
+
switch (svc.state) {
|
|
498
|
+
case 'running':
|
|
499
|
+
status = 'healthy';
|
|
500
|
+
break;
|
|
501
|
+
case 'starting':
|
|
502
|
+
case 'degraded':
|
|
503
|
+
status = 'degraded';
|
|
504
|
+
break;
|
|
505
|
+
case 'failed':
|
|
506
|
+
status = svc.criticality === 'critical' ? 'unhealthy' : 'degraded';
|
|
507
|
+
break;
|
|
508
|
+
case 'stopped':
|
|
509
|
+
case 'stopping':
|
|
510
|
+
default:
|
|
511
|
+
status = 'degraded';
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
let message: string | undefined;
|
|
516
|
+
if (svc.state === 'failed' && svc.lastError) {
|
|
517
|
+
message = svc.lastError;
|
|
518
|
+
} else if (svc.retryCount > 0 && svc.state !== 'running') {
|
|
519
|
+
message = `Retry attempt ${svc.retryCount}`;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return { name: svc.name, status, message };
|
|
526
523
|
});
|
|
527
|
-
|
|
528
|
-
const healthy =
|
|
529
|
-
|
|
524
|
+
|
|
525
|
+
const healthy = health.overall === 'healthy';
|
|
526
|
+
|
|
530
527
|
return {
|
|
531
528
|
healthy,
|
|
532
529
|
services,
|
package/ts/plugins.ts
CHANGED
|
@@ -62,8 +62,9 @@ import * as smartradius from '@push.rocks/smartradius';
|
|
|
62
62
|
import * as smartrequest from '@push.rocks/smartrequest';
|
|
63
63
|
import * as smartrx from '@push.rocks/smartrx';
|
|
64
64
|
import * as smartunique from '@push.rocks/smartunique';
|
|
65
|
+
import * as taskbuffer from '@push.rocks/taskbuffer';
|
|
65
66
|
|
|
66
|
-
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, smartguard, smartjwt, smartlog, smartmetrics, smartmongo, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique };
|
|
67
|
+
export { projectinfo, qenv, smartacme, smartdata, smartdns, smartfile, smartguard, smartjwt, smartlog, smartmetrics, smartmongo, smartmta, smartnetwork, smartpath, smartproxy, smartpromise, smartradius, smartrequest, smartrx, smartunique, taskbuffer };
|
|
67
68
|
|
|
68
69
|
// Define SmartLog types for use in error handling
|
|
69
70
|
export type TLogLevel = 'error' | 'warn' | 'info' | 'success' | 'debug';
|