diff --git a/docker-compose.yml b/docker-compose.yml index 0e563e5..33a6ddb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -154,10 +154,6 @@ services: timeout: 10s retries: 12 start_period: 60s - networks: - default: {} - idp_static: - ipv4_address: 172.28.50.10 media-ws: pull_policy: build @@ -306,11 +302,8 @@ services: interval: 30s timeout: 5s retries: 3 - networks: - default: {} - idp_static: {} - # После recreate api-gateway nginx держит старый IP upstream → 502. Этот watcher + # После recreate api-gateway nginx держит stale DNS upstream → 502. Watcher # делает nginx -s reload когда gateway снова поднялся (~55 с на Nest bootstrap). nginx-gateway-sync: profiles: ["proxy"] @@ -330,9 +323,3 @@ volumes: redis_data: rabbitmq_data: minio_data: - -networks: - idp_static: - ipam: - config: - - subnet: 172.28.50.0/24 diff --git a/infra/nginx-gateway-sync/entrypoint.sh b/infra/nginx-gateway-sync/entrypoint.sh index 88ce300..ee2ca8d 100644 --- a/infra/nginx-gateway-sync/entrypoint.sh +++ b/infra/nginx-gateway-sync/entrypoint.sh @@ -4,6 +4,7 @@ set -eu NGINX_CONTAINER="${NGINX_CONTAINER:-lendry-id-nginx}" GATEWAY_CONTAINER="${GATEWAY_CONTAINER:-lendry-id-api-gateway}" BOOT_WAIT_SEC="${BOOT_WAIT_SEC:-55}" +GATEWAY_HEALTH_URL="http://${GATEWAY_CONTAINER}:3000/health" reload_nginx() { if docker exec "$NGINX_CONTAINER" nginx -s reload 2>/dev/null; then @@ -15,19 +16,35 @@ reload_nginx() { } verify_gateway() { - docker exec "$NGINX_CONTAINER" wget -qO- --timeout=5 "http://${GATEWAY_CONTAINER}:3000/health" 2>/dev/null \ - || echo "[nginx-gateway-sync] предупреждение: nginx пока не достучался до ${GATEWAY_CONTAINER}:3000/health" + if docker exec "$NGINX_CONTAINER" wget -qO- --timeout=5 "$GATEWAY_HEALTH_URL" 2>/dev/null \ + | grep -q '"status"'; then + echo "[nginx-gateway-sync] nginx → ${GATEWAY_CONTAINER}:3000/health OK" + else + echo "[nginx-gateway-sync] предупреждение: nginx пока не достучался до ${GATEWAY_HEALTH_URL}" + fi +} + +on_gateway_start() { + echo "[nginx-gateway-sync] ${GATEWAY_CONTAINER} перезапущен, ждём Nest (${BOOT_WAIT_SEC}s)..." + sleep "$BOOT_WAIT_SEC" + reload_nginx + sleep 2 + verify_gateway } echo "[nginx-gateway-sync] слежение за ${GATEWAY_CONTAINER} (boot wait ${BOOT_WAIT_SEC}s)..." +# Первый запуск стека: gateway уже healthy, но nginx мог стартовать раньше — синхронизируем. +( + sleep "$BOOT_WAIT_SEC" + reload_nginx + sleep 2 + verify_gateway +) & + docker events \ --filter "container=${GATEWAY_CONTAINER}" \ --filter event=start \ --format '{{.Time}}' | while read -r _; do - echo "[nginx-gateway-sync] ${GATEWAY_CONTAINER} перезапущен, ждём Nest..." - sleep "$BOOT_WAIT_SEC" - reload_nginx - sleep 2 - verify_gateway + on_gateway_start done diff --git a/install.sh b/install.sh index a4e22f8..cde8d62 100644 --- a/install.sh +++ b/install.sh @@ -10,7 +10,7 @@ set -euo pipefail -SCRIPT_VERSION="2.4.0" +SCRIPT_VERSION="2.4.1" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" @@ -33,7 +33,6 @@ INSTALL_LOCAL=0 INSTALL_INTRANET=0 OFFLINE="${OFFLINE:-0}" NGINX_MODE="${NGINX_MODE:-auto}" -IDP_API_GATEWAY_IP="${IDP_API_GATEWAY_IP:-172.28.50.10}" SKIP_DOCKER_PRUNE="${SKIP_DOCKER_PRUNE:-0}" DOCKER_PRUNE_AGGRESSIVE="${DOCKER_PRUNE_AGGRESSIVE:-0}" # Принудительный Docker Nginx (контейнер lendry-id-nginx) — включается в --fix-all @@ -52,6 +51,7 @@ CLI_CERT_FULLCHAIN="" CLI_CERT_KEY="" CLI_NGINX_HTTP_PORT="" CLI_NGINX_HTTPS_PORT="" +NGINX_MODE_CLI="" RED='\033[0;31m' GREEN='\033[0;32m' @@ -171,7 +171,7 @@ parse_args() { --cert-dir) CLI_CERT_DIR="${2:-}"; shift 2 ;; --cert-fullchain) CLI_CERT_FULLCHAIN="${2:-}"; shift 2 ;; --cert-key) CLI_CERT_KEY="${2:-}"; shift 2 ;; - --nginx-mode) NGINX_MODE="${2:-auto}"; shift 2 ;; + --nginx-mode) NGINX_MODE="${2:-auto}"; NGINX_MODE_CLI="${2:-auto}"; shift 2 ;; --nginx-http-port) CLI_NGINX_HTTP_PORT="${2:-}"; shift 2 ;; --nginx-https-port) CLI_NGINX_HTTPS_PORT="${2:-}"; shift 2 ;; --api-domain) CLI_DOMAIN_API="${2:-}"; shift 2 ;; @@ -288,7 +288,7 @@ disable_host_nginx_lendry() { fi } -# --fix-all: всегда Docker Nginx (lendry-id-nginx), не системный на 80/443 +# --fix-all / --install: всегда Docker Nginx (lendry-id-nginx), не системный на 80/443 ensure_docker_nginx_mode_for_fix_all() { load_env FORCE_DOCKER_NGINX=1 @@ -328,6 +328,18 @@ ensure_docker_nginx_mode_for_fix_all() { ok "NGINX_MODE=docker — порты ${NGINX_HTTP_PORT}/${NGINX_HTTPS_PORT} готовы для lendry-id-nginx" } +# Полная установка / fresh-install: IdP в Docker — системный Nginx на 80/443 ломает /idp-api (502). +ensure_docker_nginx_mode_for_install() { + [[ "$INSTALL_LOCAL" -eq 1 ]] && return 0 + load_env + [[ -n "$(env_get DOMAIN_API "")" ]] || return 0 + if [[ "$NGINX_MODE_CLI" == "host" ]]; then + warn "NGINX_MODE=host (--nginx-mode host) — Docker Nginx не будет использован" + return 0 + fi + ensure_docker_nginx_mode_for_fix_all +} + # Не даём контейнерному nginx занять 80/443, если их уже держит системный nginx # или явно выбран NGINX_MODE=host (типичная причина «address already in use»). ensure_docker_nginx_not_conflicting() { @@ -425,30 +437,26 @@ ensure_nginx_gateway_sync() { docker_cmd "${COMPOSE_STACK_CMD[@]}" up -d nginx-gateway-sync } -verify_api_gateway_static_ip() { - nginx_api_uses_static_ip || return 0 - load_env - local expected="${IDP_API_GATEWAY_IP:-172.28.50.10}" - local net_name actual - net_name="$(docker_network_name idp_static)" - actual="$(docker_cmd inspect lendry-id-api-gateway --format "{{index .NetworkSettings.Networks \"${net_name}\" \"IPAddress\"}}" 2>/dev/null || true)" - if [[ "$actual" == "$expected" ]]; then - ok "api-gateway: статический IP ${expected} (сеть ${net_name})" - return 0 - fi - warn "api-gateway IP=${actual:-?}, ожидался ${expected} на ${net_name}" - return 1 -} - post_deploy_proxy_verify() { + local strict="${1:-0}" compose_uses_proxy_profile || return 0 wait_for_service "nginx" 30 ensure_nginx_gateway_sync - verify_api_gateway_static_ip || true + wait_for_api_grpc_ready 90 || { + [[ "$strict" -eq 1 ]] && fail "api-gateway → sso-core gRPC не готов (/health/ready). Проверьте: docker compose logs sso-core api-gateway" + warn "api-gateway → sso-core gRPC не готов (/health/ready)" + } sync_nginx_after_api_gateway_restart + verify_nginx_reaches_api_gateway 24 5 || { + [[ "$strict" -eq 1 ]] && fail "Docker Nginx не достучался до api-gateway:3000 — см. docker compose logs nginx api-gateway" + warn "Docker Nginx → api-gateway: проверка не прошла" + } verify_nginx_reaches_media_ws || true if [[ "$(env_get USE_NGINX_SSL false)" == "true" ]]; then - check_sso_idp_api_proxy || warn "SSO /idp-api/health не прошёл — выполните ./install.sh --fix-all" + check_sso_idp_api_proxy || { + [[ "$strict" -eq 1 ]] && fail "SSO /idp-api/health → 502. Выполните: ./install.sh --fix-all" + warn "SSO /idp-api/health не прошёл — выполните ./install.sh --fix-all" + } fi } @@ -929,7 +937,7 @@ load_env() { # shellcheck disable=SC1091 set -a && source .env && set +a fi - IDP_API_GATEWAY_IP="${IDP_API_GATEWAY_IP:-172.28.50.10}" + NGINX_MODE="${NGINX_MODE:-auto}" } env_get() { @@ -1388,7 +1396,6 @@ persist_domain_env() { env_set SSL_CERT_KEY "${SSL_CERT_KEY:-$(env_get SSL_CERT_KEY "")}" env_set MINIO_PUBLIC_ENDPOINT "${MINIO_PUBLIC_ENDPOINT:-localhost:9000}" env_set MINIO_USE_SSL "${MINIO_USE_SSL:-false}" - env_set IDP_API_GATEWAY_IP "${IDP_API_GATEWAY_IP:-172.28.50.10}" if [[ "$(env_get USE_NGINX_SSL false)" == "true" ]]; then env_set FEDCM_COOKIE_SECURE "true" @@ -1614,12 +1621,7 @@ nginx_upstream() { case "$1" in api) if [[ "$NGINX_MODE" == "docker" ]]; then - # Статический IP — nginx не кеширует DNS; переживает recreate api-gateway без reload. - if [[ -n "${IDP_API_GATEWAY_IP:-}" ]]; then - echo "http://${IDP_API_GATEWAY_IP}:3000" - else - echo "http://lendry-id-api-gateway:3000" - fi + echo "http://lendry-id-api-gateway:3000" else echo "http://127.0.0.1:3000" fi @@ -2048,19 +2050,16 @@ nginx_upstream_var() { } nginx_api_uses_static_ip() { - [[ "$NGINX_MODE" == "docker" && -n "${IDP_API_GATEWAY_IP:-}" ]] + return 1 } # proxy_pass к КОРНЮ апстрима ($request_uri сохраняет путь и query). -# docker + IDP_API_GATEWAY_IP: прямой proxy_pass без DNS (надёжно после recreate). -# docker без static IP: resolver+переменная. +# docker: resolver + переменная (перерезолв DNS после recreate api-gateway). # host: статический proxy_pass на 127.0.0.1. render_proxy_root() { local key="$1" up var up="$(nginx_upstream "$key")" - if nginx_api_uses_static_ip && [[ "$key" == "api" ]]; then - printf ' proxy_pass %s$request_uri;' "$up" - elif [[ "$NGINX_MODE" == "docker" ]]; then + 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" @@ -2087,9 +2086,7 @@ render_proxy_rewrite() { render_proxy_idp_api() { local key="api" up var up="$(nginx_upstream "$key")" - if nginx_api_uses_static_ip; then - printf ' rewrite ^/idp-api/?(.*)$ /$1 break;\n proxy_pass %s;' "$up" - elif [[ "$NGINX_MODE" == "docker" ]]; then + 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" @@ -2597,14 +2594,6 @@ validate_docker_api_nginx_config() { fail "Конфиг ${conf} содержит legacy upstream{} — выполните ./install.sh --fix-all" fi - if nginx_api_uses_static_ip; then - if ! grep -q "${IDP_API_GATEWAY_IP}:3000" "$conf" 2>/dev/null; then - fail "Конфиг ${conf} без proxy_pass на ${IDP_API_GATEWAY_IP}:3000 — выполните ./install.sh --fix-all" - fi - ok "Конфиг API-домена: статический upstream ${IDP_API_GATEWAY_IP}:3000" - return 0 - fi - if ! grep -q 'resolver 127.0.0.11' "$conf" 2>/dev/null; then fail "Конфиг ${conf} без resolver 127.0.0.11 — API-домен не переживёт пересборку api-gateway" fi @@ -2623,17 +2612,6 @@ validate_docker_frontend_nginx_config() { fail "Конфиг ${conf} без location /idp-api — браузер SPA получит 502. Выполните ./install.sh --fix-all" fi - if nginx_api_uses_static_ip; then - if ! grep -q "${IDP_API_GATEWAY_IP}:3000" "$conf" 2>/dev/null; then - fail "Конфиг ${conf} без proxy_pass на ${IDP_API_GATEWAY_IP}: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 → ${IDP_API_GATEWAY_IP}:3000 (статический IP)" - return 0 - 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 @@ -2652,25 +2630,14 @@ verify_nginx_reaches_api_gateway() { local max_attempts="${1:-1}" local delay_sec="${2:-0}" local attempt=0 - local body gw_ip static_ip="${IDP_API_GATEWAY_IP:-172.28.50.10}" + local body gw_ip while [[ $attempt -lt $max_attempts ]]; do gw_ip="$(docker_cmd inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' lendry-id-api-gateway 2>/dev/null || true)" - - if nginx_api_uses_static_ip; then - body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \ - wget -qO- --timeout=5 "http://${static_ip}:3000/health" 2>/dev/null || true)" - else - body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \ - wget -qO- --timeout=5 http://lendry-id-api-gateway:3000/health 2>/dev/null || true)" - fi - + body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \ + wget -qO- --timeout=5 http://lendry-id-api-gateway:3000/health 2>/dev/null || true)" if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then - if nginx_api_uses_static_ip; then - ok "Docker Nginx → ${static_ip}:3000/health OK (статический IP api-gateway)" - else - ok "Docker Nginx → lendry-id-api-gateway:3000/health OK (IP api-gateway: ${gw_ip:-?})" - fi + ok "Docker Nginx → lendry-id-api-gateway:3000/health OK (IP api-gateway: ${gw_ip:-?})" return 0 fi attempt=$((attempt + 1)) @@ -2679,8 +2646,8 @@ verify_nginx_reaches_api_gateway() { fi done - warn "Docker Nginx не достучался до api-gateway:3000/health (IP: ${gw_ip:-?}, static: ${static_ip}, попыток: ${max_attempts})" - warn "Проверьте: docker compose ps api-gateway nginx && docker compose logs --tail=40 api-gateway nginx" + warn "Docker Nginx не достучался до lendry-id-api-gateway:3000/health (IP: ${gw_ip:-?}, попыток: ${max_attempts})" + warn "Проверьте: docker compose --profile proxy ps api-gateway nginx && docker compose logs --tail=40 api-gateway nginx" return 1 } @@ -2729,12 +2696,8 @@ check_sso_idp_api_proxy() { 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|172.28.50.10|idp_up_api' nginx/conf.d/${NGINX_PREFIX}-frontend.conf" - if nginx_api_uses_static_ip; then - echo " docker compose --profile proxy exec nginx wget -qO- http://${IDP_API_GATEWAY_IP}:3000/health" - else - echo " docker compose --profile proxy exec nginx wget -qO- http://lendry-id-api-gateway:3000/health" - fi + 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)" @@ -2949,7 +2912,7 @@ deploy_stack() { wait_for_api_grpc_ready 60 || warn "gRPC api-gateway → sso-core не готов — API может отдавать 502" wait_for_service "frontend" 60 wait_for_service "docs" 60 - post_deploy_proxy_verify + post_deploy_proxy_verify 1 } save_install_state() { @@ -3170,7 +3133,12 @@ diagnose_common_issues() { echo "" if docker_nginx_container_running; then - if [[ "$(env_get NGINX_MODE docker)" == "host" ]] \ + if system_nginx_is_active \ + && [[ "$(env_get NGINX_MODE docker)" == "docker" ]]; then + echo -e " ${RED}✘${NC} Конфликт: системный Nginx на 80/443 и lendry-id-nginx одновременно — браузер может ходить не туда (502)" + echo " Решение: sudo systemctl stop nginx && ./install.sh --fix-all" + issues=$((issues + 1)) + elif [[ "$(env_get NGINX_MODE docker)" == "host" ]] \ || port_in_use "${NGINX_HTTP_PORT:-80}" \ || port_in_use "${NGINX_HTTPS_PORT:-443}"; then echo -e " ${RED}✘${NC} Конфликт: контейнер lendry-id-nginx запущен при занятых портах 80/443 или NGINX_MODE=host" @@ -3189,11 +3157,11 @@ diagnose_common_issues() { echo -e " ${RED}✘${NC} Конфиг API-домена: legacy upstream{} — после пересборки api-gateway будет 502" echo " Решение: ./install.sh --fix-all" issues=$((issues + 1)) - elif nginx_api_uses_static_ip \ - && grep -q "${IDP_API_GATEWAY_IP}:3000" "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" 2>/dev/null; then - echo -e " ${GREEN}✔${NC} Nginx /idp-api → ${IDP_API_GATEWAY_IP}:3000 (статический IP, без stale DNS)" + 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)" + echo -e " ${GREEN}✔${NC} Nginx /idp-api: динамический DNS (resolver + idp_up_api → lendry-id-api-gateway)" else echo -e " ${YELLOW}!${NC} Конфиг SSO без /idp-api proxy — выполните ./install.sh --fix-all" issues=$((issues + 1)) @@ -3360,6 +3328,7 @@ show_status() { action_full_install() { configure_domains_interactive persist_domain_env + ensure_docker_nginx_mode_for_install ensure_split_domain_public_urls setup_nginx_proxy deploy_stack @@ -3386,6 +3355,7 @@ docker_fresh_purge() { log "Очистка устаревших конфигов Nginx..." rm -f "${LOCAL_CONF_DIR}/${NGINX_PREFIX}"*.conf 2>/dev/null || true + disable_host_nginx_lendry ok "Docker полностью очищен" docker_disk_usage_brief