@serve.zone/dcrouter 13.20.0 → 13.21.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/dist_serve/bundle.js +519 -519
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.dcrouter.d.ts +5 -0
- package/dist_ts/classes.dcrouter.js +34 -10
- 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/monitoring/classes.metricsmanager.d.ts +6 -2
- package/dist_ts/monitoring/classes.metricsmanager.js +67 -41
- package/dist_ts/opsserver/handlers/security.handler.js +11 -5
- package/dist_ts/opsserver/handlers/stats.handler.js +2 -1
- package/dist_ts/vpn/classes.vpn-manager.d.ts +5 -1
- package/dist_ts/vpn/classes.vpn-manager.js +55 -17
- package/dist_ts_interfaces/data/stats.d.ts +10 -0
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/appstate.js +23 -16
- package/dist_ts_web/elements/network/ops-view-network-activity.js +7 -3
- package/dist_ts_web/elements/network/ops-view-vpn.d.ts +3 -0
- package/dist_ts_web/elements/network/ops-view-vpn.js +44 -16
- package/package.json +3 -3
- package/readme.md +123 -155
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.dcrouter.ts +51 -14
- package/ts/config/classes.route-config-manager.ts +6 -0
- package/ts/monitoring/classes.metricsmanager.ts +71 -40
- package/ts/opsserver/handlers/security.handler.ts +11 -5
- package/ts/opsserver/handlers/stats.handler.ts +1 -0
- package/ts/readme.md +46 -103
- package/ts/vpn/classes.vpn-manager.ts +66 -15
- package/ts_apiclient/readme.md +57 -59
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/appstate.ts +23 -18
- package/ts_web/elements/network/ops-view-network-activity.ts +6 -2
- package/ts_web/elements/network/ops-view-vpn.ts +50 -14
- package/ts_web/readme.md +27 -47
|
@@ -560,7 +560,9 @@ export class MetricsManager {
|
|
|
560
560
|
requestsPerSecond: 0,
|
|
561
561
|
requestsTotal: 0,
|
|
562
562
|
backends: [] as Array<any>,
|
|
563
|
-
domainActivity: [] as Array<{ domain: string; bytesInPerSecond: number; bytesOutPerSecond: number; activeConnections: number; routeCount: number; requestCount: number }>,
|
|
563
|
+
domainActivity: [] as Array<{ domain: string; bytesInPerSecond: number; bytesOutPerSecond: number; activeConnections: number; routeCount: number; requestCount: number; requestsPerSecond?: number; requestsLastMinute?: number }>,
|
|
564
|
+
frontendProtocols: null,
|
|
565
|
+
backendProtocols: null,
|
|
564
566
|
};
|
|
565
567
|
}
|
|
566
568
|
|
|
@@ -592,6 +594,7 @@ export class MetricsManager {
|
|
|
592
594
|
// Get HTTP request rates
|
|
593
595
|
const requestsPerSecond = proxyMetrics.requests.perSecond();
|
|
594
596
|
const requestsTotal = proxyMetrics.requests.total();
|
|
597
|
+
const domainRequestRates = proxyMetrics.requests.byDomain();
|
|
595
598
|
|
|
596
599
|
// Get frontend/backend protocol distribution
|
|
597
600
|
const frontendProtocols = proxyMetrics.connections.frontendProtocols() ?? null;
|
|
@@ -619,47 +622,48 @@ export class MetricsManager {
|
|
|
619
622
|
const seenCacheKeys = new Set<string>();
|
|
620
623
|
|
|
621
624
|
for (const [key, bm] of backendMetrics) {
|
|
625
|
+
backends.push({
|
|
626
|
+
id: `backend:${key}`,
|
|
627
|
+
backend: key,
|
|
628
|
+
domain: null,
|
|
629
|
+
protocol: bm.protocol,
|
|
630
|
+
activeConnections: bm.activeConnections,
|
|
631
|
+
totalConnections: bm.totalConnections,
|
|
632
|
+
connectErrors: bm.connectErrors,
|
|
633
|
+
handshakeErrors: bm.handshakeErrors,
|
|
634
|
+
requestErrors: bm.requestErrors,
|
|
635
|
+
avgConnectTimeMs: Math.round(bm.avgConnectTimeMs * 10) / 10,
|
|
636
|
+
poolHitRate: Math.round(bm.poolHitRate * 1000) / 1000,
|
|
637
|
+
h2Failures: bm.h2Failures,
|
|
638
|
+
h2Suppressed: false,
|
|
639
|
+
h3Suppressed: false,
|
|
640
|
+
h2CooldownRemainingSecs: null,
|
|
641
|
+
h3CooldownRemainingSecs: null,
|
|
642
|
+
h2ConsecutiveFailures: null,
|
|
643
|
+
h3ConsecutiveFailures: null,
|
|
644
|
+
h3Port: null,
|
|
645
|
+
cacheAgeSecs: null,
|
|
646
|
+
});
|
|
647
|
+
|
|
622
648
|
const cacheEntries = cacheByBackend.get(key);
|
|
623
|
-
if (
|
|
624
|
-
//
|
|
625
|
-
backends.push({
|
|
626
|
-
backend: key,
|
|
627
|
-
domain: null,
|
|
628
|
-
protocol: bm.protocol,
|
|
629
|
-
activeConnections: bm.activeConnections,
|
|
630
|
-
totalConnections: bm.totalConnections,
|
|
631
|
-
connectErrors: bm.connectErrors,
|
|
632
|
-
handshakeErrors: bm.handshakeErrors,
|
|
633
|
-
requestErrors: bm.requestErrors,
|
|
634
|
-
avgConnectTimeMs: Math.round(bm.avgConnectTimeMs * 10) / 10,
|
|
635
|
-
poolHitRate: Math.round(bm.poolHitRate * 1000) / 1000,
|
|
636
|
-
h2Failures: bm.h2Failures,
|
|
637
|
-
h2Suppressed: false,
|
|
638
|
-
h3Suppressed: false,
|
|
639
|
-
h2CooldownRemainingSecs: null,
|
|
640
|
-
h3CooldownRemainingSecs: null,
|
|
641
|
-
h2ConsecutiveFailures: null,
|
|
642
|
-
h3ConsecutiveFailures: null,
|
|
643
|
-
h3Port: null,
|
|
644
|
-
cacheAgeSecs: null,
|
|
645
|
-
});
|
|
646
|
-
} else {
|
|
647
|
-
// One row per domain, each enriched with the shared backend metrics
|
|
649
|
+
if (cacheEntries && cacheEntries.length > 0) {
|
|
650
|
+
// Protocol cache rows are domain-scoped metadata, not live backend connections.
|
|
648
651
|
for (const cache of cacheEntries) {
|
|
649
652
|
const compositeKey = `${cache.host}:${cache.port}:${cache.domain ?? ''}`;
|
|
650
653
|
seenCacheKeys.add(compositeKey);
|
|
651
654
|
backends.push({
|
|
655
|
+
id: `cache:${compositeKey}`,
|
|
652
656
|
backend: key,
|
|
653
657
|
domain: cache.domain ?? null,
|
|
654
658
|
protocol: cache.protocol ?? bm.protocol,
|
|
655
|
-
activeConnections:
|
|
656
|
-
totalConnections:
|
|
657
|
-
connectErrors:
|
|
658
|
-
handshakeErrors:
|
|
659
|
-
requestErrors:
|
|
660
|
-
avgConnectTimeMs:
|
|
661
|
-
poolHitRate:
|
|
662
|
-
h2Failures:
|
|
659
|
+
activeConnections: 0,
|
|
660
|
+
totalConnections: 0,
|
|
661
|
+
connectErrors: 0,
|
|
662
|
+
handshakeErrors: 0,
|
|
663
|
+
requestErrors: 0,
|
|
664
|
+
avgConnectTimeMs: 0,
|
|
665
|
+
poolHitRate: 0,
|
|
666
|
+
h2Failures: 0,
|
|
663
667
|
h2Suppressed: cache.h2Suppressed,
|
|
664
668
|
h3Suppressed: cache.h3Suppressed,
|
|
665
669
|
h2CooldownRemainingSecs: cache.h2CooldownRemainingSecs,
|
|
@@ -678,6 +682,7 @@ export class MetricsManager {
|
|
|
678
682
|
const compositeKey = `${entry.host}:${entry.port}:${entry.domain ?? ''}`;
|
|
679
683
|
if (!seenCacheKeys.has(compositeKey)) {
|
|
680
684
|
backends.push({
|
|
685
|
+
id: `cache:${compositeKey}`,
|
|
681
686
|
backend: `${entry.host}:${entry.port}`,
|
|
682
687
|
domain: entry.domain,
|
|
683
688
|
protocol: entry.protocol,
|
|
@@ -750,6 +755,9 @@ export class MetricsManager {
|
|
|
750
755
|
|
|
751
756
|
// Resolve wildcards using domains seen in request metrics
|
|
752
757
|
const allKnownDomains = new Set<string>(domainRequestTotals.keys());
|
|
758
|
+
for (const domain of domainRequestRates.keys()) {
|
|
759
|
+
allKnownDomains.add(domain);
|
|
760
|
+
}
|
|
753
761
|
for (const entry of protocolCache) {
|
|
754
762
|
if (entry.domain) allKnownDomains.add(entry.domain);
|
|
755
763
|
}
|
|
@@ -775,11 +783,20 @@ export class MetricsManager {
|
|
|
775
783
|
}
|
|
776
784
|
}
|
|
777
785
|
|
|
778
|
-
|
|
779
|
-
|
|
786
|
+
const hasLiveDomainRates = domainRequestRates.size > 0;
|
|
787
|
+
const getDomainWeight = (domain: string): number => {
|
|
788
|
+
const liveRate = domainRequestRates.get(domain);
|
|
789
|
+
return hasLiveDomainRates
|
|
790
|
+
? (liveRate?.lastMinute ?? 0)
|
|
791
|
+
: (domainRequestTotals.get(domain) || 0);
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// For each route, compute the total activity weight across all resolved domains
|
|
795
|
+
// so we can distribute route-level throughput/connections. Prefer live domain
|
|
796
|
+
// request rates from SmartProxy 27.8+, falling back to lifetime counters.
|
|
780
797
|
const routeTotalRequests = new Map<string, number>();
|
|
781
798
|
for (const [domain, routeKeys] of domainToRoutes) {
|
|
782
|
-
const reqs =
|
|
799
|
+
const reqs = getDomainWeight(domain);
|
|
783
800
|
for (const routeKey of routeKeys) {
|
|
784
801
|
routeTotalRequests.set(routeKey, (routeTotalRequests.get(routeKey) || 0) + reqs);
|
|
785
802
|
}
|
|
@@ -792,10 +809,13 @@ export class MetricsManager {
|
|
|
792
809
|
bytesOutPerSec: number;
|
|
793
810
|
routeCount: number;
|
|
794
811
|
requestCount: number;
|
|
812
|
+
requestsPerSecond: number;
|
|
813
|
+
requestsLastMinute: number;
|
|
795
814
|
}>();
|
|
796
815
|
|
|
797
816
|
for (const [domain, routeKeys] of domainToRoutes) {
|
|
798
|
-
const domainReqs =
|
|
817
|
+
const domainReqs = getDomainWeight(domain);
|
|
818
|
+
const requestRate = domainRequestRates.get(domain);
|
|
799
819
|
let totalConns = 0;
|
|
800
820
|
let totalIn = 0;
|
|
801
821
|
let totalOut = 0;
|
|
@@ -816,7 +836,9 @@ export class MetricsManager {
|
|
|
816
836
|
bytesInPerSec: totalIn,
|
|
817
837
|
bytesOutPerSec: totalOut,
|
|
818
838
|
routeCount: routeKeys.length,
|
|
819
|
-
requestCount:
|
|
839
|
+
requestCount: domainRequestTotals.get(domain) || 0,
|
|
840
|
+
requestsPerSecond: requestRate?.perSecond ?? 0,
|
|
841
|
+
requestsLastMinute: requestRate?.lastMinute ?? 0,
|
|
820
842
|
});
|
|
821
843
|
}
|
|
822
844
|
|
|
@@ -828,8 +850,17 @@ export class MetricsManager {
|
|
|
828
850
|
activeConnections: data.activeConnections,
|
|
829
851
|
routeCount: data.routeCount,
|
|
830
852
|
requestCount: data.requestCount,
|
|
853
|
+
requestsPerSecond: data.requestsPerSecond,
|
|
854
|
+
requestsLastMinute: data.requestsLastMinute,
|
|
831
855
|
}))
|
|
832
|
-
.sort((a, b) =>
|
|
856
|
+
.sort((a, b) => {
|
|
857
|
+
if (hasLiveDomainRates) {
|
|
858
|
+
return (b.requestsPerSecond - a.requestsPerSecond) ||
|
|
859
|
+
(b.requestsLastMinute - a.requestsLastMinute) ||
|
|
860
|
+
((b.bytesInPerSecond + b.bytesOutPerSecond) - (a.bytesInPerSecond + a.bytesOutPerSecond));
|
|
861
|
+
}
|
|
862
|
+
return (b.bytesInPerSecond + b.bytesOutPerSecond) - (a.bytesInPerSecond + a.bytesOutPerSecond);
|
|
863
|
+
});
|
|
833
864
|
|
|
834
865
|
return {
|
|
835
866
|
connectionsByIP,
|
|
@@ -50,19 +50,21 @@ export class SecurityHandler {
|
|
|
50
50
|
localAddress: conn.destination.ip,
|
|
51
51
|
startTime: conn.startTime,
|
|
52
52
|
protocol: conn.type === 'http' ? 'https' : conn.type as any,
|
|
53
|
-
state: conn.status as any,
|
|
53
|
+
state: conn.status === 'active' ? 'connected' : conn.status as any,
|
|
54
54
|
bytesReceived: (conn as any)._throughputIn || 0,
|
|
55
55
|
bytesSent: (conn as any)._throughputOut || 0,
|
|
56
|
+
connectionCount: conn.bytesTransferred || 1,
|
|
56
57
|
}));
|
|
58
|
+
const totalConnections = connectionInfos.reduce((sum, conn) => sum + (conn.connectionCount || 1), 0);
|
|
57
59
|
|
|
58
60
|
const summary = {
|
|
59
|
-
total:
|
|
61
|
+
total: totalConnections,
|
|
60
62
|
byProtocol: connectionInfos.reduce((acc, conn) => {
|
|
61
|
-
acc[conn.protocol] = (acc[conn.protocol] || 0) + 1;
|
|
63
|
+
acc[conn.protocol] = (acc[conn.protocol] || 0) + (conn.connectionCount || 1);
|
|
62
64
|
return acc;
|
|
63
65
|
}, {} as { [protocol: string]: number }),
|
|
64
66
|
byState: connectionInfos.reduce((acc, conn) => {
|
|
65
|
-
acc[conn.state] = (acc[conn.state] || 0) + 1;
|
|
67
|
+
acc[conn.state] = (acc[conn.state] || 0) + (conn.connectionCount || 1);
|
|
66
68
|
return acc;
|
|
67
69
|
}, {} as { [state: string]: number }),
|
|
68
70
|
};
|
|
@@ -104,6 +106,8 @@ export class SecurityHandler {
|
|
|
104
106
|
requestsPerSecond: networkStats.requestsPerSecond || 0,
|
|
105
107
|
requestsTotal: networkStats.requestsTotal || 0,
|
|
106
108
|
backends: networkStats.backends || [],
|
|
109
|
+
frontendProtocols: networkStats.frontendProtocols || null,
|
|
110
|
+
backendProtocols: networkStats.backendProtocols || null,
|
|
107
111
|
};
|
|
108
112
|
}
|
|
109
113
|
|
|
@@ -120,6 +124,8 @@ export class SecurityHandler {
|
|
|
120
124
|
requestsPerSecond: 0,
|
|
121
125
|
requestsTotal: 0,
|
|
122
126
|
backends: [],
|
|
127
|
+
frontendProtocols: null,
|
|
128
|
+
backendProtocols: null,
|
|
123
129
|
};
|
|
124
130
|
}
|
|
125
131
|
)
|
|
@@ -335,4 +341,4 @@ export class SecurityHandler {
|
|
|
335
341
|
limits: [],
|
|
336
342
|
};
|
|
337
343
|
}
|
|
338
|
-
}
|
|
344
|
+
}
|
package/ts/readme.md
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
# @serve.zone/dcrouter
|
|
2
2
|
|
|
3
|
-
The
|
|
4
|
-
|
|
5
|
-
This is the main entry point for DcRouter. It provides the `DcRouter` class that wires together SmartProxy, smartmta, SmartDNS, SmartRadius, RemoteIngress, and the OpsServer dashboard into a single cohesive service.
|
|
3
|
+
The `ts/` directory is the main dcrouter runtime package. It exposes the `DcRouter` orchestrator, `IDcRouterOptions`, `runCli()`, and the server-side exports that matter when you want to boot the full router stack from code.
|
|
6
4
|
|
|
7
5
|
## Issue Reporting and Security
|
|
8
6
|
|
|
@@ -14,7 +12,19 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
|
|
14
12
|
pnpm add @serve.zone/dcrouter
|
|
15
13
|
```
|
|
16
14
|
|
|
17
|
-
##
|
|
15
|
+
## Core Exports
|
|
16
|
+
|
|
17
|
+
| Export | Purpose |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `DcRouter` | Main orchestrator for proxying, DNS, email, VPN, RADIUS, remote ingress, DB, and OpsServer |
|
|
20
|
+
| `IDcRouterOptions` | Top-level configuration shape |
|
|
21
|
+
| `runCli()` | Bootstrap helper; uses OCI env-driven config when `DCROUTER_MODE=OCI_CONTAINER` |
|
|
22
|
+
| `UnifiedEmailServer` and smartmta types | Re-exported email server primitives |
|
|
23
|
+
| `RadiusServer` and related types | RADIUS server runtime exports |
|
|
24
|
+
| `RemoteIngressManager` and `TunnelManager` | Remote ingress orchestration exports |
|
|
25
|
+
| `IHttp3Config` | HTTP/3 configuration for qualifying HTTPS routes |
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
18
28
|
|
|
19
29
|
```typescript
|
|
20
30
|
import { DcRouter } from '@serve.zone/dcrouter';
|
|
@@ -23,120 +33,53 @@ const router = new DcRouter({
|
|
|
23
33
|
smartProxyConfig: {
|
|
24
34
|
routes: [
|
|
25
35
|
{
|
|
26
|
-
name: '
|
|
27
|
-
match: {
|
|
36
|
+
name: 'local-app',
|
|
37
|
+
match: {
|
|
38
|
+
domains: ['localhost'],
|
|
39
|
+
ports: [18080],
|
|
40
|
+
},
|
|
28
41
|
action: {
|
|
29
42
|
type: 'forward',
|
|
30
|
-
targets: [{ host: '
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
43
|
+
targets: [{ host: '127.0.0.1', port: 3001 }],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
34
46
|
],
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
},
|
|
48
|
+
opsServerPort: 3000,
|
|
37
49
|
});
|
|
38
50
|
|
|
39
51
|
await router.start();
|
|
40
|
-
// OpsServer dashboard at http://localhost:3000 (configurable via opsServerPort)
|
|
41
|
-
|
|
42
|
-
// Graceful shutdown
|
|
43
|
-
await router.stop();
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
## Module Structure
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
ts/
|
|
50
|
-
├── index.ts # Main exports (DcRouter, re-exported smartmta types)
|
|
51
|
-
├── classes.dcrouter.ts # DcRouter orchestrator class + IDcRouterOptions
|
|
52
|
-
├── classes.cert-provision-scheduler.ts # Per-domain cert backoff scheduler
|
|
53
|
-
├── classes.storage-cert-manager.ts # SmartAcme cert manager backed by StorageManager
|
|
54
|
-
├── logger.ts # Structured logging utility
|
|
55
|
-
├── paths.ts # Centralized data directory paths
|
|
56
|
-
├── plugins.ts # All dependency imports
|
|
57
|
-
├── cache/ # Cache database (smartdata + LocalTsmDb)
|
|
58
|
-
│ ├── classes.cachedb.ts # CacheDb singleton
|
|
59
|
-
│ ├── classes.cachecleaner.ts # TTL-based cleanup
|
|
60
|
-
│ └── documents/ # Cached document models
|
|
61
|
-
├── config/ # Configuration utilities
|
|
62
|
-
├── errors/ # Error classes and retry logic
|
|
63
|
-
├── http3/ # HTTP/3 (QUIC) route augmentation
|
|
64
|
-
│ ├── index.ts # Barrel export
|
|
65
|
-
│ └── http3-route-augmentation.ts # Pure utility: augmentRoutesWithHttp3(), IHttp3Config
|
|
66
|
-
├── monitoring/ # MetricsManager (SmartMetrics integration)
|
|
67
|
-
├── opsserver/ # OpsServer dashboard + API handlers
|
|
68
|
-
│ ├── classes.opsserver.ts # HTTP server + TypedRouter setup
|
|
69
|
-
│ └── handlers/ # TypedRequest handlers by domain
|
|
70
|
-
│ ├── admin.handler.ts # Auth (login/logout/verify)
|
|
71
|
-
│ ├── stats.handler.ts # Statistics + health
|
|
72
|
-
│ ├── config.handler.ts # Configuration (read-only)
|
|
73
|
-
│ ├── logs.handler.ts # Log retrieval
|
|
74
|
-
│ ├── email.handler.ts # Email operations
|
|
75
|
-
│ ├── certificate.handler.ts # Certificate management
|
|
76
|
-
│ ├── radius.handler.ts # RADIUS management
|
|
77
|
-
│ ├── remoteingress.handler.ts # Remote ingress edge + token management
|
|
78
|
-
│ ├── route-management.handler.ts # Programmatic route CRUD
|
|
79
|
-
│ ├── api-token.handler.ts # API token management
|
|
80
|
-
│ └── security.handler.ts # Security metrics + connections
|
|
81
|
-
├── radius/ # RADIUS server integration
|
|
82
|
-
├── remoteingress/ # Remote ingress hub integration
|
|
83
|
-
│ ├── classes.remoteingress-manager.ts # Edge CRUD + port derivation
|
|
84
|
-
│ └── classes.tunnel-manager.ts # Rust hub lifecycle + status tracking
|
|
85
|
-
├── security/ # Security utilities
|
|
86
|
-
├── sms/ # SMS integration
|
|
87
|
-
└── storage/ # StorageManager (filesystem/custom/memory)
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
## Exports
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// Main class
|
|
94
|
-
export { DcRouter, IDcRouterOptions } from './classes.dcrouter.js';
|
|
95
|
-
|
|
96
|
-
// Re-exported from smartmta
|
|
97
|
-
export { UnifiedEmailServer } from '@push.rocks/smartmta';
|
|
98
|
-
export type { IUnifiedEmailServerOptions, IEmailRoute, IEmailDomainConfig } from '@push.rocks/smartmta';
|
|
99
|
-
|
|
100
|
-
// RADIUS
|
|
101
|
-
export { RadiusServer, IRadiusServerConfig } from './radius/index.js';
|
|
102
|
-
|
|
103
|
-
// Remote Ingress
|
|
104
|
-
export { RemoteIngressManager, TunnelManager } from './remoteingress/index.js';
|
|
105
|
-
|
|
106
|
-
// HTTP/3
|
|
107
|
-
export type { IHttp3Config } from './http3/index.js';
|
|
108
52
|
```
|
|
109
53
|
|
|
110
|
-
##
|
|
111
|
-
|
|
112
|
-
### `DcRouter`
|
|
113
|
-
|
|
114
|
-
The central orchestrator. Accepts `IDcRouterOptions` and manages the lifecycle of all sub-services:
|
|
54
|
+
## What `DcRouter` Manages
|
|
115
55
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
| `remoteIngressConfig` | RemoteIngressManager + TunnelManager | `@serve.zone/remoteingress` |
|
|
123
|
-
| `tls` + `dnsChallenge` | SmartAcme (ACME cert provisioning) | `@push.rocks/smartacme` |
|
|
124
|
-
| `http3` | HTTP/3 route augmentation (enabled by default) | built-in |
|
|
125
|
-
| `cacheConfig` | CacheDb (embedded MongoDB) | `@push.rocks/smartdata` |
|
|
126
|
-
| *(always)* | OpsServer (dashboard + API) | `@api.global/typedserver` |
|
|
127
|
-
| *(always)* | MetricsManager | `@push.rocks/smartmetrics` |
|
|
56
|
+
- SmartProxy for HTTP/HTTPS/TCP routes
|
|
57
|
+
- `UnifiedEmailServer` for SMTP ingress and delivery when `emailConfig` is present
|
|
58
|
+
- DB-backed managers for routes, API tokens, target profiles, domains, records, ACME config, and email domains when the DB is enabled
|
|
59
|
+
- embedded authoritative DNS and DoH route generation from `dnsNsDomains` and `dnsScopes`
|
|
60
|
+
- VPN, RADIUS, and remote ingress services when their config blocks are enabled
|
|
61
|
+
- OpsServer and the dashboard, which start on every boot
|
|
128
62
|
|
|
129
|
-
|
|
63
|
+
## Important Runtime Behavior
|
|
130
64
|
|
|
131
|
-
|
|
65
|
+
- The DB is enabled by default and uses an embedded local database when no external MongoDB URL is provided.
|
|
66
|
+
- System routes from config, email, and DNS are persisted with stable ownership and are toggle-only.
|
|
67
|
+
- API-created routes are the only routes intended for full CRUD from the dashboard or client SDK.
|
|
68
|
+
- Qualifying HTTPS forward routes on port `443` get HTTP/3 augmentation by default.
|
|
69
|
+
- `runCli()` is the supported code-level bootstrap entrypoint; the package does not expose a separate npm `bin` command.
|
|
132
70
|
|
|
133
|
-
|
|
71
|
+
## Use Another Module When...
|
|
134
72
|
|
|
135
|
-
|
|
73
|
+
| Need | Module |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| A higher-level client SDK for a running router | `@serve.zone/dcrouter-apiclient` or `@serve.zone/dcrouter/apiclient` |
|
|
76
|
+
| Raw TypedRequest request/data contracts | `@serve.zone/dcrouter-interfaces` or `@serve.zone/dcrouter/interfaces` |
|
|
77
|
+
| The standalone migration runner | `@serve.zone/dcrouter-migrations` |
|
|
78
|
+
| The browser dashboard module boundary | `@serve.zone/dcrouter-web` |
|
|
136
79
|
|
|
137
80
|
## License and Legal Information
|
|
138
81
|
|
|
139
|
-
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [
|
|
82
|
+
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](../license) file.
|
|
140
83
|
|
|
141
84
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
|
142
85
|
|
|
@@ -148,7 +91,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
|
|
148
91
|
|
|
149
92
|
### Company Information
|
|
150
93
|
|
|
151
|
-
Task Venture Capital GmbH
|
|
94
|
+
Task Venture Capital GmbH
|
|
152
95
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
|
153
96
|
|
|
154
97
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
|
@@ -112,14 +112,11 @@ export class VpnManager {
|
|
|
112
112
|
const subnet = this.getSubnet();
|
|
113
113
|
const wgListenPort = this.config.wgListenPort ?? 51820;
|
|
114
114
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
let configuredMode = this.forwardingModeOverride ?? this.config.forwardingMode ?? 'socket';
|
|
118
|
-
if (anyClientUsesHostIp && configuredMode === 'socket') {
|
|
119
|
-
configuredMode = 'hybrid';
|
|
115
|
+
const desiredForwardingMode = this.getDesiredForwardingMode(anyClientUsesHostIp);
|
|
116
|
+
if (anyClientUsesHostIp && desiredForwardingMode === 'hybrid') {
|
|
120
117
|
logger.log('info', 'VPN: Auto-upgrading forwarding mode to hybrid (client with useHostIp detected)');
|
|
121
118
|
}
|
|
122
|
-
const forwardingMode =
|
|
119
|
+
const forwardingMode = desiredForwardingMode;
|
|
123
120
|
const isBridge = forwardingMode === 'bridge';
|
|
124
121
|
this.resolvedForwardingMode = forwardingMode;
|
|
125
122
|
this.forwardingModeOverride = undefined;
|
|
@@ -218,7 +215,7 @@ export class VpnManager {
|
|
|
218
215
|
throw new Error('VPN server not running');
|
|
219
216
|
}
|
|
220
217
|
|
|
221
|
-
await this.
|
|
218
|
+
await this.ensureForwardingModeForNextClient(opts.useHostIp === true);
|
|
222
219
|
|
|
223
220
|
const doc = new VpnClientDoc();
|
|
224
221
|
doc.clientId = opts.clientId;
|
|
@@ -298,6 +295,7 @@ export class VpnManager {
|
|
|
298
295
|
if (doc) {
|
|
299
296
|
await doc.delete();
|
|
300
297
|
}
|
|
298
|
+
await this.reconcileForwardingMode();
|
|
301
299
|
this.config.onClientChanged?.();
|
|
302
300
|
}
|
|
303
301
|
|
|
@@ -368,8 +366,10 @@ export class VpnManager {
|
|
|
368
366
|
await this.persistClient(client);
|
|
369
367
|
|
|
370
368
|
if (this.vpnServer) {
|
|
371
|
-
await this.
|
|
372
|
-
|
|
369
|
+
const restarted = await this.reconcileForwardingMode();
|
|
370
|
+
if (!restarted) {
|
|
371
|
+
await this.vpnServer.updateClient(clientId, this.buildClientRuntimeUpdate(client));
|
|
372
|
+
}
|
|
373
373
|
}
|
|
374
374
|
|
|
375
375
|
this.config.onClientChanged?.();
|
|
@@ -563,6 +563,28 @@ export class VpnManager {
|
|
|
563
563
|
?? 'socket';
|
|
564
564
|
}
|
|
565
565
|
|
|
566
|
+
private hasHostIpClients(extraHostIpClient = false): boolean {
|
|
567
|
+
if (extraHostIpClient) {
|
|
568
|
+
return true;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
for (const client of this.clients.values()) {
|
|
572
|
+
if (client.useHostIp) {
|
|
573
|
+
return true;
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
private getDesiredForwardingMode(hasHostIpClients = this.hasHostIpClients()): 'socket' | 'bridge' | 'hybrid' {
|
|
581
|
+
const configuredMode = this.forwardingModeOverride ?? this.config.forwardingMode ?? 'socket';
|
|
582
|
+
if (configuredMode !== 'socket') {
|
|
583
|
+
return configuredMode;
|
|
584
|
+
}
|
|
585
|
+
return hasHostIpClients ? 'hybrid' : 'socket';
|
|
586
|
+
}
|
|
587
|
+
|
|
566
588
|
private getDefaultDestinationPolicy(
|
|
567
589
|
forwardingMode: 'socket' | 'bridge' | 'hybrid',
|
|
568
590
|
useHostIp = false,
|
|
@@ -633,16 +655,45 @@ export class VpnManager {
|
|
|
633
655
|
};
|
|
634
656
|
}
|
|
635
657
|
|
|
636
|
-
private async
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
logger.log('info',
|
|
641
|
-
this.forwardingModeOverride =
|
|
658
|
+
private async restartWithForwardingMode(
|
|
659
|
+
forwardingMode: 'socket' | 'bridge' | 'hybrid',
|
|
660
|
+
reason: string,
|
|
661
|
+
): Promise<void> {
|
|
662
|
+
logger.log('info', `VPN: Restarting server in ${forwardingMode} mode ${reason}`);
|
|
663
|
+
this.forwardingModeOverride = forwardingMode;
|
|
642
664
|
await this.stop();
|
|
643
665
|
await this.start();
|
|
644
666
|
}
|
|
645
667
|
|
|
668
|
+
private async ensureForwardingModeForNextClient(useHostIp: boolean): Promise<void> {
|
|
669
|
+
if (!this.vpnServer) return;
|
|
670
|
+
|
|
671
|
+
const desiredForwardingMode = this.getDesiredForwardingMode(this.hasHostIpClients(useHostIp));
|
|
672
|
+
if (desiredForwardingMode === this.getResolvedForwardingMode()) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
await this.restartWithForwardingMode(desiredForwardingMode, 'to support a host-IP client');
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
private async reconcileForwardingMode(): Promise<boolean> {
|
|
680
|
+
if (!this.vpnServer) {
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const desiredForwardingMode = this.getDesiredForwardingMode();
|
|
685
|
+
const currentForwardingMode = this.getResolvedForwardingMode();
|
|
686
|
+
if (desiredForwardingMode === currentForwardingMode) {
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const reason = desiredForwardingMode === 'socket'
|
|
691
|
+
? 'because no host-IP clients remain'
|
|
692
|
+
: 'to support host-IP clients';
|
|
693
|
+
await this.restartWithForwardingMode(desiredForwardingMode, reason);
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
|
|
646
697
|
private async persistClient(client: VpnClientDoc): Promise<void> {
|
|
647
698
|
await client.save();
|
|
648
699
|
}
|