comment tauri docker compose

This commit is contained in:
lendry
2026-06-30 12:47:14 +03:00
parent 0b6e00205a
commit 6a4bbd05b8

View File

@@ -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)"
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
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 " Проверьте: 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)"
&& [[ -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 ;;