feat(deploy): TURN/TLS анонсируется именем хоста с сертификатом

Браузеры отклоняют TLS-соединение к TURN по IP, если сертификат выписан на
домен: релей с публичным адресом отдавал `turns:<ip>:5349`, и этот эндпоинт был
нерабочим.

- флаг `--turn-relay-tls-host` (по умолчанию `turn.<домен>`, если он резолвится,
  иначе адрес релея) попадает в `rtc.turn_servers` как TLS-эндпоинт;
- значение сохраняется в `.env` и переиспользуется при `--reconfigure`;
- `scripts/test-install.sh` проверяет, что в `livekit.yaml` попали и UDP-адрес,
  и TLS-имя релея.
This commit is contained in:
2026-09-20 20:48:32 +03:00
parent 4a0b70ab24
commit 8b477ac314
2 changed files with 36 additions and 3 deletions
+21 -2
View File
@@ -61,6 +61,10 @@ TURN_CERT_SRC="${TURN_CERT_SRC:-}"
TURN_CERT_SRC_KEY="${TURN_CERT_SRC_KEY:-}" TURN_CERT_SRC_KEY="${TURN_CERT_SRC_KEY:-}"
TURN_RELAY_HOST="${TURN_RELAY_HOST:-}" TURN_RELAY_HOST="${TURN_RELAY_HOST:-}"
TURN_RELAY_PORT="${TURN_RELAY_PORT:-3478}" TURN_RELAY_PORT="${TURN_RELAY_PORT:-3478}"
# Имя хоста для TURN/TLS: сертификат Let's Encrypt выписан на turn.<домен>, и
# браузеры отклоняют TLS-соединение к IP (несовпадение имени). По умолчанию
# берём turn.<домен>; если он не резолвится, остаётся адрес релея.
TURN_RELAY_TLS_HOST="${TURN_RELAY_TLS_HOST:-}"
TURN_RELAY_TLS_PORT="${TURN_RELAY_TLS_PORT:-5349}" TURN_RELAY_TLS_PORT="${TURN_RELAY_TLS_PORT:-5349}"
TLS_BY_PROXY="${TLS_BY_PROXY:-false}" TLS_BY_PROXY="${TLS_BY_PROXY:-false}"
APP_BIND_ADDR="${APP_BIND_ADDR:-127.0.0.1}" APP_BIND_ADDR="${APP_BIND_ADDR:-127.0.0.1}"
@@ -149,6 +153,8 @@ install.sh — установка glchat (AGENT.md 10).
клиенты получают его в списке turn_servers клиенты получают его в списке turn_servers
--turn-relay-port <port> UDP-порт внешнего релея (по умолчанию 3478) --turn-relay-port <port> UDP-порт внешнего релея (по умолчанию 3478)
--turn-relay-tls-port <port> TLS-порт внешнего релея (по умолчанию 5349) --turn-relay-tls-port <port> TLS-порт внешнего релея (по умолчанию 5349)
--turn-relay-tls-host <host> имя хоста для TURN/TLS (по умолчанию turn.<домен>):
должно совпадать с сертификатом релея
--tls-terminated-by-proxy TLS терминирует внешний прокси: свои 80/443 не занимаем, --tls-terminated-by-proxy TLS терминирует внешний прокси: свои 80/443 не занимаем,
но ссылки в API отдаются как https/wss но ссылки в API отдаются как https/wss
--app-bind-addr <addr> адрес app (по умолчанию 127.0.0.1); для прокси по --app-bind-addr <addr> адрес app (по умолчанию 127.0.0.1); для прокси по
@@ -215,6 +221,7 @@ parse_args() {
--turn-relay-host) TURN_RELAY_HOST="${2:?}"; shift 2 ;; --turn-relay-host) TURN_RELAY_HOST="${2:?}"; shift 2 ;;
--turn-relay-port) TURN_RELAY_PORT="${2:?}"; shift 2 ;; --turn-relay-port) TURN_RELAY_PORT="${2:?}"; shift 2 ;;
--turn-relay-tls-port) TURN_RELAY_TLS_PORT="${2:?}"; shift 2 ;; --turn-relay-tls-port) TURN_RELAY_TLS_PORT="${2:?}"; shift 2 ;;
--turn-relay-tls-host) TURN_RELAY_TLS_HOST="${2:?}"; shift 2 ;;
--tls-terminated-by-proxy) TLS_BY_PROXY="true"; shift ;; --tls-terminated-by-proxy) TLS_BY_PROXY="true"; shift ;;
--app-bind-addr) APP_BIND_ADDR="${2:?}"; shift 2 ;; --app-bind-addr) APP_BIND_ADDR="${2:?}"; shift 2 ;;
--allow-port) ALLOW_PORTS="${ALLOW_PORTS} ${2:?}"; shift 2 ;; --allow-port) ALLOW_PORTS="${ALLOW_PORTS} ${2:?}"; shift 2 ;;
@@ -326,6 +333,15 @@ resolve_derived_values() {
fi fi
TURN_CERT_DIR="${TARGET_DIR}/certs" TURN_CERT_DIR="${TARGET_DIR}/certs"
TURN_RELAY_HOST_RESOLVED="${TURN_RELAY_HOST}" TURN_RELAY_HOST_RESOLVED="${TURN_RELAY_HOST}"
if [ -z "$TURN_RELAY_TLS_HOST" ] && [ -n "$TURN_RELAY_HOST" ]; then
# TURN/TLS анонсируем по имени с сертификатом, если DNS уже настроен.
if getent hosts "turn.${DOMAIN}" >/dev/null 2>&1; then
TURN_RELAY_TLS_HOST="turn.${DOMAIN}"
else
TURN_RELAY_TLS_HOST="$TURN_RELAY_HOST"
fi
fi
[ -n "$TURN_RELAY_TLS_HOST" ] || TURN_RELAY_TLS_HOST="$TURN_RELAY_HOST"
TURN_RELAY_USER="${TURN_RELAY_USER:-glchat}" TURN_RELAY_USER="${TURN_RELAY_USER:-glchat}"
tun_relay_password_from_env="" tun_relay_password_from_env=""
if [ -f "$GLCHAT_ENV_FILE" ]; then if [ -f "$GLCHAT_ENV_FILE" ]; then
@@ -378,7 +394,7 @@ resolve_derived_values() {
PROFILE_LIVEKIT_DISABLE_SIMULCAST PROFILE_LIVEKIT_USE_EXTERNAL_IP \ PROFILE_LIVEKIT_DISABLE_SIMULCAST PROFILE_LIVEKIT_USE_EXTERNAL_IP \
GLCHAT_DATA_DIR GLCHAT_CADDY_PROFILE GLCHAT_TURN_CERT_DIR \ GLCHAT_DATA_DIR GLCHAT_CADDY_PROFILE GLCHAT_TURN_CERT_DIR \
GLCHAT_APP_BIND GLCHAT_LIVEKIT_BIND TLS_ENABLED \ GLCHAT_APP_BIND GLCHAT_LIVEKIT_BIND TLS_ENABLED \
TURN_DOMAIN_RESOLVED TURN_CERT_DIR TURN_RELAY_HOST_RESOLVED \ TURN_DOMAIN_RESOLVED TURN_CERT_DIR TURN_RELAY_HOST_RESOLVED TURN_RELAY_TLS_HOST \
TURN_RELAY_USER TURN_RELAY_PASSWORD TURN_RELAY_USER TURN_RELAY_PASSWORD
} }
@@ -741,6 +757,7 @@ TURN_RELAY_HOST=@TURN_RELAY_HOST@
LIVEKIT_PUBLIC_URL=@LIVEKIT_PUBLIC_URL@ LIVEKIT_PUBLIC_URL=@LIVEKIT_PUBLIC_URL@
TURN_RELAY_PORT=@TURN_RELAY_PORT@ TURN_RELAY_PORT=@TURN_RELAY_PORT@
TURN_RELAY_TLS_PORT=@TURN_RELAY_TLS_PORT@ TURN_RELAY_TLS_PORT=@TURN_RELAY_TLS_PORT@
TURN_RELAY_TLS_HOST=@TURN_RELAY_TLS_HOST@
# --- секреты (генерируются один раз) --- # --- секреты (генерируются один раз) ---
SESSION_PEPPER=@SESSION_PEPPER@ SESSION_PEPPER=@SESSION_PEPPER@
@@ -831,6 +848,7 @@ TURN_RELAY_END=${TURN_RELAY_END}
TURN_RELAY_HOST=${TURN_RELAY_HOST_RESOLVED} TURN_RELAY_HOST=${TURN_RELAY_HOST_RESOLVED}
TURN_RELAY_PORT=${TURN_RELAY_PORT} TURN_RELAY_PORT=${TURN_RELAY_PORT}
TURN_RELAY_TLS_PORT=${TURN_RELAY_TLS_PORT} TURN_RELAY_TLS_PORT=${TURN_RELAY_TLS_PORT}
TURN_RELAY_TLS_HOST=${TURN_RELAY_TLS_HOST}
TURN_RELAY_USER=${TURN_RELAY_USER} TURN_RELAY_USER=${TURN_RELAY_USER}
TURN_RELAY_PASSWORD=${TURN_RELAY_PASSWORD} TURN_RELAY_PASSWORD=${TURN_RELAY_PASSWORD}
SESSION_PEPPER=${SESSION_PEPPER} SESSION_PEPPER=${SESSION_PEPPER}
@@ -985,8 +1003,9 @@ render_turn_relay_servers() {
printf ' turn_servers:\n' printf ' turn_servers:\n'
printf ' - host: %s\n port: %s\n protocol: udp\n username: %s\n credential: %s\n' \ printf ' - host: %s\n port: %s\n protocol: udp\n username: %s\n credential: %s\n' \
"$TURN_RELAY_HOST" "$TURN_RELAY_PORT" "$TURN_RELAY_USER" "$TURN_RELAY_PASSWORD" "$TURN_RELAY_HOST" "$TURN_RELAY_PORT" "$TURN_RELAY_USER" "$TURN_RELAY_PASSWORD"
# TURN/TLS — по имени хоста: сертификат выписан на домен, не на IP.
printf ' - host: %s\n port: %s\n protocol: tls\n username: %s\n credential: %s\n' \ printf ' - host: %s\n port: %s\n protocol: tls\n username: %s\n credential: %s\n' \
"$TURN_RELAY_HOST" "$TURN_RELAY_TLS_PORT" "$TURN_RELAY_USER" "$TURN_RELAY_PASSWORD" "$TURN_RELAY_TLS_HOST" "$TURN_RELAY_TLS_PORT" "$TURN_RELAY_USER" "$TURN_RELAY_PASSWORD"
} }
+15 -1
View File
@@ -71,7 +71,21 @@ secrets_before="$(docker exec "$CONTAINER" bash -lc '
')" ')"
printf '%s\n' "$secrets_before" printf '%s\n' "$secrets_before"
run_install "4/5 идемпотентность: повторный запуск с --reconfigure" --reconfigure # Внешний TURN-релей: в livekit.yaml должны попасть UDP-адрес и TLS-имя хоста
# (сертификат выписан на домен, поэтому TLS-эндпоинт анонсируется именем).
run_install "4/5 идемпотентность: повторный запуск с --reconfigure" --reconfigure \
--turn-relay-host 203.0.113.10 --turn-relay-tls-host turn.gl.test.local
log "4b/5 адреса TURN-релея в livekit.yaml"
relay_rendered="$(docker exec "$CONTAINER" bash -lc 'cat /opt/glchat/livekit/livekit.yaml')"
printf '%s\n' "$relay_rendered" | grep -A 3 "turn_servers" | head -12
for expected in "host: 203.0.113.10" "protocol: udp" "host: turn.gl.test.local" "protocol: tls"; do
if ! printf '%s\n' "$relay_rendered" | grep -q "$expected"; then
echo "ОШИБКА: в livekit.yaml нет строки \"$expected\"" >&2
exit 1
fi
done
echo "адреса TURN-релея отрендерены: ok"
log "5/5 секреты после повторного запуска" log "5/5 секреты после повторного запуска"
secrets_after="$(docker exec "$CONTAINER" bash -lc ' secrets_after="$(docker exec "$CONTAINER" bash -lc '