Route blah2's views through the support hostname - #7
Merged
Merged
Conversation
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 anhttps://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:3000on 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_ingressto reproduce it fails 13 of the 24.Why the API paths are named individually
retina-gui owns
/api/mode,/api/fleet/peers,/api/spectrum/readyand others on the same hostname. A blanket^/apiwould 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
reconcilenow 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:
/display/map/,/api/map,/lib/,/capture/toggleall 302 to Access)/apiroutes and fleet banner still work, so the two namespaces genuinely coexistThe 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