Skip to content

Route blah2's views through the support hostname - #7

Merged
Purple10101 merged 2 commits into
mainfrom
20260902-tunnel-port-routing
Sep 2, 2026
Merged

Purple10101 merged 2 commits into
mainfrom
20260902-tunnel-port-routing

Conversation

@Purple10101

Copy link
Copy Markdown
Contributor

Support could reach a node's interface but not the three views the access exists to show: Passive Radar, Max-Hold and Controller. Those are served on other ports, and Cloudflare proxies a fixed set that does not include them, so the links were dead over the tunnel. An http:// link on an https:// page would have been blocked as mixed content anyway.

No node-side change was needed

blah2's own JS already switches on is_localhost: cross-origin to :3000 on the LAN, same-origin relative paths everywhere else. It only ever needed the paths routed. The tunnel now carries three rules instead of one.

Ordering is the whole thing

cloudflared takes the first rule whose hostname and path both match, so a rule without a path matches everything and silently disables every rule below it. Those rules stay listed, still read correctly in the dashboard, and never fire. The only symptom is 404s that look like a dead origin.

A hand-built tunnel in this account is configured exactly that way today, which is why the tests simulate cloudflared's matching rather than assert the shape of the list. Reordering build_ingress to reproduce it fails 13 of the 24.

Why the API paths are named individually

retina-gui owns /api/mode, /api/fleet/peers, /api/spectrum/ready and others on the same hostname. A blanket ^/api would divert those to blah2 and break the interface over the tunnel while leaving it working on the LAN, which is the kind of fault nobody finds until support needs it. Naming blah2's five endpoints fails loudly if it gains a sixth instead.

Reconciliation

reconcile now compares live ingress against what we would write. It costs one call per provisioned node, so it scales with opt-ins rather than fleet size, and it is the only thing that would ever notice a hand-edit or a reordering, given the failure is silent by nature.

Verified end to end

On a real node, provisioned through the timer:

  • the three rules land in the right order
  • every path stays Access-gated, including the new ones (/display/map/, /api/map, /lib/, /capture/toggle all 302 to Access)
  • the three views load and pull data over the tunnel
  • retina-gui's own /api routes and fleet banner still work, so the two namespaces genuinely coexist

The retina-gui side (service cards pointing at these paths when remote) is on its own branch and is being held pending a separate acceptance conversation. This PR is inert without it, and inert on any node that has not opted in.

Note: Passive Radar and Max-Hold pull roughly 1.9 MB/s while open, since the delay-doppler surface is ~2.2 MB and changes about once a second. That is the data itself, not this plumbing, but worth knowing before relying on it over a slow link.

🤖 Generated with Claude Code

Purple10101 and others added 2 commits September 2, 2026 12:08
Support could reach a node's interface but not the three views it exists to
show: Passive Radar, Max-Hold and Controller. Those are served on other ports,
and Cloudflare proxies a fixed set that does not include them, so the links
were dead over the tunnel and an http:// link on an https:// page would have
been blocked as mixed content anyway.

No code change was needed on the node. blah2's own JS already switches on
is_localhost: cross-origin to :3000 on the LAN, same-origin relative paths
everywhere else. It only ever needed the paths routed.

So the tunnel now carries three rules instead of one, and ordering is the whole
thing: cloudflared takes the first rule whose hostname and path match, so a
rule without a path matches everything and silently disables every rule below
it. Those rules stay listed, still read correctly in the dashboard, and never
fire; the only symptom is 404s that look like a dead origin. A hand-built
tunnel in this account is configured exactly that way, which is what prompted
the tests to simulate the matching rather than assert the shape of the list.

blah2's API endpoints are named one by one rather than matched as ^/api,
because retina-gui owns /api/mode, /api/fleet/peers and others on the same
hostname. A blanket prefix would divert those to blah2 and break the interface
over the tunnel while leaving it working on the LAN, which is the kind of fault
nobody finds until support needs it. Naming them fails loudly if blah2 gains an
endpoint instead.

Reconciliation now compares the live ingress against what we would write. It
costs one call per provisioned node, so it scales with opt-ins rather than
fleet size, and it is the only thing that would ever notice a hand-edit or a
reordering, given the failure is silent by nature.

Verified end to end on a real node: the rules land in order, all paths stay
Access-gated, and the three views load and pull data over the tunnel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI's ruff failed on import ordering. Nothing declares tunnel_sync first-party,
so ruff groups it with pytest rather than in a section of its own.

Missed locally because I checked tunnel_sync.py by name instead of the
directory, so the new test file was never linted. CI runs `ruff check .`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Purple10101
Purple10101 merged commit ed9d899 into main Sep 2, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant