feat(turn): внешний TURN-релей и TLS через прокси

- install.sh: --tls-terminated-by-proxy (ссылки https/wss без своего 80/443),
  --turn-relay-host/--turn-relay-port/--turn-relay-tls-port, общий TURN_SECRET
  для coturn (use-auth-secret) и LiveKit
- livekit.yaml.tpl: rtc.turn_servers с внешним релеем (udp + tls)
- секрет вычисляется один раз и сохраняется в .env
This commit is contained in:
2026-09-19 20:28:03 +03:00
parent 4c17080c02
commit e8e063e52d
2 changed files with 60 additions and 2 deletions
+57 -2
View File
@@ -57,6 +57,10 @@ TURN_RELAY_START="${TURN_RELAY_START:-30000}"
TURN_RELAY_END="${TURN_RELAY_END:-40000}"
TURN_CERT_SRC="${TURN_CERT_SRC:-}"
TURN_CERT_SRC_KEY="${TURN_CERT_SRC_KEY:-}"
TURN_RELAY_HOST="${TURN_RELAY_HOST:-}"
TURN_RELAY_PORT="${TURN_RELAY_PORT:-3478}"
TURN_RELAY_TLS_PORT="${TURN_RELAY_TLS_PORT:-5349}"
TLS_BY_PROXY="${TLS_BY_PROXY:-false}"
TURN_TLS_PORT="${TURN_TLS_PORT:-5349}"
PROXY_MODE="${PROXY_MODE:-embedded}"
PROFILE="${PROFILE:-auto}"
@@ -116,6 +120,12 @@ install.sh — установка glchat (AGENT.md 10).
--turn-domain <domain> домен TURN/TLS (по умолчанию turn.<domain>)
--turn-cert <файл> сертификат для TURN/TLS (нужен при включённом TURN)
--turn-key <файл> приватный ключ сертификата TURN/TLS
--turn-relay-host <host> внешний TURN-релей с публичным IP (например, VPS):
клиенты получают его в списке turn_servers
--turn-relay-port <port> UDP-порт внешнего релея (по умолчанию 3478)
--turn-relay-tls-port <port> TLS-порт внешнего релея (по умолчанию 5349)
--tls-terminated-by-proxy TLS терминирует внешний прокси: свои 80/443 не занимаем,
но ссылки в API отдаются как https/wss
--turn-tls <port> TURN TLS (по умолчанию 5349)
--external-proxy не занимать 80/443: отдать маршруты внешнему прокси (AGENT.md 10.13)
--no-files-subdomain не использовать files.<domain> (fallback /files/*)
@@ -163,6 +173,10 @@ parse_args() {
--turn-domain) TURN_DOMAIN_OVERRIDE="${2:?}"; shift 2 ;;
--turn-cert) TURN_CERT_SRC="${2:?}"; shift 2 ;;
--turn-key) TURN_CERT_SRC_KEY="${2:?}"; shift 2 ;;
--turn-relay-host) TURN_RELAY_HOST="${2:?}"; shift 2 ;;
--turn-relay-port) TURN_RELAY_PORT="${2:?}"; shift 2 ;;
--turn-relay-tls-port) TURN_RELAY_TLS_PORT="${2:?}"; shift 2 ;;
--tls-terminated-by-proxy) TLS_BY_PROXY="true"; shift ;;
--turn-tls) TURN_TLS_PORT="${2:?}"; shift 2 ;;
--profile) PROFILE="${2:?}"; shift 2 ;;
--max-upload) MAX_UPLOAD_MB="${2:?}"; shift 2 ;;
@@ -255,6 +269,8 @@ resolve_derived_values() {
TURN_DOMAIN_RESOLVED="${TURN_DOMAIN_OVERRIDE:-turn.${DOMAIN}}"
TURN_CERT_DIR="${TARGET_DIR}/certs"
TURN_RELAY_HOST_RESOLVED="${TURN_RELAY_HOST}"
turn_shared_secret >/dev/null
disable_simulcast="true"
if [ "${PROFILE_LIVEKIT_SIMULCAST:-0}" -gt 0 ] 2>/dev/null; then
@@ -281,7 +297,7 @@ resolve_derived_values() {
livekit_extra_codecs \
PROFILE_LIVEKIT_DISABLE_SIMULCAST PROFILE_LIVEKIT_USE_EXTERNAL_IP \
GLCHAT_DATA_DIR GLCHAT_CADDY_PROFILE GLCHAT_TURN_CERT_DIR \
TURN_DOMAIN_RESOLVED TURN_CERT_DIR
TURN_DOMAIN_RESOLVED TURN_CERT_DIR TURN_RELAY_HOST_RESOLVED TURN_SECRET_RESOLVED
}
validate_inputs() {
@@ -518,13 +534,24 @@ CADDY_IMAGE_TAG=${CADDY_IMAGE_TAG}
LIVEKIT_IMAGE_TAG=${LIVEKIT_IMAGE_TAG}
# --- TLS ---
TLS_ENABLED=$([ "$SKIP_TLS" = "1" ] && echo false || echo true)
# TLS считается включённым и когда сертификаты обслуживает внешний прокси
# (--external-proxy --tls-terminated-by-proxy): клиентские ссылки должны быть https/wss.
if [ "$SKIP_TLS" = "1" ] && [ "$TLS_BY_PROXY" != "true" ]; then
TLS_ENABLED=false
else
TLS_ENABLED=true
fi
SKIP_TLS=${SKIP_TLS}
TLS_BY_PROXY=${TLS_BY_PROXY}
# --- интерполяция compose (пути и профиль) ---
GLCHAT_DATA_DIR=${GLCHAT_DATA_DIR}
GLCHAT_CADDY_PROFILE=${CADDY_PROFILE}
GLCHAT_TURN_CERT_DIR=${GLCHAT_TURN_CERT_DIR}
TURN_RELAY_HOST=${TURN_RELAY_HOST_RESOLVED}
TURN_RELAY_PORT=${TURN_RELAY_PORT}
TURN_RELAY_TLS_PORT=${TURN_RELAY_TLS_PORT}
TURN_SECRET=${TURN_SECRET_RESOLVED}
# --- порты ---
APP_PORT=${APP_PORT}
@@ -666,6 +693,33 @@ ensure_swap() {
# render_turn_relay_servers формирует YAML-список turn_servers для LiveKit:
# UDP и TLS-эндпоинты внешнего релея (VPS), у которого есть публичный IP.
# Секрет уже вычислен в resolve_derived_values (TURN_SECRET_RESOLVED).
render_turn_relay_servers() {
[ -n "$TURN_RELAY_HOST" ] || return 0
printf ' turn_servers:\n'
printf ' - host: %s\n port: %s\n protocol: udp\n secret: %s\n' \
"$TURN_RELAY_HOST" "$TURN_RELAY_PORT" "$TURN_SECRET_RESOLVED"
printf ' - host: %s\n port: %s\n protocol: tls\n secret: %s\n' \
"$TURN_RELAY_HOST" "$TURN_RELAY_TLS_PORT" "$TURN_SECRET_RESOLVED"
}
# turn_shared_secret — общий секрет для coturn (use-auth-secret) и LiveKit.
# При --reconfigure сохраняется из существующего .env, иначе генерируется.
turn_shared_secret() {
if [ -n "${TURN_SECRET_RESOLVED:-}" ]; then
printf '%s' "$TURN_SECRET_RESOLVED"
return
fi
if [ -f "$GLCHAT_ENV_FILE" ]; then
TURN_SECRET_RESOLVED="$(env_get TURN_SECRET || true)"
fi
[ -n "${TURN_SECRET_RESOLVED:-}" ] || TURN_SECRET_RESOLVED="$(rand_hex 32)"
export TURN_SECRET_RESOLVED
printf '%s' "$TURN_SECRET_RESOLVED"
}
# install_turn_certificate копирует сертификат TURN/TLS туда, где его читает
# контейнер LiveKit. Продление выполняет glchat-cert-refresh.timer.
install_turn_certificate() {
@@ -746,6 +800,7 @@ render_all_templates() {
"TURN_RELAY_END=${TURN_RELAY_END}" \
"TURN_CERT_FILE=$([ "$TURN_ENABLED" = "true" ] && echo /etc/certs/turn.crt || echo '')" \
"TURN_KEY_FILE=$([ "$TURN_ENABLED" = "true" ] && echo /etc/certs/turn.key || echo '')" \
"TURN_RELAY_SERVERS=$(render_turn_relay_servers)" \
"TURN_UDP_PORT=${TURN_UDP_PORT}" \
"TURN_TLS_PORT=${TURN_TLS_PORT}" \
"TURN_EXTERNAL_TLS=${turn_external_tls}" \
+3
View File
@@ -11,6 +11,9 @@ rtc:
use_external_ip: ${LIVEKIT_USE_EXTERNAL_IP}
# TCP-fallback, когда UDP заблокирован сетью клиента (AGENT.md 7.14).
allow_tcp_fallback: true
# Внешний TURN-релей с публичным IP (VPS): даёт рабочий путь клиентам за
# NAT/CGNAT, когда у самого инстанса белого адреса нет.
${TURN_RELAY_SERVERS}
keys: {}