gendesign/Caddyfile
bot-backend dacc8b410c
Some checks failed
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (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
CI Trade-In / frontend-checks (pull_request) Failing after 48s
refactor(caddy): разделить site-блоки по хостам под переезд
Корневой Caddyfile содержал все восемь доменов. Деплой синхронизирует
рабочее дерево с origin/main и перечитывает конфиг, поэтому на Selectel
приехал бы файл целиком — и Caddy начал бы выпускать сертификаты для
obsidian/errors/git, чей DNS указывает на Beget. ACME падал бы на HTTP-01,
с риском упереться в rate limit Let's Encrypt.

Вторая, более тяжёлая сторона той же проблемы: сейчас ЭТОТ Caddy —
единственный публичный вход и для тех трёх доменов тоже. Он уезжает, а
Forgejo, GlitchTip и CouchDB остаются. Без разделения они отдавали бы
502, оставаясь живыми. Теперь на Beget остаётся Caddy с CADDY_SITES=infra.

  caddy/sites/apps.caddy   gendsgn.ru, www, meraocenka, merahome, meraotsenka
  caddy/sites/infra.caddy  obsidian, errors, git

Корневой файл ужался с 522 строк до 96: глобальные опции, snippet'ы,
блок :80 и два import. Site-блоки перенесены дословно, вместе с
комментариями.

Проверено caddy adapt в трёх режимах:
  без переменной  -> 8 доменов (текущее поведение Beget, не меняется)
  CADDY_SITES=apps  -> 5
  CADDY_SITES=infra -> 3

Две правки, найденные валидацией: синтаксис дефолта у Caddy {$VAR:default},
а не {$VAR:-default}; путь к users.caddy.snippet из подкаталога стал
../users.caddy.snippet — import резолвится относительно импортирующего файла.

Refs #3059, #3057, #2989
2026-08-24 00:07:14 +03:00

96 lines
5.9 KiB
Caddyfile
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Caddy config.
#
# - gendsgn.ru — main production site, auto-TLS via Let's Encrypt.
# - www.gendsgn.ru — 301 redirect to apex (canonical URL).
# - :80 — fallback for raw IP access. Closed by same auth gate (pilot phase).
#
# `handle /api/*` (NOT `handle_path`) — we keep the /api prefix because
# the FastAPI router is mounted at /api/v1/*.
#
# Auth gate: basic_auth for pilot phase (2026-05-23).
# Users managed via caddy/users.caddy.snippet (git history = audit trail).
# Public exclusions: /health (liveness probe), /preview/* (static mockups).
#
# #2558: с 2026-07 basic_auth гейтит ТОЛЬКО Site Finder (`/`, `/api/*`,
# `/analytics` и т.д.). `/trade-in/*` (+ `/sale-share` redirect) вынесены ВЫШЕ
# import'ау trade-in своя авторизация (форма входа + opaque session-cookie,
# см. #2552) поверх RBAC (`tradein-mvp/backend/app/core/rbac.py`). Site Finder
# всё ещё легаси-пилотный basic_auth (roles.yaml dual-mode остаётся живым для
# него — НЕ трогать caddy/users.caddy.snippet).
#
# IMPORTANT: route { } block is required to preserve directive order.
# Without route { }, Caddy executes directives in hard-coded default order
# (basic_auth runs before handle), making /health and /preview/* exclusions
# ineffective. With route { }, handlers are matched top-to-bottom as written.
# #2213: global `log_credentials` УБРАН. Он заставлял Caddy писать raw
# Authorization header (Base64 `user:password`, тривиально обратимый) в ОБА лог-файла
# (gendsgn.ru.log + auth_audit.log). Даже при root-only доступе и коротком retention
# это plaintext-пароли пилотов на диске — недопустимый риск против скромной выгоды.
# Trade-off: glitchtip-auth-forwarder больше не извлекает `attempted_username` из 401
# (тег деградирует в "(none)" — forwarder это уже обрабатывает gracefully, не падает).
# Событие basic_auth 401 (remote_ip / uri / method) по-прежнему уходит в GlitchTip.
# ── Site-блоки вынесены по хостам (#3059, переезд 30.08) ────────────────────
# Раньше все восемь доменов жили прямо здесь. После разделения продуктов между
# двумя хостами это стало опасно: деплой синхронизирует рабочее дерево с
# origin/main и перечитывает конфиг, поэтому на Selectel приезжал бы файл
# целиком — и Caddy начинал бы выпускать сертификаты для obsidian/errors/git,
# чей DNS указывает на Beget. ACME падал бы на HTTP-01, с риском упереться в
# rate limit Let's Encrypt.
#
# caddy/sites/apps.caddy gendsgn.ru, www, meraocenka, merahome, meraotsenka
# -> уезжают на Selectel
# caddy/sites/infra.caddy obsidian, errors, git
# -> остаются на Beget (Forgejo, GlitchTip, CouchDB)
#
# CADDY_SITES выбирает подмножество. Дефолт `*` = оба файла = ТЕКУЩЕЕ поведение
# Beget, где сейчас обслуживаются все восемь доменов — то есть до переезда
# ничего не меняется. В окне: на Selectel CADDY_SITES=apps, на Beget=infra.
import caddy/sites/{$CADDY_SITES:*}.caddy
# Plain HTTP by IP — closed by same auth gate (prevent bypass via direct IP / SSH tunnel).
# Caddy issues no TLS here (no hostname). /health remains public.
:80 {
encode zstd gzip
route {
# /health — public, без auth (GHA deploy smoke check, liveness probe).
handle /health {
reverse_proxy backend:8000
}
# Auth gate (same snippet as gendsgn.ru).
import caddy/users.caddy.snippet
handle /api/* {
reverse_proxy backend:8000 {
header_up X-Authenticated-User {http.auth.user.id}
}
}
handle {
reverse_proxy frontend:3000 {
header_up X-Authenticated-User {http.auth.user.id}
}
}
}
}
# Test deploy flow 2026-05-15T21:43:32Z
# ─────────────────────────────────────────────────────────────────────────────
# Локальные site-блоки, которых не может быть в git.
#
# Мотив: garmin.gendsgn.ru (личный remote MCP на этом же VPS). Апстрим-сервер
# аутентификации не имеет вовсе, а claude.ai custom connector ходит на голый URL
# без кастомных заголовков — единственный доступный рубеж это секрет в пути.
# Секрет в git класть нельзя, а блок, вписанный руками прямо сюда, сносится
# первым же деплоем (`git reset --hard origin/main`; 2026-08-16 так и вышло —
# контейнер остался жив, но хост пропал вместе со своим сертификатом).
#
# Поэтому: сам блок лежит на VPS как untracked `caddy/local/*.caddy` (reset
# --hard untracked не трогает), а в репозитории живёт только этот import.
# Пустой glob для Caddy не ошибка — `caddy validate` проходит, на машинах без
# локальных блоков строка просто ничего не делает.
import caddy/local/*.caddy