fix(caddy): смонтировать deploy-window сниппет — иначе Caddy не адаптирует конфиг
All checks were successful
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 18s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped

Каталог 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
This commit is contained in:
bot-backend 2026-09-05 23:06:54 +05:00
parent d177b916e3
commit 3e481b0276
2 changed files with 25 additions and 7 deletions

View file

@ -778,6 +778,9 @@ services:
# роняя ВСЕ сайты хоста, а не только metrics.gendsgn.ru. # роняя ВСЕ сайты хоста, а не только metrics.gendsgn.ru.
- ./caddy/metrics-ingest.caddy.snippet:/etc/caddy/caddy/metrics-ingest.caddy.snippet:ro - ./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 - ./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). Каталог # Untracked локальные site-блоки (см. import в конце Caddyfile). Каталог
# держится в git через caddy/local/.gitignore — иначе docker создал бы # держится в git через caddy/local/.gitignore — иначе docker создал бы
# отсутствующий bind-source сам, root-owned пустышкой. # отсутствующий bind-source сам, root-owned пустышкой.

View file

@ -52,15 +52,22 @@ check() {
} }
check_post() { check_post() {
local desc="$1" url="$2" body="$3" expected="$4" local desc="$1" url="$2" body="$3" expected="$4" reject="${5:-}"
local code local out code head_and_body
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 15 \ # -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) -X POST -H 'Content-Type: application/json' -d "$body" "$url" 2>/dev/null)
if [ "$code" = "$expected" ]; then code=${out##*$'\n'}
echo "PASS: $desc ($url -> $code)" head_and_body=${out%$'\n'*}
else if [ "$code" != "$expected" ]; then
echo "FAIL: $desc ($url -> got '${code:-<no response>}', expected $expected)" echo "FAIL: $desc ($url -> got '${code:-<no response>}', expected $expected)"
fail=1 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 fi
} }
@ -251,8 +258,16 @@ check "meraocenka.ru payments/checkout — must 404 (Caddy не проксиру
# маршрут существует (не 404 — старый образ) И что приём платежей выключен # маршрут существует (не 404 — старый образ) И что приём платежей выключен
# (`payments_enabled=False`). 200 здесь означал бы, что флаг включили, не тронув # (`payments_enabled=False`). 200 здесь означал бы, что флаг включили, не тронув
# этот смоук. GET → 405 закрепляет, что путь принимает только POST. # этот смоук. 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 (маршрут есть, приём выключен)" \ 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)" \ check "trade-in payments/notify — 405 на GET (только POST)" \
"$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" 405 "$BASE_MAIN/trade-in/api/v1/trade-in/payments/notify" 405
check "trade-in payments/checkout — 401 anonymous (не публичный по построению)" \ check "trade-in payments/checkout — 401 anonymous (не публичный по построению)" \