Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ce09c53ca | |||
| 69be2295f1 |
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-17 - 6.6.0 - feat(remoteingress)
|
||||||
|
derive effective remote ingress listen ports from route configs and expose them via ops API
|
||||||
|
|
||||||
|
- Derive listen ports from SmartProxy route configs with remoteIngress.enabled; supports optional edgeFilter to target edges by id or tags.
|
||||||
|
- Add RemoteIngressManager.setRoutes(), derivePortsForEdge(), and getEffectiveListenPorts() which falls back to manual listenPorts when present.
|
||||||
|
- dcrouter now supplies route configs to RemoteIngressManager during initialization and when updating SmartProxy configuration to keep derived ports in sync.
|
||||||
|
- Ops API now returns effectiveListenPorts for edges; createRemoteIngress.listenPorts is optional and createEdge defaults listenPorts to an empty array.
|
||||||
|
- Bump dependency @serve.zone/remoteingress to ^3.0.4 to align types/behavior.
|
||||||
|
|
||||||
## 2026-02-16 - 6.5.0 - feat(ops-view-remoteingress)
|
## 2026-02-16 - 6.5.0 - feat(ops-view-remoteingress)
|
||||||
add 'Create Edge Node' header action to remote ingress table and remove duplicate createNewAction
|
add 'Create Edge Node' header action to remote ingress table and remove duplicate createNewAction
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@serve.zone/dcrouter",
|
"name": "@serve.zone/dcrouter",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "6.5.0",
|
"version": "6.6.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": {
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
"@push.rocks/smartstate": "^2.0.30",
|
"@push.rocks/smartstate": "^2.0.30",
|
||||||
"@push.rocks/smartunique": "^3.0.9",
|
"@push.rocks/smartunique": "^3.0.9",
|
||||||
"@serve.zone/interfaces": "^5.3.0",
|
"@serve.zone/interfaces": "^5.3.0",
|
||||||
"@serve.zone/remoteingress": "^3.0.2",
|
"@serve.zone/remoteingress": "^3.0.4",
|
||||||
"@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"
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -96,8 +96,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: ^3.0.2
|
specifier: ^3.0.4
|
||||||
version: 3.0.2
|
version: 3.0.4
|
||||||
'@tsclass/tsclass':
|
'@tsclass/tsclass':
|
||||||
specifier: ^9.3.0
|
specifier: ^9.3.0
|
||||||
version: 9.3.0
|
version: 9.3.0
|
||||||
@@ -1340,8 +1340,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@3.0.2':
|
'@serve.zone/remoteingress@3.0.4':
|
||||||
resolution: {integrity: sha512-FnwNVO0Dn9xuNv0t81u6pjCizSeCyMjkRKm6wN5qycCdGFoJmFbBamHqV01KtK1KcgDTd7LX+PowSqKReNrBGw==}
|
resolution: {integrity: sha512-ZD66Y8fvW7SjealziOlhaC7+Y/3gxQkZlj/X8rxgVHmGhlc/YQtn6H6LNVazbM88BXK5ns004Qo6ongAB6Ho0Q==}
|
||||||
|
|
||||||
'@sindresorhus/is@5.6.0':
|
'@sindresorhus/is@5.6.0':
|
||||||
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==}
|
||||||
@@ -6830,7 +6830,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@3.0.2':
|
'@serve.zone/remoteingress@3.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@push.rocks/qenv': 6.1.3
|
'@push.rocks/qenv': 6.1.3
|
||||||
'@push.rocks/smartrust': 1.2.1
|
'@push.rocks/smartrust': 1.2.1
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '6.5.0',
|
version: '6.6.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -955,10 +955,15 @@ export class DcRouter {
|
|||||||
|
|
||||||
// Update configuration
|
// Update configuration
|
||||||
this.options.smartProxyConfig = config;
|
this.options.smartProxyConfig = config;
|
||||||
|
|
||||||
|
// Update routes on RemoteIngressManager so derived ports stay in sync
|
||||||
|
if (this.remoteIngressManager && config.routes) {
|
||||||
|
this.remoteIngressManager.setRoutes(config.routes as any[]);
|
||||||
|
}
|
||||||
|
|
||||||
// Start new SmartProxy with updated configuration (will include email routes if configured)
|
// Start new SmartProxy with updated configuration (will include email routes if configured)
|
||||||
await this.setupSmartProxy();
|
await this.setupSmartProxy();
|
||||||
|
|
||||||
console.log('SmartProxy configuration updated');
|
console.log('SmartProxy configuration updated');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1587,6 +1592,10 @@ export class DcRouter {
|
|||||||
this.remoteIngressManager = new RemoteIngressManager(this.storageManager);
|
this.remoteIngressManager = new RemoteIngressManager(this.storageManager);
|
||||||
await this.remoteIngressManager.initialize();
|
await this.remoteIngressManager.initialize();
|
||||||
|
|
||||||
|
// Pass current routes so the manager can derive edge ports from remoteIngress-tagged routes
|
||||||
|
const currentRoutes = this.options.smartProxyConfig?.routes || [];
|
||||||
|
this.remoteIngressManager.setRoutes(currentRoutes as any[]);
|
||||||
|
|
||||||
// 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: this.options.remoteIngressConfig.tunnelPort ?? 8443,
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ export class RemoteIngressHandler {
|
|||||||
if (!manager) {
|
if (!manager) {
|
||||||
return { edges: [] };
|
return { edges: [] };
|
||||||
}
|
}
|
||||||
// Return edges without secrets
|
// Return edges without secrets, enriched with effective listen ports
|
||||||
const edges = manager.getAllEdges().map((e) => ({
|
const edges = manager.getAllEdges().map((e) => ({
|
||||||
...e,
|
...e,
|
||||||
secret: '********', // Never expose secrets via API
|
secret: '********', // Never expose secrets via API
|
||||||
|
effectiveListenPorts: manager.getEffectiveListenPorts(e),
|
||||||
}));
|
}));
|
||||||
return { edges };
|
return { edges };
|
||||||
},
|
},
|
||||||
@@ -47,7 +48,7 @@ export class RemoteIngressHandler {
|
|||||||
|
|
||||||
const edge = await manager.createEdge(
|
const edge = await manager.createEdge(
|
||||||
dataArg.name,
|
dataArg.name,
|
||||||
dataArg.listenPorts,
|
dataArg.listenPorts || [],
|
||||||
dataArg.tags,
|
dataArg.tags,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,30 @@
|
|||||||
import * as plugins from '../plugins.js';
|
import * as plugins from '../plugins.js';
|
||||||
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
import type { StorageManager } from '../storage/classes.storagemanager.js';
|
||||||
import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js';
|
import type { IRemoteIngress, IDcRouterRouteConfig } from '../../ts_interfaces/data/remoteingress.js';
|
||||||
|
|
||||||
const STORAGE_PREFIX = '/remote-ingress/';
|
const STORAGE_PREFIX = '/remote-ingress/';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flatten a port range (number | number[] | Array<{from, to}>) to a sorted unique number array.
|
||||||
|
*/
|
||||||
|
function extractPorts(portRange: number | number[] | Array<{ from: number; to: number }>): number[] {
|
||||||
|
const ports = new Set<number>();
|
||||||
|
if (typeof portRange === 'number') {
|
||||||
|
ports.add(portRange);
|
||||||
|
} else if (Array.isArray(portRange)) {
|
||||||
|
for (const entry of portRange) {
|
||||||
|
if (typeof entry === 'number') {
|
||||||
|
ports.add(entry);
|
||||||
|
} else if (typeof entry === 'object' && 'from' in entry && 'to' in entry) {
|
||||||
|
for (let p = entry.from; p <= entry.to; p++) {
|
||||||
|
ports.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [...ports].sort((a, b) => a - b);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages CRUD for remote ingress edge registrations.
|
* Manages CRUD for remote ingress edge registrations.
|
||||||
* Persists edge configs via StorageManager and provides
|
* Persists edge configs via StorageManager and provides
|
||||||
@@ -12,6 +33,7 @@ const STORAGE_PREFIX = '/remote-ingress/';
|
|||||||
export class RemoteIngressManager {
|
export class RemoteIngressManager {
|
||||||
private storageManager: StorageManager;
|
private storageManager: StorageManager;
|
||||||
private edges: Map<string, IRemoteIngress> = new Map();
|
private edges: Map<string, IRemoteIngress> = new Map();
|
||||||
|
private routes: IDcRouterRouteConfig[] = [];
|
||||||
|
|
||||||
constructor(storageManager: StorageManager) {
|
constructor(storageManager: StorageManager) {
|
||||||
this.storageManager = storageManager;
|
this.storageManager = storageManager;
|
||||||
@@ -30,12 +52,60 @@ export class RemoteIngressManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the current route configs for port derivation.
|
||||||
|
*/
|
||||||
|
public setRoutes(routes: IDcRouterRouteConfig[]): void {
|
||||||
|
this.routes = routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Derive listen ports for an edge from routes tagged with remoteIngress.enabled.
|
||||||
|
* When a route specifies edgeFilter, only edges whose id or tags match get that route's ports.
|
||||||
|
* When edgeFilter is absent, the route applies to all edges.
|
||||||
|
*/
|
||||||
|
public derivePortsForEdge(edgeId: string, edgeTags?: string[]): number[] {
|
||||||
|
const ports = new Set<number>();
|
||||||
|
|
||||||
|
for (const route of this.routes) {
|
||||||
|
if (!route.remoteIngress?.enabled) continue;
|
||||||
|
|
||||||
|
// Apply edge filter if present
|
||||||
|
const filter = route.remoteIngress.edgeFilter;
|
||||||
|
if (filter && filter.length > 0) {
|
||||||
|
const idMatch = filter.includes(edgeId);
|
||||||
|
const tagMatch = edgeTags?.some((tag) => filter.includes(tag)) ?? false;
|
||||||
|
if (!idMatch && !tagMatch) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract ports from the route match
|
||||||
|
if (route.match?.ports) {
|
||||||
|
for (const p of extractPorts(route.match.ports)) {
|
||||||
|
ports.add(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...ports].sort((a, b) => a - b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the effective listen ports for an edge.
|
||||||
|
* Returns manual listenPorts if non-empty, otherwise derives ports from tagged routes.
|
||||||
|
*/
|
||||||
|
public getEffectiveListenPorts(edge: IRemoteIngress): number[] {
|
||||||
|
if (edge.listenPorts && edge.listenPorts.length > 0) {
|
||||||
|
return edge.listenPorts;
|
||||||
|
}
|
||||||
|
return this.derivePortsForEdge(edge.id, edge.tags);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new edge registration.
|
* Create a new edge registration.
|
||||||
*/
|
*/
|
||||||
public async createEdge(
|
public async createEdge(
|
||||||
name: string,
|
name: string,
|
||||||
listenPorts: number[],
|
listenPorts: number[] = [],
|
||||||
tags?: string[],
|
tags?: string[],
|
||||||
): Promise<IRemoteIngress> {
|
): Promise<IRemoteIngress> {
|
||||||
const id = plugins.uuid.v4();
|
const id = plugins.uuid.v4();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import type { IRouteConfig } from '@push.rocks/smartproxy';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stored remote ingress edge registration.
|
* A stored remote ingress edge registration.
|
||||||
*/
|
*/
|
||||||
@@ -23,3 +25,25 @@ export interface IRemoteIngressStatus {
|
|||||||
lastHeartbeat: number | null;
|
lastHeartbeat: number | null;
|
||||||
connectedAt: number | null;
|
connectedAt: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Route-level remote ingress configuration.
|
||||||
|
* When attached to a route, signals that traffic for this route
|
||||||
|
* should be accepted from remote edge nodes.
|
||||||
|
*/
|
||||||
|
export interface IRouteRemoteIngress {
|
||||||
|
/** Whether this route receives traffic from edge nodes */
|
||||||
|
enabled: boolean;
|
||||||
|
/** Optional filter: only edges whose id or tags match get this route's ports.
|
||||||
|
* When absent, the route applies to all edges. */
|
||||||
|
edgeFilter?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extended route config used within dcrouter.
|
||||||
|
* Adds the optional `remoteIngress` property to SmartProxy's IRouteConfig.
|
||||||
|
* SmartProxy ignores unknown properties at runtime.
|
||||||
|
*/
|
||||||
|
export type IDcRouterRouteConfig = IRouteConfig & {
|
||||||
|
remoteIngress?: IRouteRemoteIngress;
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export interface IReq_CreateRemoteIngress extends plugins.typedrequestInterfaces
|
|||||||
request: {
|
request: {
|
||||||
identity?: authInterfaces.IIdentity;
|
identity?: authInterfaces.IIdentity;
|
||||||
name: string;
|
name: string;
|
||||||
listenPorts: number[];
|
listenPorts?: number[];
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
};
|
};
|
||||||
response: {
|
response: {
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@serve.zone/dcrouter',
|
name: '@serve.zone/dcrouter',
|
||||||
version: '6.5.0',
|
version: '6.6.0',
|
||||||
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
description: 'A multifaceted routing service handling mail and SMS delivery functions.'
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user