Files
ForbiddenStarsApp/README.md
T
NotBigGhostandClaude Opus 5 63fd90e017 Документация: сверка README и deploy/*.md с кодом
- README: test без порта на хосте (только через домен, Secure-cookie), прод из реестра
  (build-push + docker compose up -d), лимиты перебора и регистраций, dev_admin.py в списке
  dev-кода, структура репозитория, раздел о бэкапах, отличия test от prod; отмечены
  известные проблемы (#69, #71, #72, #73).
- deploy/README.md: три сервиса (app + tunnel + backup), источники ключа туннеля для Pi,
  test и dev-туннеля, слот 9000 у временного прода.
- deploy/pi/README.md: контейнер backup, fail-fast по секретам, ADMIN_PASSWORD только при
  первом создании админа (#73), порядок обновления.
- deploy/vps/README.md: туннель-контейнер вместо autossh, сниппет (edge), единые имена
  файлов в примере сборки сертификатов, дописывать authorized_keys через >>.
- deploy/backup/README.md: первый бэкап на новом Pi, выбор снимка с данными при
  восстановлении (#74), метка keep, --no-pre-restore, служебные команды, причины unhealthy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
2026-09-14 20:06:54 +03:00

23 KiB
Raw Blame History

Forbidden Stars — учёт партий

Мобильное веб-приложение для учёта партий настольной игры Forbidden Stars: профили игроков (вход по логину и паролю или через Telegram), группы с приглашениями, создание партий с рандомом фракций и фильтром по дополнениям группы, фото партий, уведомления, статистика и общий топ, админ-панель.

  • Бэкенд / ядро + API: Python · FastAPI · SQLModel · SQLite
  • Фронтенд: React · Vite · TypeScript (SPA; с ядром общается по REST API, живые обновления приходят SSE-потоком /api/events)
  • Хостинг: Raspberry Pi 4 (ARM64) в Docker; наружу — через VPS-привратник (см. «Домен и публикация»)

Структура

backend/                 FastAPI: ядро, REST API, БД, миграции Alembic, seed, тесты
frontend/                React + Vite SPA
deploy/                  публикация и эксплуатация: vps/ (Caddy), pi/ (прод), tunnel/ и backup/ (образы)
scripts/                 build-push.* (сборка и пуш образов), fs-backup.* (бэкапы с ПК), export-*.sh
Dockerfile               multi-stage сборка (фронт собирается node, отдаётся FastAPI)
docker-compose.yml       прод на Pi: app + tunnel + backup
docker-compose.test.yml  тест-клон прода на ПК: app + tunnel + backup
docker-compose.temp.yml  временный прод на ПК вместо Pi: app + tunnel
run.ps1 / run.sh         единый лаунчер dev/test
.env.example             шаблон единого .env

Локальная разработка

Единый лаунчер (run.ps1 / run.sh)

После разовой настройки (ниже) dev и test запускаются одной командой — что именно, решает APP_ENV в корневом .env:

.\run.ps1        # Windows   (Linux / macOS / Git Bash:  ./run.sh)
APP_ENV в .env что делает лаунчер
development сначала alembic upgrade head, затем uvicorn --reload (бэк) + vite (фронт) нативно: run.ps1 — в отдельных окнах, run.sh — в текущем терминале (Ctrl+C останавливает оба). При LOCAL_PUBLIC=vps дополнительно поднимает SSH-туннель на forbidden-stars.ru
test docker compose -f docker-compose.test.yml up --build -d — прод-клон (app + tunnel + backup); портов на хост нет, открывается на https://forbidden-stars.ru
production не запускает — прод деплоится отдельно (см. «Production» и «Git и деплой»)

Разовая настройка перед первым запуском — поднять venv бэка и зависимости фронта (после неё повседневный цикл — просто .\run.ps1). Vite проксирует /api на бэкенд.

1) Бэкенд

Windows PowerShell (команды по одной — в PowerShell 5.1 нет &&):

cd backend
python -m venv .venv
.\.venv\Scripts\Activate.ps1     # если ругается на политику: Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
pip install -e ".[dev]"          # .[dev] — один аргумент (пакет + dev-зависимости)
Copy-Item ..\.env.example ..\.env  # ЕДИНЫЙ .env лежит в КОРНЕ репозитория
alembic upgrade head             # применит миграции и сидинг справочников
python -m app.bootstrap          # справочники + создаст/синхронизирует администратора из .env (опционально)
uvicorn app.main:app --reload --timeout-graceful-shutdown 2   # http://localhost:8000  (Swagger: /api/docs)

Windows cmd.exe (здесь && поддерживается):

cd backend
python -m venv .venv
.venv\Scripts\activate.bat
pip install -e ".[dev]"
copy ..\.env.example ..\.env
alembic upgrade head
python -m app.bootstrap
uvicorn app.main:app --reload --timeout-graceful-shutdown 2

Linux / macOS / Git Bash:

cd backend
python -m venv .venv && . .venv/Scripts/activate   # на *nix: . .venv/bin/activate
pip install -e ".[dev]"
cp ../.env.example ../.env
alembic upgrade head
python -m app.bootstrap
uvicorn app.main:app --reload --timeout-graceful-shutdown 2

В dev тот же bootstrap (справочники + админ из .env) выполняется сам при старте uvicorn, поэтому python -m app.bootstrap вручную обычно не нужен. Dev-БД, загрузки и ачивки лежат в backend/data/dev/ (пути в .env относительные — запускайте из backend/).

--timeout-graceful-shutdown обязателен. Открытая вкладка держит SSE-поток /api/events, и без лимита --reload ждёт его закрытия вечно — сайт висит на загрузке, а в логе только Reloading....

Единый .env — в корне репозитория (ForbidenStarsApp/.env), рядом с .env.example. Его читают и бэкенд (через абсолютный путь, независимо от рабочей папки), и docker compose. Файл — локальный, на каждой машине свой (dev/prod различаются строкой APP_ENV).

2) Фронт (в отдельном терминале)

cd frontend
npm install
npm run gen:api                # сгенерирует типы из живого OpenAPI (бэкенд должен быть запущен)
npm run dev                    # http://127.0.0.1:5173 или http://localhost:5173 (оба стека)

Вход в dev-режиме — экран /login: логин/пароль, Telegram и вход по нику без пароля (stub); в проде stub нет (см. раздел «Аутентификация»).

Production (Docker на Pi)

На Pi нужны только два файла — docker-compose.yml и .env: образы app, tunnel и backup собираются на ПК под arm64 и пушатся в Gitea-реестр, Pi тянет их сам.

# ПК (обычно с ветки main): собрать и опубликовать образы
docker login gitea.arseniev.info
.\scripts\build-push.ps1
# Pi, папка с docker-compose.yml и .env
docker compose up -d           # pull_policy: always — тянет свежие образы, без сборки

Портов на хост нет — прод доступен только на https://forbiddenstars.ru через туннель-контейнер. FastAPI отдаёт собранный SPA и API с одного origin. Миграции, сидинг справочников и создание админа выполняются автоматически при старте (entrypoint.sh). В production закрыты OpenAPI/Swagger, а с дефолтным или коротким SECRET_KEY либо дефолтным ADMIN_PASSWORD приложение не стартует. Учтите: ADMIN_PASSWORD применяется только при первом создании админа, дальше его смена в .env ни на что не влияет (задача #73). Пошагово — deploy/pi/README.md, бэкапы — deploy/backup/README.md.

Test — прод-клон в контейнере на ПК

Тот же Dockerfile и поведение, что у прода (FastAPI отдаёт SPA, БД на томе, вход игроков по логину/паролю или через Telegram), но образ собирается локально — для проверки прод-сборки до выката на Pi. Портов на хост нет: тест-клон виден только на https://forbidden-stars.ru через свой туннель-контейнер (ключ — файл deploy/tunnel/id_tunnel).

Проще всего — через лаунчер: поставить APP_ENV=test в .env и запустить .\run.ps1. Вручную (тот же эффект):

docker compose -f docker-compose.test.yml up -d --build
# открыть https://forbidden-stars.ru  (Swagger: /api/docs)
docker compose -f docker-compose.test.yml logs -f app
docker compose -f docker-compose.test.yml down -v   # остановить и стереть тестовые данные
  • Читает тот же .env, что dev/prod (отдельного .env.test больше нет); внутри контейнера APP_ENV форсится в test (см. docker-compose.test.yml). Cookie — Secure (снаружи HTTPS).
  • От прода test отличается тем, что OpenAPI/Swagger открыт и нет fail-fast по дефолтным секретам, хотя контур публичный (задача #69). Не держите в .env дефолтные SECRET_KEY / ADMIN_PASSWORD, когда поднимаете test, — особенно после restore-test с прод-данными.
  • Данные — на отдельных томах db-data-test / uploads-data-test / achievements-data-test (и backup-data-test у контейнера бэкапов, он работает без расписания и без VPS); с dev и Pi не пересекаются.
  • Слот VPS 9001 общий с dev-туннелем (LOCAL_PUBLIC=vps) — поднимайте что-то одно.
  • Вход: админ-панель (/admin/login) работает сразу по логину/паролю; игроки — по логину/паролю сразу, через Telegram — при настроенном боте (/setdomain → forbidden-stars.ru).
  • Учебное восстановление прод-бэкапа в тест-клон — .\scripts\fs-backup.ps1 restore-test (deploy/backup/README.md, шаг 7).
  • Не используйте $ в секретах. Единый .env читают и pydantic (dev — $ дословно), и docker compose (test/prod — $ = подстановка переменной). Чтобы значение совпадало везде, в SECRET_KEY/ADMIN_PASSWORD не должно быть $. Удобно генерировать так: python -c "import secrets;print(secrets.token_urlsafe(48))" (даёт только [A-Za-z0-9_-]).

Аутентификация

Методы входа зависят от окружения (APP_ENV):

dev test / prod
Логин (= ник) и пароль ✓ ✓ (основной)
Telegram Login Widget ✓ ✓
Вход по нику без пароля (stub) ✓ ✗ (физически отсутствует)
  • Логин и пароль (app/auth/password.py) — основной вход. Логин — это ник игрока (смена ника меняет логин). Пароль: от 8 символов, не длиннее 72 байт, хранится bcrypt. От перебора — окно 15 минут в памяти процесса: 5 неудач на пару «IP + логин», 20 на IP и 50 на аккаунт (независимо от IP), дальше 429 TOO_MANY_ATTEMPTS. Регистраций — не больше 10 с одного IP за окно. Игрок без пароля (из Telegram или созданный до паролей) после входа видит обязательное окно «Задайте пароль»: закрыть его нельзя, только задать пароль или выйти; в dev-сборке есть кнопка «Позже (dev)». В профиле пароль меняется (нужен текущий) и привязывается Telegram (ник не меняется). Забытый пароль задаёт админ на вкладке аккаунтов — почту приложение не хранит. Смена или сброс пароля завершает все прежние сессии игрока (при смене в профиле текущее устройство остаётся в системе); «Выйти» отзывает токен этого устройства.
  • Stub-вход (по нику) и жёсткое удаление аккаунтов в админке — только для разработки. Их код физически не попадает в прод: файлы backend/app/auth/dev_stub.py, backend/app/routers/dev_auth.py и backend/app/routers/dev_admin.py исключены из Docker-образа (.dockerignore), роутеры подключаются лишь при APP_ENV=development (app/main.py), а на фронте dev-блоки вырезаются из прод-сборки (import.meta.env.DEV). В test/prod аккаунт можно только отключить.
  • Telegram: сервер проверяет подпись виджета (HMAC по TELEGRAM_BOT_TOKEN) и свежесть данных (не старше суток). Первый вход регистрирует игрока под Telegram-тегом; если такой ник занят или некорректен, фронт просит выбрать другой. GET /api/auth/config отдаёт доступные методы и telegram_bot_username для виджета.

Настройка Telegram (когда будете подключать реальный вход):

  1. Создать бота у @BotFather → получить токен и username.
  2. /setdomain у BotFather → оба домена: forbiddenstars.ru (prod) и forbidden-stars.ru (dev/test).
  3. В .env: TELEGRAM_BOT_TOKEN=..., TELEGRAM_BOT_USERNAME=... (без @).
  4. Виджету нужен HTTPS-домен (см. «Домен и публикация») — по голому HTTP/localhost он не работает.

Админ-вход (секретная панель, логин+пароль) — отдельный механизм, доступен во всех окружениях. Страница — /admin/login; из интерфейса туда ведёт удержание кнопки «Меню» 10 секунд.

Окружения (dev / test / prod)

Один и тот же код; контур задаёт APP_ENV в едином .env (его читает лаунчер):

dev test (прод-клон локально) prod (Pi)
Запуск .\run.ps1 → uvicorn --reload + vite .\run.ps1 → docker compose -f docker-compose.test.yml docker compose up -d
APP_ENV development test (форсится в compose) production (форсится в compose)
Env-файл единый .env единый .env единый .env (на Pi)
Раздача SPA Vite (HMR), :5173 FastAPI, только через https://forbidden-stars.ru FastAPI, только через https://forbiddenstars.ru
База данных backend/data/dev/… том db-data-test (/data) том db-data (/data)
Вход игроков пароль + Telegram + ник (stub) пароль + Telegram пароль + Telegram
Swagger (/api/docs) ✓ ✓ ✗
Fail-fast по дефолтным секретам ✗ ✗ ✓
  • Один .env на машину в корне (рядом с .env.example). APP_ENV в нём решает, что запустит лаунчер (development/test); прод-контейнер это значение игнорирует и всегда production. Отдельного .env.test больше нет.
  • Структура БД одна (общие миграции Alembic), файлы разные: dev → DEV_DATABASE_URL (backend/data/dev/), test и prod → PROD_DATABASE_URL (том /data; у test и prod это РАЗНЫЕ тома). Так же раздельно лежат загрузки (*_UPLOAD_DIR) и ачивки (*_ACHIEVEMENTS_DIR).
  • В Docker идёт только прод-код: dev-вход (stub), dev-удаление аккаунтов и тесты физически исключены из образа (.dockerignore); test собирается из того же Dockerfile, что и прод, просто локально и с APP_ENV=test.
  • Внимание: данные дева (backend/data/dev/) сейчас попадают в образ — правило data/ в .dockerignore исключает только корневую папку data/ (задача #71).

Git и деплой

  • Ветка dev — рабочая: весь код, лаунчер, тесты. Повседневная разработка и test здесь.
  • Ветка main — релиз прода: готовое промоутишь из dev через merge dev→main. Файлы во всех ветках одинаковы (окружение задаёт .env/compose, а не ветка) → merge безболезненный; чистоту прод-образа обеспечивает .dockerignore, а не разные наборы файлов.
  • Деплой на Pi: на ПК с ветки main — .\scripts\build-push.ps1 (собирает и пушит образы app + tunnel + backup под arm64), на Pi — docker compose up -d. На Windows нужна именно PS-версия скрипта (build-push.sh из PowerShell уходит в WSL).
  • Чистая выгрузка в папку без git (опц., к деплою на Pi не относится): scripts/export-prod.sh <dir> [ref] / scripts/export-test.sh <dir> [ref] — через git archive + export-ignore из .gitattributes (без тестов, stub-входа, pyproject.toml, README-файлов; у прода ещё без лаунчера и тест-compose, у теста — без прод-compose). dev_admin.py в export-ignore пока не внесён (задача #70).

Секреты (.env) и данные (data/, *.db) в git не идут — см. .gitignore.

Домен и публикация

Публичные адреса отдаёт VPS-привратник (Caddy + HTTPS твоими сертификатами), а приложение само открывает к нему SSH reverse-туннель (дома белого IP нет — CGNAT). У test/prod туннель — отдельный контейнер в их docker-compose, и портов на хост они не публикуют (доступны только через домен):

домен как выставляется слот VPS
prod forbiddenstars.ru туннель-контейнер (постоянно) → app:8000 9000
test forbidden-stars.ru туннель-контейнер → app:8000 9001
dev forbidden-stars.ru лаунчер (run.ps1/run.sh) при LOCAL_PUBLIC=vps → localhost:5173 9001
  • Прод (9000) и dev/test (9001) на разных слотах/доменах → прод и (dev|test) работают одновременно. Dev и test делят слот 9001 → по очереди. Временный прод на ПК (docker-compose.temp.yml) занимает слот 9000 — одновременно с продом на Pi не запускать.
  • Dev по умолчанию только на localhost; LOCAL_PUBLIC=vps + лаунчер выставляет его на домен.
  • COOKIE_SECURE выводится автоматически (HTTPS-домен ⇒ Secure-cookie; dev+localhost ⇒ нет).
  • Ключ туннеля: test и временный прод берут файл deploy/tunnel/id_tunnel, прод на Pi — TUNNEL_KEY_B64 (base64) в .env, dev-туннель — системный ssh с ключом по умолчанию (~/.ssh). Публичные части — в authorized_keys пользователя tunnel на VPS.
  • Пошаговая настройка — в deploy/: vps/ (Caddy, сертификаты, юзер tunnel), pi/ (app + туннель + бэкапы в Docker), образ туннеля — deploy/tunnel/.

Бэкапы

Контейнер backup (restic) в docker-compose.yml каждую ночь делает зашифрованный снимок БД, uploads и achievements — на Pi (том backup-data) и на VPS по SFTP. С ПК снимки скачиваются и проверяются учебным восстановлением в тест-клон (scripts/fs-backup.ps1). Настройка, восстановление и действия при гибели Pi — deploy/backup/README.md.

Дополнения и фракции

Дополнение Фракции
База (всем) Орки, Ультрамарины, Эльдары, Хаоситы
Forgotten Worlds Имперская гвардия, Тау, Некроны, Тираниды
Forsaken Voids Инквизиция, Сёстры битвы, Друкхари, Адептус Механикус

Группа отмечает имеющиеся дополнения; при создании партии доступны только фракции этих дополнений (база — всегда). Справочник сидится из backend/app/seed/reference_data.py. Админ может переименовать фракцию в панели, но сейчас переименование откатывается при каждом перезапуске приложения (задача #72).