Files
chatwoot/docker-compose.yaml
T
tds-1androot 5e5dc21f2f feat(whatsapp-call): add server-side WebRTC media server for call persistence
Implements a Pion Go B2BUA media server sidecar that sits between Meta's
WhatsApp Cloud API and the agent's browser, enabling call persistence
across page reloads, server-side recording, multi-participant support,
audio injection, and AI integration readiness.

Go Media Server (enterprise/media-server/):
- Pion WebRTC v4 B2BUA with Peer A (Meta) and Peer B (Agent) connections
- Real-time OGG/Opus recording with crash recovery
- Audio bridge with multi-peer fan-out and AudioConsumer plugin interface
- Audio injection from OGG files with loop support for hold music
- Session manager with graceful shutdown and orphaned recording recovery
- HTTP API with Bearer token auth, health checks, and metrics

Rails Integration:
- Whatsapp::MediaServerClient HTTP client for Go sidecar communication
- Dual-mode CallService: legacy browser-direct and server-relay paths
- Media server callback controller for agent disconnect/recording/terminate
- CallRecordingFetchJob: downloads OGG from Go server to ActiveStorage/S3
- CallCleanupJob: sweeps stale ringing and in-progress calls
- New endpoints: active, agent_answer, reconnect, join, play_audio
- DB migration: media_session_id column with indexes

Frontend:
- Dual-mode composable auto-detecting legacy vs server-relay
- handleAgentOffer() for receiving SDP from media server
- useCallReconnection composable for page reload recovery
- Removed terminateCallOnUnload in server-relay mode
- Simplified outbound call flow in ConversationHeader
- New ActionCable event: whatsapp_call.agent_offer

Documentation:
- Server-side WebRTC architecture spec (1505 lines)
- Implementation plan with 119 trackable checklist items
- Feature spec, PR breakdown, and relay architecture docs
2026-04-21 04:47:53 +00:00

147 lines
3.3 KiB
YAML

version: '3'
services:
base: &base
build:
context: .
dockerfile: ./docker/Dockerfile
args:
BUNDLE_WITHOUT: ''
EXECJS_RUNTIME: 'Node'
RAILS_ENV: 'development'
RAILS_SERVE_STATIC_FILES: 'false'
tty: true
stdin_open: true
image: chatwoot:development
env_file: .env
rails:
<<: *base
build:
context: .
dockerfile: ./docker/dockerfiles/rails.Dockerfile
image: chatwoot-rails:development
volumes:
- ./:/app:delegated
- node_modules:/app/node_modules
- packs:/app/public/packs
- cache:/app/tmp/cache
- bundle:/usr/local/bundle
depends_on:
- postgres
- redis
- vite
- mailhog
- sidekiq
ports:
- 3000:3000
env_file: .env
environment:
- VITE_DEV_SERVER_HOST=vite
- NODE_ENV=development
- RAILS_ENV=development
entrypoint: docker/entrypoints/rails.sh
command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
sidekiq:
<<: *base
image: chatwoot-rails:development
volumes:
- ./:/app:delegated
- node_modules:/app/node_modules
- packs:/app/public/packs
- cache:/app/tmp/cache
- bundle:/usr/local/bundle
depends_on:
- postgres
- redis
- mailhog
environment:
- NODE_ENV=development
- RAILS_ENV=development
command: ["bundle", "exec", "sidekiq", "-C", "config/sidekiq.yml"]
vite:
<<: *base
build:
context: .
dockerfile: ./docker/dockerfiles/vite.Dockerfile
image: chatwoot-vite:development
volumes:
- ./:/app:delegated
- node_modules:/app/node_modules
- packs:/app/public/packs
- cache:/app/tmp/cache
- bundle:/usr/local/bundle
ports:
- "3036:3036" # Vite dev server
environment:
- VITE_DEV_SERVER_HOST=0.0.0.0
- NODE_ENV=development
- RAILS_ENV=development
entrypoint: docker/entrypoints/vite.sh
command: bin/vite dev
postgres:
image: pgvector/pgvector:pg16
restart: always
ports:
- '5432:5432'
volumes:
- postgres:/data/postgres
environment:
- POSTGRES_DB=chatwoot
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=
redis:
image: redis:alpine
restart: always
command: ["sh", "-c", "redis-server --requirepass \"$REDIS_PASSWORD\""]
env_file: .env
volumes:
- redis:/data/redis
ports:
- '6379:6379'
mailhog:
image: mailhog/mailhog
ports:
- 1025:1025
- 8025:8025
media-server:
image: chatwoot/media-server:latest
build:
context: ./enterprise/media-server
dockerfile: Dockerfile
ports:
- '4000:4000'
- '10000-10100:10000-10100/udp'
environment:
- AUTH_TOKEN=${MEDIA_SERVER_AUTH_TOKEN:-}
- RAILS_CALLBACK_URL=http://rails:3000
- RECORDINGS_DIR=/recordings
- LOG_LEVEL=info
- UDP_PORT_MIN=10000
- UDP_PORT_MAX=10100
- PUBLIC_IP=${MEDIA_SERVER_PUBLIC_IP:-}
- STUN_SERVERS=stun:stun.l.google.com:19302
volumes:
- media_recordings:/recordings
restart: unless-stopped
healthcheck:
test: ['CMD', 'wget', '--spider', '-q', 'http://localhost:4000/health']
interval: 10s
timeout: 5s
retries: 3
volumes:
postgres:
redis:
packs:
node_modules:
cache:
bundle:
media_recordings: