NotBigGhostandClaude Opus 5 0e5f5292ed Правила: «Справочник» в Markdown
Скан без текстового слоя распознан вручную постранично, 427 блоков:
золотые правила, глоссарий (пункт статьи — отдельный блок, «Связанные
темы» — kind: related), краткая справка и разъяснения карт. Содержание
(стр. 19) не перенесено — его заменяют заголовки.

#6

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
2026-09-19 11:51:50 +03:00

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-prod.sh
Dockerfile               multi-stage сборка (фронт собирается node, отдаётся FastAPI)
docker-compose.yml       прод на Pi: app + tunnel + backup
docker-compose.temp.yml  временный прод на ПК вместо Pi: app + tunnel
run.ps1 / run.sh         единый лаунчер dev
.env.example             шаблон единого .env

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

Всё проверяется в development — отдельного тестового контейнера нет.

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

После разовой настройки (ниже) dev запускается одной командой (лаунчер читает 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
production не запускает — прод деплоится отдельно (см. «Production» и «Git и деплой»)
другое значение отказ: допустимы только development и production (бэкенд тоже не стартует)

Разовая настройка перед первым запуском — поднять 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, docker compose up -d и docker compose exec app python -m app.bootstrap --reset-admin-password (все админские сессии завершатся; подробно — в deploy/pi/README.md). Пошагово — deploy/pi/README.md, бэкапы — deploy/backup/README.md.

  • Не используйте $ в секретах. Единый .env читают и pydantic (dev — $ дословно), и docker compose (prod — $ = подстановка переменной). Чтобы значение совпадало везде, в SECRET_KEY/ADMIN_PASSWORD не должно быть $. Удобно генерировать так: python -c "import secrets;print(secrets.token_urlsafe(48))" (даёт только [A-Za-z0-9_-]).
  • Временный прод на ПК (вместо Pi): docker compose -f docker-compose.temp.yml up -d --build — поведение production, локальная сборка x86, ключ туннеля из файла deploy/tunnel/id_tunnel, тот же слот VPS 9000, что у Pi (одновременно не запускать).

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

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

dev 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). В проде аккаунт можно только отключить.
  • 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).
  3. В .env: TELEGRAM_BOT_TOKEN=..., TELEGRAM_BOT_USERNAME=... (без @).
  4. Виджету нужен HTTPS-домен (см. «Домен и публикация») — по голому HTTP/localhost он не работает.

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

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

Один и тот же код; контур задаёт APP_ENV в едином .env. Допустимы только development и production — с любым другим значением бэкенд не стартует:

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

Git и деплой

  • Ветка dev — рабочая: весь код, лаунчер, тесты. Повседневная разработка и проверка здесь.
  • Ветка 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] — через git archive + export-ignore из .gitattributes (без тестов, stub-входа, pyproject.toml, README-файлов и лаунчера). dev_admin.py в export-ignore пока не внесён (задача #70).

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

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

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

домен как выставляется слот VPS
prod forbiddenstars.ru туннель-контейнер (постоянно) → app:8000 9000
dev forbidden-stars.ru лаунчер (run.ps1/run.sh) при LOCAL_PUBLIC=vps → localhost:5173 9001
  • Прод (9000) и dev (9001) на разных слотах/доменах → работают одновременно. Временный прод на ПК (docker-compose.temp.yml) занимает слот 9000 — одновременно с продом на Pi не запускать.
  • Dev по умолчанию только на localhost; LOCAL_PUBLIC=vps + лаунчер выставляет его на домен. В dev при этом открыты stub-вход по нику и Swagger — держите туннель поднятым только на время проверки (задача #69).
  • COOKIE_SECURE выводится автоматически (HTTPS-домен ⇒ Secure-cookie; dev+localhost ⇒ нет).
  • Ключ туннеля: временный прод берёт файл 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. С ПК снимки скачиваются со сверкой sha256 (scripts/fs-backup.ps1 pull). Настройка, восстановление и действия при гибели Pi — deploy/backup/README.md.

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

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

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

S
Description
Веб-приложение для учёта партий в настольной игре "Forbidden Stars"
https://forbiddenstars.ru
Readme
1.2 MiB
Languages
Python 57.6%
TypeScript 30.1%
Shell 7.3%
CSS 2.7%
PowerShell 1.5%
Other 0.7%