From 31251be877967261809650a61dd01f427ce60450 Mon Sep 17 00:00:00 2001
From: lendry
Date: Tue, 30 Jun 2026 16:42:48 +0300
Subject: [PATCH] fix and update
---
.../components/id/app-bootstrap-screen.tsx | 10 +-
apps/frontend/components/id/auth-provider.tsx | 42 +++--
apps/frontend/lib/api.ts | 2 +-
install.sh | 156 +++++++++---------
4 files changed, 113 insertions(+), 97 deletions(-)
diff --git a/apps/frontend/components/id/app-bootstrap-screen.tsx b/apps/frontend/components/id/app-bootstrap-screen.tsx
index 630ec46..182f851 100644
--- a/apps/frontend/components/id/app-bootstrap-screen.tsx
+++ b/apps/frontend/components/id/app-bootstrap-screen.tsx
@@ -18,11 +18,6 @@ export function AppBootstrapScreen({
Сервер авторизации временно недоступен. Обычно помогает повтор через несколько секунд.
- {onRetry ? (
-
- ) : null}
>
) : (
<>
@@ -33,6 +28,11 @@ export function AppBootstrapScreen({
>
)}
+ {onRetry ? (
+
+ ) : null}
);
}
diff --git a/apps/frontend/components/id/auth-provider.tsx b/apps/frontend/components/id/auth-provider.tsx
index d922bdf..42151fc 100644
--- a/apps/frontend/components/id/auth-provider.tsx
+++ b/apps/frontend/components/id/auth-provider.tsx
@@ -200,7 +200,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setIsApiReady(false);
try {
- await waitForStableGateway(3);
+ await waitForStableGateway(1);
const session = await fetchAuthSession(accessToken);
applySessionState(session, { setUser, setToken, activatePinLock, clearPinLock });
initialBootstrapDoneRef.current = true;
@@ -287,7 +287,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
for (let bootstrapAttempt = 0; bootstrapAttempt < 4; bootstrapAttempt += 1) {
try {
if (isInitialBootstrap) {
- await waitForStableGateway(3);
+ await waitForStableGateway(1);
}
if (currentToken) {
@@ -343,11 +343,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
} catch (error) {
if (isGatewayUnavailableError(error) && isInitialBootstrap && bootstrapAttempt + 1 < 4) {
invalidateApiGatewayReady();
- if (bootstrapAttempt >= 1) {
- setBootstrapError(
- 'Не удалось подключиться к серверу API (502). Подождите несколько секунд и нажмите «Повторить».'
- );
- }
+ setBootstrapError(
+ 'Не удалось подключиться к серверу API (502). Подождите несколько секунд и нажмите «Повторить».'
+ );
await new Promise((resolve) => setTimeout(resolve, 1200));
continue;
}
@@ -372,6 +370,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
setBootstrapError('Не удалось подключиться к серверу API. Подождите несколько секунд и нажмите «Повторить».');
setIsApiReady(false);
setIsSessionReady(false);
+ setIsLoading(false);
return;
}
if (message) showToast(message);
@@ -632,6 +631,16 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
Boolean(hasStoredSession || token) &&
(isLoading || !isApiReady || !isSessionReady);
+ React.useEffect(() => {
+ if (!isBootstrapping || bootstrapError) return;
+ const timer = window.setTimeout(() => {
+ setBootstrapError(
+ 'Сервер API не отвечает. Если недавно перезапускали контейнеры — подождите 10–20 секунд и нажмите «Повторить».'
+ );
+ }, 18000);
+ return () => window.clearTimeout(timer);
+ }, [bootstrapError, isBootstrapping]);
+
return (
{
- setBootstrapError(null);
- initialBootstrapDoneRef.current = false;
- invalidateApiGatewayReady();
- void refreshProfile();
- }
- : undefined
- }
+ onRetry={() => {
+ setBootstrapError(null);
+ initialBootstrapDoneRef.current = false;
+ invalidateApiGatewayReady();
+ setIsLoading(true);
+ setIsApiReady(false);
+ setIsSessionReady(false);
+ void refreshProfile();
+ }}
/>
) : (
children
diff --git a/apps/frontend/lib/api.ts b/apps/frontend/lib/api.ts
index f527c27..ba0054c 100644
--- a/apps/frontend/lib/api.ts
+++ b/apps/frontend/lib/api.ts
@@ -684,7 +684,7 @@ export function buildSystemSettingPayload(setting: Pick/dev/null || true
+}
+
+# Проба /idp-api/health изнутри nginx (HTTPS при SSL — иначе HTTP даёт 301 на :80).
+nginx_probe_idp_api_health_from_container() {
+ local frontend_domain="$1"
+ local use_ssl ssl_type
+ load_env
+ ssl_type="$(env_get SSL_TYPE none)"
+ use_ssl="$(env_get USE_NGINX_SSL false)"
+ build_compose_stack_cmd
+
+ if [[ "$use_ssl" == "true" || "$ssl_type" != "none" ]]; then
+ docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
+ wget -qO- --timeout=6 --no-check-certificate \
+ --header="Host: ${frontend_domain}" \
+ "https://127.0.0.1/idp-api/health" 2>/dev/null || true
+ return 0
+ fi
+
+ docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
+ wget -qO- --timeout=6 \
+ --header="Host: ${frontend_domain}" \
+ "http://127.0.0.1/idp-api/health" 2>/dev/null || true
+}
+
post_deploy_proxy_verify() {
local strict="${1:-0}"
compose_uses_proxy_profile || return 0
@@ -483,6 +513,7 @@ deploy_phased_stack() {
wait_for_service "docs" 60
log "Фаза 5/5: Docker Nginx (после готовности backend)..."
+ ensure_nginx_gateway_watch_script
compose_build_and_up nginx
post_deploy_proxy_verify 1
}
@@ -2062,61 +2093,41 @@ nginx_proxy_headers=" proxy_set_header Host \$host;
proxy_set_header X-Forwarded-Proto \$scheme;
proxy_set_header X-Forwarded-Host \$host;
proxy_set_header Authorization \$http_authorization;
- proxy_set_header Connection \"close\";
proxy_read_timeout 300s;
proxy_connect_timeout 75s;"
-# Встроенный DNS Docker. valid=1s — быстрее подхватывает новый IP после recreate api-gateway.
-NGINX_DOCKER_RESOLVER=" resolver 127.0.0.11 ipv6=off valid=1s;
- resolver_timeout 3s;"
-NGINX_DOCKER_SERVER_DNS=" resolver 127.0.0.11 ipv6=off valid=1s;
- resolver_timeout 3s;"
-
-# Имя nginx-переменной из ключа апстрима (только [A-Za-z0-9_]).
-nginx_upstream_var() {
- printf 'idp_up_%s' "$(printf '%s' "$1" | tr -c 'A-Za-z0-9' '_')"
-}
+# API upstream: keepalive вместо Connection: close — иначе лавина параллельных запросов SPA даёт 502.
+nginx_proxy_headers_api=" proxy_set_header Host \$host;
+ proxy_set_header X-Real-IP \$remote_addr;
+ proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto \$scheme;
+ proxy_set_header X-Forwarded-Host \$host;
+ proxy_set_header Authorization \$http_authorization;
+ proxy_http_version 1.1;
+ proxy_set_header Connection \"\";
+ proxy_read_timeout 300s;
+ proxy_connect_timeout 75s;"
# proxy_pass к КОРНЮ апстрима ($request_uri сохраняет путь и query).
-# docker: resolver + переменная (перерезолв DNS после recreate api-gateway).
-# host: статический proxy_pass на 127.0.0.1.
+# docker/host: статический proxy_pass; после recreate api-gateway — nginx -s reload (sync_nginx_after_api_gateway_restart).
render_proxy_root() {
- local key="$1" up var
+ local key="$1" up
up="$(nginx_upstream "$key")"
- if [[ "$NGINX_MODE" == "docker" ]]; then
- var="$(nginx_upstream_var "$key")"
- printf '%s\n set $%s %s;\n proxy_pass $%s$request_uri;' \
- "$NGINX_DOCKER_RESOLVER" "$var" "$up" "$var"
- else
- printf ' proxy_pass %s;' "$up"
- fi
+ printf ' proxy_pass %s$request_uri;' "$up"
}
# proxy_pass с заменой пути на фиксированный ($2, например /ws или /oauth/).
-# Используется там, где префикс локейшна нужно «срезать»/заменить.
render_proxy_rewrite() {
- local key="$1" target="$2" up var
+ local key="$1" target="$2" up
up="$(nginx_upstream "$key")"
- if [[ "$NGINX_MODE" == "docker" ]]; then
- var="$(nginx_upstream_var "$key")"
- printf '%s\n set $%s %s;\n rewrite ^.*$ %s break;\n proxy_pass $%s;' \
- "$NGINX_DOCKER_RESOLVER" "$var" "$up" "$target" "$var"
- else
- printf ' proxy_pass %s%s;' "$up" "$target"
- fi
+ printf ' rewrite ^.*$ %s break;\n proxy_pass %s;' "$target" "$up"
}
# proxy_pass для /idp-api/ — prefix ^~ выше regex; rewrite снимает префикс перед api-gateway.
render_proxy_idp_api() {
- local key="api" up var
- up="$(nginx_upstream "$key")"
- if [[ "$NGINX_MODE" == "docker" ]]; then
- var="$(nginx_upstream_var "$key")"
- printf '%s\n set $%s %s;\n rewrite ^/idp-api/?(.*)$ /$1 break;\n proxy_pass $%s;' \
- "$NGINX_DOCKER_RESOLVER" "$var" "$up" "$var"
- else
- printf ' rewrite ^/idp-api/?(.*)$ /$1 break;\n proxy_pass %s;' "$up"
- fi
+ local up
+ up="$(nginx_upstream api)"
+ printf ' rewrite ^/idp-api/?(.*)$ /$1 break;\n proxy_pass %s;' "$up"
}
nginx_write_conf() {
@@ -2231,8 +2242,7 @@ build_nginx_api_location_block() {
cat </dev/null; then
- fail "Конфиг ${conf} без resolver 127.0.0.11 — API-домен не переживёт пересборку api-gateway"
+ if ! grep -q 'lendry-id-api-gateway:3000' "$conf" 2>/dev/null; then
+ fail "Конфиг ${conf} без proxy_pass на lendry-id-api-gateway:3000 — выполните ./install.sh --fix-all"
fi
- if ! grep -q 'idp_up_api' "$conf" 2>/dev/null; then
- fail "Конфиг ${conf} без динамического proxy_pass — выполните ./install.sh --fix-all"
- fi
- ok "Конфиг API-домена: динамический DNS (resolver + idp_up_api)"
+ ok "Конфиг API-домена: proxy_pass → lendry-id-api-gateway:3000"
}
validate_docker_frontend_nginx_config() {
@@ -2636,16 +2639,13 @@ validate_docker_frontend_nginx_config() {
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"
+ if ! grep -q 'lendry-id-api-gateway:3000' "$conf" 2>/dev/null; then
+ fail "Конфиг ${conf} без proxy_pass на lendry-id-api-gateway:3000 — выполните ./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)"
+ ok "Конфиг SSO-домена: /idp-api → lendry-id-api-gateway:3000"
}
verify_nginx_reaches_api_gateway() {
@@ -2679,15 +2679,13 @@ verify_nginx_reaches_api_gateway() {
# Тот же путь, что в браузере: location ^~ /idp-api/ + rewrite + proxy_pass $idp_up_api
verify_nginx_idp_api_location() {
docker_nginx_container_running || return 0
- build_compose_stack_cmd
load_env
- local frontend_domain body code
+ local frontend_domain body code ssl_type
frontend_domain="$(env_get DOMAIN_FRONTEND "")"
[[ -n "$frontend_domain" ]] || return 0
+ ssl_type="$(env_get SSL_TYPE none)"
- body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
- wget -qO- --timeout=6 --header="Host: ${frontend_domain}" \
- "http://127.0.0.1/idp-api/health" 2>/dev/null || true)"
+ body="$(nginx_probe_idp_api_health_from_container "$frontend_domain")"
if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then
ok "Docker Nginx location /idp-api/ → api-gateway OK (Host: ${frontend_domain})"
return 0
@@ -2695,16 +2693,26 @@ verify_nginx_idp_api_location() {
reload_docker_nginx
sleep 2
- body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \
- wget -qO- --timeout=6 --header="Host: ${frontend_domain}" \
- "http://127.0.0.1/idp-api/health" 2>/dev/null || true)"
+ body="$(nginx_probe_idp_api_health_from_container "$frontend_domain")"
if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then
ok "Docker Nginx location /idp-api/ → api-gateway OK после reload"
return 0
fi
- warn "Nginx location /idp-api/ не проксирует на api-gateway (Host: ${frontend_domain})"
- warn "Проверьте: grep -E 'idp-api|idp_up_api|resolver' nginx/conf.d/${NGINX_PREFIX}-frontend.conf"
+ # Fallback: проверка с хоста (как curl из браузера / DevTools)
+ if [[ "$(env_get USE_NGINX_SSL false)" == "true" || "$ssl_type" != "none" ]]; then
+ code="$(curl -sk --max-time 6 -o /dev/null -w '%{http_code}' "https://${frontend_domain}/idp-api/health" 2>/dev/null || echo 000)"
+ else
+ code="$(curl -fsS --max-time 6 -o /dev/null -w '%{http_code}' "http://${frontend_domain}/idp-api/health" 2>/dev/null || echo 000)"
+ fi
+ if [[ "$code" == "200" ]]; then
+ ok "SSO /idp-api/health → 200 (проверка с хоста; внутри контейнера — см. SSL/301)"
+ return 0
+ fi
+
+ warn "Nginx location /idp-api/ не проксирует на api-gateway (Host: ${frontend_domain}, HTTP ${code:-?})"
+ warn "Проверьте: curl -sk https://${frontend_domain}/idp-api/health"
+ warn " и: docker compose --env-file .env --profile proxy exec nginx wget -qO- --no-check-certificate --header=\"Host: ${frontend_domain}\" https://127.0.0.1/idp-api/health"
return 1
}
@@ -3219,8 +3227,8 @@ diagnose_common_issues() {
elif grep -q '172\.28\.50\.10:3000' "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" 2>/dev/null; then
echo -e " ${RED}✘${NC} Устаревший конфиг (статический IP 172.28.50.10) — выполните ./install.sh --fix-all"
issues=$((issues + 1))
- elif grep -q 'idp_up_api' "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" 2>/dev/null; then
- echo -e " ${GREEN}✔${NC} Nginx /idp-api: динамический DNS (resolver + idp_up_api → lendry-id-api-gateway)"
+ elif grep -q 'lendry-id-api-gateway:3000' "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" 2>/dev/null; then
+ echo -e " ${GREEN}✔${NC} Nginx /idp-api: proxy_pass → lendry-id-api-gateway:3000"
else
echo -e " ${YELLOW}!${NC} Конфиг SSO без /idp-api proxy — выполните ./install.sh --fix-all"
issues=$((issues + 1))