- docker-compose with all services, health, depends, obs-config volume seed - mediamtx with live + live/produced paths - obs/ bakes defaults so start_broadcast + set_text + v4l2/alsa just work - bootstrap.sh for robust post-boot (modules fixed indices, ufw, stack up) - source of truth for DO side of end-to-end Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
155 lines
6 KiB
YAML
155 lines
6 KiB
YAML
# Production-ready relay stack for quinn.cast / broadcast.
|
|
# (no top-level version; modern compose ignores the legacy key)
|
|
# mediamtx (SRT ingest + RTMP for OBS produced) + v4l2/audio bridges + OBS (seeded) + LLM controller.
|
|
# Run on DO droplet after provision-stream-droplet.sh (which deploys these files).
|
|
#
|
|
# Usage on droplet:
|
|
# cd /opt/stream
|
|
# cp .env.example .env
|
|
# $EDITOR .env # XAI_API_KEY, OBS_WS_PASSWORD, UI_PASSPHRASE
|
|
# docker compose up -d --build
|
|
#
|
|
# Health: docker compose ps ; curl -f http://localhost:8080/health
|
|
# Logs: docker compose logs -f mediamtx bridge-obs-audio obs llm-obs-controller
|
|
#
|
|
# Security: change passphrases; ufw (or DO cloud firewall) restrict 8080/UI to trusted IPs;
|
|
# SRT 8890/udp must stay public (or front with a relay). Use strong srt passphrase.
|
|
|
|
services:
|
|
mediamtx:
|
|
image: bluenviron/mediamtx:latest
|
|
container_name: mediamtx
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8890:8890/udp" # SRT contribution from hotel (main path; use ?streamid=publish:live [&passphrase=...])
|
|
- "1935:1935" # RTMP (for OBS "Custom" output to local produced feed, and optional ingest)
|
|
- "8554:8554" # RTSP (optional)
|
|
- "8889:8889" # WebRTC (optional)
|
|
- "9997:9997" # API (used for health/stats)
|
|
volumes:
|
|
- ./mediamtx/mediamtx.yml:/mediamtx.yml:ro
|
|
# health uses builtin /dev/tcp (works in most base images without curl/wget)
|
|
healthcheck:
|
|
test: ["CMD-SHELL", "sh -c 'echo > /dev/tcp/127.0.0.1/9997' || exit 1"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 10s
|
|
|
|
# Video bridge: SRT -> v4l2loopback device so OBS "Video Capture Device (V4L2)" can see the hotel feed.
|
|
# Runs privileged to access /dev/video10 (created by host modprobe v4l2loopback).
|
|
# Robust loop + auto-reconnect. Decodes incoming (modest bitrate) to raw frames for the loopback.
|
|
video-bridge:
|
|
image: alpine:3.20
|
|
container_name: feed-bridge-video
|
|
restart: unless-stopped
|
|
privileged: true
|
|
volumes:
|
|
- /dev:/dev
|
|
- /proc/asound:/proc/asound:ro
|
|
command: >
|
|
sh -c "
|
|
apk add --no-cache ffmpeg curl &&
|
|
echo 'video-bridge: waiting for mediamtx...' &&
|
|
while ! curl -fsS http://mediamtx:9997/v3/paths/list >/dev/null 2>&1; do sleep 2; done &&
|
|
echo 'video-bridge: starting SRT -> /dev/video10 loop' &&
|
|
while true; do
|
|
ffmpeg -nostdin -loglevel warning -i 'srt://mediamtx:8890?streamid=read:live' \
|
|
-f v4l2 -pix_fmt yuv420p /dev/video10 || true
|
|
sleep 2
|
|
done
|
|
"
|
|
depends_on:
|
|
mediamtx:
|
|
condition: service_healthy
|
|
|
|
# Audio bridge: SRT audio -> alsa loopback (snd-aloop) so OBS can capture as virtual mic.
|
|
# Host must have: modprobe snd-aloop (index=2,3) in post-boot / bootstrap.
|
|
# If audio is silent in stream, check 'cat /proc/asound/cards' on host and adjust device in OBS source
|
|
# (or use one-time webtop GUI to tune the ALSA device in the seeded 'Hotel Mic' source; changes persist in volume).
|
|
audio-bridge:
|
|
image: alpine:3.20
|
|
container_name: feed-bridge-audio
|
|
restart: unless-stopped
|
|
privileged: true
|
|
volumes:
|
|
- /dev/snd:/dev/snd
|
|
- /proc/asound:/proc/asound:ro
|
|
command: >
|
|
sh -c "
|
|
apk add --no-cache ffmpeg &&
|
|
echo 'audio-bridge: cards visible to container:' &&
|
|
cat /proc/asound/cards || true &&
|
|
echo 'audio-bridge: starting SRT audio -> alsa loop (hw:2,1,0)' &&
|
|
while true; do
|
|
ffmpeg -nostdin -loglevel warning -i 'srt://mediamtx:8890?streamid=read:live' -vn \
|
|
-ac 2 -ar 48000 -f alsa hw:2,1,0 || true
|
|
sleep 2
|
|
done
|
|
"
|
|
depends_on:
|
|
mediamtx:
|
|
condition: service_healthy
|
|
|
|
# OBS container with websocket for LLM control.
|
|
# Custom image seeds "Hotel Cam" scene (v4l2 video + alsa audio stub + LowerThird text) + preconfigured
|
|
# Custom RTMP output to rtmp://127.0.0.1:1935/live (key=produced) so "start broadcast" just works.
|
|
# Uses named volume so first-run seed from image is captured, later edits (via GUI or LLM) persist.
|
|
# If you need extra plugins (obs-multi-rtmp etc) or one-time GUI setup, temporarily swap image
|
|
# to a webtop (linuxserver/webtop:ubuntu-mate) + forward noVNC/RDP, do your tweaks, export the
|
|
# ~/.config/obs-studio , then restore this image + volume (or bake new seed).
|
|
obs:
|
|
build:
|
|
context: ./obs
|
|
dockerfile: Dockerfile
|
|
container_name: obs
|
|
restart: unless-stopped
|
|
environment:
|
|
- OBS_WEBSOCKET_PORT=4455
|
|
- OBS_WEBSOCKET_PASSWORD=${OBS_WS_PASSWORD}
|
|
- OBS_WEBSOCKET_PASSWORD=${OBS_WS_PASSWORD} # some images read this
|
|
ports:
|
|
- "4455:4455"
|
|
volumes:
|
|
- obs-config:/root/.config/obs-studio
|
|
depends_on:
|
|
video-bridge:
|
|
condition: service_started
|
|
audio-bridge:
|
|
condition: service_started
|
|
# No easy health without extra tools in the obs image; rely on restart + controller waiting for WS.
|
|
|
|
# The LLM chat control surface + fanout manager.
|
|
# Talks xAI (Grok), obs-websocket, manages dynamic ffmpeg -c copy fanouts for all destinations.
|
|
# Exposes the chat UI on 8080 (gate with ?p=UI_PASSPHRASE).
|
|
controller:
|
|
build:
|
|
context: ./controller
|
|
dockerfile: Dockerfile
|
|
container_name: llm-obs-controller
|
|
restart: unless-stopped
|
|
env_file:
|
|
- .env
|
|
environment:
|
|
- OBS_WS_URL=ws://obs:4455
|
|
- OBS_WS_PASSWORD=${OBS_WS_PASSWORD}
|
|
- XAI_API_KEY=${XAI_API_KEY}
|
|
- PORT=8080
|
|
- UI_PASSPHRASE=${UI_PASSPHRASE}
|
|
# Comma or JSON array of initial {name,url} if you want baked destinations (optional)
|
|
- INITIAL_RTMP_TARGETS=${INITIAL_RTMP_TARGETS:-[]}
|
|
ports:
|
|
- "8080:8080"
|
|
depends_on:
|
|
obs:
|
|
condition: service_started
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/health"]
|
|
interval: 20s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 15s
|
|
|
|
volumes:
|
|
obs-config:
|
|
# Named volume auto-seeds from the obs image's /root/.config/obs-studio on first creation.
|