From 3e481b0276edc32dd21c852fb1998ab759067f48 Mon Sep 17 00:00:00 2001 From: bot-backend Date: Sat, 5 Sep 2026 23:06:54 +0500 Subject: [PATCH] =?UTF-8?q?fix(caddy):=20=D1=81=D0=BC=D0=BE=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20deploy-window=20?= =?UTF-8?q?=D1=81=D0=BD=D0=B8=D0=BF=D0=BF=D0=B5=D1=82=20=E2=80=94=20=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D1=87=D0=B5=20Caddy=20=D0=BD=D0=B5=20=D0=B0=D0=B4?= =?UTF-8?q?=D0=B0=D0=BF=D1=82=D0=B8=D1=80=D1=83=D0=B5=D1=82=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BD=D1=84=D0=B8=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Каталог caddy/ пробрасывается в контейнер ПОФАЙЛОВО (users + metrics-*, плюс каталоги sites/ и local/). apps.caddy импортирует `../deploy-window.caddy.snippet`, которого без этой строки в контейнере нет: Caddy падает на 'File to import not found', уходит в restart-loop и роняет ВСЕ домены хоста — ровно постмортем #3102. Гейт scripts/check-caddy-snippet-mounts.py на ветке был красный (две строки apps.caddy + сам сниппет), после этой строки зелёный. Смоук периметра: 503 на payments/notify перестал что-либо доказывать — тот же код теперь отдаёт заглушка окна деплоя. check_post получил необязательный шаблон-дискриминатор: код совпал, но в ответе `Retry-After: 30` или `service_unavailable` — это Caddy, а не приложение, и это FAIL. Refs #3274 --- docker-compose.prod.yml | 3 +++ scripts/smoke-mera-perimeter.sh | 29 ++++++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 03a76d08..c915a65f 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -778,6 +778,9 @@ services: # роняя ВСЕ сайты хоста, а не только metrics.gendsgn.ru. - ./caddy/metrics-ingest.caddy.snippet:/etc/caddy/caddy/metrics-ingest.caddy.snippet:ro - ./caddy/metrics-ui.caddy.snippet:/etc/caddy/caddy/metrics-ui.caddy.snippet:ro + # То же самое для страницы окна деплоя (#3274): caddy/sites/apps.caddy + # импортирует её как `import ../deploy-window.caddy.snippet`. + - ./caddy/deploy-window.caddy.snippet:/etc/caddy/caddy/deploy-window.caddy.snippet:ro # Untracked локальные site-блоки (см. import в конце Caddyfile). Каталог # держится в git через caddy/local/.gitignore — иначе docker создал бы # отсутствующий bind-source сам, root-owned пустышкой. diff --git a/scripts/smoke-mera-perimeter.sh b/scripts/smoke-mera-perimeter.sh index a0906279..84629c9c 100644 --- a/scripts/smoke-mera-perimeter.sh +++ b/scripts/smoke-mera-perimeter.sh @@ -52,15 +52,22 @@ check() { } check_post() { - local desc="$1" url="$2" body="$3" expected="$4" - local code - code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \ + local desc="$1" url="$2" body="$3" expected="$4" reject="${5:-}" + local out code head_and_body + # -i: заголовки попадают в вывод вместе с телом — по ним отличаем ответ + # приложения от заглушки Caddy (см. $reject у вызова payments/notify). + out=$(curl -s -i -w '\n%{http_code}' --max-time 15 \ -X POST -H 'Content-Type: application/json' -d "$body" "$url" 2>/dev/null) - if [ "$code" = "$expected" ]; then - echo "PASS: $desc ($url -> $code)" - else + code=${out##*$'\n'} + head_and_body=${out%$'\n'*} + if [ "$code" != "$expected" ]; then echo "FAIL: $desc ($url -> got '${code:-}', expected $expected)" fail=1 + elif [ -n "$reject" ] && printf '%s' "$head_and_body" | grep -qEi "$reject"; then + echo "FAIL: $desc ($url -> $code, но ответ от заглушки окна деплоя, а не от приложения)" + fail=1 + else + echo "PASS: $desc ($url -> $code)" fi } @@ -251,8 +258,16 @@ check "meraocenka.ru payments/checkout — must 404 (Caddy не проксиру # маршрут существует (не 404 — старый образ) И что приём платежей выключен # (`payments_enabled=False`). 200 здесь означал бы, что флаг включили, не тронув # этот смоук. GET → 405 закрепляет, что путь принимает только POST. +# +# С #3274 голый код 503 больше не доказывает ничего: Caddy сам отдаёт 503 на +# окне деплоя (`handle_errors` -> caddy/deploy-window.caddy.snippet), и тогда +# запрос до приложения не дошёл вовсе. Отличаем по признакам заглушки — +# заголовок `Retry-After: 30` и `"error":"service_unavailable"` в теле; у +# приложения тело `{"detail":"payments are disabled"}` и никакого Retry-After. +# Совпал код, но пришла заглушка → FAIL, а не молчаливый PASS. check_post "trade-in payments/notify — 503 anonymous (маршрут есть, приём выключен)" \ - "$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" '{}' 503 + "$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" '{}' 503 \ + 'service_unavailable|^retry-after: *30' check "trade-in payments/notify — 405 на GET (только POST)" \ "$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" 405 check "trade-in payments/checkout — 401 anonymous (не публичный по построению)" \