fix(install,e2e): установка на VPS владельца — четыре дефекта шаблонов и хелперов
Установка на VPS (Debian 13, 1 vCPU/2 ГБ) с внешним прокси на том же хосте
вскрыла дефекты, которые не проявлялись на стенде:
- compose: с --no-turn порты TURN всё равно публиковались, и стек не стартовал
(«failed to bind host port 0.0.0.0:3478/udp» — порт держит чужой coturn).
Теперь строки портов TURN подставляет установщик (GLCHAT_TURN_PORTS).
- LiveKit: добавлен отдельный адрес публикации медиапортов
(--livekit-media-bind-addr / GLCHAT_LIVEKIT_MEDIA_BIND): сигналинг остаётся
внутренним для прокси, а 7881/tcp и 7882/udp доступны клиентам из интернета.
- external-proxy.conf: адреса апстримов берутся из фактической привязки
(--app-bind-addr/--livekit-bind-addr), а не жёсткого 127.0.0.1.
- при --no-files-subdomain шаблоны внешнего прокси и встроенного Caddy писали
два блока с одним адресом сайта (Caddy отвергает такой конфиг и в embedded
режиме установка ломалась целиком): блок пользовательского контента теперь
появляется только при отдельном домене (GLCHAT_FILES_BLOCK).
- e2e: postWithRetry/repeat429 читают retry_after_ms из конверта ошибки
({"error":{...}}) — без этого хелперы сдавались раньше, чем сервер разрешал
повтор, и регистрация пробных аккаунтов на свежем инстансе падала.
- e2e: voice.spec.ts ищет комнату «Голос» точным именем: пока сайдбар
дорисовывает сервер, видны комнаты предыдущего («Голос A»/«Голос B»).
This commit is contained in:
+62
-5
@@ -78,6 +78,11 @@ APP_BIND_ADDR="${APP_BIND_ADDR:-127.0.0.1}"
|
||||
ALLOW_PORTS="${ALLOW_PORTS:-}"
|
||||
ALLOW_FROM="${ALLOW_FROM:-10.77.0.0/24}"
|
||||
LIVEKIT_BIND_ADDR="${LIVEKIT_BIND_ADDR:-127.0.0.1}"
|
||||
# Адрес публикации медиапортов LiveKit (TCP/UDP): по умолчанию совпадает с
|
||||
# адресом привязки сигналинга. Разделять нужно, когда прокси стоит на том же
|
||||
# хосте: сигналинг публикуется только для прокси, а медиа обязано быть
|
||||
# доступно клиентам из интернета (AGENT.md 10.4, 10.13).
|
||||
LIVEKIT_MEDIA_BIND_ADDR="${LIVEKIT_MEDIA_BIND_ADDR:-}"
|
||||
TURN_CERT_REMOTE="${TURN_CERT_REMOTE:-}"
|
||||
TURN_CERT_REMOTE_CRT="${TURN_CERT_REMOTE_CRT:-}"
|
||||
TURN_CERT_REMOTE_KEY="${TURN_CERT_REMOTE_KEY:-}"
|
||||
@@ -189,7 +194,11 @@ install.sh — установка glchat (AGENT.md 10).
|
||||
--allow-port <порт/прото> разрешить порт только из подсети --allow-from
|
||||
(можно указывать несколько раз), например 8080/tcp
|
||||
--allow-from <cidr> источник для --allow-port (по умолчанию 10.77.0.0/24)
|
||||
--livekit-bind-addr <addr> адрес LiveKit (по умолчанию 127.0.0.1)
|
||||
--livekit-bind-addr <addr> адрес публикации LiveKit WS (по умолчанию 127.0.0.1)
|
||||
--livekit-media-bind-addr <addr> адрес публикации медиапортов LiveKit TCP/UDP
|
||||
(по умолчанию — как у WS; отдельный адрес нужен,
|
||||
когда прокси стоит на том же хосте: WS публикуется
|
||||
только для прокси, медиа — для клиентов из интернета)
|
||||
--turn-cert-remote <user@host> тянуть сертификат TURN/TLS с внешнего узла по SSH
|
||||
--turn-cert-remote-crt <путь> путь к сертификату на внешнем узле
|
||||
--turn-cert-remote-key <путь> путь к ключу на внешнем узле
|
||||
@@ -254,6 +263,7 @@ parse_args() {
|
||||
--allow-port) ALLOW_PORTS="${ALLOW_PORTS} ${2:?}"; shift 2 ;;
|
||||
--allow-from) ALLOW_FROM="${2:?}"; shift 2 ;;
|
||||
--livekit-bind-addr) LIVEKIT_BIND_ADDR="${2:?}"; shift 2 ;;
|
||||
--livekit-media-bind-addr) LIVEKIT_MEDIA_BIND_ADDR="${2:?}"; shift 2 ;;
|
||||
--turn-cert-remote) TURN_CERT_REMOTE="${2:?}"; shift 2 ;;
|
||||
--turn-cert-remote-crt) TURN_CERT_REMOTE_CRT="${2:?}"; shift 2 ;;
|
||||
--turn-cert-remote-key) TURN_CERT_REMOTE_KEY="${2:?}"; shift 2 ;;
|
||||
@@ -430,11 +440,49 @@ resolve_derived_values() {
|
||||
fi
|
||||
GLCHAT_APP_BIND="${APP_BIND_ADDR}"
|
||||
GLCHAT_LIVEKIT_BIND="${LIVEKIT_BIND_ADDR}"
|
||||
# Пустое значение — прежнее поведение: медиа публикуется там же, где сигналинг.
|
||||
[ -n "$LIVEKIT_MEDIA_BIND_ADDR" ] || LIVEKIT_MEDIA_BIND_ADDR="$LIVEKIT_BIND_ADDR"
|
||||
GLCHAT_LIVEKIT_MEDIA_BIND="${LIVEKIT_MEDIA_BIND_ADDR}"
|
||||
# Порты TURN публикуются только при включённом TURN. С --no-turn встроенного
|
||||
# TURN в стеке нет: публикация занимала бы порты хоста (3478/udp и диапазон
|
||||
# релея) и ломала запуск, если их держит чужой сервис — например, отдельный
|
||||
# coturn на том же VPS (найдено при установке на VPS 27.09.2026), а наружу
|
||||
# открывались бы порты, за которыми ничего не слушает.
|
||||
if [ "$TURN_ENABLED" = "true" ]; then
|
||||
GLCHAT_TURN_PORTS=" # TURN: UDP, TURN/TLS и диапазон релея (медиа через relay).
|
||||
- \"${LIVEKIT_MEDIA_BIND_ADDR}:${TURN_UDP_PORT}:${TURN_UDP_PORT}/udp\"
|
||||
- \"${LIVEKIT_MEDIA_BIND_ADDR}:${TURN_TLS_PORT}:${TURN_TLS_PORT}/tcp\"
|
||||
- \"${LIVEKIT_MEDIA_BIND_ADDR}:${TURN_RELAY_START}-${TURN_RELAY_END}:${TURN_RELAY_START}-${TURN_RELAY_END}/udp\""
|
||||
else
|
||||
GLCHAT_TURN_PORTS=" # TURN выключен (--no-turn): порты 3478/5349 и диапазон релея не публикуются"
|
||||
fi
|
||||
# Блок пользовательского контента (files.<домен>) в конфиг прокси попадает
|
||||
# только когда для него есть отдельный домен. Иначе в Caddyfile оказывались
|
||||
# два блока с одним и тем же адресом, и Caddy отказывался стартовать
|
||||
# («ambiguous site definition») — найдено при установке с --no-files-subdomain
|
||||
# на VPS 27.09.2026. Адрес апстрима зависит от режима: во встроенном прокси
|
||||
# это имя контейнера, во внешнем — адрес привязки приложения.
|
||||
if [ "$PROXY_MODE" = "embedded" ]; then
|
||||
files_upstream="app:8080"
|
||||
else
|
||||
files_upstream="${APP_BIND_ADDR}:${APP_PORT}"
|
||||
fi
|
||||
if [ "$FILES_DOMAIN_RESOLVED" != "$DOMAIN" ]; then
|
||||
GLCHAT_FILES_BLOCK="$(printf '# Пользовательский контент — отдельный origin (изоляция cookies/CSP).\n%s {\n\tencode zstd gzip\n\timport glchat_security_headers\n\thandle {\n\t\treverse_proxy %s\n\t}\n}' \
|
||||
"${FILES_DOMAIN_RESOLVED}" "${files_upstream}")"
|
||||
if [ "$PROXY_MODE" = "embedded" ]; then
|
||||
# Во встроенном Caddy сниппет заголовков называется иначе.
|
||||
GLCHAT_FILES_BLOCK="${GLCHAT_FILES_BLOCK/import glchat_security_headers/import security_headers}"
|
||||
fi
|
||||
else
|
||||
GLCHAT_FILES_BLOCK="# Отдельного домена для пользовательского контента нет
|
||||
# (--no-files-subdomain): файлы отдаются с основного домена по /files/*."
|
||||
fi
|
||||
export FILES_DOMAIN_RESOLVED CADDY_PROFILE LIVEKIT_PUBLISH TURN_EXTERNAL_TLS \
|
||||
livekit_extra_codecs \
|
||||
livekit_extra_codecs GLCHAT_TURN_PORTS GLCHAT_FILES_BLOCK \
|
||||
PROFILE_LIVEKIT_DISABLE_SIMULCAST PROFILE_LIVEKIT_USE_EXTERNAL_IP \
|
||||
GLCHAT_DATA_DIR GLCHAT_UPDATES_DIR GLCHAT_CADDY_PROFILE GLCHAT_TURN_CERT_DIR \
|
||||
GLCHAT_APP_BIND GLCHAT_LIVEKIT_BIND TLS_ENABLED \
|
||||
GLCHAT_APP_BIND GLCHAT_LIVEKIT_BIND GLCHAT_LIVEKIT_MEDIA_BIND TLS_ENABLED \
|
||||
TURN_DOMAIN_RESOLVED TURN_CERT_DIR TURN_RELAY_HOST_RESOLVED TURN_RELAY_TLS_HOST \
|
||||
TURN_RELAY_USER TURN_RELAY_PASSWORD
|
||||
}
|
||||
@@ -521,6 +569,7 @@ print_plan() {
|
||||
if [ "$PROXY_MODE" = "external" ]; then
|
||||
printf ' %-18s %-10s %s\n' "app (API+web)" "${APP_BIND_ADDR}:${APP_PORT}" "внутренний, наружу не публикуется"
|
||||
printf ' %-18s %-10s %s\n' "LiveKit WS" "${LIVEKIT_BIND_ADDR}:${LIVEKIT_WS_PORT}" "внутренний"
|
||||
printf ' %-18s %-10s %s\n' "LiveKit медиа" "${LIVEKIT_MEDIA_BIND_ADDR}:${LIVEKIT_TCP_PORT}/${LIVEKIT_UDP_PORT}" "TCP+UDP для клиентов"
|
||||
printf ' %-18s %-10s %s\n' "80/443" "-" "обслуживает внешний прокси (не меняем)"
|
||||
else
|
||||
printf ' %-18s %-10s %s\n' "HTTP" "${HTTP_PORT}/tcp" "ACME + редирект на HTTPS"
|
||||
@@ -851,6 +900,9 @@ GLCHAT_CADDY_PROFILE=@CADDY_PROFILE@
|
||||
GLCHAT_TURN_CERT_DIR=@GLCHAT_TURN_CERT_DIR@
|
||||
GLCHAT_APP_BIND=@GLCHAT_APP_BIND@
|
||||
GLCHAT_LIVEKIT_BIND=@GLCHAT_LIVEKIT_BIND@
|
||||
# Адрес публикации медиапортов LiveKit: обычно совпадает с адресом WS, но при
|
||||
# прокси на том же хосте отличается (WS — только для прокси, медиа — в интернет).
|
||||
GLCHAT_LIVEKIT_MEDIA_BIND=@GLCHAT_LIVEKIT_MEDIA_BIND@
|
||||
|
||||
# --- порты ---
|
||||
APP_PORT=@APP_PORT@
|
||||
@@ -974,6 +1026,7 @@ CADDY_PROFILE=${CADDY_PROFILE}
|
||||
GLCHAT_TURN_CERT_DIR=${TURN_CERT_DIR}
|
||||
GLCHAT_APP_BIND=${APP_BIND_ADDR}
|
||||
GLCHAT_LIVEKIT_BIND=${LIVEKIT_BIND_ADDR}
|
||||
GLCHAT_LIVEKIT_MEDIA_BIND=${LIVEKIT_MEDIA_BIND_ADDR}
|
||||
APP_PORT=${APP_PORT}
|
||||
LIVEKIT_WS_PORT=${LIVEKIT_WS_PORT}
|
||||
LIVEKIT_TCP_PORT=${LIVEKIT_TCP_PORT}
|
||||
@@ -1258,6 +1311,8 @@ render_all_templates() {
|
||||
"GLCHAT_CADDY_PROFILE=${CADDY_PROFILE}" \
|
||||
"GLCHAT_TURN_CERT_DIR=${TURN_CERT_DIR}" \
|
||||
"GLCHAT_APP_BIND=${APP_BIND_ADDR}" "GLCHAT_LIVEKIT_BIND=${LIVEKIT_BIND_ADDR}" \
|
||||
"GLCHAT_LIVEKIT_MEDIA_BIND=${LIVEKIT_MEDIA_BIND_ADDR}" \
|
||||
"GLCHAT_TURN_PORTS=${GLCHAT_TURN_PORTS}" \
|
||||
"TURN_UDP_PORT=${TURN_UDP_PORT}" "TURN_TLS_PORT=${TURN_TLS_PORT}" \
|
||||
"TURN_RELAY_START=${TURN_RELAY_START}" "TURN_RELAY_END=${TURN_RELAY_END}" \
|
||||
"LIVEKIT_API_KEY=${LIVEKIT_API_KEY}" "LIVEKIT_API_SECRET=${LIVEKIT_API_SECRET}" \
|
||||
@@ -1266,11 +1321,13 @@ render_all_templates() {
|
||||
if [ "$PROXY_MODE" = "external" ]; then
|
||||
render_template "${DEPLOY_DIR}/caddy/external-proxy.conf.tpl" "${TARGET_DIR}/external-proxy.conf" \
|
||||
"DOMAIN=${DOMAIN}" "FILES_DOMAIN=${FILES_DOMAIN_RESOLVED}" "ACME_EMAIL=${ACME_EMAIL}" \
|
||||
"APP_PORT=${APP_PORT}" "LIVEKIT_WS_PORT=${LIVEKIT_WS_PORT}"
|
||||
"APP_PORT=${APP_PORT}" "LIVEKIT_WS_PORT=${LIVEKIT_WS_PORT}" \
|
||||
"APP_BIND=${APP_BIND_ADDR}" "LIVEKIT_BIND=${LIVEKIT_BIND_ADDR}" \
|
||||
"GLCHAT_FILES_BLOCK=${GLCHAT_FILES_BLOCK}"
|
||||
else
|
||||
render_template "${DEPLOY_DIR}/caddy/Caddyfile.tpl" "${TARGET_DIR}/caddy/Caddyfile" \
|
||||
"DOMAIN=${DOMAIN}" "FILES_DOMAIN=${FILES_DOMAIN_RESOLVED}" "ACME_EMAIL=${ACME_EMAIL}" \
|
||||
"PROXY_MODE=${PROXY_MODE}"
|
||||
"PROXY_MODE=${PROXY_MODE}" "GLCHAT_FILES_BLOCK=${GLCHAT_FILES_BLOCK}"
|
||||
fi
|
||||
|
||||
render_template "${DEPLOY_DIR}/livekit/livekit.yaml.tpl" "${TARGET_DIR}/livekit/livekit.yaml" \
|
||||
|
||||
Reference in New Issue
Block a user