fix and update

This commit is contained in:
lendry
2026-06-29 21:00:23 +03:00
parent 69e6fced48
commit 7233e8b70a
2 changed files with 109 additions and 17 deletions

View File

@@ -625,7 +625,7 @@ export function buildSystemSettingPayload(setting: Pick<SystemSetting, 'key' | '
} }
const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]); const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]);
const GATEWAY_RETRY_ATTEMPTS = 3; const GATEWAY_RETRY_ATTEMPTS = 4;
function sleep(ms: number): Promise<void> { function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
@@ -638,14 +638,14 @@ async function fetchWithGatewayRetry(url: string, init: RequestInit): Promise<Re
try { try {
const response = await fetch(url, init); const response = await fetch(url, init);
if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < GATEWAY_RETRY_ATTEMPTS) { if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < GATEWAY_RETRY_ATTEMPTS) {
await sleep(250 * (attempt + 1)); await sleep(400 * (attempt + 1));
continue; continue;
} }
return response; return response;
} catch (error) { } catch (error) {
lastNetworkError = error; lastNetworkError = error;
if (attempt + 1 < GATEWAY_RETRY_ATTEMPTS) { if (attempt + 1 < GATEWAY_RETRY_ATTEMPTS) {
await sleep(250 * (attempt + 1)); await sleep(400 * (attempt + 1));
continue; continue;
} }
throw mapFetchError(error); throw mapFetchError(error);

View File

@@ -10,7 +10,7 @@
set -euo pipefail set -euo pipefail
SCRIPT_VERSION="2.3.8" SCRIPT_VERSION="2.3.9"
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR" cd "$ROOT_DIR"
@@ -1533,13 +1533,13 @@ nginx_upstream() {
[[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-api-gateway:3000" || echo "http://127.0.0.1:3000" [[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-api-gateway:3000" || echo "http://127.0.0.1:3000"
;; ;;
frontend) frontend)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://frontend:3000" || echo "http://127.0.0.1:3002" [[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-frontend:3000" || echo "http://127.0.0.1:3002"
;; ;;
docs) docs)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://docs:3000" || echo "http://127.0.0.1:3003" [[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-docs:3000" || echo "http://127.0.0.1:3003"
;; ;;
ws) ws)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://media-ws:8085" || echo "http://127.0.0.1:8085" [[ "$NGINX_MODE" == "docker" ]] && echo "http://lendry-id-media-ws:8085" || echo "http://127.0.0.1:8085"
;; ;;
minio) minio)
[[ "$NGINX_MODE" == "docker" ]] && echo "http://minio:9000" || echo "http://127.0.0.1:9000" [[ "$NGINX_MODE" == "docker" ]] && echo "http://minio:9000" || echo "http://127.0.0.1:9000"
@@ -1871,7 +1871,10 @@ nginx_proxy_headers=" proxy_set_header Host \$host;
proxy_set_header X-Forwarded-Proto \$scheme; proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header X-Forwarded-Host \$host; proxy_set_header X-Forwarded-Host \$host;
proxy_read_timeout 300s; proxy_read_timeout 300s;
proxy_connect_timeout 75s;" proxy_connect_timeout 75s;
proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;"
# Встроенный DNS Docker. В docker-режиме nginx обязан перерезолвлять имена # Встроенный DNS Docker. В docker-режиме nginx обязан перерезолвлять имена
# бэкенд-контейнеров на каждый запрос — иначе после пересборки контейнера # бэкенд-контейнеров на каждый запрос — иначе после пересборки контейнера
@@ -2102,7 +2105,8 @@ build_nginx_idp_api_proxy_block() {
proxy_wk="$(render_proxy_root api)" proxy_wk="$(render_proxy_root api)"
proxy_fedcm="$(render_proxy_root api)" proxy_fedcm="$(render_proxy_root api)"
cat <<EOF cat <<EOF
location /idp-api/ws { # ^~ обязателен: иначе regex ниже перехватывает /idp-api/ws и шлёт WS на api-gateway вместо media-ws
location ^~ /idp-api/ws {
${proxy_ws} ${proxy_ws}
proxy_http_version 1.1; proxy_http_version 1.1;
proxy_set_header Upgrade \$http_upgrade; proxy_set_header Upgrade \$http_upgrade;
@@ -2112,6 +2116,7 @@ ${proxy_ws}
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme; proxy_set_header X-Forwarded-Proto \$scheme;
proxy_read_timeout 86400s; proxy_read_timeout 86400s;
proxy_connect_timeout 75s;
} }
location ~ ^/idp-api/(?<api_path>.*)$ { location ~ ^/idp-api/(?<api_path>.*)$ {
@@ -2420,6 +2425,26 @@ validate_docker_api_nginx_config() {
ok "Конфиг API-домена: динамический DNS (resolver + idp_up_api)" ok "Конфиг API-домена: динамический DNS (resolver + idp_up_api)"
} }
validate_docker_frontend_nginx_config() {
[[ "$NGINX_MODE" == "docker" ]] || return 0
local conf="${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf"
[[ -f "$conf" ]] || fail "Не найден ${conf} — конфиг SSO-домена не записан"
if ! grep -q 'location.*idp-api' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} без location /idp-api — браузер SPA получит 502. Выполните ./install.sh --fix-all"
fi
if ! grep -q 'resolver 127.0.0.11' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} без resolver 127.0.0.11 — /idp-api на SSO не переживёт пересборку api-gateway"
fi
if ! grep -q 'idp_up_api' "$conf" 2>/dev/null; then
fail "Конфиг ${conf} без idp_up_api — /idp-api проксируется некорректно. Выполните ./install.sh --fix-all"
fi
if ! grep -q 'location \^~ /idp-api/ws' "$conf" 2>/dev/null; then
warn "Конфиг ${conf}: location ^~ /idp-api/ws отсутствует — WebSocket может идти на api-gateway вместо media-ws"
fi
ok "Конфиг SSO-домена: /idp-api с динамическим DNS (resolver + idp_up_api)"
}
verify_nginx_reaches_api_gateway() { verify_nginx_reaches_api_gateway() {
docker_nginx_container_running || return 0 docker_nginx_container_running || return 0
build_compose_stack_cmd build_compose_stack_cmd
@@ -2436,6 +2461,59 @@ verify_nginx_reaches_api_gateway() {
return 1 return 1
} }
verify_nginx_reaches_media_ws() {
docker_nginx_container_running || return 0
build_compose_stack_cmd
local body
body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
wget -qO- --timeout=5 http://lendry-id-media-ws:8085/health 2>/dev/null || true)"
if [[ "$body" == *ok* ]] || [[ "$body" == *healthy* ]]; then
ok "Docker Nginx → lendry-id-media-ws:8085/health OK"
return 0
fi
warn "Docker Nginx не достучался до lendry-id-media-ws:8085"
warn "Проверьте: docker compose ps media-ws && docker compose logs --tail=40 media-ws"
return 1
}
# Проверка того же пути, что использует браузер после логина: https://sso.../idp-api/*
check_sso_idp_api_proxy() {
local frontend_domain code body
frontend_domain="$(env_get DOMAIN_FRONTEND "")"
[[ -n "$frontend_domain" ]] || return 0
if docker_nginx_container_running; then
body="$(docker_cmd exec lendry-id-frontend \
wget -qO- --timeout=5 http://lendry-id-api-gateway:3000/health 2>/dev/null || true)"
if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then
echo -e " ${GREEN}${NC} frontend → api-gateway:3000/health OK (fallback через Next.js rewrites)"
else
echo -e " ${RED}${NC} frontend → api-gateway:3000 недоступен (Next.js rewrites тоже дадут 502)"
echo " Проверьте: docker compose exec frontend wget -qO- http://lendry-id-api-gateway:3000/health"
return 1
fi
fi
if [[ "$(env_get USE_NGINX_SSL false)" != "true" ]]; then
code="$(curl -fsS --max-time 5 -o /dev/null -w '%{http_code}' "http://${frontend_domain}/idp-api/health" 2>/dev/null || echo 000)"
else
code="$(curl -sk --max-time 5 -o /dev/null -w '%{http_code}' "https://${frontend_domain}/idp-api/health" 2>/dev/null || echo 000)"
fi
if [[ "$code" == "200" ]]; then
echo -e " ${GREEN}${NC} SSO /idp-api/health → 200 (путь браузера после логина)"
return 0
elif [[ "$code" == "502" ]]; then
echo -e " ${RED}${NC} SSO /idp-api/health → 502 — Nginx не проксирует /idp-api на api-gateway"
echo " Проверьте: grep -E 'idp-api|idp_up_api|resolver' nginx/conf.d/${NGINX_PREFIX}-frontend.conf"
echo " docker compose --profile proxy exec nginx wget -qO- http://lendry-id-api-gateway:3000/health"
return 1
else
echo -e " ${YELLOW}!${NC} SSO /idp-api/health → HTTP ${code} (ожидалось 200)"
return 1
fi
}
write_all_nginx_configs() { write_all_nginx_configs() {
ensure_nginx_mode_for_config_write ensure_nginx_mode_for_config_write
local ssl_type="${1:-$(env_get SSL_TYPE none)}" local ssl_type="${1:-$(env_get SSL_TYPE none)}"
@@ -2473,6 +2551,7 @@ write_all_nginx_configs() {
[[ -n "${DOMAIN_MINIO_CONSOLE:-}" ]] && write_nginx_site_minio "$DOMAIN_MINIO_CONSOLE" minio-console minio-console "$ssl_type" [[ -n "${DOMAIN_MINIO_CONSOLE:-}" ]] && write_nginx_site_minio "$DOMAIN_MINIO_CONSOLE" minio-console minio-console "$ssl_type"
validate_docker_api_nginx_config validate_docker_api_nginx_config
validate_docker_frontend_nginx_config
if [[ "$NGINX_MODE" == "host" ]]; then if [[ "$NGINX_MODE" == "host" ]]; then
if run_root nginx -t; then if run_root nginx -t; then
@@ -2916,6 +2995,16 @@ diagnose_common_issues() {
issues=$((issues + 1)) issues=$((issues + 1))
elif docker_cmd ps --format '{{.Names}} {{.Status}}' 2>/dev/null | grep -q 'lendry-id-api-gateway.*Up'; then elif docker_cmd ps --format '{{.Names}} {{.Status}}' 2>/dev/null | grep -q 'lendry-id-api-gateway.*Up'; then
echo -e " ${GREEN}${NC} api-gateway: контейнер запущен" echo -e " ${GREEN}${NC} api-gateway: контейнер запущен"
local restart_count started_at
restart_count="$(docker_cmd inspect lendry-id-api-gateway --format '{{.RestartCount}}' 2>/dev/null || echo '?')"
started_at="$(docker_cmd inspect lendry-id-api-gateway --format '{{.State.StartedAt}}' 2>/dev/null || echo '?')"
if [[ "$restart_count" =~ ^[0-9]+$ ]] && [[ "$restart_count" -gt 2 ]]; then
echo -e " ${RED}${NC} api-gateway RestartCount=${restart_count} (Started=${started_at}) — частые перезапуски дают 502 в браузере"
echo " Проверьте: docker compose logs --tail=80 api-gateway sso-core"
issues=$((issues + 1))
else
echo -e " ${GREEN}${NC} api-gateway RestartCount=${restart_count} (Started=${started_at})"
fi
local grpc_code local grpc_code
grpc_code="$(curl -fsS --max-time 3 -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/health/ready 2>/dev/null || echo 000)" grpc_code="$(curl -fsS --max-time 3 -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/health/ready 2>/dev/null || echo 000)"
if [[ "$grpc_code" == "200" ]]; then if [[ "$grpc_code" == "200" ]]; then
@@ -2930,6 +3019,7 @@ diagnose_common_issues() {
if [[ "$(env_get USE_NGINX_SSL false)" == "true" ]] \ if [[ "$(env_get USE_NGINX_SSL false)" == "true" ]] \
&& [[ -n "$(env_get DOMAIN_API "")" && -n "$(env_get DOMAIN_FRONTEND "")" ]]; then && [[ -n "$(env_get DOMAIN_API "")" && -n "$(env_get DOMAIN_FRONTEND "")" ]]; then
check_https_api_with_origin || issues=$((issues + 1)) check_https_api_with_origin || issues=$((issues + 1))
check_sso_idp_api_proxy || issues=$((issues + 1))
fi fi
local api_domain frontend_domain public_api local api_domain frontend_domain public_api
@@ -2937,15 +3027,15 @@ diagnose_common_issues() {
frontend_domain="$(env_get DOMAIN_FRONTEND "")" frontend_domain="$(env_get DOMAIN_FRONTEND "")"
public_api="$(env_get PUBLIC_API_URL "")" public_api="$(env_get PUBLIC_API_URL "")"
if [[ -n "$api_domain" && -n "$frontend_domain" && "$api_domain" != "$frontend_domain" ]]; then if [[ -n "$api_domain" && -n "$frontend_domain" && "$api_domain" != "$frontend_domain" ]]; then
if [[ "$public_api" == *"/idp-api"* ]]; then if [[ "$public_api" == *"${frontend_domain}"*"/idp-api"* ]]; then
echo -e " ${RED}${NC} Split-domain: PUBLIC_API_URL=${public_api} — должно быть https://${api_domain} (не /idp-api на SSO)" echo -e " ${GREEN}${NC} Split-domain + same-origin SPA: PUBLIC_API_URL=${public_api}, браузер → /idp-api на SSO"
echo " Решение: ./install.sh --fix-all" elif [[ "$public_api" == *"${api_domain}"* ]]; then
issues=$((issues + 1)) echo -e " ${GREEN}${NC} Split-domain: PUBLIC_API_URL=${public_api} (OAuth issuer), SPA → /idp-api на SSO"
elif [[ "$public_api" == *"${frontend_domain}"* && "$public_api" != *"${api_domain}"* ]]; then elif [[ "$public_api" == *"/idp-api"* ]]; then
echo -e " ${RED}${NC} PUBLIC_API_URL указывает на домен SSO (${frontend_domain}), а не API (${api_domain})" echo -e " ${YELLOW}!${NC} PUBLIC_API_URL=${public_api} — для OAuth issuer лучше https://${api_domain}"
issues=$((issues + 1))
else else
echo -e " ${GREEN}${NC} Split-domain: PUBLIC_API_URL=${public_api}" echo -e " ${YELLOW}!${NC} Split-domain: PUBLIC_API_URL=${public_api} — проверьте настройки"
issues=$((issues + 1))
fi fi
fi fi
@@ -3244,8 +3334,10 @@ action_fix_all_errors() {
apply_intranet_selfsigned_runtime apply_intranet_selfsigned_runtime
recreate_docker_nginx recreate_docker_nginx
verify_nginx_reaches_api_gateway || compose_build_and_up api-gateway verify_nginx_reaches_api_gateway || compose_build_and_up api-gateway
verify_nginx_reaches_media_ws || compose_build_and_up media-ws
recreate_docker_nginx recreate_docker_nginx
verify_nginx_reaches_api_gateway || fail "Nginx не проксирует на api-gateway — см. docker compose logs nginx api-gateway" verify_nginx_reaches_api_gateway || fail "Nginx не проксирует на api-gateway — см. docker compose logs nginx api-gateway"
check_sso_idp_api_proxy || fail "SSO /idp-api/health недоступен — см. nginx/conf.d/${NGINX_PREFIX}-frontend.conf"
if ! docker_nginx_container_running; then if ! docker_nginx_container_running; then
fail "Контейнер lendry-id-nginx не запущен. Проверьте: docker compose --profile proxy logs nginx" fail "Контейнер lendry-id-nginx не запущен. Проверьте: docker compose --profile proxy logs nginx"
fi fi