Files
split-trainer/docs/DEPLOY.md
T
darkoandClaude Sonnet 5.5 e897a6c5eb Развёртывание на 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>
2026-10-12 12:35:47 +03:00

186 lines
11 KiB
Markdown
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.
# Развёртывание на своём сервере (Debian 12 / 13)
Сплит-тренер состоит из статических файлов (HTML, CSS, JS) и ничего не хранит на сервере. Нет бэкенда, базы данных, куки и обращений к внешним сайтам. Сервер только отдаёт файлы, поэтому требования минимальные.
## 1. Минимальные требования
### Сервер
| | Минимум | Комментарий |
|---|---|---|
| ОС | Debian 12 (bookworm) или Debian 13 (trixie), amd64 или arm64 | |
| CPU / RAM | 1 vCPU, 128 МБ свободной памяти | nginx в простое занимает 5–10 МБ; для контейнера задан лимит 64 МБ |
| Диск | порядка 500 МБ с Docker Engine (сам образ занимает несколько десятков МБ) или ~10 МБ с nginx из пакетов | сам сайт меньше 1 МБ |
| Сеть | один TCP-порт; по умолчанию `127.0.0.1:8080` | наружу выставляет обратный прокси |
| Исходящий доступ | только на время установки (apt или docker pull) | во время работы сайту интернет не нужен |
| ПО (вариант A) | Docker Engine 24+ с плагином Compose v2 | рекомендуется официальный репозиторий: docs.docker.com/engine/install/debian |
| ПО (вариант B) | nginx 1.22+ из пакетов Debian (в Debian 12 это 1.22, в 13 это 1.26) | ставится скриптом |
| Права | root или sudo на время установки | сайт работает без root-прав |
Нужно выбрать один вариант: A или B.
### Домен и HTTPS
- Для работы сайта HTTPS **не обязателен**: приложение не использует API, требующие защищённого контекста.
- Но на публичном адресе HTTPS нужен: его даёт ваш обратный прокси (раздел 4).
### Браузеры пользователей
Современные: Chrome и Edge 111+, Firefox 113+, Safari 16.2+ (примерно 2023 год и новее). Для работы нужен включённый JavaScript и `localStorage`.
## 2. Что важно знать про данные
- Прогресс и статистика хранятся **в браузере пользователя** (`localStorage`), на сервер ничего не отправляется. Резервировать на сервере нечего.
- Данные привязаны к адресу сайта (схема + домен + порт). Если сменить домен, пользователи начнут «с нуля»; смена пути (`/trainer/`) прогресс не затрагивает.
- Файл раскладки Vial (.vil) читается локально в браузере и никуда не загружается.
## 3. Установка
Склонируйте репозиторий на сервер:
```bash
git clone https://gittea.khad1997pav.ru/khadzhibekovpa/split-trainer.git
cd split-trainer
```
### Вариант A: Docker (рекомендуется)
```bash
docker compose up -d --build
curl -i http://127.0.0.1:8080/healthz # должно ответить 200 и "ok"
```
- Порт по умолчанию открыт **только на localhost**. Изменить: файл `.env` рядом с `docker-compose.yml`, например `PORT=9000` и/или `BIND=0.0.0.0` (откроет наружу: тогда закройте порт файрволом для всех, кроме прокси).
- Контейнер работает без root-прав, с файловой системой «только чтение», без лишних возможностей ядра и с лимитом памяти.
- Если прокси тоже в Docker, уберите блок `ports:` и подключите сервис к общей сети прокси: он слушает `8080` внутри контейнера.
### Вариант B: nginx из пакетов Debian
```bash
sudo ./deploy/install-debian.sh
curl -i http://127.0.0.1:8080/healthz
```
Скрипт ставит nginx, копирует файлы в `/var/www/split-trainer` и подключает отдельный конфиг `split-trainer.conf`. Чужие сайты и настройки он не трогает.
Параметры (переменные окружения):
| Переменная | По умолчанию | Назначение |
|---|---|---|
| `LISTEN` | `127.0.0.1:8080` | адрес и порт (или только порт) |
| `DOMAIN` | `_` | `server_name`, можно несколько через пробел |
| `WEBROOT` | `/var/www/split-trainer` | куда копировать файлы |
| `FORCE` | `0` | `1`: разрешить запуск не на Debian 12/13 |
Без прокси, прямо на порту 80:
```bash
sudo LISTEN=80 DOMAIN=trainer.example.com ./deploy/install-debian.sh
```
Если порт 80 уже занят «сайтом по умолчанию» nginx и вы не задали `DOMAIN`, будет открываться страница «Welcome to nginx»: укажите домен или отключите сайт по умолчанию (`sudo rm /etc/nginx/sites-enabled/default && sudo systemctl reload nginx`).
### Обновление
```bash
git pull
docker compose up -d --build # вариант A
# или
sudo ./deploy/install-debian.sh # вариант B
```
Браузеры подхватят новую версию сами: сайт отдаётся с `Cache-Control: no-cache`, то есть при каждом открытии версия сверяется по ETag.
## 4. За обратным прокси
От прокси ничего особенного не требуется: сайт статический, без WebSocket, без нестандартных заголовков, все ссылки внутри относительные. Достаточно передать запрос на `127.0.0.1:8080`.
### nginx
```nginx
server {
listen 443 ssl;
server_name trainer.example.com;
ssl_certificate /etc/letsencrypt/live/trainer.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/trainer.example.com/privkey.pem;
# HTTPS завершается здесь, поэтому HSTS должен ставить именно прокси
add_header Strict-Transport-Security "max-age=31536000" always;
location / {
proxy_pass http://127.0.0.1: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;
}
}
```
### Caddy
```
trainer.example.com {
reverse_proxy 127.0.0.1:8080
}
```
HTTPS-сертификат Caddy получит сам.
### Traefik (Docker, метки в `docker-compose.override.yml`)
```yaml
services:
split-trainer:
ports: !reset []
networks: [traefik]
labels:
- traefik.enable=true
- traefik.http.routers.trainer.rule=Host(`trainer.example.com`)
- traefik.http.routers.trainer.tls.certresolver=letsencrypt
- traefik.http.services.trainer.loadbalancer.server.port=8080
networks:
traefik:
external: true
```
Имена сети и резолвера замените на свои. Тег `!reset` требует Docker Compose 2.24 или новее.
### Под подпутём (`https://example.com/trainer/`)
Приложение можно отдавать из подкаталога: все пути в нём относительные, свой конфиг менять не нужно. Прокси должен убрать префикс (завершающий `/` в `proxy_pass` обязателен):
```nginx
location = /trainer { return 301 /trainer/; }
location /trainer/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; }
```
Для Caddy:
```
example.com {
redir /trainer /trainer/
handle_path /trainer/* { reverse_proxy 127.0.0.1:8080 }
}
```
### Настоящий IP клиента в логах
Конфиг доверяет заголовку `X-Forwarded-For` только от прокси из частных сетей и localhost (`127.0.0.0/8`, `10/8`, `172.16/12`, `192.168/16`). Если ваш прокси стоит на другом сервере с публичным адресом, добавьте его в `set_real_ip_from` в `deploy/split-trainer.conf` и пересоберите или переустановите.
## 5. Что отдаёт сервер
- `GET /healthz` отвечает `200 ok` (для мониторинга и healthcheck).
- Заголовки безопасности: строгий `Content-Security-Policy` (только свои скрипты и стили, никаких внешних адресов), `X-Content-Type-Options`, `Referrer-Policy: no-referrer`, `X-Frame-Options`, `Permissions-Policy`.
- Файлы сжимаются gzip, скрытые файлы (`.git`, `.env`) не отдаются, версия nginx в ответах скрыта.
## 6. Если что-то не работает
| Симптом | Причина и решение |
|---|---|
| 502 / 504 от прокси | Сайт не запущен или слушает другой порт: `docker compose ps`, `curl http://127.0.0.1:8080/healthz` |
| Белая страница под `/trainer/` | Нет завершающего `/` в `proxy_pass` или нет редиректа `/trainer` на `/trainer/` (см. выше) |
| После обновления видна старая версия | Обновите страницу с Ctrl+F5; если стоит CDN или кеш на прокси, исключите сайт из кеширования |
| Прогресс пропал после смены адреса | `localStorage` привязан к адресу сайта (схема + домен + порт); вернуть старый адрес |
| `nginx -t` ругается (вариант B) | Покажите вывод `sudo nginx -t`; чаще всего порт занят другим сайтом |
| Нужно изменить текст упражнений | Правьте `public/data.js` и обновите сайт командой из раздела «Обновление» |