In Docker the loopback origin gate (`require_loopback`) is unenforceable: Docker's NAT rewrites `request.client.host` to the bridge gateway (e.g. 172.17.0.1) even for a localhost-only `-p 127.0.0.1:3900:3900` mapping, so every request looks non-loopback. The gate then 403s the operator out of the routes the web UI needs — `/system/*` (incl. `/system/info`, which left the version blank, re-breaking #249 in Docker) and `/api/settings/*` (HF-token entry) — surfacing as "Loopback origin required" all over the UI. Fix: add an explicit, opt-in `OMNIVOICE_SERVER_MODE` flag. When set, `require_loopback` becomes a no-op; exposure is then governed by the operator's port mapping plus the optional share PIN (NetworkAccessMiddleware still 401s unauthenticated non-loopback clients whenever a PIN is set). The Docker image sets `OMNIVOICE_SERVER_MODE=1` (Dockerfile + documented in compose). Security: the desktop build NEVER sets this, so its loopback boundary is unchanged — LAN share guests are still denied the admin/system routes. New unit tests lock the contract (strict 403 by default incl. the PR #81 vectors; relaxed only under the flag). Existing non-loopback 403 tests still pass. Docs: docker.md troubleshooting entry for "Loopback origin required". Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
105 lines
4.2 KiB
YAML
105 lines
4.2 KiB
YAML
# ──────────────────────────────────────────────────────────────
|
|
# OmniVoice Studio — Docker Compose
|
|
#
|
|
# Quick start:
|
|
# docker compose -f deploy/docker-compose.yml --profile cpu up # CPU mode
|
|
# docker compose -f deploy/docker-compose.yml --profile gpu up # GPU mode
|
|
#
|
|
# Both services bind to port 3900, so they MUST be opt-in via profiles —
|
|
# otherwise `compose up` would race them and one would fail to bind.
|
|
#
|
|
# First run downloads ~4 GB of models. Progress is shown in logs.
|
|
# Open http://localhost:3900 once the health check passes.
|
|
#
|
|
# SECURITY: The port is bound to 127.0.0.1 by default — only this
|
|
# machine can reach the API. To expose OmniVoice on your LAN (or
|
|
# through a reverse proxy / tunnel), change the port mapping to
|
|
# "0.0.0.0:3900:3900" or "3900:3900". OmniVoice itself ships no
|
|
# authentication — if you expose it, put it behind a reverse proxy
|
|
# with auth (Caddy basic_auth, nginx + htpasswd, Tailscale, etc.).
|
|
# ──────────────────────────────────────────────────────────────
|
|
|
|
services:
|
|
# ── CPU mode — activate with: docker compose --profile cpu up
|
|
omnivoice:
|
|
image: ghcr.io/debpalash/omnivoice-studio:latest
|
|
# To build from source instead of pulling, comment out `image:` and
|
|
# uncomment the two lines below:
|
|
build:
|
|
context: ..
|
|
dockerfile: deploy/Dockerfile
|
|
container_name: omnivoice-studio
|
|
profiles: ["cpu"]
|
|
ports:
|
|
- "127.0.0.1:3900:3900"
|
|
volumes:
|
|
- omnivoice-data:/app/omnivoice_data
|
|
environment:
|
|
- HF_HOME=/app/omnivoice_data/huggingface
|
|
- HF_TOKEN=${HF_TOKEN:-}
|
|
- OMNIVOICE_DATA_DIR=/app/omnivoice_data
|
|
- PYTHONPATH=/app/backend
|
|
- PYTHONUNBUFFERED=1
|
|
# Bind uvicorn to 0.0.0.0 *inside* the container so the host-side port
|
|
# mapping above can forward traffic in. The 127.0.0.1 prefix on the
|
|
# `ports:` mapping is what enforces loopback-only on the host —
|
|
# OMNIVOICE_BIND_HOST=0.0.0.0 here only opens the container's own
|
|
# interface. The backend default is 127.0.0.1 (see backend/main.py).
|
|
- OMNIVOICE_BIND_HOST=0.0.0.0
|
|
# Headless server: relax the desktop-only loopback origin gate so the
|
|
# web UI's /system/* and /api/settings/* routes work through Docker's
|
|
# NAT (issue #261). Already baked into the image; shown here so it's
|
|
# discoverable. If you front the container with your own auth proxy on
|
|
# loopback, set this to 0 to re-enable the strict gate.
|
|
- OMNIVOICE_SERVER_MODE=1
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-sf", "http://localhost:3900/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 120s
|
|
restart: unless-stopped
|
|
|
|
# ── GPU mode — activate with: docker compose --profile gpu up
|
|
omnivoice-gpu:
|
|
image: ghcr.io/debpalash/omnivoice-studio:latest
|
|
build:
|
|
context: ..
|
|
dockerfile: deploy/Dockerfile
|
|
container_name: omnivoice-studio-gpu
|
|
profiles: ["gpu"]
|
|
ports:
|
|
- "127.0.0.1:3900:3900"
|
|
volumes:
|
|
- omnivoice-data:/app/omnivoice_data
|
|
environment:
|
|
- HF_HOME=/app/omnivoice_data/huggingface
|
|
- HF_TOKEN=${HF_TOKEN:-}
|
|
- OMNIVOICE_DATA_DIR=/app/omnivoice_data
|
|
- PYTHONPATH=/app/backend
|
|
- PYTHONUNBUFFERED=1
|
|
# Bind uvicorn to 0.0.0.0 *inside* the container — same as the CPU
|
|
# service above. The host-side `127.0.0.1:3900:3900` mapping keeps
|
|
# LAN reachability off by default.
|
|
- OMNIVOICE_BIND_HOST=0.0.0.0
|
|
# See the CPU service above — relaxes the loopback origin gate for the
|
|
# headless Docker deployment (issue #261). Set to 0 to re-enable it.
|
|
- OMNIVOICE_SERVER_MODE=1
|
|
healthcheck:
|
|
test: ["CMD", "curl", "-sf", "http://localhost:3900/health"]
|
|
interval: 30s
|
|
timeout: 10s
|
|
retries: 3
|
|
start_period: 180s
|
|
deploy:
|
|
resources:
|
|
reservations:
|
|
devices:
|
|
- driver: nvidia
|
|
count: 1
|
|
capabilities: [gpu]
|
|
restart: unless-stopped
|
|
|
|
volumes:
|
|
omnivoice-data:
|