add FORCE_TURN_RELAY option in docker-compose

This commit is contained in:
Abhishek Kumar 2026-05-11 16:33:36 +05:30
parent c0e8b631c4
commit e2fe2919bb
6 changed files with 96 additions and 7 deletions

View file

@ -241,12 +241,10 @@ async def _run_pipeline(
raise HTTPException(status_code=400, detail="Workflow run already completed")
merged_call_context_vars = workflow_run.initial_context
# If there is some extra call_context_vars, update them
# If there is some extra call_context_vars, fold them in. Persistence
# happens once below, after runtime_configuration is also resolved.
if call_context_vars:
merged_call_context_vars = {**merged_call_context_vars, **call_context_vars}
await db_client.update_workflow_run(
workflow_run_id, initial_context=merged_call_context_vars
)
# Get user configuration
user_config = await db_client.get_user_configurations(user_id)
@ -312,6 +310,36 @@ async def _run_pipeline(
llm = create_llm_service(user_config)
inference_llm = None
# Stamp the providers/models actually resolved for this run onto
# initial_context so they're available for post-call analytics
# (model_overrides may have shifted them away from the org-level
# user_config).
if is_realtime:
# llm_* refers to the side-channel text LLM (variable extraction,
# voicemail detection); realtime_* is the speech-to-speech service.
runtime_configuration = {
"realtime_provider": user_config.realtime.provider,
"realtime_model": user_config.realtime.model,
"llm_provider": user_config.llm.provider,
"llm_model": user_config.llm.model,
}
else:
runtime_configuration = {
"stt_provider": user_config.stt.provider,
"stt_model": user_config.stt.model,
"tts_provider": user_config.tts.provider,
"tts_model": user_config.tts.model,
"llm_provider": user_config.llm.provider,
"llm_model": user_config.llm.model,
}
merged_call_context_vars = {
**merged_call_context_vars,
"runtime_configuration": runtime_configuration,
}
await db_client.update_workflow_run(
workflow_run_id, initial_context=merged_call_context_vars
)
workflow_graph = WorkflowGraph(ReactFlowDTO.model_validate(run_workflow_json))
# Pre-call fetch: fire early so it runs concurrently with remaining setup

View file

@ -106,7 +106,7 @@ async def check_dograh_quota(
logger.info(
f"Dograh quota check passed for key ...{api_key[-8:]}: "
f"${remaining:.2f} remaining"
f"{remaining:.2f} credits remaining"
)
except Exception as e:
logger.error(f"Failed to check quota for Dograh key: {str(e)}")

View file

@ -121,6 +121,7 @@ services:
# Uses time-limited credentials via TURN REST API (HMAC-SHA1)
TURN_HOST: "${TURN_HOST:-}"
TURN_SECRET: "${TURN_SECRET:-}"
FORCE_TURN_RELAY: "${FORCE_TURN_RELAY:-false}"
OSS_JWT_SECRET: "${OSS_JWT_SECRET:-ChangeMeInProduction}"

View file

@ -46,6 +46,38 @@ http://localhost:3010
You can disable telemetry by setting `ENABLE_TELEMETRY=false` in the command above.
</Note>
### Troubleshooting WebRTC Connectivity
The Quick Start above relies on direct peer-to-peer WebRTC between your browser and the API container. On most single-machine setups this works without any extra configuration. If you hit any of the following, you likely need a TURN server in the path:
- The call connects but no audio flows in either direction
- The browser console reports `iceConnectionState: failed`
- You are testing from a phone or another device on your LAN against the laptop running Docker
- A VPN, corporate firewall, or strict NAT sits between the browser and Docker
For these cases, use the alternate local setup script which configures a coturn TURN server alongside the rest of the stack:
```bash
curl -o setup_local.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_local.sh && chmod +x setup_local.sh && ./setup_local.sh
```
The script will prompt you for:
- Whether to enable coturn (answer `y`)
- The host browsers should use to reach TURN (press Enter for `127.0.0.1`; use your LAN IP if testing from another device on the same network)
- A shared secret for the TURN server (press Enter to generate a random one)
It creates `docker-compose.yaml`, `turnserver.conf`, and a `.env` file with TURN credentials. Start the stack with the `local-turn` profile so coturn comes up alongside the other services:
```bash
docker compose --profile local-turn up --pull always
```
The application is still available at `http://localhost:3010`.
<Note>
To verify that media is actually traversing TURN (and not silently falling back to a direct path), set `FORCE_TURN_RELAY=true` in `.env` and restart the API. The browser will then only use relay ICE candidates — if TURN is misconfigured or unreachable, the call fails cleanly with `ICE failed` in the console instead of appearing to work. Set it back to `false` once you have verified connectivity.
</Note>
## Option 2: Remote Server Deployment
Watch the video tutorial below for a step-by-step walkthrough of deploying Dograh AI to a remote server.

View file

@ -93,6 +93,7 @@ Dograh uses **MinIO by default**, which is bundled with the self-hosted deployme
| `TURN_TLS_PORT` | `5349` | TURN server TLS port |
| `TURN_SECRET` | `null` | **Required for WebRTC.** Shared secret for TURN credential generation |
| `TURN_CREDENTIAL_TTL` | `86400` | TURN credential validity in seconds (default: 24h) |
| `FORCE_TURN_RELAY` | `false` | Diagnostic flag. When `true`, restricts ICE to relay-only candidates on both server (SDP filter) and browser (`iceTransportPolicy: 'relay'`). Use to verify TURN connectivity end-to-end — calls fail cleanly if TURN is misconfigured instead of silently falling back to a direct path. |
---

View file

@ -27,12 +27,39 @@ if [[ -z "$ENABLE_COTURN" ]]; then
fi
if [[ "$ENABLE_COTURN" == "true" ]]; then
# Pick a TURN_HOST that's reachable from BOTH the browser (running on the
# host) and the API container (running in docker). 127.0.0.1 is tempting
# but doesn't work for the api container — its own loopback isn't where
# coturn lives, so aiortc can't allocate a relay and FORCE_TURN_RELAY
# ends up with an empty answer SDP. The host's LAN IP works for both.
detect_lan_ip() {
local ip=""
if command -v ipconfig >/dev/null 2>&1; then
for iface in en0 en1 en2 en3 en4; do
ip=$(ipconfig getifaddr "$iface" 2>/dev/null)
[[ -n "$ip" ]] && { echo "$ip"; return; }
done
fi
if command -v ip >/dev/null 2>&1; then
ip=$(ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if($i=="src") print $(i+1)}')
[[ -n "$ip" ]] && { echo "$ip"; return; }
fi
if command -v hostname >/dev/null 2>&1; then
ip=$(hostname -I 2>/dev/null | awk '{print $1}')
[[ -n "$ip" ]] && { echo "$ip"; return; }
fi
}
DEFAULT_TURN_HOST="$(detect_lan_ip)"
DEFAULT_TURN_HOST="${DEFAULT_TURN_HOST:-127.0.0.1}"
# Get the host browsers/peers will use to reach the TURN server
if [[ -z "$TURN_HOST" ]]; then
echo -e "${YELLOW}Enter the host browsers should use to reach TURN (press Enter for 127.0.0.1):${NC}"
echo -e "${YELLOW}Enter the host browsers AND the API container will use to reach TURN${NC}"
echo -e "${YELLOW}(press Enter for ${DEFAULT_TURN_HOST}):${NC}"
read -p "> " TURN_HOST
fi
TURN_HOST="${TURN_HOST:-127.0.0.1}"
TURN_HOST="${TURN_HOST:-$DEFAULT_TURN_HOST}"
# Validate that TURN_HOST is either an IP or a hostname (basic check)
if ! [[ "$TURN_HOST" =~ ^[A-Za-z0-9.-]+$ ]]; then