Files
sencho/mesh-sidecar/src/index.ts
T
Anso 6893ece898 feat(pilot): add tcp tunnel frames + mesh sidecar package (#857)
Lays the dormant data-plane foundation for Sencho Mesh. The pilot tunnel
gains TCP forwarding frames (tcp_open / tcp_open_ack / tcp_close JSON
plus a 0x04 TcpData binary type) and a TcpStream surface on the bridge
so a future MeshService can ride the existing WSS tunnel for cross-node
container traffic. The agent rejects every tcp_open with mesh_not_enabled
until a follow-up PR wires the Dockerode resolver gated by a
mesh_stacks opt-in table; ships dormant.

A new top-level mesh-sidecar/ package provides the per-node container
that will host the L4 forwarder + control WS in production. Built as a
small Node 22 alpine image and published in lockstep with the main
sencho image via a parallel docker-publish workflow job.

Tests cover protocol roundtrips on both packages and the sidecar
forwarder end-to-end including resolve, splice, close, and stats.
2026-05-01 00:28:18 -04:00

59 lines
1.9 KiB
TypeScript

import { ControlClient } from './control';
import { Forwarder } from './forwarder';
const SIDECAR_VERSION = '0.0.1';
function requireEnv(name: string): string {
const value = process.env[name];
if (!value) {
console.error(`[mesh-sidecar] missing required env: ${name}`);
process.exit(1);
}
return value;
}
function main(): void {
const controlUrl = requireEnv('SENCHO_CONTROL_URL');
const token = requireEnv('SENCHO_MESH_TOKEN');
const nodeIdRaw = requireEnv('MESH_NODE_ID');
const nodeId = Number.parseInt(nodeIdRaw, 10);
if (!Number.isFinite(nodeId)) {
console.error('[mesh-sidecar] MESH_NODE_ID must be an integer');
process.exit(1);
}
let client: ControlClient | null = null;
// Bridge with explicit named params so types stay tight; client is wired
// immediately after construction so the null check is only racing against
// the 5s stats timer first tick.
const forwarder = new Forwarder({
resolve: (connId, port, remoteAddr) => client?.resolve(connId, port, remoteAddr),
sendData: (streamId, payload) => client?.sendData(streamId, payload),
sendClose: (streamId) => client?.sendClose(streamId),
sendStats: (streamId, bytesIn, bytesOut, lastActivity) =>
client?.sendStats(streamId, bytesIn, bytesOut, lastActivity),
});
forwarder.start();
client = new ControlClient({
controlUrl,
token,
nodeId,
sidecarVersion: SIDECAR_VERSION,
forwarder,
});
client.start();
const shutdown = async () => {
await forwarder.shutdown();
await client?.shutdown();
process.exit(0);
};
process.on('SIGTERM', () => { void shutdown(); });
process.on('SIGINT', () => { void shutdown(); });
console.log(`[mesh-sidecar] started for node=${nodeId} version=${SIDECAR_VERSION}`);
}
main();