ci(caddy): гард — import обязан быть покрыт volume-маунтом #3104

Merged
lekss361 merged 1 commit from ci/caddy-import-mount-guard into main 2026-08-26 09:27:10 +00:00
Owner

Follow-up к #3103 (прод лежал ~30 минут 2026-08-26).

Зачем

Причина инцидента: PR завёл import ../metrics-*.caddy.snippet в caddy/sites/infra.caddy, но не добавил bind-монты этих файлов в docker-compose.prod.yml. Файлы приехали на хост, внутрь контейнера — нет. Caddy не адаптировал конфиг, ушёл в restart-loop и уронил все сайты машины.

Существующий гард caddy validate эту дыру не ловит принципиально: он копирует каталог caddy/ целиком (docker cp caddy ...), а на проде смонтированы только отдельные файлы плюс два каталога. Расхождение между «что лежит в репозитории» и «что реально видит контейнер» видно только при сверке с маунтами.

Что делает

scripts/check-caddy-snippet-mounts.py:

  • разбирает bind-монты сервиса caddy: из docker-compose.prod.yml;
  • резолвит каждый import в Caddyfile / caddy/sites/*.caddy / caddy/*.caddy.snippet относительно контейнерного пути импортирующего файла;
  • падает с exit 1 и GH-аннотацией, если цель не покрыта ни одним монтом;
  • пропускает именованные Caddy-сниппеты (name) { } — это не файлы;
  • для glob/placeholder-импортов (caddy/sites/{$CADDY_SITES:*}.caddy) проверяет каталог.

stdlib-only, LF, --selftest из 10 кейсов воспроизводит ровно баг #3102. Стиль — по образцу соседнего scripts/check-workflow-ports.py; шаг в ci.yml встал рядом с ним.

Проверка

Прогон на текущем main:

selftest OK
✓ все import покрыты volume-маунтами caddy: (6 конфигов, 7 bind-маунтов)

Негативный кейс — вручную удалил монт metrics-ingest.caddy.snippet из docker-compose.prod.yml, скрипт поймал (exit=1):

::error file=caddy/sites/infra.caddy::caddy/sites/infra.caddy:87: import
'../metrics-ingest.caddy.snippet' -> файл /etc/caddy/caddy/metrics-ingest.caddy.snippet
НЕ смонтирован в сервисе caddy: ...

То есть на CI инцидент #3103 был бы пойман до мёрджа.

Follow-up к #3103 (прод лежал ~30 минут 2026-08-26). ## Зачем Причина инцидента: PR завёл `import ../metrics-*.caddy.snippet` в `caddy/sites/infra.caddy`, но не добавил bind-монты этих файлов в `docker-compose.prod.yml`. Файлы приехали на хост, внутрь контейнера — нет. Caddy не адаптировал конфиг, ушёл в restart-loop и уронил **все** сайты машины. Существующий гард `caddy validate` эту дыру не ловит **принципиально**: он копирует каталог `caddy/` целиком (`docker cp caddy ...`), а на проде смонтированы только отдельные файлы плюс два каталога. Расхождение между «что лежит в репозитории» и «что реально видит контейнер» видно только при сверке с маунтами. ## Что делает `scripts/check-caddy-snippet-mounts.py`: - разбирает bind-монты сервиса `caddy:` из `docker-compose.prod.yml`; - резолвит каждый `import` в `Caddyfile` / `caddy/sites/*.caddy` / `caddy/*.caddy.snippet` относительно **контейнерного** пути импортирующего файла; - падает с exit 1 и GH-аннотацией, если цель не покрыта ни одним монтом; - пропускает именованные Caddy-сниппеты `(name) { }` — это не файлы; - для glob/placeholder-импортов (`caddy/sites/{$CADDY_SITES:*}.caddy`) проверяет каталог. stdlib-only, LF, `--selftest` из 10 кейсов воспроизводит ровно баг #3102. Стиль — по образцу соседнего `scripts/check-workflow-ports.py`; шаг в `ci.yml` встал рядом с ним. ## Проверка Прогон на текущем main: ``` selftest OK ✓ все import покрыты volume-маунтами caddy: (6 конфигов, 7 bind-маунтов) ``` Негативный кейс — вручную удалил монт `metrics-ingest.caddy.snippet` из `docker-compose.prod.yml`, скрипт поймал (exit=1): ``` ::error file=caddy/sites/infra.caddy::caddy/sites/infra.caddy:87: import '../metrics-ingest.caddy.snippet' -> файл /etc/caddy/caddy/metrics-ingest.caddy.snippet НЕ смонтирован в сервисе caddy: ... ``` То есть на CI инцидент #3103 был бы пойман до мёрджа.
lekss361 added 1 commit 2026-08-26 09:07:47 +00:00
ci(caddy): гард — import обязан быть покрыт volume-маунтом
All checks were successful
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m10s
CI / openapi-codegen-check (pull_request) Successful in 2m18s
CI / backend-tests (pull_request) Successful in 17m41s
46b42c80db
Follow-up к #3103. Прод лёг на ~30 минут потому, что PR завёл
`import ../metrics-*.caddy.snippet` в caddy/sites/infra.caddy, но не добавил
bind-монты этих файлов в docker-compose.prod.yml.

Соседний гард `caddy validate` эту дыру не ловит принципиально: он копирует
каталог caddy/ целиком (`docker cp caddy ...`), а на проде смонтированы только
отдельные файлы плюс два каталога. Расхождение между «что лежит в репозитории»
и «что реально видит контейнер» видно только если сверять с маунтами.

check-caddy-snippet-mounts.py разбирает bind-монты сервиса caddy:, резолвит
каждый `import` в Caddyfile / caddy/sites/*.caddy / caddy/*.caddy.snippet
относительно КОНТЕЙНЕРНОГО пути импортирующего файла и падает, если цель не
покрыта ни одним маунтом. Именованные сниппеты `(name) { }` пропускаются,
для glob/placeholder-импортов (`caddy/sites/{$CADDY_SITES:*}.caddy`)
проверяется каталог. --selftest воспроизводит ровно баг #3102.
lekss361 merged commit f9fc07aea3 into main 2026-08-26 09:27:10 +00:00
lekss361 deleted branch ci/caddy-import-mount-guard 2026-08-26 09:27:11 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3104
No description provided.