# 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). # # 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. { servers { # ВКЛЮЧАЕТ raw Authorization/Cookie headers в access log # (по default Caddy 2.x редактирует их как "REDACTED"). # Plain password будет в gendsgn.ru.log + auth_audit.log — оба root-only на VPS. # См. forwarder.py _extract_attempted_username для использования. log_credentials } } gendsgn.ru { encode zstd gzip log { output file /var/log/caddy/gendsgn.ru.log { roll_size 50MiB roll_keep 5 roll_keep_for 720h } format json } # Отдельный лог только для auth-событий. # Forwarder (ops/glitchtip-auth-forwarder) читает именно этот файл. # Retention 7 дней (меньше чем main log) — содержит plain Base64 credentials. log auth_audit { output file /var/log/caddy/auth_audit.log { roll_size 10MiB roll_keep 3 roll_keep_for 168h } format json } route { # /health и /preview/* — public, без auth, short-circuit. handle /health { reverse_proxy backend:8000 } # Static HTML mockups для review (audit alternatives). # Public access — без auth (по запросу 2026-05-17). handle_path /preview/* { root * /srv/preview file_server browse } # Trade-In UI preview — public CI surface (#801). Рендерит mock-фикстуру # «денежного экрана» без бэкенда → axe/lighthouse гоняются без креды. # Реальных клиентских данных нет (статичная фикстура) → безопасно публично. # ДО auth-import: route матчит сверху вниз, handle short-circuit'ит. # Без strip — Next.js basePath=/trade-in ждёт префикс в URL (как @tradein). # ui-preview + его статика (_next/static — CSS/JS бандлы, без секретов). # Оба ДО auth-import, иначе ассеты страницы уходят в @tradein (под auth) → 401 → без CSS. @uipreview path /trade-in/ui-preview/* /trade-in/_next/static/* handle @uipreview { reverse_proxy tradein-frontend:3000 } # Auth gate (applies to all routes below within this route block). import caddy/users.caddy.snippet # Trade-In MVP subproject (tradein-mvp/) — gendesign-tradein docker stack, # подключен через gendesign_shared network. Routes ДО универсального handle # потому что Caddy матчит handle-блоки сверху вниз. handle /trade-in/api/* { # `handle_path /trade-in/api/*` стрипал бы целиком /trade-in/api; # FastAPI router замаунтен на /api/v1/trade-in/* — нужен strip только # префикса basePath /trade-in (Next.js basePath leak). uri strip_prefix /trade-in reverse_proxy tradein-backend:8000 { header_up X-Authenticated-User {http.auth.user.id} } } # Matcher `path /trade-in /trade-in/*` ловит И /trade-in (без слеша), # И /trade-in/ + /trade-in/anything. Без обоих случаев `handle /trade-in/*` # пропускал /trade-in без слеша → попадал в общий frontend → пустой ответ. @tradein path /trade-in /trade-in/* handle @tradein { # Next.js basePath=/trade-in — фронт сам ждёт префикса в URL reverse_proxy tradein-frontend:3000 { header_up X-Authenticated-User {http.auth.user.id} } } 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} } } } } www.gendsgn.ru { redir https://gendsgn.ru{uri} permanent } # Obsidian Self-hosted LiveSync (CouchDB backend). # Auto-TLS Let's Encrypt. CORS уже включён на стороне CouchDB через bootstrap # (см. scripts/setup-couchdb.sh). Basic-auth — на стороне CouchDB (admin user). # # DNS: A-record obsidian.gendsgn.ru → IP VPS. # Клиенты Obsidian + Self-hosted LiveSync plugin указывают на этот URL. obsidian.gendsgn.ru { encode zstd gzip reverse_proxy couchdb:5984 { # Большие документы (vault attachments / images) — увеличиваем timeout transport http { response_header_timeout 120s } } } # GlitchTip — self-hosted error tracking (Sentry-compatible). # DNS: A-record errors.gendsgn.ru → IP VPS. errors.gendsgn.ru { encode zstd gzip reverse_proxy glitchtip-web:8080 log { output file /var/log/caddy/errors.gendsgn.ru.log } } # Forgejo — self-hosted git (migration 2026-05-16). # DNS: A-record git.gendsgn.ru → IP VPS. # Forgejo container из forgejo-migration/docker-compose.yml на shared # gendesign_default network. HTTP port 3000 (default Forgejo). # Был добавлен вручную при migration, потерян при первом auto-deploy после # изменения Caddyfile (deploy.yml делает git reset --hard). См. fix issue. git.gendsgn.ru { encode zstd gzip reverse_proxy forgejo:3000 log { output file /var/log/caddy/git.gendsgn.ru.log } } # 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