food-market/deploy/nginx.conf
nns 466e546ae2
Some checks are pending
Auto-tag / Create date-tag (push) Waiting to run
CI / Backend (.NET 8) (push) Waiting to run
CI / Web (React + Vite) (push) Waiting to run
CI / POS (WPF, Windows) (push) Waiting to run
Docker Public / Build + push Public (test + latest) (push) Waiting to run
Docker Public / Deploy :test → test.food-market.kz (push) Blocked by required conditions
Docker Web / Build + push Web (push) Waiting to run
Docker Web / Deploy Web on stage (push) Blocked by required conditions
rebrand: кэш-бастинг логотипа (старое лого залипало в SW/кэше)
Причина: SW веб-админки прекэшировал /logo.svg и отдавал stale, CACHE_VERSION не
менялся при ребрендинге; публичный сайт отдавал лого/favicon без Cache-Control.

- sw.js: CACHE_VERSION fm-v2 → am-v3 (+ASSET_VERSION am3); activate чистит старые
  кэши, skipWaiting+clients.claim; прекэш бренд-ассетов версионирован; бренд в
  комментариях → ALL MARKET.
- ?v=am3 на нехэшированных бренд-ассетах: index.html, manifest, Logo.tsx,
  публичный Logo.astro + BaseLayout favicon.
- nginx web: index.html + бренд-ассеты + manifest → no-cache; /assets/* → immutable.
- nginx public: html + лого/favicon → no-cache; /_astro/* → immutable.

Проверено Playwright: старый клиент (SW fm-v2 + лого Food Market) → после выкладки
+ 1 перезагрузки кэш am-v3, fm-v2 удалён, лого ALL MARKET. Заметка на будущее про
очистку API_CACHE при logout — в прогресс-доке.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:47:41 +05:00

165 lines
8.2 KiB
Nginx Configuration File
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.

server {
listen 80 default_server;
root /usr/share/nginx/html;
index index.html;
# Sprint 13 — security-заголовки (для SPA HTML; для API те же выставляются
# уже SecurityHeadersMiddleware'ом на api-side). add_header с always
# обеспечивает применение даже на 4xx/5xx (без always — только на 2xx/3xx).
# CSP синхронен с SecurityHeadersOptions.DefaultCsp.
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' wss: ws:; img-src 'self' data: blob:; font-src 'self' data:; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
add_header X-Permitted-Cross-Domain-Policies "none" always;
# Sprint 28: HSTS. Brower honors HSTS only on HTTPS responses, поэтому
# безопасно добавлять unconditionally — если клиент пришёл по HTTP,
# header игнорируется. Без includeSubDomains и без preload — это
# pre-emptive consent: можно безопасно убрать. Когда production stack
# устаканится и admin.food-market.kz будет подан в hstspreload.org,
# увеличить max-age до 31536000 + добавить preload и includeSubDomains.
add_header Strict-Transport-Security "max-age=2592000" always;
# Long-running admin imports (MoySklad etc.) read from upstream for tens of
# minutes. Bump timeouts only on that path so normal API stays snappy.
location /api/admin/import/ {
proxy_pass http://api:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_read_timeout 60m;
proxy_send_timeout 60m;
proxy_request_buffering off;
proxy_buffering off;
}
# API reverse-proxy — upstream name "api" resolves in the compose network.
location /api/ {
proxy_pass http://api:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
location /connect/ {
proxy_pass http://api:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
# SignalR хаб для live-уведомлений (см. NotificationsHub).
# WebSocket требует upgrade-хедеры и большой read_timeout (иначе nginx
# будет рвать idle-коннекшен каждые 60 сек). access_token приходит как
# query (?access_token=...), Authorization-хедер middleware на API его
# перекладывает в нужный вид до UseAuthentication.
location /hubs/ {
proxy_pass http://api:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 86400; # 24h — webSocket долгоживущий
proxy_send_timeout 86400;
proxy_buffering off;
}
location /health {
proxy_pass http://api:8080;
}
# Prometheus метрики API. Без этого блока запрос ловится SPA fallback'ом и
# возвращает index.html (947 байт) вместо exposition format. На prod-домене
# имеет смысл закрыть IP-фильтром (allow 192.168.0.0/16; deny all;), на
# stage оставляем открытым — за gateway nginx уже есть auth/TLS-обвязка.
location = /metrics {
proxy_pass http://api:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Swagger UI + OpenAPI-doc. На контейнере api подключается только когда
# IncludeSwagger=true (env-флаг, см. Program.cs). На prod-домене флаг не
# выставляем, /swagger вернёт 404 от api — это ожидаемо.
location /swagger/ {
proxy_pass http://api:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location = /swagger {
return 301 /swagger/;
}
# Sprint 13: Hangfire Dashboard — внутренний инструмент мониторинга
# фоновых джобов. Доступ только SuperAdmin'у (см. SuperAdminHangfireFilter
# в API). Без этой location'и /hangfire ловился бы SPA-fallback'ом и
# возвращал index.html — что выглядит как «всё ок», но дашборда нет.
location /hangfire {
proxy_pass http://api:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Статика изображений товаров — api раздаёт /uploads/... из volume.
location /uploads/ {
proxy_pass http://api:8080;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
# PWA: SW и manifest должны отдаваться с правильным content-type и без
# кеша на самом ответе (внутри SW свой versioned cache). Иначе старый
# SW залипает на клиенте и не подхватывает обновления.
location = /sw.js {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
expires off;
try_files /sw.js =404;
}
location = /manifest.webmanifest {
types { } default_type application/manifest+json;
# no-cache: при ребрендинге manifest (name/иконки) должен обновляться сразу.
add_header Cache-Control "no-cache, must-revalidate";
try_files /manifest.webmanifest =404;
}
location = /offline.html {
try_files /offline.html =404;
}
# Хэшированные ассеты Vite (имя содержит контент-хэш) — иммутабельно на год.
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
# Нехэшированные бренд-ассеты (лого/иконки/favicon) — no-cache + ETag, чтобы
# смена логотипа не упиралась в кэш браузера (ETag даёт 304, если не менялось).
location ~* ^/(logo|logo-light|logo-bg|logo-fg|favicon|favicon-32|apple-touch-icon|icon-192|icon-512)\.(svg|png)$ {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri =404;
}
# index.html (SPA-оболочка) — no-cache: всегда свежая, подтягивает новые
# хэшированные бандлы и версии бренд-ассетов. SPA fallback для всех маршрутов.
location / {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri $uri/ /index.html;
}
}