From 6a4bbd05b873ed4abd4908c1394216fef62f7df2 Mon Sep 17 00:00:00 2001 From: lendry Date: Tue, 30 Jun 2026 12:47:14 +0300 Subject: [PATCH] comment tauri docker compose --- install.sh | 198 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 177 insertions(+), 21 deletions(-) diff --git a/install.sh b/install.sh index 2ba8a40..a4e22f8 100644 --- a/install.sh +++ b/install.sh @@ -10,7 +10,7 @@ set -euo pipefail -SCRIPT_VERSION="2.3.9" +SCRIPT_VERSION="2.4.0" ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$ROOT_DIR" @@ -71,6 +71,7 @@ ${BOLD}Lendry ID v${SCRIPT_VERSION}${NC} — установка и управл ./install.sh Интерактивное меню ./install.sh --install Полная установка + ./install.sh --fresh-install Полная очистка Docker + установка с нуля ./install.sh --configure-domains Изменить домены / Nginx / SSL ./install.sh --renew-ssl Обновить сертификаты (Let's Encrypt / custom / selfsigned) ./install.sh --restart Перезапуск контейнеров @@ -108,6 +109,7 @@ ${BOLD}Прочие опции:${NC} ${BOLD}Переменные окружения:${NC} FORCE_ENV=1 Новые JWT-секреты (пароли БД/MinIO/RabbitMQ сохраняются если volume уже есть) OFFLINE=1 То же, что --offline + FORCE_ENV=1 При --fresh-install: удалить .env и секреты, создать заново SKIP_DOCKER_PRUNE=1 Не очищать образы/кеш перед пересборкой DOCKER_PRUNE_AGGRESSIVE=1 Глубже чистить неиспользуемые образы проекта COMPOSE_PARALLEL_LIMIT=4 Параллельная сборка сервисов (по умолчанию 4) @@ -135,6 +137,14 @@ ${BOLD}Примеры:${NC} --docs-domain docs.example.ru \\ --ssl letsencrypt --email admin@example.ru ./install.sh --configure-domains + +${BOLD}Полная переустановка (очистка Docker + volumes + установка):${NC} + ./install.sh --fresh-install --yes \\ + --intranet \\ + --api-domain api.idpmvk.lpr \\ + --frontend-domain sso.idpmvk.lpr \\ + --docs-domain docs.idpmvk.lpr \\ + --ssl selfsigned EOF } @@ -143,6 +153,7 @@ parse_args() { case "$1" in -y | --yes) NON_INTERACTIVE=1; shift ;; --install) ACTION="install"; shift ;; + --fresh-install) ACTION="fresh-install"; shift ;; --configure-domains) ACTION="configure-domains"; shift ;; --renew-ssl) ACTION="renew-ssl"; shift ;; --restart) ACTION="restart"; shift ;; @@ -403,10 +414,44 @@ recreate_docker_nginx() { compose_uses_proxy_profile || return 0 build_compose_stack_cmd log "Пересоздание контейнера lendry-id-nginx (подхват конфигов из ./nginx/conf.d)..." - docker_cmd "${COMPOSE_STACK_CMD[@]}" up -d --force-recreate nginx + docker_cmd "${COMPOSE_STACK_CMD[@]}" up -d --force-recreate nginx nginx-gateway-sync wait_for_service "nginx" 60 } +ensure_nginx_gateway_sync() { + compose_uses_proxy_profile || return 0 + build_compose_stack_cmd + log "Запуск nginx-gateway-sync (авто-reload Nginx после пересборки api-gateway)..." + 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() { + compose_uses_proxy_profile || return 0 + wait_for_service "nginx" 30 + ensure_nginx_gateway_sync + verify_api_gateway_static_ip || true + sync_nginx_after_api_gateway_restart + 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" + fi +} + prepare_compose_stack() { resolve_nginx_mode_and_ports write_compose_override @@ -884,6 +929,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}" } env_get() { @@ -1342,6 +1388,7 @@ 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" @@ -2547,8 +2594,17 @@ validate_docker_api_nginx_config() { [[ -f "$conf" ]] || fail "Не найден ${conf} — конфиг API-домена не записан" if grep -q "upstream ${NGINX_PREFIX}_api" "$conf" 2>/dev/null; then - fail "Конфиг ${conf} содержит static upstream — обновите install.sh и выполните ./install.sh --fix-all" + 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 @@ -2566,6 +2622,18 @@ validate_docker_frontend_nginx_config() { if ! grep -q 'location \^~ /idp-api/' "$conf" 2>/dev/null && ! grep -q 'location.*idp-api' "$conf" 2>/dev/null; then 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 @@ -2584,14 +2652,25 @@ verify_nginx_reaches_api_gateway() { local max_attempts="${1:-1}" local delay_sec="${2:-0}" local attempt=0 - local body gw_ip + local body gw_ip static_ip="${IDP_API_GATEWAY_IP:-172.28.50.10}" 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)" - 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 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 + if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then - ok "Docker Nginx → lendry-id-api-gateway:3000/health OK (IP api-gateway: ${gw_ip:-?})" + 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 return 0 fi attempt=$((attempt + 1)) @@ -2600,8 +2679,8 @@ verify_nginx_reaches_api_gateway() { fi done - warn "Docker Nginx не достучался до lendry-id-api-gateway:3000 (IP: ${gw_ip:-?}, попыток: ${max_attempts})" - warn "Проверьте: docker compose ps api-gateway && docker compose logs --tail=40 api-gateway" + 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" return 1 } @@ -2622,6 +2701,7 @@ verify_nginx_reaches_media_ws() { # Проверка того же пути, что использует браузер после логина: https://sso.../idp-api/* check_sso_idp_api_proxy() { + load_env local frontend_domain code body frontend_domain="$(env_get DOMAIN_FRONTEND "")" [[ -n "$frontend_domain" ]] || return 0 @@ -2649,8 +2729,12 @@ 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|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" + 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 return 1 else echo -e " ${YELLOW}!${NC} SSO /idp-api/health → HTTP ${code} (ожидалось 200)" @@ -2862,12 +2946,10 @@ deploy_stack() { wait_for_service "sso-core" 120 wait_for_service "api-gateway" 90 - sync_nginx_after_api_gateway_restart + wait_for_api_grpc_ready 60 || warn "gRPC api-gateway → sso-core не готов — API может отдавать 502" wait_for_service "frontend" 60 wait_for_service "docs" 60 - if compose_uses_proxy_profile; then - wait_for_service "nginx" 30 - fi + post_deploy_proxy_verify } save_install_state() { @@ -2944,6 +3026,7 @@ print_summary() { echo -e " ${BOLD}Смена доменов:${NC} ./install.sh --configure-domains" echo -e " ${BOLD}Логи:${NC} docker compose logs -f api-gateway nginx" echo -e " ${BOLD}Очистка Docker:${NC} ./install.sh --docker-prune" + echo -e " ${BOLD}С нуля:${NC} ./install.sh --fresh-install --yes" echo "" echo " Первый зарегистрированный пользователь станет супер-администратором." echo " Секреты в .env — не коммитьте в git." @@ -3100,11 +3183,21 @@ diagnose_common_issues() { echo -e " ${YELLOW}!${NC} Docker Nginx не запущен (ожидался в режиме docker)" issues=$((issues + 1)) elif [[ "$(env_get NGINX_MODE docker)" == "docker" ]] \ - && [[ -f "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-api.conf" ]] \ - && grep -q "upstream ${NGINX_PREFIX}_api" "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-api.conf" 2>/dev/null; then - echo -e " ${RED}✘${NC} Конфиг API-домена использует статический upstream — после пересборки api-gateway будет 502" - echo " Решение: ./install.sh --fix-all (перегенерирует конфиг с динамическим DNS)" - issues=$((issues + 1)) + && [[ -f "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-frontend.conf" ]]; then + load_env + if grep -q "upstream ${NGINX_PREFIX}_api" "${LOCAL_CONF_DIR}/${NGINX_PREFIX}-api.conf" 2>/dev/null; then + 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 '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)" + else + echo -e " ${YELLOW}!${NC} Конфиг SSO без /idp-api proxy — выполните ./install.sh --fix-all" + issues=$((issues + 1)) + fi elif [[ "$(env_get NGINX_MODE docker)" == "host" ]]; then if system_nginx_is_active; then echo -e " ${GREEN}✔${NC} Системный Nginx: активен" @@ -3267,12 +3360,71 @@ show_status() { action_full_install() { configure_domains_interactive persist_domain_env + ensure_split_domain_public_urls setup_nginx_proxy deploy_stack save_install_state + diagnose_common_issues || true print_summary } +docker_fresh_purge() { + prepare_compose_stack + build_compose_stack_cmd + + log "Остановка стека и удаление volumes (PostgreSQL, Redis, MinIO, RabbitMQ)..." + docker_cmd "${COMPOSE_STACK_CMD[@]}" down -v --remove-orphans 2>/dev/null || true + docker_cmd compose --env-file .env down -v --remove-orphans 2>/dev/null || true + docker_cmd compose --env-file .env --profile proxy down -v --remove-orphans 2>/dev/null || true + + log "Удаление образов, кеша сборки и неиспользуемых volumes..." + docker_cmd container prune -f 2>/dev/null || true + docker_cmd image prune -af 2>/dev/null || true + docker_cmd volume prune -f 2>/dev/null || true + docker_cmd builder prune -af 2>/dev/null || true + docker_prune_stale_project_images "$(compose_project_name)" 1 + + log "Очистка устаревших конфигов Nginx..." + rm -f "${LOCAL_CONF_DIR}/${NGINX_PREFIX}"*.conf 2>/dev/null || true + + ok "Docker полностью очищен" + docker_disk_usage_brief +} + +action_fresh_install() { + echo "" + echo -e "${BOLD}Полная переустановка Lendry ID${NC}" + echo " Будут удалены: все контейнеры проекта, volumes (БД, Redis, MinIO), образы и build cache." + echo " Файл .env сохранится (домены и секреты). Для нового .env: FORCE_ENV=1 ./install.sh --fresh-install" + echo "" + + if [[ "$NON_INTERACTIVE" -ne 1 ]]; then + local confirm + read -r -p "Продолжить полную очистку и установку? [y/N]: " confirm + case "${confirm,,}" in + y | yes | д | да) ;; + *) fail "Отменено" ;; + esac + fi + + if [[ -f .env ]]; then + load_env + docker_fresh_purge + else + warn ".env не найден — выполняется только очистка Docker..." + docker_fresh_purge 2>/dev/null || docker_cmd system prune -af --volumes 2>/dev/null || true + fi + + if [[ "${FORCE_ENV:-0}" -eq 1 ]]; then + warn "FORCE_ENV=1: удаление .env и файлов состояния..." + rm -f .env "$STATE_FILE" "$INFRA_SECRETS_FILE" + fi + + SKIP_DOCKER_PRUNE=1 + DOCKER_PRUNE_AGGRESSIVE=0 + action_full_install +} + action_configure_domains() { if [[ ! -f .env ]]; then warn ".env не найден — выполняем полную установку" @@ -3486,6 +3638,7 @@ action_fix_all_errors() { esac apply_intranet_selfsigned_runtime recreate_docker_nginx + ensure_nginx_gateway_sync sync_nginx_after_api_gateway_restart verify_nginx_reaches_api_gateway 24 5 || compose_build_and_up api-gateway sync_nginx_after_api_gateway_restart @@ -3540,6 +3693,7 @@ show_menu() { [[ -f .env ]] && load_env && echo -e " Режим: ${INSTALL_MODE:-local} | API: ${DOMAIN_API:-localhost:3000}" echo "" echo " 1) Полная установка / переустановка стека" + echo " 10) Полная очистка Docker + установка с нуля (--fresh-install)" echo " 2) Настроить / изменить домены и Nginx + SSL" echo " 3) SSL-сертификаты (обновить / включить custom / Let's Encrypt)" echo " 4) Перезапуск / пересборка контейнеров (+ очистка Docker)" @@ -3552,10 +3706,11 @@ show_menu() { echo "" local choice - read -r -p "Выберите пункт [0-9]: " choice + read -r -p "Выберите пункт [0-10]: " choice case "$choice" in 1) NON_INTERACTIVE=0; action_full_install ;; + 10) NON_INTERACTIVE=0; action_fresh_install ;; 2) NON_INTERACTIVE=0; action_configure_domains ;; 3) action_renew_ssl ;; 4) action_restart ;; @@ -3580,6 +3735,7 @@ main() { case "${ACTION:-}" in install) action_full_install ;; + fresh-install) action_fresh_install ;; configure-domains) action_configure_domains ;; renew-ssl) action_renew_ssl ;; restart) action_restart ;;