Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e58e24a92d | |||
| 12070bc7b5 | |||
| 37d62c51f3 | |||
| ea9427d46b | |||
| bc77321752 | |||
| 65aa546c1c | |||
| 54484518dc | |||
| 6fe1247d4d |
31
changelog.md
31
changelog.md
@@ -1,5 +1,36 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-26 - 9.3.0 - feat(remoteingress)
|
||||||
|
add TLS certificate resolution and passthrough for RemoteIngress tunnel
|
||||||
|
|
||||||
|
- Resolve TLS certs for the RemoteIngress tunnel with priority: explicit certPath/keyPath files → stored ACME cert for hubDomain → fallback to self-signed
|
||||||
|
- Expose tls option on ITunnelManagerConfig and forward certPem/keyPem into hub.start so the hub can use the provided TLS materials
|
||||||
|
- Add logging for cert selection and file read failures
|
||||||
|
- Bump dependency @serve.zone/remoteingress from ^4.2.0 to ^4.3.0
|
||||||
|
|
||||||
|
## 2026-02-26 - 9.2.0 - feat(remoteingress)
|
||||||
|
expose connected edge IPs and detected public IP; resolve proxy IPs from SmartProxy and improve ops UI
|
||||||
|
|
||||||
|
- Add detectedPublicIp to DC Router and populate it when a configured or auto-discovered public IP is chosen
|
||||||
|
- Use dcRouter.detectedPublicIp as a fallback for system.publicIp in the config handler
|
||||||
|
- Resolve proxy IPs from SmartProxy runtime settings when opts.proxyIps is not provided
|
||||||
|
- TunnelManager: capture peerAddr on edgeConnected and from Rust heartbeats, store per-edge publicIp, and add getConnectedEdgeIps()
|
||||||
|
- Expose connectedEdgeIps in the config API and return it in remoteIngress config
|
||||||
|
- Ops UI: show Connected Edge IPs, annotate 127.0.0.1 proxy IP as 'Remote Ingress' when applicable, and refresh remote ingress data during combined refresh when viewing remoteingress
|
||||||
|
- Bump dependency @serve.zone/remoteingress to ^4.2.0
|
||||||
|
|
||||||
|
## 2026-02-26 - 9.1.10 - fix(deps)
|
||||||
|
bump @push.rocks/smartproxy to ^25.8.5
|
||||||
|
|
||||||
|
- package.json: @push.rocks/smartproxy version updated from ^25.8.4 to ^25.8.5
|
||||||
|
- No other files changed
|
||||||
|
|
||||||
|
## 2026-02-26 - 9.1.9 - fix(deps(smartmta))
|
||||||
|
bump @push.rocks/smartmta to ^5.3.0
|
||||||
|
|
||||||
|
- Updated @push.rocks/smartmta from ^5.2.6 to ^5.3.0 in package.json
|
||||||
|
- Patch release recommended (no source code changes)
|
||||||
|
|
||||||
## 2026-02-26 - 9.1.8 - fix(deps)
|
## 2026-02-26 - 9.1.8 - fix(deps)
|
||||||
bump @serve.zone/remoteingress to ^4.1.0
|
bump @serve.zone/remoteingress to ^4.1.0
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "9.1.8",
|
"version": "9.3.0",
|
||||||
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
"description": "A multifaceted routing service handling mail and SMS delivery functions.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -45,11 +45,11 @@
|
|||||||
"@push.rocks/smartlog": "^3.2.1",
|
"@push.rocks/smartlog": "^3.2.1",
|
||||||
"@push.rocks/smartmetrics": "^3.0.1",
|
"@push.rocks/smartmetrics": "^3.0.1",
|
||||||
"@push.rocks/smartmongo": "^5.1.0",
|
"@push.rocks/smartmongo": "^5.1.0",
|
||||||
"@push.rocks/smartmta": "^5.2.6",
|
"@push.rocks/smartmta": "^5.3.0",
|
||||||
"@push.rocks/smartnetwork": "^4.4.0",
|
"@push.rocks/smartnetwork": "^4.4.0",
|
||||||
"@push.rocks/smartpath": "^6.0.0",
|
"@push.rocks/smartpath": "^6.0.0",
|
||||||
"@push.rocks/smartpromise": "^4.2.3",
|
"@push.rocks/smartpromise": "^4.2.3",
|
||||||
"@push.rocks/smartproxy": "^25.8.4",
|
"@push.rocks/smartproxy": "^25.8.5",
|
||||||
"@push.rocks/smartradius": "^1.1.1",
|
"@push.rocks/smartradius": "^1.1.1",
|
||||||
"@push.rocks/smartrequest": "^5.0.1",
|
"@push.rocks/smartrequest": "^5.0.1",
|
||||||
"@push.rocks/smartrx": "^3.0.10",
|
"@push.rocks/smartrx": "^3.0.10",
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@serve.zone/catalog": "^2.5.0",
|
"@serve.zone/catalog": "^2.5.0",
|
||||||
"@serve.zone/interfaces": "^5.3.0",
|
"@serve.zone/interfaces": "^5.3.0",
|
||||||
"@serve.zone/remoteingress": "^4.1.0",
|
"@serve.zone/remoteingress": "^4.3.0",
|
||||||
"@tsclass/tsclass": "^9.3.0",
|
"@tsclass/tsclass": "^9.3.0",
|
||||||
"lru-cache": "^11.2.6",
|
"lru-cache": "^11.2.6",
|
||||||
"uuid": "^13.0.0"
|
"uuid": "^13.0.0"
|
||||||
|
|||||||
30
pnpm-lock.yaml
generated
30
pnpm-lock.yaml
generated
@@ -63,8 +63,8 @@ importers:
|
|||||||
specifier: ^5.1.0
|
specifier: ^5.1.0
|
||||||
version: 5.1.0(socks@2.8.7)
|
version: 5.1.0(socks@2.8.7)
|
||||||
'@push.rocks/smartmta':
|
'@push.rocks/smartmta':
|
||||||
specifier: ^5.2.6
|
specifier: ^5.3.0
|
||||||
version: 5.2.6
|
version: 5.3.0
|
||||||
'@push.rocks/smartnetwork':
|
'@push.rocks/smartnetwork':
|
||||||
specifier: ^4.4.0
|
specifier: ^4.4.0
|
||||||
version: 4.4.0
|
version: 4.4.0
|
||||||
@@ -75,8 +75,8 @@ importers:
|
|||||||
specifier: ^4.2.3
|
specifier: ^4.2.3
|
||||||
version: 4.2.3
|
version: 4.2.3
|
||||||
'@push.rocks/smartproxy':
|
'@push.rocks/smartproxy':
|
||||||
specifier: ^25.8.4
|
specifier: ^25.8.5
|
||||||
version: 25.8.4
|
version: 25.8.5
|
||||||
'@push.rocks/smartradius':
|
'@push.rocks/smartradius':
|
||||||
specifier: ^1.1.1
|
specifier: ^1.1.1
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
@@ -99,8 +99,8 @@ importers:
|
|||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
'@serve.zone/remoteingress':
|
'@serve.zone/remoteingress':
|
||||||
specifier: ^4.1.0
|
specifier: ^4.3.0
|
||||||
version: 4.1.0
|
version: 4.3.0
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
version: 9.3.0
|
version: 9.3.0
|
||||||
@@ -996,8 +996,8 @@ packages:
|
|||||||
'@push.rocks/smartmongo@5.1.0':
|
'@push.rocks/smartmongo@5.1.0':
|
||||||
resolution: {integrity: sha512-2tpKf8K+SMdLHOEpafgKPIN+ypWTLwHc33hCUDNMQ1KaL7vokkavA44+fHxQydOGPMtDi22tSMFeVMCcUSzs4w==}
|
resolution: {integrity: sha512-2tpKf8K+SMdLHOEpafgKPIN+ypWTLwHc33hCUDNMQ1KaL7vokkavA44+fHxQydOGPMtDi22tSMFeVMCcUSzs4w==}
|
||||||
|
|
||||||
'@push.rocks/smartmta@5.2.6':
|
'@push.rocks/smartmta@5.3.0':
|
||||||
resolution: {integrity: sha512-MJKgcsgcPicCezm6DCFkni2zdY+mMsfMaqeEjPorhadRCd0Qeo0jP6Ozz82+SjhKHrVHuPPCPJuDG37PsEUqsw==}
|
resolution: {integrity: sha512-uJI25fslzvrcenU36WCdt5gB8cCfkjUlY7PqlxEtFp474/l/kZxNnvirv1gnZLRNNa+ioe5aH18HKE+KcAjuxA==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
cpu: [x64, arm64]
|
cpu: [x64, arm64]
|
||||||
os: [darwin, linux, win32]
|
os: [darwin, linux, win32]
|
||||||
@@ -1035,8 +1035,8 @@ packages:
|
|||||||
'@push.rocks/smartpromise@4.2.3':
|
'@push.rocks/smartpromise@4.2.3':
|
||||||
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
resolution: {integrity: sha512-Ycg/TJR+tMt+S3wSFurOpEoW6nXv12QBtKXgBcjMZ4RsdO28geN46U09osPn9N9WuwQy1PkmTV5J/V4F9U8qEw==}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@25.8.4':
|
'@push.rocks/smartproxy@25.8.5':
|
||||||
resolution: {integrity: sha512-j1qRbO4qFV1HJgBPzF56FzTVY0u4/8kEQBK52Qt+/FDnUITGVGVkEWZrbe2H7zodjH6t+EGNdN4QEywBBq3Ylw==}
|
resolution: {integrity: sha512-oLmV+Bq7sSgQP9McTao/imb6Xb62QM7wlTFt5kNynrS5WK2wAe8cEjDKOcyu8N/WmzNCEClT5f/0xAtI6JxtkA==}
|
||||||
|
|
||||||
'@push.rocks/smartpuppeteer@2.0.5':
|
'@push.rocks/smartpuppeteer@2.0.5':
|
||||||
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
resolution: {integrity: sha512-yK/qSeWVHIGWRp3c8S5tfdGP6WCKllZC4DR8d8CQlEjszOSBmHtlTdyyqOMBZ/BA4kd+eU5f3A1r4K2tGYty1g==}
|
||||||
@@ -1344,8 +1344,8 @@ packages:
|
|||||||
'@serve.zone/interfaces@5.3.0':
|
'@serve.zone/interfaces@5.3.0':
|
||||||
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
resolution: {integrity: sha512-venO7wtDR9ixzD9NhdERBGjNKbFA5LL0yHw4eqGh0UpmvtXVc3SFG0uuHDilOKMZqZ8bttV88qVsFy1aSTJrtA==}
|
||||||
|
|
||||||
'@serve.zone/remoteingress@4.1.0':
|
'@serve.zone/remoteingress@4.3.0':
|
||||||
resolution: {integrity: sha512-iGLEyqDsh1oK3tJxJUklc8vxJDNviGSsyA9EiUSwUsTW9LlqFkWzdujU7w4Ebj/DIBGlhnQe1opzv3+q/jOY5w==}
|
resolution: {integrity: sha512-yk14uS6oWIP83Zpem4hGf8zi3W9pefnxijtSWp45WvZ+u9XTXIADQNaUZBSTCId8CYkfPkfRGaaaARunVdjFXg==}
|
||||||
|
|
||||||
'@sindresorhus/is@5.6.0':
|
'@sindresorhus/is@5.6.0':
|
||||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||||
@@ -6233,7 +6233,7 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- vue
|
- vue
|
||||||
|
|
||||||
'@push.rocks/smartmta@5.2.6':
|
'@push.rocks/smartmta@5.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartfile': 13.1.2
|
'@push.rocks/smartfile': 13.1.2
|
||||||
'@push.rocks/smartfs': 1.3.1
|
'@push.rocks/smartfs': 1.3.1
|
||||||
@@ -6340,7 +6340,7 @@ snapshots:
|
|||||||
|
|
||||||
'@push.rocks/smartpromise@4.2.3': {}
|
'@push.rocks/smartpromise@4.2.3': {}
|
||||||
|
|
||||||
'@push.rocks/smartproxy@25.8.4':
|
'@push.rocks/smartproxy@25.8.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/smartcrypto': 2.0.4
|
'@push.rocks/smartcrypto': 2.0.4
|
||||||
'@push.rocks/smartlog': 3.2.1
|
'@push.rocks/smartlog': 3.2.1
|
||||||
@@ -6827,7 +6827,7 @@ snapshots:
|
|||||||
'@push.rocks/smartlog-interfaces': 3.0.2
|
'@push.rocks/smartlog-interfaces': 3.0.2
|
||||||
'@tsclass/tsclass': 9.3.0
|
'@tsclass/tsclass': 9.3.0
|
||||||
|
|
||||||
'@serve.zone/remoteingress@4.1.0':
|
'@serve.zone/remoteingress@4.3.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/qenv': 6.1.3
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartrust': 1.3.1
|
'@push.rocks/smartrust': 1.3.1
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '9.1.8',
|
version: '9.3.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -217,6 +217,9 @@ export class DcRouter {
|
|||||||
public routeConfigManager?: RouteConfigManager;
|
public routeConfigManager?: RouteConfigManager;
|
||||||
public apiTokenManager?: ApiTokenManager;
|
public apiTokenManager?: ApiTokenManager;
|
||||||
|
|
||||||
|
// Auto-discovered public IP (populated by generateAuthoritativeRecords)
|
||||||
|
public detectedPublicIp: string | null = null;
|
||||||
|
|
||||||
// DNS query logging rate limiter state
|
// DNS query logging rate limiter state
|
||||||
private dnsLogWindow: number[] = [];
|
private dnsLogWindow: number[] = [];
|
||||||
private dnsBatchCount: number = 0;
|
private dnsBatchCount: number = 0;
|
||||||
@@ -1574,6 +1577,7 @@ export class DcRouter {
|
|||||||
} else if (this.options.publicIp) {
|
} else if (this.options.publicIp) {
|
||||||
// Use explicitly configured public IP
|
// Use explicitly configured public IP
|
||||||
publicIp = this.options.publicIp;
|
publicIp = this.options.publicIp;
|
||||||
|
this.detectedPublicIp = publicIp;
|
||||||
logger.log('info', `Using configured public IP for nameserver A records: ${publicIp}`);
|
logger.log('info', `Using configured public IP for nameserver A records: ${publicIp}`);
|
||||||
} else {
|
} else {
|
||||||
// Auto-discover public IP using smartnetwork
|
// Auto-discover public IP using smartnetwork
|
||||||
@@ -1584,6 +1588,7 @@ export class DcRouter {
|
|||||||
|
|
||||||
if (publicIps.v4) {
|
if (publicIps.v4) {
|
||||||
publicIp = publicIps.v4;
|
publicIp = publicIps.v4;
|
||||||
|
this.detectedPublicIp = publicIp;
|
||||||
logger.log('info', `Auto-discovered public IPv4: ${publicIp}`);
|
logger.log('info', `Auto-discovered public IPv4: ${publicIp}`);
|
||||||
} else {
|
} else {
|
||||||
logger.log('warn', 'Could not auto-discover public IPv4 address');
|
logger.log('warn', 'Could not auto-discover public IPv4 address');
|
||||||
@@ -1709,10 +1714,42 @@ export class DcRouter {
|
|||||||
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
||||||
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
|
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
|
||||||
|
|
||||||
|
// Resolve TLS certs for tunnel: explicit paths > ACME for hubDomain > self-signed (Rust default)
|
||||||
|
const riCfg = this.options.remoteIngressConfig;
|
||||||
|
let tlsConfig: { certPem: string; keyPem: string } | undefined;
|
||||||
|
|
||||||
|
// Priority 1: Explicit cert/key file paths
|
||||||
|
if (riCfg.tls?.certPath && riCfg.tls?.keyPath) {
|
||||||
|
try {
|
||||||
|
const certPem = plugins.fs.readFileSync(riCfg.tls.certPath, 'utf8');
|
||||||
|
const keyPem = plugins.fs.readFileSync(riCfg.tls.keyPath, 'utf8');
|
||||||
|
tlsConfig = { certPem, keyPem };
|
||||||
|
logger.log('info', 'Using explicit TLS cert/key for RemoteIngress tunnel');
|
||||||
|
} catch (err) {
|
||||||
|
logger.log('warn', `Failed to read RemoteIngress TLS cert/key files: ${err.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Priority 2: Existing cert from SmartProxy cert store for hubDomain
|
||||||
|
if (!tlsConfig && riCfg.hubDomain) {
|
||||||
|
try {
|
||||||
|
const stored = await this.storageManager.getJSON(`/proxy-certs/${riCfg.hubDomain}`);
|
||||||
|
if (stored?.publicKey && stored?.privateKey) {
|
||||||
|
tlsConfig = { certPem: stored.publicKey, keyPem: stored.privateKey };
|
||||||
|
logger.log('info', `Using stored ACME cert for RemoteIngress tunnel TLS: ${riCfg.hubDomain}`);
|
||||||
|
}
|
||||||
|
} catch { /* no stored cert, fall through */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tlsConfig) {
|
||||||
|
logger.log('info', 'No TLS cert configured for RemoteIngress tunnel — using auto-generated self-signed');
|
||||||
|
}
|
||||||
|
|
||||||
// Create and start the tunnel manager
|
// Create and start the tunnel manager
|
||||||
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
this.tunnelManager = new TunnelManager(this.remoteIngressManager, {
|
||||||
tunnelPort: this.options.remoteIngressConfig.tunnelPort ?? 8443,
|
tunnelPort: riCfg.tunnelPort ?? 8443,
|
||||||
targetHost: '127.0.0.1',
|
targetHost: '127.0.0.1',
|
||||||
|
tls: tlsConfig,
|
||||||
});
|
});
|
||||||
await this.tunnelManager.start();
|
await this.tunnelManager.start();
|
||||||
|
|
||||||
|
|||||||
@@ -40,11 +40,20 @@ export class ConfigHandler {
|
|||||||
? 'filesystem'
|
? 'filesystem'
|
||||||
: 'memory';
|
: 'memory';
|
||||||
|
|
||||||
|
// Resolve proxy IPs: fall back to SmartProxy's runtime proxyIPs if not in opts
|
||||||
|
let proxyIps = opts.proxyIps || [];
|
||||||
|
if (proxyIps.length === 0 && dcRouter.smartProxy) {
|
||||||
|
const spSettings = (dcRouter.smartProxy as any).settings;
|
||||||
|
if (spSettings?.proxyIPs?.length > 0) {
|
||||||
|
proxyIps = spSettings.proxyIPs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const system: interfaces.requests.IConfigData['system'] = {
|
const system: interfaces.requests.IConfigData['system'] = {
|
||||||
baseDir: resolvedPaths.dcrouterHomeDir,
|
baseDir: resolvedPaths.dcrouterHomeDir,
|
||||||
dataDir: resolvedPaths.dataDir,
|
dataDir: resolvedPaths.dataDir,
|
||||||
publicIp: opts.publicIp || null,
|
publicIp: opts.publicIp || dcRouter.detectedPublicIp || null,
|
||||||
proxyIps: opts.proxyIps || [],
|
proxyIps,
|
||||||
uptime: Math.floor(process.uptime()),
|
uptime: Math.floor(process.uptime()),
|
||||||
storageBackend,
|
storageBackend,
|
||||||
storagePath: opts.storage?.fsPath || null,
|
storagePath: opts.storage?.fsPath || null,
|
||||||
@@ -169,11 +178,13 @@ export class ConfigHandler {
|
|||||||
|
|
||||||
// --- Remote Ingress ---
|
// --- Remote Ingress ---
|
||||||
const riCfg = opts.remoteIngressConfig;
|
const riCfg = opts.remoteIngressConfig;
|
||||||
|
const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || [];
|
||||||
const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
|
const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
|
||||||
enabled: !!dcRouter.remoteIngressManager,
|
enabled: !!dcRouter.remoteIngressManager,
|
||||||
tunnelPort: riCfg?.tunnelPort || null,
|
tunnelPort: riCfg?.tunnelPort || null,
|
||||||
hubDomain: riCfg?.hubDomain || null,
|
hubDomain: riCfg?.hubDomain || null,
|
||||||
tlsConfigured: !!(riCfg?.tls?.certPath && riCfg?.tls?.keyPath),
|
tlsConfigured: !!(riCfg?.tls?.certPath && riCfg?.tls?.keyPath),
|
||||||
|
connectedEdgeIps,
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import type { RemoteIngressManager } from './classes.remoteingress-manager.js';
|
|||||||
export interface ITunnelManagerConfig {
|
export interface ITunnelManagerConfig {
|
||||||
tunnelPort?: number;
|
tunnelPort?: number;
|
||||||
targetHost?: string;
|
targetHost?: string;
|
||||||
|
tls?: {
|
||||||
|
certPem?: string;
|
||||||
|
keyPem?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,12 +27,11 @@ export class TunnelManager {
|
|||||||
this.hub = new plugins.remoteingress.RemoteIngressHub();
|
this.hub = new plugins.remoteingress.RemoteIngressHub();
|
||||||
|
|
||||||
// Listen for edge connect/disconnect events
|
// Listen for edge connect/disconnect events
|
||||||
this.hub.on('edgeConnected', (data: { edgeId: string }) => {
|
this.hub.on('edgeConnected', (data: { edgeId: string; peerAddr: string }) => {
|
||||||
const existing = this.edgeStatuses.get(data.edgeId);
|
|
||||||
this.edgeStatuses.set(data.edgeId, {
|
this.edgeStatuses.set(data.edgeId, {
|
||||||
edgeId: data.edgeId,
|
edgeId: data.edgeId,
|
||||||
connected: true,
|
connected: true,
|
||||||
publicIp: existing?.publicIp ?? null,
|
publicIp: data.peerAddr || null,
|
||||||
activeTunnels: 0,
|
activeTunnels: 0,
|
||||||
lastHeartbeat: Date.now(),
|
lastHeartbeat: Date.now(),
|
||||||
connectedAt: Date.now(),
|
connectedAt: Date.now(),
|
||||||
@@ -62,6 +65,7 @@ export class TunnelManager {
|
|||||||
await this.hub.start({
|
await this.hub.start({
|
||||||
tunnelPort: this.config.tunnelPort ?? 8443,
|
tunnelPort: this.config.tunnelPort ?? 8443,
|
||||||
targetHost: this.config.targetHost ?? '127.0.0.1',
|
targetHost: this.config.targetHost ?? '127.0.0.1',
|
||||||
|
tls: this.config.tls,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send allowed edges to the hub
|
// Send allowed edges to the hub
|
||||||
@@ -103,12 +107,16 @@ export class TunnelManager {
|
|||||||
if (existing) {
|
if (existing) {
|
||||||
existing.activeTunnels = rustEdge.activeStreams;
|
existing.activeTunnels = rustEdge.activeStreams;
|
||||||
existing.lastHeartbeat = Date.now();
|
existing.lastHeartbeat = Date.now();
|
||||||
|
// Update peer address if available from Rust hub
|
||||||
|
if (rustEdge.peerAddr) {
|
||||||
|
existing.publicIp = rustEdge.peerAddr;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Missed edgeConnected event — add entry
|
// Missed edgeConnected event — add entry
|
||||||
this.edgeStatuses.set(rustEdge.edgeId, {
|
this.edgeStatuses.set(rustEdge.edgeId, {
|
||||||
edgeId: rustEdge.edgeId,
|
edgeId: rustEdge.edgeId,
|
||||||
connected: true,
|
connected: true,
|
||||||
publicIp: null,
|
publicIp: rustEdge.peerAddr || null,
|
||||||
activeTunnels: rustEdge.activeStreams,
|
activeTunnels: rustEdge.activeStreams,
|
||||||
lastHeartbeat: Date.now(),
|
lastHeartbeat: Date.now(),
|
||||||
connectedAt: rustEdge.connectedAt * 1000,
|
connectedAt: rustEdge.connectedAt * 1000,
|
||||||
@@ -158,6 +166,19 @@ export class TunnelManager {
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the public IPs of all connected edges.
|
||||||
|
*/
|
||||||
|
public getConnectedEdgeIps(): string[] {
|
||||||
|
const ips: string[] = [];
|
||||||
|
for (const status of this.edgeStatuses.values()) {
|
||||||
|
if (status.connected && status.publicIp) {
|
||||||
|
ips.push(status.publicIp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the total number of active tunnels across all edges.
|
* Get the total number of active tunnels across all edges.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export interface IConfigData {
|
|||||||
tunnelPort: number | null;
|
tunnelPort: number | null;
|
||||||
hubDomain: string | null;
|
hubDomain: string | null;
|
||||||
tlsConfigured: boolean;
|
tlsConfigured: boolean;
|
||||||
|
connectedEdgeIps: string[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '9.1.8',
|
version: '9.3.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1321,6 +1321,15 @@ async function dispatchCombinedRefreshAction() {
|
|||||||
console.error('Certificate refresh failed:', error);
|
console.error('Certificate refresh failed:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh remote ingress data if on remoteingress view
|
||||||
|
if (currentView === 'remoteingress') {
|
||||||
|
try {
|
||||||
|
await remoteIngressStatePart.dispatchAction(fetchRemoteIngressAction, null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Remote ingress refresh failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Combined refresh failed:', error);
|
console.error('Combined refresh failed:', error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,11 +103,20 @@ export class OpsViewConfig extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private renderSystemSection(sys: appstate.IConfigState['config']['system']): TemplateResult {
|
private renderSystemSection(sys: appstate.IConfigState['config']['system']): TemplateResult {
|
||||||
|
// Annotate proxy IPs with source hint when Remote Ingress is active
|
||||||
|
const ri = this.configState.config?.remoteIngress;
|
||||||
|
let proxyIpValues: string[] | null = sys.proxyIps.length > 0 ? [...sys.proxyIps] : null;
|
||||||
|
if (proxyIpValues && ri?.enabled && proxyIpValues.includes('127.0.0.1')) {
|
||||||
|
proxyIpValues = proxyIpValues.map(ip =>
|
||||||
|
ip === '127.0.0.1' ? '127.0.0.1 (Remote Ingress)' : ip
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const fields: IConfigField[] = [
|
const fields: IConfigField[] = [
|
||||||
{ key: 'Base Directory', value: sys.baseDir },
|
{ key: 'Base Directory', value: sys.baseDir },
|
||||||
{ key: 'Data Directory', value: sys.dataDir },
|
{ key: 'Data Directory', value: sys.dataDir },
|
||||||
{ key: 'Public IP', value: sys.publicIp },
|
{ key: 'Public IP', value: sys.publicIp },
|
||||||
{ key: 'Proxy IPs', value: sys.proxyIps.length > 0 ? sys.proxyIps : null, type: 'pills' },
|
{ key: 'Proxy IPs', value: proxyIpValues, type: 'pills' },
|
||||||
{ key: 'Uptime', value: this.formatUptime(sys.uptime) },
|
{ key: 'Uptime', value: this.formatUptime(sys.uptime) },
|
||||||
{ key: 'Storage Backend', value: sys.storageBackend, type: 'badge' },
|
{ key: 'Storage Backend', value: sys.storageBackend, type: 'badge' },
|
||||||
{ key: 'Storage Path', value: sys.storagePath },
|
{ key: 'Storage Path', value: sys.storagePath },
|
||||||
@@ -292,6 +301,7 @@ export class OpsViewConfig extends DeesElement {
|
|||||||
{ key: 'Tunnel Port', value: ri.tunnelPort },
|
{ key: 'Tunnel Port', value: ri.tunnelPort },
|
||||||
{ key: 'Hub Domain', value: ri.hubDomain },
|
{ key: 'Hub Domain', value: ri.hubDomain },
|
||||||
{ key: 'TLS Configured', value: ri.tlsConfigured, type: 'boolean' },
|
{ key: 'TLS Configured', value: ri.tlsConfigured, type: 'boolean' },
|
||||||
|
{ key: 'Connected Edge IPs', value: ri.connectedEdgeIps?.length > 0 ? ri.connectedEdgeIps : null, type: 'pills' },
|
||||||
];
|
];
|
||||||
|
|
||||||
const actions: IConfigSectionAction[] = [
|
const actions: IConfigSectionAction[] = [
|
||||||
|
|||||||
Reference in New Issue
Block a user