Развёртывание на Debian 12/13: Docker, nginx, работа за обратным прокси

- приложение перенесено в public/, инструкции в docs/
- Dockerfile (nginx без root-прав), docker-compose.yml с ограничениями
- deploy/split-trainer.conf: CSP и заголовки безопасности, no-cache по ETag,
  /healthz, реальный IP из X-Forwarded-For только от частных сетей,
  редиректы без внутреннего хоста
- deploy/install-debian.sh: установка и обновление без Docker
- docs/DEPLOY.md: требования, установка, примеры прокси (nginx, Caddy, Traefik), подпуть

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
darko
2026-10-12 12:35:47 +03:00
co-authored by Claude Sonnet 5.5
parent 42b90a056c
commit e897a6c5eb
14 changed files with 376 additions and 11 deletions
+58
View File
@@ -0,0 +1,58 @@
# Сплит-тренер: статический сайт. Один и тот же файл годится и для контейнера, и для nginx на хосте.
# Скрипт deploy/install-debian.sh заменяет ровно три строки с пометкой "# <-": listen, server_name и root.
# Совместимо с nginx 1.22 (Debian 12) и 1.26 (Debian 13).
server {
listen 8080; # <- порт или адрес:порт
server_name _; # <- имя сайта
root /usr/share/nginx/html; # <- каталог с файлами
index index.html;
server_tokens off;
# За обратным прокси редиректы не должны подставлять внутренний хост и порт
absolute_redirect off;
port_in_redirect off;
# Настоящий адрес клиента (для логов) берём из X-Forwarded-For, но только от прокси из частных сетей и localhost
set_real_ip_from 127.0.0.1;
set_real_ip_from ::1;
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
set_real_ip_from fc00::/7;
real_ip_header X-Forwarded-For;
real_ip_recursive on;
gzip on;
gzip_vary on;
gzip_min_length 512;
gzip_types text/css application/javascript text/javascript text/plain application/json image/svg+xml;
# Проверка живости для мониторинга и healthcheck контейнера
location = /healthz {
access_log off;
default_type text/plain;
return 200 "ok\n";
}
# Все заголовки безопасности заданы в одном location: add_header не наследуется между уровнями.
# no-cache не запрещает кеш, а заставляет браузер сверять версию (ETag) при каждом открытии:
# после обновления никто не застрянет на старом app.js.
location / {
try_files $uri $uri/ =404;
add_header Cache-Control "no-cache" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'" always;
# HSTS не ставим здесь: HTTPS завершает внешний прокси, он и должен отдавать Strict-Transport-Security.
}
# Скрытые файлы (.git, .env и т. п.) не отдаём никогда
location ~ /\. {
return 404;
}
}