@serve.zone/remoteingress
@serve.zone/remoteingress is a Rust-powered, TypeScript-controlled edge tunnel for moving TCP and UDP traffic from public edge nodes into a private dcrouter or SmartProxy host while preserving the original client IP through PROXY protocol. It also supports controlled outbound TCP egress through QUIC-connected edges for use cases such as SMTP delivery from the edge IP.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
What It Does
Remote ingress solves the common edge problem: your workload or gateway lives behind NAT, a firewall, or a private network, but public traffic should enter through one or more hardened edge VPS nodes.
Internet clients
|
v
public edge node: RemoteIngressEdge
|
| TLS or QUIC tunnel
v
private site: RemoteIngressHub -> dcrouter / SmartProxy
The edge binds public TCP and UDP ports assigned by the hub. Each accepted connection or datagram is tunneled to the hub, which forwards it to a local target host with PROXY headers so downstream routing can still see the real client address.
For outbound egress, the hub creates a one-shot localhost TCP proxy. The selected edge resolves the logical target, enforces the configured egress policy on the resolved IP address, dials the destination from the edge host, and pipes bytes over the tunnel. Outbound egress does not use PROXY protocol.
Highlights
- ⚡ Rust networking core managed from TypeScript through
@push.rocks/smartrust - 🔁 Hub/edge model with dynamic
updateAllowedEdges()reconciliation - 🌐 TCP forwarding over frame-multiplexed TLS or native QUIC streams
- 📡 UDP forwarding over TCP frames or QUIC datagrams
- 🧾 PROXY protocol preservation for client IP visibility at SmartProxy
- 🛡️ Hub-pushed nftables firewall snapshots for blocklists, rate limits, and custom rules
- 🔐 Shared-secret edge authentication and compact connection tokens
- 🚦 Transport modes:
tcpTls,quic, andquicWithFallback - 📤 One-shot outbound TCP egress proxy over QUIC-connected edges
- 📊 EventEmitter status events for edge lifecycle, streams, port assignments, and crash recovery
Install
Use the package as a TypeScript library:
pnpm add @serve.zone/remoteingress
Install the CLI on a Linux edge or hub host with the released self-extracting binary:
curl -sSL https://code.foss.global/serve.zone/remoteingress/raw/branch/main/install.sh | sudo bash
The installer downloads remoteingress-linux-x64 or remoteingress-linux-arm64 from the latest Gitea release, installs it under /opt/remoteingress, and links /usr/local/bin/remoteingress. Use --version vX.Y.Z to pin a release, --install-dir /path to change the target directory, or --source to clone the tag and build the NodeNext package locally.
Without --version the installer takes the newest Gitea release. While a fleet is mid-migration to a new major, provision every new edge with --version pinned to the major its hub already runs; see Protocol Capabilities.
curl -sSL https://code.foss.global/serve.zone/remoteingress/raw/branch/main/install.sh | sudo bash -s -- --source
Hub Side
Run the hub next to the private service you want to expose. In dcrouter deployments the target is usually SmartProxy on 127.0.0.1.
import { RemoteIngressHub } from '@serve.zone/remoteingress';
const hub = new RemoteIngressHub();
hub.on('edgeConnected', ({ edgeId }) => console.log('edge connected', edgeId));
hub.on('edgeDisconnected', ({ edgeId }) => console.log('edge disconnected', edgeId));
hub.on('streamSummary', ({ edgeId, activeStreams }) => console.log('streams', edgeId, activeStreams));
hub.on('egressConnection', (event) => console.log('egress', event));
hub.on('egressIdentityChanged', ({ edgeId, egressIdentity }) => {
console.log('egress identity', edgeId, egressIdentity);
});
await hub.start({
tunnelPort: 8443,
targetHost: '127.0.0.1',
streamEventMode: 'summary',
});
await hub.updateAllowedEdges([
{
id: 'edge-fra-01',
secret: 'replace-with-a-long-random-secret',
listenPorts: [80, 443],
listenPortsUdp: [443],
stunIntervalSecs: 300,
egress: {
enabled: true,
allowedPorts: [25],
allowedHostPatterns: ['*.example.net'],
allowPrivateRanges: false,
deniedCidrs: ['10.0.0.0/8', '192.168.0.0/16'],
maxConcurrentStreams: 50,
},
firewallConfig: {
blockedIps: ['198.51.100.25'],
rateLimits: [
{ id: 'https-per-ip', port: 443, protocol: 'tcp', rate: '200/second', burst: 100, perSourceIP: true },
],
},
},
]);
const status = await hub.getStatus();
console.log(status.connectedEdges);
const proxy = await hub.startEgressTcpProxy({
edgeId: 'edge-fra-01',
logicalHost: 'mx.example.net',
port: 25,
serverFirst: true,
connectTimeoutMs: 10_000,
});
// Connect your SMTP client to proxy.listenHost:proxy.listenPort.
// The proxy accepts one local connection and then removes itself.
console.log(proxy);
const sourceEvidence = await hub.waitForEgressConnectionEvidence(
proxy.proxyId,
proxy.edgeId,
);
console.log(sourceEvidence?.sourceIp, sourceEvidence?.addressFamily);
await hub.stop();
Edge Side
The edge runs on the public node. It normally needs root privileges to bind privileged ports and apply nftables rules. If nftables cannot be initialized, the tunnel can still run, but kernel-level edge firewalling is skipped.
import { RemoteIngressEdge } from '@serve.zone/remoteingress';
const edge = new RemoteIngressEdge();
edge.on('tunnelConnected', () => console.log('tunnel connected'));
edge.on('portsAssigned', ({ listenPorts }) => console.log('TCP ports', listenPorts));
edge.on('firewallConfigUpdated', () => console.log('firewall snapshot applied'));
await edge.start({
hubHost: 'ingress-hub.example.com',
hubPort: 8443,
edgeId: 'edge-fra-01',
secret: 'replace-with-a-long-random-secret',
transportMode: 'quicWithFallback',
egressSourceIpv4: '203.0.113.10',
egressSourceIpv6: '2001:db8::10',
});
console.log(await edge.getStatus());
await edge.stop();
Outbound TCP Egress Proxy
Outbound egress is disabled by default. Enable it per edge with TAllowedEdge.egress, then request one-shot proxies from the hub with startEgressTcpProxy().
The proxy always binds 127.0.0.1 on the hub and accepts a single local connection. It then opens an egress stream to the selected QUIC-connected edge. The edge resolves logicalHost, checks the requested port, filters resolved IPs, dials the destination, returns the chosen IP for audit/status, and pipes bytes.
Policy rules:
allowedPortsdefaults to[25]when omitted.allowedHostPatternsmay contain exact names or*.example.compatterns.deniedCidrsalways wins after DNS resolution.allowPrivateRangesonly allows RFC1918/ULA private ranges; loopback, link-local, metadata, multicast, unspecified, documentation, shared-address, and reserved ranges remain denied.- Egress currently requires the edge to be connected over QUIC. If
quicWithFallbackfalls back totcpTls, egress proxy creation fails instead of falling back to direct hub egress. - IPv4 and IPv6 destinations require the matching
egressSourceIpv4oregressSourceIpv6edge setting. Each address must be a concrete unicast address assigned to the edge host. The edge binds the outbound socket to that address and fails closed when the matching family is missing or cannot be bound; it never falls back to an OS-selected source address.
Use serverFirst: true for SMTP and other protocols where the remote server sends the first bytes after connect.
Egress identity status
Edges with configured, locally bindable egress source addresses report the egressIdentity capability. RemoteIngressEdge.getStatus() exposes the latest IEgressIdentityReport, and each connected entry from RemoteIngressHub.getStatus() exposes IEgressIdentityStatus.
The proof method is localSocketBind: it proves that the edge successfully bound a local socket to the configured address, not that an external service observed that address after routing or NAT. Edge observations include observedAtUnixMs; hub status adds hubReceivedAtUnixMs and marks an address stale when no fresh observation has arrived for more than 45 seconds. The value is null for an edge without a currently bindable configured source address.
The report travels in the heartbeat PONG payload as JSON. It carries no version member: the package version is the protocol version, so the hub decodes the payload strictly and refuses an unknown or missing member by name instead of accepting a payload it does not fully understand. An empty PONG payload carries no report and leaves identity status untouched.
The hub emits egressIdentityChanged with { edgeId, egressIdentity } when the reported address or proof changes. Timestamp-only refreshes and stale-state transitions do not emit the event.
Connection Tokens
Tokens are base64url-encoded compact JSON. They are useful when the hub operator provisions an edge and wants to hand over one opaque string.
import {
RemoteIngressEdge,
encodeConnectionToken,
decodeConnectionToken,
} from '@serve.zone/remoteingress';
const token = encodeConnectionToken({
hubHost: 'ingress-hub.example.com',
hubPort: 8443,
edgeId: 'edge-fra-01',
secret: 'replace-with-a-long-random-secret',
});
console.log(decodeConnectionToken(token));
const edge = new RemoteIngressEdge();
await edge.start({
token,
egressSourceIpv4: '203.0.113.10',
egressSourceIpv6: '2001:db8::10',
});
RemoteIngressEdge.start() accepts either IEdgeConfig with explicit hub credentials or IEdgeTokenConfig with a token. Both forms accept bindAddress, transportMode, egressSourceIpv4, and egressSourceIpv6.
CLI Mode
The package entry point exports runCli(), and the remoteingress CLI can run hub or edge mode.
remoteingress hub --tunnel-port 8443 --target-host 127.0.0.1 --stream-event-mode summary
remoteingress edge --token eyJoIjoiaW5ncmVzcy1odWIuZXhhbXBsZS5jb20i... \
--egress-source-ipv4 203.0.113.10 --egress-source-ipv6 2001:db8::10
Environment-based startup is also supported. Every variable below is read in runCli
(ts/index.ts) as the fallback of the matching CLI flag, which always wins, so RemoteIngress needs
no environment file and declares no required variable: this table is the complete set it reads, and
test/test.environment.node.ts fails when the sources, this table and remoteingress --help
disagree.
| Variable | Flag | Default | Purpose |
|---|---|---|---|
REMOTEINGRESS_MODE |
--mode |
none | hub or edge; anything else prints the usage text and exits without starting. |
REMOTEINGRESS_TUNNEL_PORT |
--tunnel-port |
8443 |
Hub-side tunnel listener port; a value that is not a TCP port number refuses the start. |
REMOTEINGRESS_TARGET_HOST |
--target-host |
127.0.0.1 |
Hub-side forwarding target. |
REMOTEINGRESS_TLS_CERT_PEM |
--tls-cert-pem |
none | Hub TLS certificate in PEM form; handed to the native hub only together with the key. |
REMOTEINGRESS_TLS_KEY_PEM |
--tls-key-pem |
none | Hub TLS private key in PEM form; handed to the native hub only together with the certificate. |
REMOTEINGRESS_ALLOWED_EDGES_JSON |
--allowed-edges-json |
empty list | Hub-side allowed edge list; entries can include egress policy objects. Malformed JSON refuses the start by name, before the hub listens. |
REMOTEINGRESS_PERFORMANCE_JSON |
--performance-json |
none | Optional performance configuration. Malformed JSON refuses the start by name. |
REMOTEINGRESS_STREAM_EVENT_MODE |
--stream-event-mode |
none | Hub stream event mode: perStream, summary or off; any other value refuses the start. |
REMOTEINGRESS_TOKEN |
--token |
none | Edge connection token; carries hub host, edge id and secret in one value. |
REMOTEINGRESS_HUB_HOST |
--hub-host |
none | Explicit edge connection host, mandatory without a token. |
REMOTEINGRESS_HUB_PORT |
--hub-port |
8443 |
Explicit edge connection port; a value that is not a TCP port number refuses the start. |
REMOTEINGRESS_EDGE_ID |
--edge-id |
none | Explicit edge id, mandatory without a token. |
REMOTEINGRESS_SECRET |
--secret |
none | Explicit edge secret, mandatory without a token. |
REMOTEINGRESS_BIND_ADDRESS |
--bind-address |
none | Edge-side local bind address for the tunnel connection. |
REMOTEINGRESS_EGRESS_SOURCE_IPV4 |
--egress-source-ipv4 |
none | Concrete locally assigned IPv4 source for outbound egress. |
REMOTEINGRESS_EGRESS_SOURCE_IPV6 |
--egress-source-ipv6 |
none | Concrete locally assigned IPv6 source for outbound egress. |
Seven start-time refusals exist. Every one of them happens before the hub or the edge starts anything, and every one names the variable and the flag the value came from:
- an edge without a token and without the full
REMOTEINGRESS_HUB_HOST,REMOTEINGRESS_EDGE_IDandREMOTEINGRESS_SECRETtrio - the CLI names all three; - a
REMOTEINGRESS_TUNNEL_PORTthat is not a TCP port number; - a
REMOTEINGRESS_HUB_PORTthat is not a TCP port number; - an unknown
REMOTEINGRESS_STREAM_EVENT_MODE; - a malformed
REMOTEINGRESS_PERFORMANCE_JSON, refused with the parser's message; - a malformed
REMOTEINGRESS_ALLOWED_EDGES_JSON, refused with the parser's message; - an unknown
--transport-mode, refused with the admitted values (tcpTls,quic,quicWithFallback).
--transport-mode is the one value with no environment variable of its own; it applies to both
edge branches, the connection token as well as the explicit trio.
Docker Image
From 32.0.0 on the image is built from Dockerfile_##version##, so tsdocker publishes a multi-arch image for linux/amd64 and linux/arm64 under the release version tag only — code.foss.global/serve.zone/remoteingress:32.0.0 for this release — and no latest tag is published again. The existing remoteingress:latest stays frozen at the 5.2.5 build; pin the image by version. Edge and hub hosts install the npm package or the released binary, not the image.
Hub example:
docker run --rm --name remoteingress-hub \
--network host \
-e REMOTEINGRESS_MODE=hub \
-e REMOTEINGRESS_TARGET_HOST=127.0.0.1 \
-e REMOTEINGRESS_ALLOWED_EDGES_JSON='[{"id":"edge-fra-01","secret":"replace-me","listenPorts":[80,443],"egress":{"enabled":true,"allowedPorts":[25]}}]' \
code.foss.global/serve.zone/remoteingress:32.0.0
Edge example:
docker run --rm --name remoteingress-edge \
--network host \
--cap-add NET_ADMIN \
-e REMOTEINGRESS_MODE=edge \
-e REMOTEINGRESS_TOKEN='<connection-token>' \
-e REMOTEINGRESS_EGRESS_SOURCE_IPV4='203.0.113.10' \
-e REMOTEINGRESS_EGRESS_SOURCE_IPV6='2001:db8::10' \
code.foss.global/serve.zone/remoteingress:32.0.0
Use host networking when the container must bind public ports directly or reach a localhost target on the host. NET_ADMIN is needed only when the edge should apply nftables firewall snapshots.
Transport Modes
| Mode | Behavior |
|---|---|
tcpTls |
Single TLS connection with frame-based stream multiplexing. Good for conservative networks. |
quic |
QUIC streams for TCP and QUIC datagrams for UDP. Best latency and no TCP head-of-line blocking. |
quicWithFallback |
Default edge mode. Tries QUIC, falls back to TCP/TLS when UDP is blocked, and periodically promotes the live fallback to QUIC after UDP recovers. |
Outbound egress proxies require a QUIC-connected edge because the hub opens a dedicated QUIC bidirectional stream to the edge. A quicWithFallback edge can use egress when QUIC succeeds; if it falls back to tcpTls, egress proxy creation fails closed.
While TCP/TLS fallback is active, QUIC re-probes use bounded exponential backoff and do not interrupt fallback traffic after a failed attempt. A successful probe completes RemoteIngress authentication before replacing the fallback, and the hub treats that transport replacement as one logical edge connection.
Protocol Capabilities
The edge names its capabilities on the auth line at session open and the hub answers with the negotiated set in the handshake config. Negotiation is by name, not by a protocol version number; the package version is the protocol version.
| Capability | Advertised by | Effect |
|---|---|---|
egressTcp |
Edge | The hub may open outbound TCP egress proxies through this edge. An edge that does not name it is refused egress. |
egressIdentity |
Edge, echoed by the hub | The edge reports its bound egress source addresses in heartbeat PONG payloads, and the hub exposes them as identity status. |
udpAdmission |
Edge, echoed by the hub when the edge names it | TCP and UDP share one admission budget and UDP sessions are torn down reliably. Without it, UDP keeps a separate budget. |
An unknown capability name is ignored, so the hub and an edge that do not agree on a name simply do not get that feature: tunnelling still works, egress is refused, and identity status stays empty.
Upgrading across a major: these names are part of the wire contract and change with the major. Upgrade the hub and every edge of one hub in a single attended slot — hub first, then each edge — so no edge sits in a skew window. A skewed edge stays connected and keeps tunnelling, but it is not egress-eligible. A hub that selects mail edges by egress capability — dcrouter does — then finds no eligible mail edge for the length of the skew and withdraws its managed mail MX records until the last edge is upgraded, so agree that exposure with the operator before the slot.
Performance and Flow Control
Set IHubConfig.performance for hub-wide defaults and TAllowedEdge.performance for per-edge overrides. quicDatagramReceiveBufferBytes is the exception: Quinn fixes this receive buffer when the hub endpoint starts, so only the IHubConfig.performance value applies and connected-edge status reports that hub-wide value. The remaining effective configuration is returned in each connected edge entry from RemoteIngressHub.getStatus().
await hub.start({
tunnelPort: 8443,
targetHost: '127.0.0.1',
performance: {
profile: 'balanced',
totalWindowBudgetBytes: 256 * 1024 * 1024,
maxHalfClosedStreams: 64,
halfCloseDrainTimeoutMs: 300_000,
},
});
balanced is the default profile. Profile defaults:
| Profile | Max streams | TCP/TLS window budget | Stream window range | Sustained window | Hub QUIC receive buffer | Frame payload | Max retained backend half-closes |
|---|---|---|---|---|---|---|---|
balanced |
1024 | 256 MiB | 128 KiB to 8 MiB | 512 KiB | 4 MiB | 64 KiB | 64 |
throughput |
512 | 512 MiB | 256 KiB to 32 MiB | 2 MiB | 8 MiB | 128 KiB | 32 |
highConcurrency |
4096 | 256 MiB | 64 KiB to 4 MiB | 256 KiB | 16 MiB | 32 KiB | 256 |
All profiles default firstDataConnectTimeoutMs to 5000, clientWriteTimeoutMs to 30000, and halfCloseDrainTimeoutMs to 300000. serverFirstPorts defaults to [21, 22, 25, 110, 143, 587, 3306]. Explicit fields override the selected profile. Effective stream counts and windows are normalized to the aggregate budget, streamFramePayloadBytes is clamped to 16-256 KiB, and maxHalfClosedStreams is clamped to maxStreamsPerEdge. Current peers enforce maxStreamsPerEdge as one shared admission budget for accepted TCP sockets and UDP sessions; TCP admission is reserved before first-data gating. On the edge, UDP sessions may hold at most half of that budget (rounded down, minimum one), so a flood of spoofed datagrams cannot starve TCP admission. During a rolling upgrade, a connection to a peer that does not yet advertise this capability keeps UDP in a separate budget (on the edge still capped at half of maxStreamsPerEdge), rather than retaining shared capacity that the older peer cannot explicitly release. Hub status reports established tunnel streams as activeStreams and UDP separately as udp.activeSessions; it does not expose accepted sockets waiting for first data.
TCP/TLS transport uses adaptive per-stream windows plus an exact aggregate credit budget in each data direction. Credits remain charged while DATA is queued and are reclaimed only after a window acknowledgement or confirmed terminal queue drain. This prevents concurrent streams from exceeding totalWindowBudgetBytes. QUIC uses native stream flow control, so flowControl.applies is false for QUIC connections. For TCP/TLS, flowControl.estimatedInFlightBytes reports the current hub-to-edge outstanding credit count rather than a window-size estimate.
A peer that does not send maxHalfClosedStreams or halfCloseDrainTimeoutMs leaves the receiving side on the profile defaults clamped to maxStreamsPerEdge, so an upgrade in either order keeps working.
Stream resets and UDP session closes travel on reserved control capacity. When that capacity is exhausted they are parked and coalesced per stream id instead of dropping the connection, so a burst of thousands of idle UDP closes or rejected opens never resets the tunnel, and no terminal is ever dropped. The edge answers PING with a PONG on the same reserved capacity and no longer disconnects when the regular control queue is saturated; if even the reserved capacity is momentarily full the PONG is skipped, which is safe because the hub treats any received frame as liveness and the next PING gets a fresh PONG.
maxHalfClosedStreams limits streams retained after the backend sends FIN while the client upload direction remains open. Ingress retention is enforced at the hub, and outbound egress retention is enforced at the edge. Uploads that already completed do not consume a retention slot, so client-FIN-first request/response protocols continue to work when the value is 0; setting 0 disables only backend-initiated retention. halfCloseDrainTimeoutMs bounds directional half-close draining generally, including waiting for a response after client FIN; it is also the hard deadline for counted backend-FIN-first retention. When the cap or deadline is reached, the stream is reset and its admission and retention capacity is released.
Crash Recovery
RemoteIngressHub and RemoteIngressEdge automatically recover an unexpectedly exited Rust child. Recovery is serialized, restores the saved runtime configuration, and on the hub also restores the allowed-edge snapshot. The edge reapplies nftables state and restarts status reporting after recovery.
Retries begin after 1 second, use exponential backoff capped at 30 seconds, and continue until recovery succeeds or stop() aborts the active recovery generation. When the tenth accumulated recovery attempt begins, crashRecoveryDelayed is emitted once with { attempts, nextRetryDelayMs }; this event is advisory and retries continue. A successful restart emits crashRecovered. Retry counters reset only after the recovered process remains stable for 60 seconds. crashRecoveryFailed is reserved for an unexpected terminal failure of the recovery loop.
Concurrent lifecycle calls are ownership-safe: duplicate start() calls are rejected, duplicate stop() calls join the same operation, start() is rejected while stop or recovery owns the bridge, and stop() waits for any in-flight start or recovery work before confirming child termination.
Stream Event Modes
The hub can emit stream telemetry in three modes through IHubConfig.streamEventMode or REMOTEINGRESS_STREAM_EVENT_MODE.
| Mode | Behavior |
|---|---|
perStream |
Compatibility/debug mode. Emits streamOpened and streamClosed for each stream. This is the library default. |
summary |
Production dashboard mode. Suppresses per-stream events and emits streamSummary once per second per connected edge. |
off |
Suppresses stream telemetry events. Consumers should poll getStatus() when they need counts. |
streamSummary carries { edgeId, activeStreams, streamsOpenedTotal, streamsClosedTotal }. The hub status returned by getStatus() remains the authoritative source for current stream and traffic counters.
Firewall Config
firewallConfig travels in the same hub-to-edge configuration update as port assignments. Each update is a full desired-state snapshot.
await hub.updateAllowedEdges([
{
id: 'edge-fra-01',
secret: 'secret',
listenPorts: [80, 443],
firewallConfig: {
blockedIps: ['203.0.113.0/24'],
rateLimits: [
{ id: 'http', port: 80, protocol: 'tcp', rate: '100/second', perSourceIP: true },
],
rules: [
{ id: 'allow-monitoring', direction: 'input', action: 'accept', sourceIP: '10.0.0.0/8', destPort: 9090, protocol: 'tcp' },
],
},
},
]);
API Surface
| Export | Purpose |
|---|---|
RemoteIngressHub |
Starts/stops the private hub, authorizes edges, pushes runtime config, and reports connected edges. |
RemoteIngressEdge |
Starts/stops the public edge, connects to the hub, binds assigned ports, and applies firewall rules. |
encodeConnectionToken() |
Encodes hub host, port, edge ID, and secret into a token. |
decodeConnectionToken() |
Decodes and validates a token. |
IHubConfig, IEdgeConfig, IEdgeTokenConfig, TAllowedEdge, TStreamEventMode |
Primary TypeScript shapes for integrating the module. |
IPerformanceConfig, IEffectivePerformanceConfig, TPerformanceProfile, IFlowControlStatus |
Performance inputs and effective runtime flow-control status. |
IEgressPolicy, IEgressTcpProxyRequest, IEgressTcpProxyInfo, TAddressFamilyPreference |
Outbound TCP egress policy and proxy request/response types. |
IEgressIdentityReport, IEgressIdentityStatus, IEgressSourceBindingObservation, IEgressSourceBindingStatus, TEgressIdentityProofMethod |
Edge-reported and hub-received source-binding identity shapes. |
IRemoteIngressHubEgressIdentityChangedEvent |
Typed payload for the public egressIdentityChanged hub event. |
IRemoteIngressHubEgressConnectionEvent |
Per-proxy connection outcome plus the actual local source IP and address family used by the edge. |
IRemoteIngressCrashRecoveryDelayedEvent |
Retry count and next delay emitted after prolonged child-process recovery. |
RemoteIngressHub additionally exposes startEgressTcpProxy(), stopEgressTcpProxy(), and race-free waitForEgressConnectionEvidence(proxyId, edgeId, timeoutMs?). Connected edge status entries include capabilities, egressEnabled, and egressIdentity. The hub emits egressConnection events with { proxyId, edgeId, logicalHost, port, outcome, resolvedIp, sourceIp, addressFamily, error } and egressIdentityChanged events with { edgeId, egressIdentity }.
Development
pnpm run build
pnpm test
Useful source entry points:
ts/index.tsexports the public API and CLI runner.ts/classes.remoteingresshub.tswraps hub management commands and hub events.ts/classes.remoteingressedge.tswraps edge management commands, nftables application, and edge events.ts/classes.token.tsimplements compact connection tokens.rust/contains the performance-critical tunnel implementation compiled bytsrust.
There are two version numbers with two owners. The npm package version in package.json is what a release publishes and what hubs and edges pin; cli.js --version reports it. The native binary under dist_rust/ reports the Rust crate version, declared once in [workspace.package] of rust/Cargo.toml and rendered from CARGO_PKG_VERSION; it tracks the tunnel implementation and does not follow the npm release number, because tsrust does not inject the npm version into the cargo build. The two are tied together by the *.tsrust-build.json provenance sidecar next to each binary, which records the npm version, commit and SHA-256 the binary was built from; image qualification verifies that pairing and prints both numbers.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.
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.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.