coturn (nobody:nogroup, без root-фазы в entrypoint) не может сам прочитать
приватный ключ Let's Encrypt — coturn-certs-init (по образцу
recordings-init/llm-models-init) копирует fullchain/privkey в отдельный
volume под правами 644, не трогая права на ключ на хосте.
TURN_TLS_HOST в .env — единственный переключатель фичи: пусто (dev-дефолт)
вырезает TLS-блоки из turnserver.conf и rtc.turn_servers целиком (маркеры
BEGIN/END-TLS-* в *.template, render-templates.sh), непустое значение
включает оба сразу — TLS без анонса LiveKit клиентам не имеет смысла
(история 0.0.14: coturn работал healthy, но клиенты о нём не знали, и не
было ни одной аллокации). Значение обязано быть доменом сертификата, а не
IP — иначе браузер не пройдёт TLS-валидацию по имени хоста для turns:.
TLS-запись в rtc.turn_servers стоит последней в списке (фолбэк дороже
прямого UDP/TCP).
Образ собран с `uv sync --frozen --no-dev`, но `uv run` перед каждым запуском
заново синхронизирует venv и подтягивает dev-группу. В логах старта
vidconf-backend-1 и vidconf-worker-1 на проде это видно как «Downloading ruff /
mypy / pygments» и «Installed 12 packages». Хуже всего healthcheck'и: они
выполняют ту же синхронизацию каждые 15 секунд всю жизнь контейнера.
Проверено на локально собранном образе, одна и та же команда:
uv run — качает 12 пакетов, venv 456 → 574 МБ
uv run --no-sync — не качает ничего, venv остаётся 456 МБ
Флаг добавлен во все вызовы в прод-путях: CMD образа, command/entrypoint/
healthcheck всех сервисов compose, миграции и seed в install.sh, те же команды
в docs/deploy. Локальная разработка (dev-setup.md, backend/README.md, CI) не
затронута — там dev-зависимости нужны. Заодно убрано устаревшее объяснение
ретрая `up -d --wait`: первый старт больше не синхронизирует окружение.
Версия uv в образе — 0.11.33, `--no-sync` поддерживается.
set_env_var COMPOSE_PROFILES перезаписывает значение целиком по пресету,
затирая media,monitoring из .env.example — Grafana/Prometheus оставались
не поднятыми, пока оператор не допишет профиль в .env вручную.
--monitoring дописывает monitoring к профилю, который выставил пресет,
и включает его сразу в тот же docker compose up (не только в .env).
На свежем клоне install.sh молча принимал непустые dev-плейсхолдеры
.env.example (LIVEKIT_PUBLIC_URL=ws://localhost:7880, NGINX_SERVER_NAMES=
example.com, LIVEKIT_API_KEY=devkey и т.д.) — ensure_secret/ensure_default
генерируют значения только для ПУСТЫХ ключей. Итог: рапорт об успехе,
на деле — mixed-content и nginx на несуществующем домене.
validate_prod_env проверяет согласованность .env, когда NGINX_SERVER_NAMES
указывает на реальный домен (не localhost) — останавливает установку
понятной ошибкой, если что-то из прод-обязательных значений осталось
плейсхолдером.
Redis without a password was the root cause of the security incident
(cron miner via unauthenticated replication RCE, see
.forcc/deploy/SESSION2-FINDINGS.md). Loopback binding alone doesn't
protect against a compromised container inside the same compose
network, so wire REDIS_PASSWORD as a required secret everywhere redis
is used: backend/worker/worker-transcriber(-gpu), redis-exporter,
livekit and egress (via rendered templates). docker compose now
refuses to start without it instead of silently running unauthenticated.
docker compose определяет .env для подстановки ${VAR} по каталогу
compose-файла (deploy/), а не по текущей директории — repo-root .env,
который использует install.sh и вся документация, молча не подхватывался.
Это и была причина "WARN: LIVEKIT_API_KEY not set" на боевом сервере:
секреты были в .env, но compose их не видел и подставлял небезопасные
дефолты (см. таблицу разведки сервера).
Теперь ставшие обязательными ${VAR:?} в docker-compose.yml (см. предыдущий
коммит) без этого немедленно проваливали бы конфиг на любой из
документированных команд. Добавлен `--env-file .env`/"$ENV_FILE" ко всем
вызовам docker compose в install.sh и в командах из README/docs.
install.sh дополнительно: ensure_default (аналог ensure_secret без генерации
секрета) для новых не-секретных параметров nginx/coturn/livekit
(NGINX_SERVER_NAMES, NGINX_CERT_NAME, LIVEKIT_USE_EXTERNAL_IP,
LIVEKIT_NODE_IP, TURN_EXTERNAL_IP) — дефолты только для локальной
разработки, не перезаписывают значения, заданные вручную на боевом
сервере. Плюс вызов deploy/render-templates.sh перед сборкой/подъёмом
стека.