Appearance
Ручная установка Callbox с Docker Compose
Инструкция предназначена для NextBox oak-1.11.0, уже установленного по основной инструкции Docker Compose. Для новой production-установки предпочтителен установщик NextBox: он автоматически генерирует секреты, TURN/TLS и согласованные конфигурации LiveKit.
Перед началом сделайте резервную копию docker-compose.yaml, default.conf и PostgreSQL.
1. Подготовить секреты и базу
Основная инструкция уже добавляет в .env необходимые значения:
JWT_SIGN_KEY— общий ключ пользовательских JWT NextBox;SERVICE_TOKEN— общий токен межсервисных запросов;POSTGRES_PASSWORDиRABBITMQ_PASSWORD— пароли инфраструктурных сервисов;LIVEKIT_API_KEY— идентификатор ключа, по умолчаниюlivekit-prod;LIVEKIT_API_SECRET— случайный секрет длиной не менее 32 байт.
Если .env был создан по старой версии инструкции и переменных LiveKit в нем нет, добавьте их, не изменяя существующие секреты:
bash
if ! grep -q '^LIVEKIT_API_KEY=' .env; then
printf 'LIVEKIT_API_KEY=livekit-prod\n' >>.env
fi
if ! grep -q '^LIVEKIT_API_SECRET=' .env; then
printf 'LIVEKIT_API_SECRET=%s\n' "$(openssl rand -hex 32)" >>.env
fi
chmod 600 .envСоздайте базу один раз. Команда ниже рассчитана на параметры из основной инструкции NextBox:
bash
docker compose exec postgresql \
psql -U nextbox -d postgres \
-c "CREATE DATABASE callbox OWNER nextbox;"Если база уже существует, не выполняйте команду повторно. Миграции таблиц Callbox применит автоматически.
2. Создать конфигурации LiveKit и Egress
Docker Compose подставляет .env в docker-compose.yaml, но не подставляет переменные в произвольные YAML-файлы. Сохраните следующий скрипт как generate-callbox-configs.sh: он читает ключи из .env, проверяет их формат и атомарно создаёт livekit.yaml и egress.yaml.
bash
#!/usr/bin/env bash
set -Eeuo pipefail
for file in .env; do
[[ -f "$file" ]] || { echo "Ошибка: не найден $file." >&2; exit 1; }
done
for file in livekit.yaml egress.yaml; do
[[ ! -e "$file" ]] || {
echo "Ошибка: $file уже существует; конфигурация не изменена." >&2
exit 1
}
done
read_env() {
local name=$1 value
value=$(sed -n "s/^${name}=//p" .env)
[[ -n "$value" ]] || { echo "Ошибка: в .env отсутствует $name." >&2; exit 1; }
[[ "$value" =~ ^[A-Za-z0-9._-]+$ ]] || {
echo "Ошибка: $name содержит недопустимые символы." >&2
exit 1
}
printf '%s' "$value"
}
livekit_api_key=$(read_env LIVEKIT_API_KEY)
livekit_api_secret=$(read_env LIVEKIT_API_SECRET)
[[ ${#livekit_api_secret} -ge 32 ]] || {
echo "Ошибка: LIVEKIT_API_SECRET должен содержать не менее 32 символов." >&2
exit 1
}
umask 077
livekit_tmp=$(mktemp livekit.yaml.tmp.XXXXXX)
egress_tmp=$(mktemp egress.yaml.tmp.XXXXXX)
trap 'rm -f "$livekit_tmp" "$egress_tmp"' EXIT
cat >"$livekit_tmp" <<EOF
port: 7880
bind_addresses:
- ""
rtc:
tcp_port: 7881
port_range_start: 50100
port_range_end: 50200
use_external_ip: false
enable_loopback_candidate: false
redis:
address: redis:6379
db: 1
keys:
${livekit_api_key}: ${livekit_api_secret}
webhook:
api_key: ${livekit_api_key}
urls:
- http://callbox:8081/callbox/api/egress/webhook
EOF
cat >"$egress_tmp" <<EOF
logging:
level: info
api_key: ${livekit_api_key}
api_secret: ${livekit_api_secret}
ws_url: ws://livekit:7880
insecure: true
redis:
address: redis:6379
db: 1
cpu_cost:
room_composite_cpu_cost: 4.0
EOF
mv -- "$livekit_tmp" livekit.yaml
mv -- "$egress_tmp" egress.yaml
trap - EXIT
echo "Созданы livekit.yaml и egress.yaml."Запустите скрипт из директории установки:
bash
chmod +x generate-callbox-configs.sh
./generate-callbox-configs.shПолучившийся livekit.yaml имеет следующую структуру:
yaml
port: 7880
bind_addresses:
- ""
rtc:
tcp_port: 7881
port_range_start: 50100
port_range_end: 50200
use_external_ip: false
enable_loopback_candidate: false
redis:
address: redis:6379
db: 1
keys:
<LIVEKIT_API_KEY>: <LIVEKIT_API_SECRET>
webhook:
api_key: <LIVEKIT_API_KEY>
urls:
- http://callbox:8081/callbox/api/egress/webhookДля сервера с публичным IP установите rtc.use_external_ip: true. Если на хосте несколько сетевых интерфейсов, дополнительно задайте rtc.node_ip публичным адресом, доступным клиентам.
Встроенный TURN и TURN over TLS в этом сокращённом примере не настроены. Для сетей со строгими firewall используйте установщик NextBox либо добавьте TURN по рекомендациям LiveKit.
Получившийся egress.yaml использует те же ключи, что и livekit.yaml:
yaml
logging:
level: info
api_key: <LIVEKIT_API_KEY>
api_secret: <LIVEKIT_API_SECRET>
ws_url: ws://livekit:7880
insecure: true
redis:
address: redis:6379
db: 1
cpu_cost:
room_composite_cpu_cost: 4.03. Добавить сервисы в Compose
Добавьте блоки внутрь существующей секции services:. Значения ${...} Docker Compose возьмёт из .env основной установки.
yaml
livekit:
image: livekit/livekit-server:v1.10.1
restart: always
command: --config /etc/livekit.yaml
ports:
- "7880:7880"
- "7881:7881/tcp"
- "50100-50200:50100-50200/udp"
volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro
- livekit_data:/var/livekit
networks:
- nextbox
depends_on:
redis:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:7880"]
interval: 5s
timeout: 3s
retries: 20
start_period: 10s
egress:
image: livekit/egress:latest
restart: always
cpus: "4.0"
shm_size: "1gb"
environment:
EGRESS_CONFIG_FILE: /etc/egress.yaml
volumes:
- ./egress.yaml:/etc/egress.yaml:ro
- egress_data:/data
cap_add:
- SYS_ADMIN
networks:
- nextbox
depends_on:
livekit:
condition: service_healthy
redis:
condition: service_healthy
egress-files:
image: nextbox-docker.ptnl.moscow/nextbox/callbox-egress-files:oak-1.11.0
restart: always
environment:
BEARER_TOKEN: ${LIVEKIT_API_KEY}
DATA_DIR: /data
PORT: "9100"
MAX_AGE: "60m"
CLEANUP_INTERVAL: "10m"
volumes:
- egress_data:/data
networks:
- nextbox
depends_on:
egress:
condition: service_started
callbox:
image: nextbox-docker.ptnl.moscow/nextbox/callbox:oak-1.11.0
restart: always
ports:
- "8081:8081"
environment:
PORT: "8081"
BASE_PATH: "/callbox"
APP_VERSION: "1.11.0"
LOG_LEVEL: "info"
JWT_SECRET: ${JWT_SIGN_KEY}
JWT_COOKIE_NAME: "access_token"
DATABASE_DSN: "postgres://nextbox:${POSTGRES_PASSWORD}@postgresql:5432/callbox?sslmode=disable"
DATABASE_MAX_OPEN_CONNS: "20"
DATABASE_MAX_IDLE_CONNS: "10"
DATABASE_CONN_MAX_IDLE_TIME: "5m"
DATABASE_CONN_MAX_LIFETIME: "30m"
REDIS_URL: "redis://redis:6379/0"
REDIS_KEY_PREFIX: "callbox:v1"
LIVEKIT_SERVER_URL: "http://livekit:7880"
LIVEKIT_CLIENT_URL: "ws://localhost:7880"
LIVEKIT_API_KEY: ${LIVEKIT_API_KEY}
LIVEKIT_API_SECRET: ${LIVEKIT_API_SECRET}
NEXTBOX_API_URL: "http://nginx:80"
FILE_STORAGE_PATH: "/var/callbox/files"
CANVAS_STORAGE_PATH: "/var/callbox/canvases"
FILE_MAX_SIZE: "1073741824"
RECORDING_ENABLED: "false"
EGRESS_FILES_URL: "http://egress-files:9100"
DISCOVERY_URL: "http://discovery:6011"
SERVICE_TOKEN: ${SERVICE_TOKEN}
SERVICE_ROUTE_NAME: "callbox"
SERVICE_BACK_URL: "http://callbox:8081"
SERVICE_INSTANCE_NUMBER: "1"
NEXTBOX_NOTIFICATIONS_ENABLED: "true"
RABBITMQ_URL: "amqp://nextbox:${RABBITMQ_PASSWORD}@rabbitmq:5672/nextbox"
healthcheck:
test:
- CMD-SHELL
- >-
wget -q -O /dev/null
--header="Authorization: Service $$SERVICE_TOKEN"
http://127.0.0.1:8081/callbox/api/health-check
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
volumes:
- callbox_files:/var/callbox/files
- callbox_canvases:/var/callbox/canvases
networks:
- nextbox
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
rabbitmq:
condition: service_healthy
discovery:
condition: service_healthy
livekit:
condition: service_healthyДобавьте volumes в существующую корневую секцию volumes::
yaml
volumes:
# существующие volumes NextBox
callbox_files:
callbox_canvases:
livekit_data:
egress_data:Проверки livekit и callbox выполняются через wget. Образы egress и egress-files не предоставляют отдельного HTTP health endpoint, поэтому их состояние проверяйте командами docker compose ps и docker compose logs.
Одинаковые значения
LIVEKIT_API_KEY должен совпадать в livekit.yaml, egress.yaml, Callbox и BEARER_TOKEN сервиса egress-files. LIVEKIT_API_SECRET должен совпадать в livekit.yaml, egress.yaml и Callbox. Скрипт и Compose получают оба значения из одного .env.
Общий Redis
LiveKit и Egress должны использовать одинаковые address: redis:6379 и db: 1. Не направляйте только один из них в другой Redis или другую DB: Egress использует Redis для связи с LiveKit и очередей заданий.
NEXTBOX_API_URL=http://nginx:80 намеренно указывает на внутренний reverse proxy, а не только на Gateway: Egress должен открыть встроенный шаблон по пути /callbox/static/egress/.
Добавить Whisper (опционально)
Для транскрибации записей добавьте сервис Whisper в секцию services::
yaml
whisper:
image: nextbox-docker.ptnl.moscow/nextbox/callbox-whisper-cpu:oak-1.11.0
restart: always
environment:
HF_TOKEN: ${HF_TOKEN:-}
TRANSCRIPTION_ENGINE: "whisperx"
DEVICE: "cpu"
COMPUTE_TYPE: "int8"
MODEL_SIZE: "large-v3"
WHISPER_LANGUAGE: ""
HF_HOME: "/data/models"
TORCH_FORCE_NO_WEIGHTS_ONLY_LOAD: "1"
volumes:
- whisper_models:/data/models
networks:
- nextbox
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=5)"]
interval: 15s
timeout: 10s
start_period: 5m
retries: 5Добавьте в environment: сервиса callbox:
yaml
WHISPER_URL: "http://whisper:8000"
WHISPER_SPEED_FACTOR: "1.0"
WHISPER_MAX_TIMEOUT: "2h"
TRANSCRIPTION_WORKER_CONCURRENCY: "1"
TRANSCRIPTION_MAX_ATTEMPTS: "3"
TRANSCRIPTION_HISTORY_RETENTION_DAYS: "180"и зависимость Callbox от готовности Whisper:
yaml
depends_on:
whisper:
condition: service_healthyВ корневую секцию volumes: добавьте постоянный cache моделей:
yaml
volumes:
whisper_models:Первый запуск может занять несколько минут, пока модель загружается в whisper_models. HF_TOKEN нужен только для загрузки моделей pyannote и разделения результата по спикерам; без него основная транскрибация продолжит работать.
Для NVIDIA GPU используйте образ nextbox-docker.ptnl.moscow/nextbox/callbox-whisper-cuda:oak-1.11.0, измените DEVICE на cuda, COMPUTE_TYPE на float16, добавьте gpus: all, а в healthcheck замените python на python3.11. На хосте должны быть установлены драйвер NVIDIA и NVIDIA Container Toolkit.
Вместо локального контейнера можно указать внешний совместимый сервис:
yaml
WHISPER_URL: "http://transcription-gpu.internal:8000"Callbox ожидает от него GET /health и POST /v1/transcribe. Не публикуйте этот сервис в интернет без отдельной аутентификации и TLS.
4. Настроить reverse proxy
Добавьте в существующий server { ... } файла default.conf маршрут Callbox:
nginx
location /callbox {
proxy_pass http://callbox:8081;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $proxy_connection;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Host $the_host;
proxy_set_header X-Forwarded-Proto $the_scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_request_buffering off;
proxy_read_timeout 900s;
proxy_send_timeout 900s;
}LiveKit в примере доступен клиентам напрямую на ws://<host>:7880. Для HTTPS-развёртывания настройте отдельный TLS-host, например wss://calls.example.com, который проксирует корень на livekit:7880, и укажите его в LIVEKIT_CLIENT_URL. Не используйте ws:// со страницей NextBox, открытой по HTTPS: браузер заблокирует mixed content.
5. Проверить и запустить
bash
docker compose config --quiet
docker compose pull
docker compose up -d --wait
docker compose restart nginx
docker compose psЕсли Whisper включён, проверьте его из контейнера Callbox:
bash
docker compose exec callbox wget -qO- http://whisper:8000/healthПроверьте API и регистрацию Discovery:
bash
set -a
. ./.env
set +a
curl -fsS \
-H "Authorization: Service ${SERVICE_TOKEN}" \
http://127.0.0.1:8081/callbox/api/health-checkИнтерфейс будет доступен по адресу http://localhost:8095/callbox, если основной reverse proxy NextBox опубликован на порту 8095.
6. Включить запись
После базовой проверки измените:
yaml
RECORDING_ENABLED: "true"и пересоздайте Callbox:
bash
docker compose up -d callbox
docker compose logs -f --tail=100 callbox egress egress-filesСоздайте короткую тестовую запись. Успешный сценарий выглядит так: Egress завершает файл в egress_data, webhook приходит в Callbox, Callbox скачивает файл с Bearer-токеном и сохраняет его в callbox_files.
Обновление
Перед обновлением сохраните PostgreSQL и конфигурационные файлы, затем смените теги обоих образов NextBox Callbox на одну и ту же версию:
bash
docker compose pull callbox egress-files
docker compose up -d --wait callbox egress-files
docker compose logs --tail=100 callboxНе удаляйте volumes callbox_files, callbox_canvases и базу callbox при обычном обновлении.
Диагностика
- Callbox не стартует: проверьте пять обязательных переменных и доступность базы
callbox. - Discovery видит сервис как unhealthy: проверьте
SERVICE_BACK_URL=http://callbox:8081, общийSERVICE_TOKENи ответ корневого endpoint/api/health-check. - Клиент не подключается к звонку: проверьте
LIVEKIT_CLIENT_URL, TCP7880/7881, UDP50100-50200и отсутствие mixed content. - Запись не появляется: проверьте webhook в
livekit.yaml, совпадение ключей, общийegress_dataи логи трёх сервисов. - LiveKit не видит Egress: проверьте, что в
livekit.yamlиegress.yamlодинаково указаныredis:6379и DB1. - Egress возвращает
401:BEARER_TOKENдолжен совпадать сLIVEKIT_API_KEY. - Видео Egress пустое: убедитесь, что
http://nginx/callbox/static/egress/открывается из контейнераegressи чтоNEXTBOX_API_URLмаршрутизирует/callbox. - Транскрибация недоступна: проверьте
WHISPER_URL, healthcheck и логи сервисовwhisperиcallbox. При первом запуске дождитесь загрузки модели.