@serve.zone/dcrouter 13.43.5 → 13.44.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/deno.json +1 -1
- package/dist_serve/bundle.js +491 -436
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +16 -13
- package/dist_ts/classes.dcrouter.js +320 -82
- package/dist_ts/config/classes.route-config-manager.d.ts +1 -0
- package/dist_ts/config/classes.route-config-manager.js +4 -1
- package/dist_ts/db/documents/classes.email-server-settings.doc.d.ts +14 -0
- package/dist_ts/db/documents/classes.email-server-settings.doc.js +103 -0
- package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.d.ts +3 -0
- package/dist_ts/db/documents/classes.remote-ingress-hub-settings.doc.js +20 -2
- package/dist_ts/db/documents/index.d.ts +1 -0
- package/dist_ts/db/documents/index.js +2 -1
- package/dist_ts/email/classes.email-domain.manager.d.ts +3 -1
- package/dist_ts/email/classes.email-domain.manager.js +6 -3
- package/dist_ts/email/classes.email-settings.manager.d.ts +25 -0
- package/dist_ts/email/classes.email-settings.manager.js +184 -0
- package/dist_ts/email/index.d.ts +1 -0
- package/dist_ts/email/index.js +2 -1
- package/dist_ts/opsserver/classes.opsserver.d.ts +1 -0
- package/dist_ts/opsserver/classes.opsserver.js +3 -1
- package/dist_ts/opsserver/handlers/config.handler.js +17 -14
- package/dist_ts/opsserver/handlers/email-settings.handler.d.ts +10 -0
- package/dist_ts/opsserver/handlers/email-settings.handler.js +57 -0
- package/dist_ts/opsserver/handlers/index.d.ts +1 -0
- package/dist_ts/opsserver/handlers/index.js +2 -1
- package/dist_ts/opsserver/handlers/remoteingress.handler.js +24 -6
- package/dist_ts/opsserver/handlers/workhoster.handler.js +2 -2
- package/dist_ts/remoteingress/classes.remoteingress-manager.d.ts +4 -6
- package/dist_ts/remoteingress/classes.remoteingress-manager.js +58 -19
- package/dist_ts_interfaces/data/email-settings.d.ts +39 -0
- package/dist_ts_interfaces/data/email-settings.js +2 -0
- package/dist_ts_interfaces/data/index.d.ts +1 -0
- package/dist_ts_interfaces/data/index.js +2 -1
- package/dist_ts_interfaces/data/remoteingress.d.ts +7 -0
- package/dist_ts_interfaces/requests/email-settings.d.ts +26 -0
- package/dist_ts_interfaces/requests/email-settings.js +2 -0
- package/dist_ts_interfaces/requests/index.d.ts +1 -0
- package/dist_ts_interfaces/requests/index.js +2 -1
- package/dist_ts_interfaces/requests/remoteingress.d.ts +4 -1
- package/dist_ts_migrations/index.d.ts +14 -1
- package/dist_ts_migrations/index.js +107 -2
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.d.ts +6 -1
- package/dist_ts_web/appstate.js +23 -1
- package/dist_ts_web/elements/email/ops-view-email-domains.d.ts +4 -0
- package/dist_ts_web/elements/email/ops-view-email-domains.js +122 -1
- package/dist_ts_web/elements/network/ops-view-remoteingress.d.ts +2 -1
- package/dist_ts_web/elements/network/ops-view-remoteingress.js +57 -17
- package/package.json +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +361 -100
- package/ts/config/classes.route-config-manager.ts +4 -0
- package/ts/db/documents/classes.email-server-settings.doc.ts +40 -0
- package/ts/db/documents/classes.remote-ingress-hub-settings.doc.ts +9 -0
- package/ts/db/documents/index.ts +1 -0
- package/ts/email/classes.email-domain.manager.ts +6 -2
- package/ts/email/classes.email-settings.manager.ts +221 -0
- package/ts/email/index.ts +1 -0
- package/ts/opsserver/classes.opsserver.ts +2 -0
- package/ts/opsserver/handlers/config.handler.ts +16 -13
- package/ts/opsserver/handlers/email-settings.handler.ts +72 -0
- package/ts/opsserver/handlers/index.ts +1 -0
- package/ts/opsserver/handlers/remoteingress.handler.ts +25 -5
- package/ts/opsserver/handlers/workhoster.handler.ts +1 -1
- package/ts/remoteingress/classes.remoteingress-manager.ts +65 -18
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +33 -1
- package/ts_web/elements/email/ops-view-email-domains.ts +126 -0
- package/ts_web/elements/network/ops-view-remoteingress.ts +59 -17
package/ts/classes.dcrouter.ts
CHANGED
|
@@ -31,9 +31,10 @@ import { SecurityLogger, ContentScanner, IPReputationChecker, SecurityPolicyMana
|
|
|
31
31
|
import { type IHttp3Config, augmentRoutesWithHttp3 } from './http3/index.js';
|
|
32
32
|
import { DnsManager } from './dns/manager.dns.js';
|
|
33
33
|
import { AcmeConfigManager } from './acme/manager.acme-config.js';
|
|
34
|
-
import { EmailDomainManager, SmartMtaStorageManager, WorkAppMailManager, buildEmailDnsRecords } from './email/index.js';
|
|
34
|
+
import { EmailDomainManager, EmailSettingsManager, SmartMtaStorageManager, WorkAppMailManager, buildEmailDnsRecords } from './email/index.js';
|
|
35
35
|
import type { IRoute } from '../ts_interfaces/data/route-management.js';
|
|
36
|
-
import type {
|
|
36
|
+
import type { IEmailPortConfig, IEmailServerSettings, IEmailServerSettingsSeed, TEmailServerSettingsUpdate } from '../ts_interfaces/data/email-settings.js';
|
|
37
|
+
import type { IDcRouterRouteConfig, IRemoteIngressHubSettings, IRemoteIngressPerformanceConfig, TRemoteIngressHubSettingsUpdate } from '../ts_interfaces/data/remoteingress.js';
|
|
37
38
|
import type { ISecurityCompiledPolicy } from '../ts_interfaces/data/security-policy.js';
|
|
38
39
|
|
|
39
40
|
export interface IDcRouterOptions {
|
|
@@ -57,14 +58,7 @@ export interface IDcRouterOptions {
|
|
|
57
58
|
* Allows configuring specific ports for email handling
|
|
58
59
|
* This overrides the default port mapping in the emailConfig
|
|
59
60
|
*/
|
|
60
|
-
emailPortConfig?:
|
|
61
|
-
/** External to internal port mapping */
|
|
62
|
-
portMapping?: Record<number, number>;
|
|
63
|
-
/** Custom port configuration for specific ports */
|
|
64
|
-
portSettings?: Record<number, any>;
|
|
65
|
-
/** Path to store received emails */
|
|
66
|
-
receivedEmailsPath?: string;
|
|
67
|
-
};
|
|
61
|
+
emailPortConfig?: IEmailPortConfig;
|
|
68
62
|
|
|
69
63
|
/** TLS/certificate configuration */
|
|
70
64
|
tls?: {
|
|
@@ -282,6 +276,8 @@ export class DcRouter {
|
|
|
282
276
|
public remoteIngressManager?: RemoteIngressManager;
|
|
283
277
|
public tunnelManager?: TunnelManager;
|
|
284
278
|
private remoteIngressHubLifecycleChain: Promise<void> = Promise.resolve();
|
|
279
|
+
private smartProxyLifecycleChain: Promise<void> = Promise.resolve();
|
|
280
|
+
private emailLifecycleChain: Promise<void> = Promise.resolve();
|
|
285
281
|
private remoteIngressHubStopping = false;
|
|
286
282
|
private remoteIngressHubGeneration = 0;
|
|
287
283
|
|
|
@@ -300,6 +296,7 @@ export class DcRouter {
|
|
|
300
296
|
|
|
301
297
|
// ACME configuration (DB-backed singleton, replaces tls.contactEmail)
|
|
302
298
|
public acmeConfigManager?: AcmeConfigManager;
|
|
299
|
+
public emailSettingsManager?: EmailSettingsManager;
|
|
303
300
|
public emailDomainManager?: EmailDomainManager;
|
|
304
301
|
public workAppMailManager: WorkAppMailManager;
|
|
305
302
|
public securityPolicyManager?: SecurityPolicyManager;
|
|
@@ -341,7 +338,7 @@ export class DcRouter {
|
|
|
341
338
|
|
|
342
339
|
// Seed routes assembled during setupSmartProxy, passed to RouteConfigManager for DB seeding
|
|
343
340
|
private seedConfigRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
|
344
|
-
private seedEmailRoutes:
|
|
341
|
+
private seedEmailRoutes: IDcRouterRouteConfig[] = [];
|
|
345
342
|
private seedDnsRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
|
346
343
|
// Live DoH routes used during SmartProxy bootstrap before RouteConfigManager re-applies stored routes.
|
|
347
344
|
private runtimeDnsRoutes: plugins.smartproxy.IRouteConfig[] = [];
|
|
@@ -482,7 +479,7 @@ export class DcRouter {
|
|
|
482
479
|
this.serviceManager.addService(
|
|
483
480
|
new plugins.taskbuffer.Service('EmailDomainManager')
|
|
484
481
|
.optional()
|
|
485
|
-
.dependsOn('DcRouterDb')
|
|
482
|
+
.dependsOn('DcRouterDb', 'EmailSettingsManager')
|
|
486
483
|
.withStart(async () => {
|
|
487
484
|
this.emailDomainManager = new EmailDomainManager(this);
|
|
488
485
|
await this.emailDomainManager.start();
|
|
@@ -496,6 +493,28 @@ export class DcRouter {
|
|
|
496
493
|
);
|
|
497
494
|
}
|
|
498
495
|
|
|
496
|
+
// EmailSettingsManager: optional, depends on DcRouterDb — owns the DB-backed
|
|
497
|
+
// singleton email server config and projects it into runtime options before
|
|
498
|
+
// SmartProxy and EmailDomainManager read email settings.
|
|
499
|
+
if (this.options.dbConfig?.enabled !== false) {
|
|
500
|
+
this.serviceManager.addService(
|
|
501
|
+
new plugins.taskbuffer.Service('EmailSettingsManager')
|
|
502
|
+
.optional()
|
|
503
|
+
.dependsOn('DcRouterDb')
|
|
504
|
+
.withStart(async () => {
|
|
505
|
+
this.emailSettingsManager = new EmailSettingsManager(this.options);
|
|
506
|
+
await this.emailSettingsManager.start();
|
|
507
|
+
})
|
|
508
|
+
.withStop(async () => {
|
|
509
|
+
if (this.emailSettingsManager) {
|
|
510
|
+
await this.emailSettingsManager.stop();
|
|
511
|
+
this.emailSettingsManager = undefined;
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
.withRetry({ maxRetries: 1, baseDelayMs: 500 }),
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
|
|
499
518
|
// SecurityPolicyManager: optional, depends on DcRouterDb — owns IP intelligence
|
|
500
519
|
// and compiles the global block policy for SmartProxy and remote ingress edges.
|
|
501
520
|
if (this.options.dbConfig?.enabled !== false) {
|
|
@@ -519,13 +538,34 @@ export class DcRouter {
|
|
|
519
538
|
);
|
|
520
539
|
}
|
|
521
540
|
|
|
541
|
+
// RemoteIngressManager: optional, depends on DcRouterDb — owns DB-backed
|
|
542
|
+
// hub settings and edge registrations. It starts before SmartProxy so
|
|
543
|
+
// SmartProxy can use the DB-backed enabled flag for PROXY protocol setup.
|
|
544
|
+
if (this.options.dbConfig?.enabled !== false) {
|
|
545
|
+
this.serviceManager.addService(
|
|
546
|
+
new plugins.taskbuffer.Service('RemoteIngressManager')
|
|
547
|
+
.optional()
|
|
548
|
+
.dependsOn('DcRouterDb')
|
|
549
|
+
.withStart(async () => {
|
|
550
|
+
this.remoteIngressManager = new RemoteIngressManager();
|
|
551
|
+
await this.remoteIngressManager.initialize();
|
|
552
|
+
})
|
|
553
|
+
.withStop(async () => {
|
|
554
|
+
this.remoteIngressManager = undefined;
|
|
555
|
+
})
|
|
556
|
+
.withRetry({ maxRetries: 1, baseDelayMs: 500 }),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
|
|
522
560
|
// SmartProxy: critical, depends on DcRouterDb + DnsManager + AcmeConfigManager (if enabled)
|
|
523
561
|
const smartProxyDeps: string[] = [];
|
|
524
562
|
if (this.options.dbConfig?.enabled !== false) {
|
|
525
563
|
smartProxyDeps.push('DcRouterDb');
|
|
526
564
|
smartProxyDeps.push('DnsManager');
|
|
527
565
|
smartProxyDeps.push('AcmeConfigManager');
|
|
566
|
+
smartProxyDeps.push('EmailSettingsManager');
|
|
528
567
|
smartProxyDeps.push('SecurityPolicyManager');
|
|
568
|
+
smartProxyDeps.push('RemoteIngressManager');
|
|
529
569
|
}
|
|
530
570
|
this.serviceManager.addService(
|
|
531
571
|
new plugins.taskbuffer.Service('SmartProxy')
|
|
@@ -535,11 +575,20 @@ export class DcRouter {
|
|
|
535
575
|
await this.setupSmartProxy();
|
|
536
576
|
})
|
|
537
577
|
.withStop(async () => {
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
578
|
+
await this.queueSmartProxyLifecycleTask(async () => {
|
|
579
|
+
try {
|
|
580
|
+
if (this.smartProxy) {
|
|
581
|
+
const existingSmartProxy = this.smartProxy;
|
|
582
|
+
existingSmartProxy.removeAllListeners();
|
|
583
|
+
await existingSmartProxy.stop();
|
|
584
|
+
if (this.smartProxy === existingSmartProxy) {
|
|
585
|
+
this.smartProxy = undefined;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} finally {
|
|
589
|
+
await this.stopSmartAcme();
|
|
590
|
+
}
|
|
591
|
+
});
|
|
543
592
|
})
|
|
544
593
|
.withRetry({ maxRetries: 0 }),
|
|
545
594
|
);
|
|
@@ -630,7 +679,7 @@ export class DcRouter {
|
|
|
630
679
|
}
|
|
631
680
|
|
|
632
681
|
// Email Server: optional, depends on SmartProxy
|
|
633
|
-
if (this.options.emailConfig) {
|
|
682
|
+
if (this.options.dbConfig?.enabled !== false || this.options.emailConfig) {
|
|
634
683
|
const emailServiceDeps = ['SmartProxy', 'MetricsManager'];
|
|
635
684
|
if (this.options.dbConfig?.enabled !== false) {
|
|
636
685
|
emailServiceDeps.push('EmailDomainManager');
|
|
@@ -640,14 +689,18 @@ export class DcRouter {
|
|
|
640
689
|
.optional()
|
|
641
690
|
.dependsOn(...emailServiceDeps)
|
|
642
691
|
.withStart(async () => {
|
|
643
|
-
await this.
|
|
692
|
+
await this.queueEmailLifecycleTask(async () => {
|
|
693
|
+
if (!this.options.emailConfig) {
|
|
694
|
+
logger.log('info', 'EmailServer: no email settings configured, skipping startup');
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
await this.setupUnifiedEmailHandling();
|
|
698
|
+
});
|
|
644
699
|
})
|
|
645
700
|
.withStop(async () => {
|
|
646
|
-
|
|
647
|
-
this.
|
|
648
|
-
|
|
649
|
-
this.emailServer = undefined;
|
|
650
|
-
}
|
|
701
|
+
await this.queueEmailLifecycleTask(async () => {
|
|
702
|
+
await this.stopUnifiedEmailComponents();
|
|
703
|
+
});
|
|
651
704
|
})
|
|
652
705
|
.withRetry({ maxRetries: 3, baseDelayMs: 2000, maxDelayMs: 30_000 }),
|
|
653
706
|
);
|
|
@@ -658,7 +711,7 @@ export class DcRouter {
|
|
|
658
711
|
this.serviceManager.addService(
|
|
659
712
|
new plugins.taskbuffer.Service('DnsServer')
|
|
660
713
|
.optional()
|
|
661
|
-
.dependsOn('SmartProxy', ...(this.options.emailConfig ? ['EmailServer'] : []))
|
|
714
|
+
.dependsOn('SmartProxy', ...((this.options.dbConfig?.enabled !== false || this.options.emailConfig) ? ['EmailServer'] : []))
|
|
662
715
|
.withStart(async () => {
|
|
663
716
|
await this.setupDnsWithSocketHandler();
|
|
664
717
|
})
|
|
@@ -702,12 +755,14 @@ export class DcRouter {
|
|
|
702
755
|
);
|
|
703
756
|
}
|
|
704
757
|
|
|
705
|
-
// Remote Ingress: optional, depends on SmartProxy
|
|
706
|
-
|
|
758
|
+
// Remote Ingress: optional, depends on SmartProxy and DB-backed settings.
|
|
759
|
+
// The service starts as a no-op when the DB setting is disabled, so the UI
|
|
760
|
+
// can still manage edge registrations and hub settings.
|
|
761
|
+
if (this.options.dbConfig?.enabled !== false) {
|
|
707
762
|
this.serviceManager.addService(
|
|
708
763
|
new plugins.taskbuffer.Service('RemoteIngress')
|
|
709
764
|
.optional()
|
|
710
|
-
.dependsOn('SmartProxy')
|
|
765
|
+
.dependsOn('SmartProxy', 'RemoteIngressManager')
|
|
711
766
|
.withStart(async () => {
|
|
712
767
|
await this.setupRemoteIngress();
|
|
713
768
|
})
|
|
@@ -752,6 +807,42 @@ export class DcRouter {
|
|
|
752
807
|
});
|
|
753
808
|
}
|
|
754
809
|
|
|
810
|
+
private isRemoteIngressHubEnabled(): boolean {
|
|
811
|
+
return this.remoteIngressManager?.getHubSettings().enabled
|
|
812
|
+
?? this.options.remoteIngressConfig?.enabled
|
|
813
|
+
?? false;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private getRemoteIngressHubSettingsLegacySeed(): TRemoteIngressHubSettingsUpdate {
|
|
817
|
+
const remoteIngressConfig = this.options.remoteIngressConfig;
|
|
818
|
+
const seed: TRemoteIngressHubSettingsUpdate = {};
|
|
819
|
+
if (remoteIngressConfig?.enabled !== undefined) {
|
|
820
|
+
seed.enabled = remoteIngressConfig.enabled;
|
|
821
|
+
}
|
|
822
|
+
if (remoteIngressConfig?.tunnelPort !== undefined) {
|
|
823
|
+
seed.tunnelPort = remoteIngressConfig.tunnelPort;
|
|
824
|
+
}
|
|
825
|
+
if (remoteIngressConfig?.hubDomain !== undefined) {
|
|
826
|
+
seed.hubDomain = remoteIngressConfig.hubDomain;
|
|
827
|
+
}
|
|
828
|
+
if (remoteIngressConfig?.performance !== undefined) {
|
|
829
|
+
seed.performance = remoteIngressConfig.performance;
|
|
830
|
+
}
|
|
831
|
+
return seed;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
private getEmailSettingsLegacySeed(): IEmailServerSettingsSeed {
|
|
835
|
+
const seed: IEmailServerSettingsSeed = {};
|
|
836
|
+
if (this.options.emailConfig) {
|
|
837
|
+
seed.enabled = true;
|
|
838
|
+
seed.emailConfig = JSON.parse(JSON.stringify(this.options.emailConfig));
|
|
839
|
+
}
|
|
840
|
+
if (this.options.emailPortConfig) {
|
|
841
|
+
seed.emailPortConfig = JSON.parse(JSON.stringify(this.options.emailPortConfig));
|
|
842
|
+
}
|
|
843
|
+
return seed;
|
|
844
|
+
}
|
|
845
|
+
|
|
755
846
|
private startSmartAcmeInBackground(): void {
|
|
756
847
|
if (!this.smartAcme) {
|
|
757
848
|
this.smartAcmeReady = false;
|
|
@@ -965,10 +1056,11 @@ export class DcRouter {
|
|
|
965
1056
|
}
|
|
966
1057
|
|
|
967
1058
|
// Remote Ingress summary
|
|
968
|
-
|
|
1059
|
+
const remoteIngressHubSettings = this.remoteIngressManager?.getHubSettings();
|
|
1060
|
+
if (this.tunnelManager && remoteIngressHubSettings?.enabled) {
|
|
969
1061
|
const edgeCount = this.remoteIngressManager?.getAllEdges().length || 0;
|
|
970
1062
|
const connectedCount = this.tunnelManager.getConnectedCount();
|
|
971
|
-
logger.log('info', `Remote Ingress: tunnel port=${
|
|
1063
|
+
logger.log('info', `Remote Ingress: tunnel port=${remoteIngressHubSettings.tunnelPort}, edges=${edgeCount} registered/${connectedCount} connected`);
|
|
972
1064
|
}
|
|
973
1065
|
|
|
974
1066
|
// Database summary
|
|
@@ -1013,7 +1105,10 @@ export class DcRouter {
|
|
|
1013
1105
|
|
|
1014
1106
|
// Run any pending data migrations before anything else reads from the DB.
|
|
1015
1107
|
// This must complete before ConfigManagers loads profiles.
|
|
1016
|
-
const migration = await createMigrationRunner(this.dcRouterDb.getDb(), commitinfo.version
|
|
1108
|
+
const migration = await createMigrationRunner(this.dcRouterDb.getDb(), commitinfo.version, {
|
|
1109
|
+
remoteIngressHubSettings: this.getRemoteIngressHubSettingsLegacySeed(),
|
|
1110
|
+
emailServerSettings: this.getEmailSettingsLegacySeed(),
|
|
1111
|
+
});
|
|
1017
1112
|
const migrationResult = await migration.run();
|
|
1018
1113
|
if (migrationResult.stepsApplied.length > 0) {
|
|
1019
1114
|
logger.log('info',
|
|
@@ -1043,8 +1138,16 @@ export class DcRouter {
|
|
|
1043
1138
|
|
|
1044
1139
|
// Clean up any existing SmartProxy instance (e.g. from a retry)
|
|
1045
1140
|
if (this.smartProxy) {
|
|
1046
|
-
this.smartProxy
|
|
1047
|
-
|
|
1141
|
+
const existingSmartProxy = this.smartProxy;
|
|
1142
|
+
try {
|
|
1143
|
+
existingSmartProxy.removeAllListeners();
|
|
1144
|
+
await existingSmartProxy.stop();
|
|
1145
|
+
if (this.smartProxy === existingSmartProxy) {
|
|
1146
|
+
this.smartProxy = undefined;
|
|
1147
|
+
}
|
|
1148
|
+
} finally {
|
|
1149
|
+
await this.stopSmartAcme();
|
|
1150
|
+
}
|
|
1048
1151
|
}
|
|
1049
1152
|
|
|
1050
1153
|
// Assemble serializable seed routes from constructor config — these will be seeded into DB
|
|
@@ -1279,7 +1382,7 @@ export class DcRouter {
|
|
|
1279
1382
|
|
|
1280
1383
|
// When remoteIngress is enabled, the hub binary forwards tunneled connections
|
|
1281
1384
|
// to SmartProxy with PROXY protocol v1 headers to preserve client IPs.
|
|
1282
|
-
if (this.
|
|
1385
|
+
if (this.isRemoteIngressHubEnabled()) {
|
|
1283
1386
|
smartProxyConfig.acceptProxyProtocol = true;
|
|
1284
1387
|
if (!smartProxyConfig.proxyIPs) {
|
|
1285
1388
|
smartProxyConfig.proxyIPs = [];
|
|
@@ -1303,16 +1406,17 @@ export class DcRouter {
|
|
|
1303
1406
|
// Create SmartProxy instance
|
|
1304
1407
|
logger.log('info', `Creating SmartProxy instance: routes=${smartProxyConfig.routes?.length}, acme=${smartProxyConfig.acme?.enabled}, certProvisionFunction=${!!smartProxyConfig.certProvisionFunction}`);
|
|
1305
1408
|
|
|
1306
|
-
|
|
1409
|
+
const smartProxy = new plugins.smartproxy.SmartProxy(smartProxyConfig);
|
|
1410
|
+
this.smartProxy = smartProxy;
|
|
1307
1411
|
|
|
1308
1412
|
// Set up event listeners
|
|
1309
|
-
|
|
1413
|
+
smartProxy.on('error', (err) => {
|
|
1310
1414
|
logger.log('error', `SmartProxy error: ${err.message}`, { stack: err.stack });
|
|
1311
1415
|
});
|
|
1312
1416
|
|
|
1313
1417
|
// Always listen for certificate events — emitted by both ACME and certProvisionFunction paths
|
|
1314
1418
|
// Events are keyed by domain for domain-centric certificate tracking
|
|
1315
|
-
|
|
1419
|
+
smartProxy.on('certificate-issued', (event: plugins.smartproxy.ICertificateIssuedEvent) => {
|
|
1316
1420
|
logger.log('info', `Certificate issued for ${event.domain} via ${event.source}, expires ${event.expiryDate}`);
|
|
1317
1421
|
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
1318
1422
|
this.certificateStatusMap.set(event.domain, {
|
|
@@ -1326,7 +1430,7 @@ export class DcRouter {
|
|
|
1326
1430
|
// Renewals come through 'certificate-issued' (with optional isRenewal? in the payload).
|
|
1327
1431
|
// The vestigial 'certificate-renewed' event from common-types.ts is never emitted.
|
|
1328
1432
|
|
|
1329
|
-
|
|
1433
|
+
smartProxy.on('certificate-failed', (event: plugins.smartproxy.ICertificateFailedEvent) => {
|
|
1330
1434
|
logger.log('error', `Certificate failed for ${event.domain} (${event.source}): ${event.error}`);
|
|
1331
1435
|
const routeNames = this.findRouteNamesForDomain(event.domain);
|
|
1332
1436
|
this.certificateStatusMap.set(event.domain, {
|
|
@@ -1337,7 +1441,23 @@ export class DcRouter {
|
|
|
1337
1441
|
|
|
1338
1442
|
// Start SmartProxy
|
|
1339
1443
|
logger.log('info', 'Starting SmartProxy...');
|
|
1340
|
-
|
|
1444
|
+
try {
|
|
1445
|
+
await smartProxy.start();
|
|
1446
|
+
} catch (err) {
|
|
1447
|
+
smartProxy.removeAllListeners();
|
|
1448
|
+
if (this.smartProxy === smartProxy) {
|
|
1449
|
+
this.smartProxy = undefined;
|
|
1450
|
+
}
|
|
1451
|
+
await this.stopSmartAcme();
|
|
1452
|
+
if (this.certProvisionScheduler) {
|
|
1453
|
+
this.certProvisionScheduler.clear();
|
|
1454
|
+
this.certProvisionScheduler = undefined;
|
|
1455
|
+
}
|
|
1456
|
+
await smartProxy.stop().catch((stopErr) => {
|
|
1457
|
+
logger.log('warn', `Failed to clean up SmartProxy after startup failure: ${(stopErr as Error).message}`);
|
|
1458
|
+
});
|
|
1459
|
+
throw err;
|
|
1460
|
+
}
|
|
1341
1461
|
logger.log('info', 'SmartProxy started successfully');
|
|
1342
1462
|
|
|
1343
1463
|
// Populate certificateStatusMap for certs loaded from store at startup
|
|
@@ -1460,8 +1580,8 @@ export class DcRouter {
|
|
|
1460
1580
|
/**
|
|
1461
1581
|
* Generate SmartProxy routes for email configuration
|
|
1462
1582
|
*/
|
|
1463
|
-
private generateEmailRoutes(emailConfig: IUnifiedEmailServerOptions):
|
|
1464
|
-
const emailRoutes:
|
|
1583
|
+
private generateEmailRoutes(emailConfig: IUnifiedEmailServerOptions): IDcRouterRouteConfig[] {
|
|
1584
|
+
const emailRoutes: IDcRouterRouteConfig[] = [];
|
|
1465
1585
|
|
|
1466
1586
|
// Create routes for each email port
|
|
1467
1587
|
for (const port of emailConfig.ports) {
|
|
@@ -1535,13 +1655,17 @@ export class DcRouter {
|
|
|
1535
1655
|
}
|
|
1536
1656
|
|
|
1537
1657
|
// Create the route configuration
|
|
1538
|
-
const routeConfig:
|
|
1658
|
+
const routeConfig: IDcRouterRouteConfig = {
|
|
1539
1659
|
name: routeName,
|
|
1540
1660
|
match: {
|
|
1541
1661
|
ports: [port]
|
|
1542
1662
|
},
|
|
1543
1663
|
action: action
|
|
1544
1664
|
};
|
|
1665
|
+
|
|
1666
|
+
if (this.isRemoteIngressHubEnabled()) {
|
|
1667
|
+
routeConfig.remoteIngress = { enabled: true };
|
|
1668
|
+
}
|
|
1545
1669
|
|
|
1546
1670
|
// Add the route to our list
|
|
1547
1671
|
emailRoutes.push(routeConfig);
|
|
@@ -1768,19 +1892,33 @@ export class DcRouter {
|
|
|
1768
1892
|
});
|
|
1769
1893
|
|
|
1770
1894
|
// Create unified email server
|
|
1771
|
-
|
|
1895
|
+
const emailServer = new UnifiedEmailServer(this, emailConfig);
|
|
1896
|
+
this.emailServer = emailServer;
|
|
1772
1897
|
this.clearEmailEventSubscriptions();
|
|
1773
1898
|
|
|
1774
1899
|
// Set up error handling
|
|
1775
|
-
this.addEmailEventSubscription(
|
|
1900
|
+
this.addEmailEventSubscription(emailServer, 'error', (err: Error) => {
|
|
1776
1901
|
logger.log('error', `UnifiedEmailServer error: ${err.message}`);
|
|
1777
1902
|
});
|
|
1778
1903
|
|
|
1779
1904
|
// Start the server
|
|
1780
|
-
|
|
1905
|
+
try {
|
|
1906
|
+
await emailServer.start();
|
|
1907
|
+
} catch (error: unknown) {
|
|
1908
|
+
this.clearEmailEventSubscriptions();
|
|
1909
|
+
try {
|
|
1910
|
+
await emailServer.stop();
|
|
1911
|
+
} catch (stopError: unknown) {
|
|
1912
|
+
logger.log('warn', `Error cleaning up failed UnifiedEmailServer start: ${(stopError as Error).message}`);
|
|
1913
|
+
}
|
|
1914
|
+
if (this.emailServer === emailServer) {
|
|
1915
|
+
this.emailServer = undefined;
|
|
1916
|
+
}
|
|
1917
|
+
throw error;
|
|
1918
|
+
}
|
|
1781
1919
|
|
|
1782
1920
|
// Wire delivery events to MetricsManager and logger using smartmta's public queue APIs.
|
|
1783
|
-
if (this.metricsManager
|
|
1921
|
+
if (this.metricsManager) {
|
|
1784
1922
|
const getEnvelope = (item: { processingResult?: any; lastError?: string }) => {
|
|
1785
1923
|
const emailLike = item?.processingResult;
|
|
1786
1924
|
const from = emailLike?.from || emailLike?.email?.from || '';
|
|
@@ -1795,34 +1933,34 @@ export class DcRouter {
|
|
|
1795
1933
|
};
|
|
1796
1934
|
};
|
|
1797
1935
|
const updateQueueSize = () => {
|
|
1798
|
-
this.metricsManager!.updateQueueSize(
|
|
1936
|
+
this.metricsManager!.updateQueueSize(emailServer.getQueueStats().queueSize);
|
|
1799
1937
|
};
|
|
1800
1938
|
|
|
1801
|
-
this.addEmailEventSubscription(
|
|
1939
|
+
this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemEnqueued', (item: any) => {
|
|
1802
1940
|
const envelope = getEnvelope(item);
|
|
1803
1941
|
this.metricsManager!.trackEmailReceived(envelope.from);
|
|
1804
1942
|
updateQueueSize();
|
|
1805
1943
|
logger.log('info', `Email queued: ${envelope.from} → ${envelope.recipients.join(', ') || 'unknown'}`, { zone: 'email' });
|
|
1806
1944
|
});
|
|
1807
|
-
this.addEmailEventSubscription(
|
|
1945
|
+
this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemDelivered', (item: any) => {
|
|
1808
1946
|
const envelope = getEnvelope(item);
|
|
1809
1947
|
this.metricsManager!.trackEmailSent(envelope.recipients[0]);
|
|
1810
1948
|
updateQueueSize();
|
|
1811
1949
|
logger.log('info', `Email delivered to ${envelope.recipients.join(', ') || 'unknown'}`, { zone: 'email' });
|
|
1812
1950
|
});
|
|
1813
|
-
this.addEmailEventSubscription(
|
|
1951
|
+
this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemFailed', (item: any) => {
|
|
1814
1952
|
const envelope = getEnvelope(item);
|
|
1815
1953
|
this.metricsManager!.trackEmailFailed(envelope.recipients[0], item?.lastError);
|
|
1816
1954
|
updateQueueSize();
|
|
1817
1955
|
logger.log('warn', `Email delivery failed to ${envelope.recipients.join(', ') || 'unknown'}: ${item?.lastError || 'unknown error'}`, { zone: 'email' });
|
|
1818
1956
|
});
|
|
1819
|
-
this.addEmailEventSubscription(
|
|
1957
|
+
this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemDeferred', () => {
|
|
1820
1958
|
updateQueueSize();
|
|
1821
1959
|
});
|
|
1822
|
-
this.addEmailEventSubscription(
|
|
1960
|
+
this.addEmailEventSubscription(emailServer.deliveryQueue, 'itemRemoved', () => {
|
|
1823
1961
|
updateQueueSize();
|
|
1824
1962
|
});
|
|
1825
|
-
this.addEmailEventSubscription(
|
|
1963
|
+
this.addEmailEventSubscription(emailServer, 'bounceProcessed', () => {
|
|
1826
1964
|
this.metricsManager!.trackEmailBounced();
|
|
1827
1965
|
logger.log('warn', 'Email bounce processed', { zone: 'email' });
|
|
1828
1966
|
});
|
|
@@ -1837,16 +1975,57 @@ export class DcRouter {
|
|
|
1837
1975
|
* @param config New email configuration
|
|
1838
1976
|
*/
|
|
1839
1977
|
public async updateEmailConfig(config: IUnifiedEmailServerOptions): Promise<void> {
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1978
|
+
await this.queueEmailLifecycleTask(async () => {
|
|
1979
|
+
// Stop existing email components
|
|
1980
|
+
await this.stopUnifiedEmailComponents();
|
|
1981
|
+
|
|
1982
|
+
// Update configuration
|
|
1983
|
+
this.options.emailConfig = config;
|
|
1984
|
+
this.emailDomainManager?.setBaseEmailDomains(config.domains as IEmailDomainConfig[] | undefined);
|
|
1985
|
+
await this.emailDomainManager?.syncManagedDomainsToRuntime();
|
|
1986
|
+
|
|
1987
|
+
// Start email handling with new configuration
|
|
1988
|
+
await this.setupUnifiedEmailHandling();
|
|
1989
|
+
|
|
1990
|
+
logger.log('info', 'Unified email configuration updated');
|
|
1991
|
+
});
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
public async updateEmailServerSettings(
|
|
1995
|
+
settings: TEmailServerSettingsUpdate,
|
|
1996
|
+
updatedBy = 'system',
|
|
1997
|
+
): Promise<IEmailServerSettings> {
|
|
1998
|
+
return await this.queueEmailLifecycleTask(async () => {
|
|
1999
|
+
if (!this.emailSettingsManager) {
|
|
2000
|
+
throw new Error('EmailSettingsManager is not initialized');
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
const updatedSettings = await this.emailSettingsManager.updateSettings(settings, updatedBy);
|
|
2004
|
+
this.emailDomainManager?.setBaseEmailDomains(this.options.emailConfig?.domains as IEmailDomainConfig[] | undefined);
|
|
2005
|
+
await this.emailDomainManager?.syncManagedDomainsToRuntime();
|
|
2006
|
+
this.seedEmailRoutes = this.options.emailConfig
|
|
2007
|
+
? this.generateEmailRoutes(this.options.emailConfig)
|
|
2008
|
+
: [];
|
|
2009
|
+
|
|
2010
|
+
if (this.routeConfigManager) {
|
|
2011
|
+
await this.routeConfigManager.initialize(
|
|
2012
|
+
this.seedConfigRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
2013
|
+
this.seedEmailRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
2014
|
+
this.seedDnsRoutes as import('../ts_interfaces/data/remoteingress.js').IDcRouterRouteConfig[],
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
if (this.options.emailConfig) {
|
|
2019
|
+
if (this.emailServer) {
|
|
2020
|
+
await this.stopUnifiedEmailComponents();
|
|
2021
|
+
}
|
|
2022
|
+
await this.setupUnifiedEmailHandling();
|
|
2023
|
+
} else if (this.emailServer) {
|
|
2024
|
+
await this.stopUnifiedEmailComponents();
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
return updatedSettings;
|
|
2028
|
+
});
|
|
1850
2029
|
}
|
|
1851
2030
|
|
|
1852
2031
|
/**
|
|
@@ -2438,7 +2617,14 @@ export class DcRouter {
|
|
|
2438
2617
|
* Set up Remote Ingress hub for edge tunnel connections
|
|
2439
2618
|
*/
|
|
2440
2619
|
private async setupRemoteIngress(): Promise<void> {
|
|
2441
|
-
|
|
2620
|
+
const remoteIngressManager = this.remoteIngressManager;
|
|
2621
|
+
if (!remoteIngressManager) {
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
const hubSettings = remoteIngressManager.getHubSettings();
|
|
2626
|
+
if (!hubSettings.enabled) {
|
|
2627
|
+
logger.log('info', 'Remote Ingress hub is disabled in DB settings');
|
|
2442
2628
|
return;
|
|
2443
2629
|
}
|
|
2444
2630
|
|
|
@@ -2446,14 +2632,6 @@ export class DcRouter {
|
|
|
2446
2632
|
this.remoteIngressHubStopping = false;
|
|
2447
2633
|
const generation = ++this.remoteIngressHubGeneration;
|
|
2448
2634
|
|
|
2449
|
-
// Initialize the edge registration manager
|
|
2450
|
-
const remoteIngressManager = new RemoteIngressManager(this.options.remoteIngressConfig.performance);
|
|
2451
|
-
this.remoteIngressManager = remoteIngressManager;
|
|
2452
|
-
await remoteIngressManager.initialize();
|
|
2453
|
-
if (!this.isRemoteIngressHubGenerationCurrent(generation, remoteIngressManager)) {
|
|
2454
|
-
return;
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
2635
|
const firewallConfig = await this.securityPolicyManager?.compileRemoteIngressFirewall();
|
|
2458
2636
|
if (!this.isRemoteIngressHubGenerationCurrent(generation, remoteIngressManager)) {
|
|
2459
2637
|
return;
|
|
@@ -2483,7 +2661,7 @@ export class DcRouter {
|
|
|
2483
2661
|
}
|
|
2484
2662
|
|
|
2485
2663
|
const edgeCount = remoteIngressManager.getAllEdges().length;
|
|
2486
|
-
logger.log('info', `Remote Ingress hub started on port ${
|
|
2664
|
+
logger.log('info', `Remote Ingress hub started on port ${hubSettings.tunnelPort} with ${edgeCount} registered edge(s)`);
|
|
2487
2665
|
}
|
|
2488
2666
|
|
|
2489
2667
|
private isRemoteIngressHubGenerationCurrent(generation: number, manager: RemoteIngressManager): boolean {
|
|
@@ -2498,17 +2676,30 @@ export class DcRouter {
|
|
|
2498
2676
|
return run;
|
|
2499
2677
|
}
|
|
2500
2678
|
|
|
2679
|
+
private queueSmartProxyLifecycleTask<T>(task: () => Promise<T>): Promise<T> {
|
|
2680
|
+
const run = this.smartProxyLifecycleChain.then(task);
|
|
2681
|
+
this.smartProxyLifecycleChain = run.then(() => undefined, () => undefined);
|
|
2682
|
+
return run;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
private queueEmailLifecycleTask<T>(task: () => Promise<T>): Promise<T> {
|
|
2686
|
+
const run = this.emailLifecycleChain.then(task);
|
|
2687
|
+
this.emailLifecycleChain = run.then(() => undefined, () => undefined);
|
|
2688
|
+
return run;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2501
2691
|
private async stopRemoteIngress(): Promise<void> {
|
|
2502
2692
|
this.remoteIngressHubStopping = true;
|
|
2503
2693
|
this.remoteIngressHubGeneration++;
|
|
2504
2694
|
await this.queueRemoteIngressHubTask(async () => {
|
|
2505
2695
|
const currentTunnelManager = this.tunnelManager;
|
|
2506
|
-
this.tunnelManager = undefined;
|
|
2507
2696
|
if (currentTunnelManager) {
|
|
2508
2697
|
await currentTunnelManager.stop();
|
|
2698
|
+
if (this.tunnelManager === currentTunnelManager) {
|
|
2699
|
+
this.tunnelManager = undefined;
|
|
2700
|
+
}
|
|
2509
2701
|
}
|
|
2510
2702
|
});
|
|
2511
|
-
this.remoteIngressManager = undefined;
|
|
2512
2703
|
}
|
|
2513
2704
|
|
|
2514
2705
|
public async mutateRemoteIngressEdges<T>(
|
|
@@ -2544,35 +2735,96 @@ export class DcRouter {
|
|
|
2544
2735
|
}
|
|
2545
2736
|
|
|
2546
2737
|
public async updateRemoteIngressHubSettings(
|
|
2547
|
-
updates:
|
|
2738
|
+
updates: TRemoteIngressHubSettingsUpdate,
|
|
2548
2739
|
updatedBy: string,
|
|
2549
2740
|
): Promise<IRemoteIngressHubSettings> {
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2741
|
+
const manager = this.remoteIngressManager;
|
|
2742
|
+
if (!manager) {
|
|
2743
|
+
throw new Error('RemoteIngress is not configured');
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
const previousSettings = manager.getHubSettings();
|
|
2747
|
+
const settings = await manager.updateHubSettings(updates, updatedBy);
|
|
2748
|
+
const enabledChanged = previousSettings.enabled !== settings.enabled;
|
|
2749
|
+
|
|
2750
|
+
if (!settings.enabled) {
|
|
2751
|
+
await this.queueRemoteIngressHubTask(async () => {
|
|
2752
|
+
await this.stopRemoteIngressTunnelHubLocked();
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2557
2755
|
|
|
2558
|
-
|
|
2559
|
-
|
|
2756
|
+
if (enabledChanged) {
|
|
2757
|
+
await this.restartSmartProxyForRemoteIngressSettings();
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2760
|
+
if (settings.enabled) {
|
|
2761
|
+
await this.queueRemoteIngressHubTask(async () => {
|
|
2560
2762
|
await this.restartRemoteIngressTunnelHubLocked();
|
|
2763
|
+
});
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
return settings;
|
|
2767
|
+
}
|
|
2768
|
+
|
|
2769
|
+
private async restartSmartProxyForRemoteIngressSettings(): Promise<void> {
|
|
2770
|
+
await this.queueSmartProxyLifecycleTask(async () => {
|
|
2771
|
+
const restartSmartProxy = async () => {
|
|
2772
|
+
try {
|
|
2773
|
+
if (this.smartProxy) {
|
|
2774
|
+
const existingSmartProxy = this.smartProxy;
|
|
2775
|
+
existingSmartProxy.removeAllListeners();
|
|
2776
|
+
await existingSmartProxy.stop();
|
|
2777
|
+
if (this.smartProxy === existingSmartProxy) {
|
|
2778
|
+
this.smartProxy = undefined;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
} finally {
|
|
2782
|
+
await this.stopSmartAcme();
|
|
2783
|
+
}
|
|
2784
|
+
await this.setupSmartProxy();
|
|
2785
|
+
};
|
|
2786
|
+
|
|
2787
|
+
if (this.routeConfigManager) {
|
|
2788
|
+
await this.routeConfigManager.runExclusiveRouteUpdate(restartSmartProxy);
|
|
2789
|
+
} else {
|
|
2790
|
+
await restartSmartProxy();
|
|
2791
|
+
}
|
|
2792
|
+
|
|
2793
|
+
if (!this.routeConfigManager) {
|
|
2794
|
+
return;
|
|
2561
2795
|
}
|
|
2562
|
-
|
|
2796
|
+
await this.routeConfigManager.initialize(
|
|
2797
|
+
this.seedConfigRoutes as IDcRouterRouteConfig[],
|
|
2798
|
+
this.seedEmailRoutes as IDcRouterRouteConfig[],
|
|
2799
|
+
this.seedDnsRoutes as IDcRouterRouteConfig[],
|
|
2800
|
+
);
|
|
2563
2801
|
});
|
|
2564
2802
|
}
|
|
2565
2803
|
|
|
2804
|
+
private async stopRemoteIngressTunnelHubLocked(): Promise<void> {
|
|
2805
|
+
this.remoteIngressHubGeneration++;
|
|
2806
|
+
const currentTunnelManager = this.tunnelManager;
|
|
2807
|
+
if (currentTunnelManager) {
|
|
2808
|
+
await currentTunnelManager.stop();
|
|
2809
|
+
if (this.tunnelManager === currentTunnelManager) {
|
|
2810
|
+
this.tunnelManager = undefined;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
|
|
2566
2815
|
private async restartRemoteIngressTunnelHubLocked(): Promise<void> {
|
|
2567
2816
|
const generation = ++this.remoteIngressHubGeneration;
|
|
2568
|
-
|
|
2817
|
+
const hubSettings = this.remoteIngressManager?.getHubSettings();
|
|
2818
|
+
if (!this.remoteIngressManager || !hubSettings?.enabled || this.remoteIngressHubStopping) {
|
|
2569
2819
|
return;
|
|
2570
2820
|
}
|
|
2571
2821
|
|
|
2572
2822
|
const currentTunnelManager = this.tunnelManager;
|
|
2573
|
-
this.tunnelManager = undefined;
|
|
2574
2823
|
if (currentTunnelManager) {
|
|
2575
2824
|
await currentTunnelManager.stop();
|
|
2825
|
+
if (this.tunnelManager === currentTunnelManager) {
|
|
2826
|
+
this.tunnelManager = undefined;
|
|
2827
|
+
}
|
|
2576
2828
|
}
|
|
2577
2829
|
|
|
2578
2830
|
if (this.remoteIngressHubStopping || generation !== this.remoteIngressHubGeneration) {
|
|
@@ -2582,19 +2834,25 @@ export class DcRouter {
|
|
|
2582
2834
|
}
|
|
2583
2835
|
|
|
2584
2836
|
private async startRemoteIngressTunnelHubLocked(generation: number): Promise<void> {
|
|
2585
|
-
const riCfg = this.options.remoteIngressConfig;
|
|
2586
2837
|
const manager = this.remoteIngressManager;
|
|
2587
|
-
|
|
2838
|
+
const hubSettings = manager?.getHubSettings();
|
|
2839
|
+
if (!manager || !hubSettings?.enabled || this.remoteIngressHubStopping || generation !== this.remoteIngressHubGeneration) {
|
|
2588
2840
|
return;
|
|
2589
2841
|
}
|
|
2590
2842
|
|
|
2591
|
-
const
|
|
2843
|
+
const firewallConfig = await this.securityPolicyManager?.compileRemoteIngressFirewall();
|
|
2844
|
+
if (this.remoteIngressHubStopping || generation !== this.remoteIngressHubGeneration || this.remoteIngressManager !== manager) {
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
manager.setFirewallConfig(firewallConfig);
|
|
2848
|
+
|
|
2849
|
+
const tlsConfig = await this.resolveRemoteIngressTlsConfig(hubSettings.hubDomain);
|
|
2592
2850
|
if (this.remoteIngressHubStopping || generation !== this.remoteIngressHubGeneration || this.remoteIngressManager !== manager) {
|
|
2593
2851
|
return;
|
|
2594
2852
|
}
|
|
2595
2853
|
|
|
2596
2854
|
const tunnelManager = new TunnelManager(manager, {
|
|
2597
|
-
tunnelPort:
|
|
2855
|
+
tunnelPort: hubSettings.tunnelPort,
|
|
2598
2856
|
targetHost: '127.0.0.1',
|
|
2599
2857
|
tls: tlsConfig,
|
|
2600
2858
|
performance: manager.getHubPerformanceConfig(),
|
|
@@ -2607,23 +2865,26 @@ export class DcRouter {
|
|
|
2607
2865
|
}
|
|
2608
2866
|
|
|
2609
2867
|
if (this.remoteIngressHubStopping || generation !== this.remoteIngressHubGeneration || this.remoteIngressManager !== manager) {
|
|
2610
|
-
await tunnelManager.stop()
|
|
2868
|
+
await tunnelManager.stop().catch((err) => {
|
|
2869
|
+
logger.log('warn', `Failed to stop stale RemoteIngress tunnel hub: ${(err as Error).message}`);
|
|
2870
|
+
});
|
|
2611
2871
|
return;
|
|
2612
2872
|
}
|
|
2613
2873
|
this.tunnelManager = tunnelManager;
|
|
2614
2874
|
}
|
|
2615
2875
|
|
|
2616
2876
|
private async resolveRemoteIngressTlsConfig(
|
|
2617
|
-
|
|
2877
|
+
hubDomain?: string,
|
|
2618
2878
|
): Promise<{ certPem: string; keyPem: string } | undefined> {
|
|
2619
2879
|
// Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)
|
|
2620
2880
|
let tlsConfig: { certPem: string; keyPem: string } | undefined;
|
|
2621
2881
|
|
|
2622
2882
|
// Priority 1: Explicit cert/key file paths
|
|
2623
|
-
|
|
2883
|
+
const explicitTls = this.options.remoteIngressConfig?.tls;
|
|
2884
|
+
if (explicitTls?.certPath && explicitTls?.keyPath) {
|
|
2624
2885
|
try {
|
|
2625
|
-
const certPem = plugins.fs.readFileSync(
|
|
2626
|
-
const keyPem = plugins.fs.readFileSync(
|
|
2886
|
+
const certPem = plugins.fs.readFileSync(explicitTls.certPath, 'utf8');
|
|
2887
|
+
const keyPem = plugins.fs.readFileSync(explicitTls.keyPath, 'utf8');
|
|
2627
2888
|
tlsConfig = { certPem, keyPem };
|
|
2628
2889
|
logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
|
|
2629
2890
|
} catch (err: unknown) {
|
|
@@ -2632,12 +2893,12 @@ export class DcRouter {
|
|
|
2632
2893
|
}
|
|
2633
2894
|
|
|
2634
2895
|
// Priority 2: Existing cert from SmartProxy cert store for hubDomain
|
|
2635
|
-
if (!tlsConfig &&
|
|
2896
|
+
if (!tlsConfig && hubDomain) {
|
|
2636
2897
|
try {
|
|
2637
|
-
const stored = await ProxyCertDoc.findByDomain(
|
|
2898
|
+
const stored = await ProxyCertDoc.findByDomain(hubDomain);
|
|
2638
2899
|
if (stored?.publicKey && stored?.privateKey) {
|
|
2639
2900
|
tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
|
|
2640
|
-
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${
|
|
2901
|
+
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${hubDomain}`);
|
|
2641
2902
|
}
|
|
2642
2903
|
} catch { /* no stored cert, fall through */ }
|
|
2643
2904
|
}
|