diff --git a/api/Dockerfile b/api/Dockerfile index 1674a56..a417a10 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -20,7 +20,7 @@ RUN pip install --user --no-cache-dir -r requirements.txt && \ # Copy and install pipecat from local submodule COPY pipecat /tmp/pipecat -RUN pip install --user --no-cache-dir '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,soundfile,silero,webrtc]' && \ +RUN pip install --user --no-cache-dir '/tmp/pipecat[cartesia,deepgram,openai,elevenlabs,groq,google,azure,soundfile,silero,webrtc,local-smart-turn-v3]' && \ # Clean up pip cache and temporary pipecat directory rm -rf /root/.cache/pip /tmp/pipecat diff --git a/api/routes/webrtc_signaling.py b/api/routes/webrtc_signaling.py index 8bc294d..8d940d8 100644 --- a/api/routes/webrtc_signaling.py +++ b/api/routes/webrtc_signaling.py @@ -10,9 +10,11 @@ Uses the SmallWebRTC API contract: """ import asyncio +import os from datetime import UTC, datetime -from typing import Dict +from typing import Dict, List +from aiortc import RTCIceServer from aiortc.sdp import candidate_from_sdp from fastapi import APIRouter, Depends, WebSocket, WebSocketDisconnect from loguru import logger @@ -28,8 +30,34 @@ from pipecat.utils.context import set_current_run_id router = APIRouter(prefix="/ws") + +def get_ice_servers() -> List[RTCIceServer]: + """Build ICE servers configuration including TURN if configured.""" + servers: List[RTCIceServer] = [RTCIceServer(urls="stun:stun.l.google.com:19302")] + + # Add TURN server if configured + turn_host = os.getenv("TURN_HOST") + turn_username = os.getenv("TURN_USERNAME") + turn_password = os.getenv("TURN_PASSWORD") + + if turn_host and turn_username and turn_password: + servers.append( + RTCIceServer( + urls=[ + f"turn:{turn_host}:3478", + f"turn:{turn_host}:3478?transport=tcp", + ], + username=turn_username, + credential=turn_password, + ) + ) + logger.info(f"TURN server configured: {turn_host}:3478") + + return servers + + # ICE servers configuration -ice_servers = ["stun:stun.l.google.com:19302"] +ice_servers = get_ice_servers() class SignalingManager: diff --git a/docker-compose.yaml b/docker-compose.yaml index ca1e2c9..292bb67 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -109,6 +109,11 @@ services: ENABLE_TELEMETRY: "${ENABLE_TELEMETRY:-true}" SENTRY_DSN: "https://3acdb63d5f1f70430953353b82de61e0@o4509486225096704.ingest.us.sentry.io/4510152922693632" + # TURN server configuration (for WebRTC NAT traversal in remote server) + TURN_HOST: "${TURN_HOST:-}" + TURN_USERNAME: "${TURN_USERNAME:-}" + TURN_PASSWORD: "${TURN_PASSWORD:-}" + ports: - "8000:8000" depends_on: @@ -149,6 +154,11 @@ services: # Sentry SENTRY_DSN: "https://d9387fed5f80e90781f1dbd9b2c0994c@o4509486225096704.ingest.us.sentry.io/4510124708200448" + + # TURN server configuration (for WebRTC NAT traversal in remote server) + NEXT_PUBLIC_TURN_HOST: "${TURN_HOST:-}" + NEXT_PUBLIC_TURN_USERNAME: "${TURN_USERNAME:-}" + NEXT_PUBLIC_TURN_PASSWORD: "${TURN_PASSWORD:-}" ports: - "3010:3010" depends_on: @@ -176,6 +186,32 @@ services: networks: - app-network + coturn: + image: coturn/coturn:4.6.3 + container_name: coturn + restart: unless-stopped + profiles: ["remote"] + network_mode: host + environment: + TURN_USERNAME: ${TURN_USERNAME} + TURN_PASSWORD: ${TURN_PASSWORD} + command: > + -n + --listening-port=3478 + --tls-listening-port=5349 + --min-port=49152 + --max-port=49200 + --realm=${TURN_REALM:-dograh.com} + --user=${TURN_USERNAME}:${TURN_PASSWORD} + --lt-cred-mech + --fingerprint + --no-cli + --log-file=stdout + --no-multicast-peers + --no-tlsv1 + --no-tlsv1_1 + ${TURN_HOST:+--external-ip=$TURN_HOST} + volumes: postgres_data: redis_data: diff --git a/docs/deployment/docker.mdx b/docs/deployment/docker.mdx index 9a13695..a849eb8 100644 --- a/docs/deployment/docker.mdx +++ b/docs/deployment/docker.mdx @@ -38,150 +38,55 @@ You can disable telemetry by setting `ENABLE_TELEMETRY=false` in the command abo ## Option 2: Remote Server Deployment -Deploy Dograh AI on a remote server to make it accessible from anywhere using your server's IP address. This setup includes HTTPS support via nginx reverse proxy with self-signed certificates. +Deploy Dograh AI on a remote server to make it accessible from anywhere using your server's IP address. This setup includes HTTPS support via nginx reverse proxy with self-signed certificates. We need to serve the application over HTTPS, since modern browsers only allow microphone permissions for websites being served over HTTPS. + +**We highly recommend you set up the platform on a fresh server, so that there are less chances of confliciting dependencies, and ports from other applications.** ### Prerequisites -- A server with Docker and Docker Compose installed +- A server with Docker and Docker Compose installed. It should have minimum of 8 GB RAM and 2 vCPUs. - Public IP address for your server -- Ports 80 and 443 accessible from the internet +- TCP Ports 80, 443, 3478, 5349 and UDP Ports 3478, 5349 and 49152:49200 reachable from Internet (Port 80 and 443 to access the UI and rest of the ports for WebRTC Signaling) -### Step 1: Create Project Directory +Please refer to your server hosting provider's documentaion on how you can open these ports in the firewall of the server. -Open your terminal and create a directory for Dograh: +### Quick Setup + +Run the automated setup script that will configure everything for you: + +```bash +curl -o setup_remote.sh https://raw.githubusercontent.com/dograh-hq/dograh/main/scripts/setup_remote.sh && chmod +x setup_remote.sh && ./setup_remote.sh +``` + +The script will prompt you for: +- Your server's public IP address +- A password for the TURN server (optional, press Enter for default) + +It will automatically: +- Download the docker-compose.yaml file +- Create and configure nginx.conf with your IP address +- Generate SSL certificates +- Create an environment file with TURN server configuration + +### Start the Application + +After the setup script completes, start Dograh: ```bash -mkdir dograh cd dograh -``` - -### Step 2: Download Docker Compose File - -Download the docker-compose.yaml file: - -```bash -curl -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml -``` - -### Step 3: Generate SSL Certificates - -Create a script to generate self-signed certificates for HTTPS: - -```bash -nano generate_certificate.sh -``` - -Copy the following content into the file: - -```bash -mkdir -p certs -openssl req -x509 -nodes -newkey rsa:2048 \ - -keyout certs/local.key \ - -out certs/local.crt \ - -days 365 \ - -subj "/CN=YOUR_SERVER_IP" -``` - - -Replace `YOUR_SERVER_IP` with your server's public IP address. - - -Make the script executable and run it: - -```bash -chmod +x generate_certificate.sh -./generate_certificate.sh -``` - -### Step 4: Configure nginx - -Create an nginx configuration file: - -```bash -nano nginx.conf -``` - -Copy the following content into the file: - -```nginx -server { - listen 80; - server_name YOUR_SERVER_IP; - - # Optional: redirect all HTTP to HTTPS - return 301 https://$host$request_uri; -} - -server { - listen 443 ssl; - server_name YOUR_SERVER_IP; - - ssl_certificate /etc/nginx/certs/local.crt; - ssl_certificate_key /etc/nginx/certs/local.key; - - # Basic TLS settings for dev - ssl_protocols TLSv1.2 TLSv1.3; - ssl_prefer_server_ciphers on; - - location / { - proxy_pass http://ui:3010; - proxy_http_version 1.1; - - # Important for WebSockets / hot reload etc. - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto https; - - # Rewrite localhost MinIO URLs in API responses to use current domain - sub_filter 'http://localhost:9000/voice-audio/' 'https://$host/voice-audio/'; - sub_filter_once off; - sub_filter_types application/json text/html; - } - - location /voice-audio/ { - proxy_pass http://minio:9000/voice-audio/; - - proxy_http_version 1.1; - - # Headers for file downloads from MinIO - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto https; - - # Allow large file downloads - proxy_buffering off; - client_max_body_size 100M; - } -} -``` - - -Replace `YOUR_SERVER_IP` with your server's public IP address in both locations (lines 3 and 11). - - -### Step 5: Deploy with Remote Profile - -Start the application with the remote profile to include nginx: - -```bash docker compose --profile remote up -d ``` -This command starts all services including the nginx reverse proxy with HTTPS support. - -### Step 6: Access Your Application +### Access Your Application Your application will be available at: ``` https://YOUR_SERVER_IP ``` -Replace `YOUR_SERVER_IP` with your actual server IP address. + +Since we are using a self-signed certificate, your browser will show a security warning. You can safely accept it to proceed. + ### Configuration Notes @@ -189,4 +94,18 @@ Replace `YOUR_SERVER_IP` with your actual server IP address. - File downloads (transcripts, recordings) are automatically routed through nginx - WebSocket connections for real-time features are properly proxied - The setup uses self-signed certificates - browsers will show a security warning that you can safely accept for testing +- The TURN server (coturn) is configured for WebRTC NAT traversal - For production deployments with proper SSL and domain names, see the [Custom Domain](custom-domain) documentation + +### Files Created + +The setup script creates the following files in the `dograh/` directory: + +| File | Purpose | +|------|---------| +| `docker-compose.yaml` | Main Docker Compose configuration | +| `nginx.conf` | nginx reverse proxy configuration with your IP | +| `generate_certificate.sh` | Script to regenerate SSL certificates | +| `certs/local.crt` | Self-signed SSL certificate | +| `certs/local.key` | SSL private key | +| `.env` | Environment variables for TURN server | diff --git a/docs/deployment/index.mdx b/docs/deployment/index.mdx new file mode 100644 index 0000000..2a90a4b --- /dev/null +++ b/docs/deployment/index.mdx @@ -0,0 +1,12 @@ +--- +title: "Deployments" +description: "Options to deploy Dograh Voice Agent Platform on your own infrastructure." +--- + +### Deployment Options +You can deploy Dograh Platform using Docker on a remote server using Docker, either using terminal commands, or using PaaS like Heroku, Coolify. + +- [Docker](deployment/docker) +- [Custom Domain](deployment/custom-domain) +- [Heroku](deployment/heroku) +- [Coolify](deployment/coolify) diff --git a/docs/docs.json b/docs/docs.json index 2d99170..53ee7b7 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -46,17 +46,6 @@ "features/campaigns", "features/looptalk" ] - } - ] - }, - { - "tab": "Integrations", - "groups": [ - { - "group": "Overview", - "pages": [ - "integrations/overview" - ] }, { "group": "Telephony", diff --git a/docs/getting-started/index.mdx b/docs/getting-started/index.mdx index 9063ca5..4fe6d1d 100644 --- a/docs/getting-started/index.mdx +++ b/docs/getting-started/index.mdx @@ -1,11 +1,11 @@ --- title: "Introduction" -description: "The open-source alternative to Vapi - build voice AI agents with full control and transparency" +description: "Open-source alternative to Vapi - build voice AI agents with full control and transparency" --- ## About Dograh -**The open-source alternative to Vapi** - Dograh helps you build voice AI agents with an easy drag-and-drop workflow builder. Unlike proprietary solutions like Vapi, Dograh gives you: +**Dograh is an open-source alternative to Vapi** - Dograh helps you build voice AI agents with an easy drag-and-drop workflow builder. Unlike proprietary solutions like Vapi, Dograh gives you: - **100% open source** - no vendor lock-in, full transparency - **Self-hostable** - deploy anywhere, own your infrastructure @@ -14,7 +14,7 @@ description: "The open-source alternative to Vapi - build voice AI agents with f ## Setting up -Get the stack up and running using Docker with a single command. +Get the platform up and running using Docker with a single command on your local computer. If you are looking to deploy the platform on a server, please check the [Deployment](deployment) section. We collect anonymous usage data to improve the product. You can opt out by setting the `ENABLE_TELEMETRY` to `false` in the below command. diff --git a/scripts/setup_remote.sh b/scripts/setup_remote.sh new file mode 100755 index 0000000..002b670 --- /dev/null +++ b/scripts/setup_remote.sh @@ -0,0 +1,171 @@ +#!/bin/bash +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +echo -e "${BLUE}" +echo "╔══════════════════════════════════════════════════════════════╗" +echo "║ Dograh Remote Setup ║" +echo "║ Automated HTTPS deployment with TURN server ║" +echo "╚══════════════════════════════════════════════════════════════╝" +echo -e "${NC}" + +# Get the public IP address +echo -e "${YELLOW}Enter your server's public IP address:${NC}" +read -p "> " SERVER_IP + +if [[ -z "$SERVER_IP" ]]; then + echo -e "${RED}Error: IP address cannot be empty${NC}" + exit 1 +fi + +# Validate IP address format (basic validation) +if ! [[ "$SERVER_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo -e "${RED}Error: Invalid IP address format${NC}" + exit 1 +fi + +# Get the TURN password +echo -e "${YELLOW}Enter a password for the TURN server (press Enter for default 'dograh-turn-secret'):${NC}" +read -sp "> " TURN_PASSWORD +echo "" + +if [[ -z "$TURN_PASSWORD" ]]; then + TURN_PASSWORD="dograh-turn-secret" + echo -e "${BLUE}Using default TURN password${NC}" +fi + +echo "" +echo -e "${GREEN}Configuration:${NC}" +echo -e " Server IP: ${BLUE}$SERVER_IP${NC}" +echo -e " TURN Password: ${BLUE}********${NC}" +echo "" + +# Create project directory if it doesn't exist +mkdir -p dograh 2>/dev/null || true +cd dograh + +echo -e "${BLUE}[1/5] Downloading docker-compose.yaml...${NC}" +curl -sS -o docker-compose.yaml https://raw.githubusercontent.com/dograh-hq/dograh/main/docker-compose.yaml +echo -e "${GREEN}✓ docker-compose.yaml downloaded${NC}" + +echo -e "${BLUE}[2/5] Creating nginx.conf...${NC}" +cat > nginx.conf << 'NGINX_EOF' +server { + listen 80; + server_name SERVER_IP_PLACEHOLDER; + + # Redirect all HTTP to HTTPS + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl; + server_name SERVER_IP_PLACEHOLDER; + + ssl_certificate /etc/nginx/certs/local.crt; + ssl_certificate_key /etc/nginx/certs/local.key; + + # Basic TLS settings + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + location / { + proxy_pass http://ui:3010; + proxy_http_version 1.1; + + # Important for WebSockets / hot reload etc. + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + + # Rewrite localhost MinIO URLs in API responses to use current domain + sub_filter 'http://localhost:9000/voice-audio/' 'https://$host/voice-audio/'; + sub_filter_once off; + sub_filter_types application/json text/html; + } + + location /voice-audio/ { + proxy_pass http://minio:9000/voice-audio/; + + proxy_http_version 1.1; + + # Headers for file downloads from MinIO + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + + # Allow large file downloads + proxy_buffering off; + client_max_body_size 100M; + } +} +NGINX_EOF + +# Replace placeholder with actual IP +sed -i.bak "s/SERVER_IP_PLACEHOLDER/$SERVER_IP/g" nginx.conf && rm -f nginx.conf.bak +echo -e "${GREEN}✓ nginx.conf created${NC}" + +echo -e "${BLUE}[3/5] Creating SSL certificate generation script...${NC}" +cat > generate_certificate.sh << CERT_EOF +#!/bin/bash +mkdir -p certs +openssl req -x509 -nodes -newkey rsa:2048 \\ + -keyout certs/local.key \\ + -out certs/local.crt \\ + -days 365 \\ + -subj "/CN=$SERVER_IP" +CERT_EOF +chmod +x generate_certificate.sh +echo -e "${GREEN}✓ generate_certificate.sh created${NC}" + +echo -e "${BLUE}[4/5] Generating SSL certificates...${NC}" +./generate_certificate.sh +echo -e "${GREEN}✓ SSL certificates generated${NC}" + +echo -e "${BLUE}[5/5] Creating environment file...${NC}" +cat > .env << ENV_EOF +# TURN Server Configuration +TURN_HOST=$SERVER_IP +TURN_USERNAME=dograh +TURN_PASSWORD=$TURN_PASSWORD + +# Telemetry (set to false to disable) +ENABLE_TELEMETRY=true +ENV_EOF +echo -e "${GREEN}✓ .env file created${NC}" + +echo "" +echo -e "${GREEN}╔══════════════════════════════════════════════════════════════╗${NC}" +echo -e "${GREEN}║ Setup Complete! ║${NC}" +echo -e "${GREEN}╚══════════════════════════════════════════════════════════════╝${NC}" +echo "" +echo -e "Files created in ${BLUE}$(pwd)${NC}:" +echo " - docker-compose.yaml" +echo " - nginx.conf" +echo " - generate_certificate.sh" +echo " - certs/local.crt" +echo " - certs/local.key" +echo " - .env" +echo "" +echo -e "${YELLOW}To start Dograh, run:${NC}" +echo "" +echo -e " ${BLUE}docker compose --profile remote up -d${NC}" +echo "" +echo -e "${YELLOW}Your application will be available at:${NC}" +echo "" +echo -e " ${BLUE}https://$SERVER_IP${NC}" +echo "" +echo -e "${YELLOW}Note:${NC} Your browser will show a security warning for the self-signed" +echo "certificate. You can safely accept it to proceed." +echo "" diff --git a/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx b/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx index 21d6539..4218fb6 100644 --- a/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx +++ b/ui/src/app/workflow/[workflowId]/run/[runId]/hooks/useWebSocketRTC.tsx @@ -39,6 +39,12 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia const useAudio = true; const audioCodec = 'default'; + // TURN server configuration from environment variables (matching backend pattern) + const turnHost = process.env.NEXT_PUBLIC_TURN_HOST; + const turnUsername = process.env.NEXT_PUBLIC_TURN_USERNAME; + const turnPassword = process.env.NEXT_PUBLIC_TURN_PASSWORD; + const useTurn = !!(turnHost && turnUsername && turnPassword); + const audioRef = useRef(null); const pcRef = useRef(null); const wsRef = useRef(null); @@ -67,8 +73,29 @@ export const useWebSocketRTC = ({ workflowId, workflowRunId, accessToken, initia }, [workflowId, workflowRunId, accessToken]); const createPeerConnection = () => { + // Build ICE servers list + const iceServers: RTCIceServer[] = []; + + if (useStun) { + iceServers.push({ urls: ['stun:stun.l.google.com:19302'] }); + } + + if (useTurn && turnHost && turnUsername && turnPassword) { + // Add TURN server with credentials from environment variables + iceServers.push({ + urls: [ + `turn:${turnHost}:3478`, // TURN over UDP + `turn:${turnHost}:3478?transport=tcp`, // TURN over TCP + ], + username: turnUsername, + credential: turnPassword + }); + + logger.info(`TURN server configured: ${turnHost}:3478`); + } + const config: RTCConfiguration = { - iceServers: useStun ? [{ urls: ['stun:stun.l.google.com:19302'] }] : [] + iceServers }; const pc = new RTCPeerConnection(config);