feat: the quick start brings up the call relay, so calls work on a fresh clone - #2
Merged
Conversation
…esh clone Calls shipped switched off for anyone following the quick start. coturn sat behind a `calls` profile, so `podman compose up -d` skipped it, and with no relay configured the app hides the call buttons entirely — correct behaviour (force-relay means no relay is no media path) but it left the feature invisible to every reader who did not go looking for a second command. coturn now starts with the rest of the stack and the two `ICHAT_TURN_*` values join the existing `KEYCLOAK_CLIENT_SECRET` line, so the quick start is still five commands. The env vars keep their lack of defaults: `CallProperties.isConfigured()` stays the gate, and production still has to opt in deliberately rather than inherit a shared secret from a sample file. Two things had to change for the default to be safe and to work at all. The relay binds to loopback. `--listening-ip` was `0.0.0.0`, which was defensible while a profile kept the container off; starting it by default with `--static-auth-secret=dev-turn-secret` in a public repository would put an open TURN relay with a known password on every interface of every machine that runs the quick start, and on a cloud VM that is a public one. All three addresses now share `ICHAT_TURN_RELAY_IP` and default to `127.0.0.1`, which is also all the quick start needs: `getUserMedia` requires a secure context, `localhost` is the only exempt origin, so two browsers on the host are the only clients that can place a call regardless. `--allow-loopback-peers` is required and is why this was tested rather than assumed. coturn refuses loopback peer addresses out of the box. In a relayed 1:1 call each browser allocates on coturn and then asks permission to send to the *other* allocation, so with `--relay-ip=127.0.0.1` the peer address is loopback and the permission is refused — 403 Forbidden IP, after which the call rings, is answered, and silently never connects, looking exactly like a network fault. The earlier end-to-end run never hit this because it used a LAN `ICHAT_TURN_RELAY_IP`, where peers are LAN addresses and the rule does not apply. The flag is safe only because the relay accepts nothing but loopback clients, who can already reach loopback directly; exposing the service means dropping it, or remote clients gain a route into this host's own 127.0.0.1. The service comment says so. Verified: `podman compose config` renders coturn with no profile; the recreated container listens on 127.0.0.1:3478 TCP and UDP and nowhere else; `turnutils_uclient -W dev-turn-secret` completes an allocation, creates a permission and relays 4 packets each way with 0 lost, which exercises the same REST-API credential scheme `TurnCredentialService` mints. The 403 above is the observed failure before the flag, not a predicted one. Not covered: `scripts/e2e-call.py` was not re-run. It needs :8080, which an app instance already held, and the realm whitelists redirect URIs only for :8080 — so a second instance on another port cannot complete login. The browser half of this path is therefore unverified against the loopback relay, though the transport half above is the part the change touched. Docs follow the same shape throughout: README, landing page, `QUICKSTART-COMPOSE.md`, `.env.example` and the operations page in `docs.html`, plus the `--profile calls` instructions in `e2e-call.py` and the screenshot recipe, which no longer name a profile that does not exist. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Olav Gjerde <olav@intellistream.ai>
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.
Calls shipped switched off for anyone following the quick start. coturn sat behind a
callsprofile, sopodman compose up -dskipped it, and with no relay configured the app hides the call buttons entirely — correct behaviour (force-relay means no relay is no media path) but it left the feature invisible to every reader who did not go looking for a second command.coturn now starts with the rest of the stack and the two
ICHAT_TURN_*values join the existingKEYCLOAK_CLIENT_SECRETline, so the quick start is still five commands. The env vars keep their lack of defaults:CallProperties.isConfigured()stays the gate, and production still has to opt in deliberately rather than inherit a shared secret from a sample file.Two things had to change for the default to be safe and to work at all.
The relay binds to loopback.
--listening-ipwas0.0.0.0, which was defensible while a profile kept the container off; starting it by default with--static-auth-secret=dev-turn-secretin a public repository would put an open TURN relay with a known password on every interface of every machine that runs the quick start, and on a cloud VM that is a public one. All three addresses now shareICHAT_TURN_RELAY_IPand default to127.0.0.1, which is also all the quick start needs:getUserMediarequires a secure context,localhostis the only exempt origin, so two browsers on the host are the only clients that can place a call regardless.--allow-loopback-peersis required and is why this was tested rather than assumed. coturn refuses loopback peer addresses out of the box. In a relayed 1:1 call each browser allocates on coturn and then asks permission to send to the other allocation, so with--relay-ip=127.0.0.1the peer address is loopback and the permission is refused — 403 Forbidden IP, after which the call rings, is answered, and silently never connects, looking exactly like a network fault. The earlier end-to-end run never hit this because it used a LANICHAT_TURN_RELAY_IP, where peers are LAN addresses and the rule does not apply. The flag is safe only because the relay accepts nothing but loopback clients, who can already reach loopback directly; exposing the service means dropping it, or remote clients gain a route into this host's own 127.0.0.1. The service comment says so.Verified:
podman compose configrenders coturn with no profile; the recreated container listens on 127.0.0.1:3478 TCP and UDP and nowhere else;turnutils_uclient -W dev-turn-secretcompletes an allocation, creates a permission and relays 4 packets each way with 0 lost, which exercises the same REST-API credential schemeTurnCredentialServicemints. The 403 above is the observed failure before the flag, not a predicted one.Not covered:
scripts/e2e-call.pywas not re-run. It needs :8080, which an app instance already held, and the realm whitelists redirect URIs only for :8080 — so a second instance on another port cannot complete login. The browser half of this path is therefore unverified against the loopback relay, though the transport half above is the part the change touched.Docs follow the same shape throughout: README, landing page,
QUICKSTART-COMPOSE.md,.env.exampleand the operations page indocs.html, plus the--profile callsinstructions ine2e-call.pyand the screenshot recipe, which no longer name a profile that does not exist.