mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-18 06:23:18 +00:00
fix(mesh): route peer→central traffic over the existing forward WS (#1094)
* fix(mesh): route peer→central traffic over the existing forward WS The reverse mesh callback path (`/api/mesh/proxy-tunnel-from-peer`) needed SENCHO_PRIMARY_URL on central plus a publicly reachable origin from the peer's perspective. In a typical homelab where central sits behind NAT, peer→central dispatch silently failed at the dialer's short-circuit and the headline "call any service on any node by hostname" worked one way only. The forward WS at `/api/mesh/proxy-tunnel` is already bidirectional end to end. Make the bridge a persistent control-plane primitive: dial every mesh-enabled proxy peer at startup, reconcile every 60 s, never idle-close. Peer→central traffic multiplexes over the same WS via `tcp_open_reverse`. Removed: - `meshProxyTunnelFromPeer.ts` WS handler and dispatch - `MeshCentralRegistry`, `PeerToCentralMeshSessionDialer` - `mesh_handshake` first-frame state machine in `meshProxyTunnel.ts` - `maybeSendBootstrap`, `buildHandshakeFrame` in the dialer - `mesh_proxy_callback_bootstrap` capability and `maybeWarnUnsetPrimaryUrl` - `mesh_centrals` table (drop migration; greenfield, no users) - `PilotTunnelManager.replaceOrRegisterProxyBridge` (dead after handler removal) - twelve associated unit/integration tests plus the peer-recovery branch in `MeshService.openCrossNode` Added: - `MeshService.proactiveBridgeFanout` selects every mesh-enabled proxy peer (no longer gated on `mesh_stacks` rows) - `startBridgeReconcileLoop` runs the fanout every 60 s (override via `SENCHO_MESH_RECONCILE_INTERVAL_MS`) - `MeshProxyTunnelDialer` default idle TTL is now `0` and exposes `isDialing(nodeId)` for the status surface - `MeshNodeStatus.reverseCallbackStatus` discriminator (`connected | connecting | unavailable | not_applicable`) surfaced via `/api/mesh/status` and rendered as a pill in the Routing tab - `openCrossNode` error message distinguishes "no proxy target" from "waiting for central to dial the reverse bridge" - New tests: `mesh-service-proxy-tunnel-reconcile`, `mesh-status-reverse-callback`, `mesh-proxy-tunnel-dialer-no-idle-close` SENCHO_PRIMARY_URL is no longer required for any mesh function. * fix(mesh): rewrite proxy-tunnel reconcile test contents The previous commit renamed the file but the rewritten test bodies stayed unstaged on top of the rename. This commit lands the actual rewrite: the fanout assertion now requires every mesh-enabled proxy peer to be dialed, not just those with `mesh_stacks` rows, and adds a reconcile-tick repeated-call test.
This commit is contained in:
@@ -5,13 +5,11 @@ import type { MeshActivityEvent, MeshDataPlaneStatus } from '../services/MeshSer
|
||||
let tmpDir: string;
|
||||
let MeshService: typeof import('../services/MeshService').MeshService;
|
||||
let DockerController: typeof import('../services/DockerController').default;
|
||||
let capability: typeof import('../services/CapabilityRegistry');
|
||||
|
||||
beforeAll(async () => {
|
||||
tmpDir = await setupTestDb();
|
||||
({ MeshService } = await import('../services/MeshService'));
|
||||
({ default: DockerController } = await import('../services/DockerController'));
|
||||
capability = await import('../services/CapabilityRegistry');
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -43,7 +41,6 @@ beforeEach(() => {
|
||||
message: 'mesh data plane has not initialized yet',
|
||||
subnet: '',
|
||||
};
|
||||
capability.enableCapability('mesh_proxy_callback_bootstrap');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -51,7 +48,6 @@ afterEach(() => {
|
||||
else process.env.SENCHO_MESH_SUBNET = prevSubnetEnv;
|
||||
if (prevHostnameEnv === undefined) delete process.env.HOSTNAME;
|
||||
else process.env.HOSTNAME = prevHostnameEnv;
|
||||
capability.enableCapability('mesh_proxy_callback_bootstrap');
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
@@ -94,7 +90,6 @@ describe('MeshService.setupMeshNetwork failure classification', () => {
|
||||
expect(status.ok).toBe(false);
|
||||
expect(status.reason).toBe('subnet_invalid');
|
||||
expect(status.subnet).toBe('not-a-cidr');
|
||||
expect(capability.getActiveCapabilities()).not.toContain('mesh_proxy_callback_bootstrap');
|
||||
const entry = lastDisable(svc);
|
||||
expect(entry?.level).toBe('error');
|
||||
expect(entry?.details?.reason).toBe('subnet_invalid');
|
||||
@@ -114,7 +109,6 @@ describe('MeshService.setupMeshNetwork failure classification', () => {
|
||||
expect(status.reason).toBe('subnet_overlap');
|
||||
expect(status.subnet).toBe('10.42.0.0/24');
|
||||
expect(status.message).toMatch(/overlap/i);
|
||||
expect(capability.getActiveCapabilities()).not.toContain('mesh_proxy_callback_bootstrap');
|
||||
const entry = lastDisable(svc);
|
||||
expect(entry?.level).toBe('error');
|
||||
expect(entry?.details?.reason).toBe('subnet_overlap');
|
||||
@@ -169,9 +163,6 @@ describe('MeshService.setupMeshNetwork failure classification', () => {
|
||||
const entry = lastDisable(svc);
|
||||
expect(entry?.level).toBe('warn');
|
||||
expect(entry?.details?.reason).toBe('not_in_docker');
|
||||
// Capability is still stripped even in the warn-level not_in_docker
|
||||
// case; pilot processes outside Docker must not advertise it.
|
||||
expect(capability.getActiveCapabilities()).not.toContain('mesh_proxy_callback_bootstrap');
|
||||
});
|
||||
|
||||
it('records the 404-on-inspect path as not_in_docker at level warn', async () => {
|
||||
@@ -187,11 +178,9 @@ describe('MeshService.setupMeshNetwork failure classification', () => {
|
||||
expect(lastDisable(svc)?.level).toBe('warn');
|
||||
});
|
||||
|
||||
it('records success as ok and re-enables the capability', async () => {
|
||||
it('records success as ok', async () => {
|
||||
process.env.SENCHO_MESH_SUBNET = '10.42.0.0/24';
|
||||
process.env.HOSTNAME = 'sencho';
|
||||
// Force the capability off first to prove the success path flips it back on.
|
||||
capability.disableCapability('mesh_proxy_callback_bootstrap');
|
||||
mockDocker();
|
||||
const svc = MeshService.getInstance();
|
||||
await callSetup(svc);
|
||||
@@ -200,7 +189,6 @@ describe('MeshService.setupMeshNetwork failure classification', () => {
|
||||
expect(status.reason).toBe('ok');
|
||||
expect(status.subnet).toBe('10.42.0.0/24');
|
||||
expect(status.message).toBeNull();
|
||||
expect(capability.getActiveCapabilities()).toContain('mesh_proxy_callback_bootstrap');
|
||||
});
|
||||
|
||||
it('preserves the legacy networkSetupError getter on failure', async () => {
|
||||
|
||||
Reference in New Issue
Block a user