- приложение перенесено в 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>
11 KiB
Развёртывание на своём сервере (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. Установка
Склонируйте репозиторий на сервер:
git clone https://gittea.khad1997pav.ru/khadzhibekovpa/split-trainer.git
cd split-trainer
Вариант A: Docker (рекомендуется)
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
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:
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).
Обновление
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
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)
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 обязателен):
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 и обновите сайт командой из раздела «Обновление» |