fix and update
This commit is contained in:
@@ -625,7 +625,7 @@ export function buildSystemSettingPayload(setting: Pick<SystemSetting, 'key' | '
|
||||
}
|
||||
|
||||
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> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
@@ -638,14 +638,14 @@ async function fetchWithGatewayRetry(url: string, init: RequestInit): Promise<Re
|
||||
try {
|
||||
const response = await fetch(url, init);
|
||||
if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < GATEWAY_RETRY_ATTEMPTS) {
|
||||
await sleep(250 * (attempt + 1));
|
||||
await sleep(400 * (attempt + 1));
|
||||
continue;
|
||||
}
|
||||
return response;
|
||||
} catch (error) {
|
||||
lastNetworkError = error;
|
||||
if (attempt + 1 < GATEWAY_RETRY_ATTEMPTS) {
|
||||
await sleep(250 * (attempt + 1));
|
||||
await sleep(400 * (attempt + 1));
|
||||
continue;
|
||||
}
|
||||
throw mapFetchError(error);
|
||||
|
||||
120
install.sh
120
install.sh
@@ -10,7 +10,7 @@
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_VERSION="2.3.8"
|
||||
SCRIPT_VERSION="2.3.9"
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
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"
|
||||
;;
|
||||
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)
|
||||
[[ "$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)
|
||||
[[ "$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)
|
||||
[[ "$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-Host \$host;
|
||||
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 обязан перерезолвлять имена
|
||||
# бэкенд-контейнеров на каждый запрос — иначе после пересборки контейнера
|
||||
@@ -2102,7 +2105,8 @@ build_nginx_idp_api_proxy_block() {
|
||||
proxy_wk="$(render_proxy_root api)"
|
||||
proxy_fedcm="$(render_proxy_root api)"
|
||||
cat <<EOF
|
||||
location /idp-api/ws {
|
||||
# ^~ обязателен: иначе regex ниже перехватывает /idp-api/ws и шлёт WS на api-gateway вместо media-ws
|
||||
location ^~ /idp-api/ws {
|
||||
${proxy_ws}
|
||||
proxy_http_version 1.1;
|
||||
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-Proto \$scheme;
|
||||
proxy_read_timeout 86400s;
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
|
||||
location ~ ^/idp-api/(?<api_path>.*)$ {
|
||||
@@ -2420,6 +2425,26 @@ validate_docker_api_nginx_config() {
|
||||
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() {
|
||||
docker_nginx_container_running || return 0
|
||||
build_compose_stack_cmd
|
||||
@@ -2436,6 +2461,59 @@ verify_nginx_reaches_api_gateway() {
|
||||
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() {
|
||||
ensure_nginx_mode_for_config_write
|
||||
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"
|
||||
|
||||
validate_docker_api_nginx_config
|
||||
validate_docker_frontend_nginx_config
|
||||
|
||||
if [[ "$NGINX_MODE" == "host" ]]; then
|
||||
if run_root nginx -t; then
|
||||
@@ -2916,6 +2995,16 @@ diagnose_common_issues() {
|
||||
issues=$((issues + 1))
|
||||
elif docker_cmd ps --format '{{.Names}} {{.Status}}' 2>/dev/null | grep -q 'lendry-id-api-gateway.*Up'; then
|
||||
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
|
||||
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
|
||||
@@ -2930,6 +3019,7 @@ diagnose_common_issues() {
|
||||
if [[ "$(env_get USE_NGINX_SSL false)" == "true" ]] \
|
||||
&& [[ -n "$(env_get DOMAIN_API "")" && -n "$(env_get DOMAIN_FRONTEND "")" ]]; then
|
||||
check_https_api_with_origin || issues=$((issues + 1))
|
||||
check_sso_idp_api_proxy || issues=$((issues + 1))
|
||||
fi
|
||||
|
||||
local api_domain frontend_domain public_api
|
||||
@@ -2937,15 +3027,15 @@ diagnose_common_issues() {
|
||||
frontend_domain="$(env_get DOMAIN_FRONTEND "")"
|
||||
public_api="$(env_get PUBLIC_API_URL "")"
|
||||
if [[ -n "$api_domain" && -n "$frontend_domain" && "$api_domain" != "$frontend_domain" ]]; then
|
||||
if [[ "$public_api" == *"/idp-api"* ]]; then
|
||||
echo -e " ${RED}✘${NC} Split-domain: PUBLIC_API_URL=${public_api} — должно быть https://${api_domain} (не /idp-api на SSO)"
|
||||
echo " Решение: ./install.sh --fix-all"
|
||||
issues=$((issues + 1))
|
||||
elif [[ "$public_api" == *"${frontend_domain}"* && "$public_api" != *"${api_domain}"* ]]; then
|
||||
echo -e " ${RED}✘${NC} PUBLIC_API_URL указывает на домен SSO (${frontend_domain}), а не API (${api_domain})"
|
||||
issues=$((issues + 1))
|
||||
if [[ "$public_api" == *"${frontend_domain}"*"/idp-api"* ]]; then
|
||||
echo -e " ${GREEN}✔${NC} Split-domain + same-origin SPA: PUBLIC_API_URL=${public_api}, браузер → /idp-api на SSO"
|
||||
elif [[ "$public_api" == *"${api_domain}"* ]]; then
|
||||
echo -e " ${GREEN}✔${NC} Split-domain: PUBLIC_API_URL=${public_api} (OAuth issuer), SPA → /idp-api на SSO"
|
||||
elif [[ "$public_api" == *"/idp-api"* ]]; then
|
||||
echo -e " ${YELLOW}!${NC} PUBLIC_API_URL=${public_api} — для OAuth issuer лучше https://${api_domain}"
|
||||
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
|
||||
|
||||
@@ -3244,8 +3334,10 @@ action_fix_all_errors() {
|
||||
apply_intranet_selfsigned_runtime
|
||||
recreate_docker_nginx
|
||||
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
|
||||
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
|
||||
fail "Контейнер lendry-id-nginx не запущен. Проверьте: docker compose --profile proxy logs nginx"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user