Skip to content

Carry remote commands on the blind relay path - #14

Draft
frahlg wants to merge 1 commit into
mainfrom
cursor/relay-command-path-dc0d
Draft

frahlg wants to merge 1 commit into
mainfrom
cursor/relay-command-path-dc0d

Conversation

@frahlg

@frahlg frahlg commented Oct 15, 2026

Copy link
Copy Markdown
Member

What

FTW Cloud can now speak the Frankfurt relay's protocol, so cloud.ftw.energy can take the place of relay.ftw.energy once clients point at it. This PR does not change DNS, the running host, or the Frankfurt process.

The path is separate from /v1/box. That socket carries signed cloud envelopes, and its downlink is still only challenge, hello_ok, watch, resync and purged. Command frames are raw Noise, and ready / gone are not those five messages. Multiplexing the two would make this service parse commands, or make the box drop the uplink. Apps also dial with an Origin, which /v1/box refuses.

internal/relay forwards bytes and reads two things off them: their length, and, on the exchange socket, the 16-byte id it wrote there itself. It does not import the store, the uplink, or a Noise session.

  • GET (upgrade) /r/<epoch>/<handle>/<box|app|exchange> — same close codes (4400, 4409, 4410, 4429), ready / gone, one box and one exchange socket, four app streams, hourly handles spread over five minutes.
  • POST /x/<epoch>/<handle> — body 608 or 536, reply unchanged, previous epoch accepted during the rotation window, no CORS.
  • POST / DELETE /deadman — sealed push ciphertext, persisted at /state/deadman.json (mode 0600). A fired row is posted only to public HTTPS.
  • Limits match Frankfurt: 4096 sockets, 16 per address, 30 joins and 30 exchanges a minute without storing addresses, 64 KiB frames, 240 frames / 4 MiB burst per socket.
  • Optional box-key proof: X-FTW-Box-Key and X-FTW-Box-Sig over ftw.cloud.relay.v1. A bad proof is refused. A join with neither header is admitted.

ADR 0008 said this service reads and never controls, and that commands stay on the relay. That stance is being revised: the service carries command bytes and does not originate or open them. The ADR text is in srcfl/ftw and is not edited here. README.md, CLAUDE.md and deploy/README.md say so. deploy/compose.yml only gains a comment that the state volume also holds deadman.json.

Compatibility

An existing box, webapp or watch switches by changing the origin. Paths, frames, epochs and close-code reasons are the Frankfurt ones. The rendezvous handle remains the session capability. Box-key auth cannot be mandatory yet: current clients send no proof, and a home that has not turned cloud on has no key here. RequireBoxKey exists on the handler and is not a flag.

POST /fleet is not ported. Core's DefaultFleetPingEndpoint is https://relay.ftw.energy/fleet and is not derived from the websocket origin. The dead-man origin is derived from that websocket origin, so /deadman is ported.

Before Frankfurt can be turned off

  • Box, webapp and watch builds must dial cloud.ftw.energy. Those constants live in srcfl/ftw and srcfl/ftw-webapp, not here.
  • Fleet pings still go to Frankfurt until Core's default URL moves.
  • Dead-man rows already stored in Frankfurt are not copied. A box posts its row again on connect, so a box that is offline at the cutover has no switch until it returns. Leave Frankfurt's file in place until the fleet has moved.
  • Requiring the box-key proof waits on clients sending the headers, then a change here.
  • ADR 0008 in srcfl/ftw still says commands stay on the relay.
  • Deploying the image is a later step, by the owner. This PR does not deploy it.

Tests

go vet ./... && go test -race ./..., go test -tags ftwclouddev ./..., and scripts/check-imports.sh.

Open in Web Open in Cursor 

Port the Frankfurt relay's wire onto this server so a client can switch by
changing the origin. /r/, /x/ and /deadman forward end-to-end Noise frames
and do not read them. /v1/box stays the five-message downlink; the two
sockets do not multiplex.

Box-key proofs are accepted and not required. The fleet ping stays on
relay.ftw.energy. Nothing here changes DNS or the running host.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
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.

2 participants