diff --git a/.env.example b/.env.example index 9100191..4d7bdbb 100644 --- a/.env.example +++ b/.env.example @@ -6,7 +6,7 @@ # ─── ГЛАВНЫЙ ПЕРЕКЛЮЧАТЕЛЬ ──────────────────────────────────────────────────── # Этот параметр читает ЛАУНЧЕР (run.ps1 / run.sh) и решает, что запускать: -# development — нативно: uvicorn --reload + vite, БД в ./data/dev/, вход TG+ник +# development — нативно: uvicorn --reload + vite, БД в ./data/dev/, есть вход по нику (stub) # production — НЕ запускается лаунчером; деплой на Pi отдельно (docker compose up -d). # Прод-контейнер ИГНОРИРУЕТ это значение и всегда production. APP_ENV=development @@ -44,8 +44,8 @@ ADMIN_NICKNAME=Администратор ADMIN_BOOTSTRAP_ENABLED=true # ─── АУТЕНТИФИКАЦИЯ ИГРОКОВ ─────────────────────────────────────────────────── -# Методы входа задаёт APP_ENV: dev → Telegram + stub (вход по нику), prod → только -# Telegram. Для Telegram нужны токен и юзернейм бота (@BotFather). /setdomain у +# Вход везде — логин/пароль и Telegram; в development ещё stub (по нику без пароля). +# Для Telegram нужны токен и юзернейм бота (@BotFather). /setdomain у # BotFather укажи на ОБА домена, где открывается виджет: forbiddenstars.ru (prod) # и forbidden-stars.ru (dev). TELEGRAM_BOT_TOKEN= @@ -81,7 +81,7 @@ DEV_ACHIEVEMENTS_DIR=./data/dev/achievements PROD_ACHIEVEMENTS_DIR=/data/achievements # ─── ОБРАЗЫ ПРОДА (реестр для docker compose pull на Pi) ────────────────────── -# Образы собираются под arm64 на ПК (scripts/build-push.sh) и пушатся в Gitea-реестр, +# Образы собираются под arm64 на ПК (scripts/build-push.ps1; в Linux — .sh) и пушатся в Gitea-реестр, # а Pi их тянет (docker compose pull). Owner в пути — строчными. Тег можно версионировать. # Перед пушем/пуллом: docker login gitea.arseniev.info IMAGE_REGISTRY=gitea.arseniev.info/notbigghost diff --git a/.gitattributes b/.gitattributes index f781db5..eb036e9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,16 +3,3 @@ # Скрипты образов (entrypoint.sh, deploy/*/…) контейнер дополнительно чинит sed-ом при сборке. *.sh text eol=lf backend/entrypoint.sh text eol=lf - -# ── export-ignore: НЕ попадает в `git archive` (чистая выгрузка прода) ───────── -# В git эти файлы есть и доступны на всех ветках (нужны для разработки), -# но в архив деплоя (scripts/export-prod.sh) не идут. На Docker-сборку НЕ влияет — -# там чистоту образа обеспечивает .dockerignore. -backend/tests/ export-ignore -backend/app/auth/dev_stub.py export-ignore -backend/app/routers/dev_auth.py export-ignore -backend/pyproject.toml export-ignore -README.md export-ignore -.gitignore export-ignore -.gitattributes export-ignore -.dockerignore export-ignore diff --git a/README.md b/README.md index df57b04..827b9e4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ backend/ FastAPI: ядро, REST API, БД, миграции Alembic, seed, тесты frontend/ React + Vite SPA deploy/ публикация и эксплуатация: vps/ (Caddy), pi/ (прод), tunnel/ и backup/ (образы) -scripts/ build-push.* (сборка и пуш образов), fs-backup.* (бэкапы с ПК), export-prod.sh +scripts/ build-push.* (сборка и пуш образов), fs-backup.* (бэкапы с ПК) Dockerfile multi-stage сборка (фронт собирается node, отдаётся FastAPI) docker-compose.yml прод на Pi: app + tunnel + backup docker-compose.temp.yml временный прод на ПК вместо Pi: app + tunnel @@ -214,10 +214,6 @@ docker compose up -d # pull_policy: always — тянет свежие - **Деплой на 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 [ref]` — через `git archive` + `export-ignore` из - `.gitattributes` (без тестов, stub-входа, `pyproject.toml`, README-файлов и лаунчера). - `dev_admin.py` в `export-ignore` пока не внесён (задача #70). Секреты (`.env`) и данные (`data/`, `*.db`) в git не идут — см. `.gitignore`. diff --git a/backend/app/auth/admin_login.py b/backend/app/auth/admin_login.py index 7e1d0d9..4643d30 100644 --- a/backend/app/auth/admin_login.py +++ b/backend/app/auth/admin_login.py @@ -2,8 +2,9 @@ Тонкий слой поверх `admin_service.authenticate_admin`: throttle по IP, по паре «IP + логин» и по самому аккаунту через тот же `LoginThrottle`, что и вход игрока (`core/ratelimit`). -Сервис остаётся чистым от инфраструктуры лимитов. Пароль администратора — единственный -барьер к полному контролю приложения, поэтому перебор здесь ограничиваем строже игроцкого. +Сервис остаётся чистым от инфраструктуры лимитов. Лимиты те же, что у игрока (5 на пару, +20 на IP, 50 на аккаунт за 15 минут); отличие — при успешном входе снимаются все счётчики, +включая IP. """ from __future__ import annotations diff --git a/backend/app/auth/dev_stub.py b/backend/app/auth/dev_stub.py index 8dc37fc..0093c08 100644 --- a/backend/app/auth/dev_stub.py +++ b/backend/app/auth/dev_stub.py @@ -1,4 +1,4 @@ -"""Dev-провайдер: вход без секрета по нику/идентификатору (только не-production).""" +"""Dev-провайдер: вход без секрета по нику/идентификатору (только development).""" from __future__ import annotations from typing import Any diff --git a/backend/app/auth/login.py b/backend/app/auth/login.py index 2541851..159ca1a 100644 --- a/backend/app/auth/login.py +++ b/backend/app/auth/login.py @@ -1,7 +1,7 @@ """Общий вход: внешняя личность → пользователь → сессия. -Прод-безопасный модуль (без импортов dev-провайдера). Используется и Telegram-входом, -и dev-входом. +Прод-безопасный модуль (без импортов dev-провайдера). establish_session зовут вход через +Telegram, /auth/login, /auth/register и dev-вход. """ from __future__ import annotations diff --git a/backend/app/auth/telegram.py b/backend/app/auth/telegram.py index 806f196..db01b1a 100644 --- a/backend/app/auth/telegram.py +++ b/backend/app/auth/telegram.py @@ -2,7 +2,7 @@ Проверяет подпись данных виджета (HMAC-SHA256 ключом SHA256(BOT_TOKEN)) и свежесть auth_date. Нужны TELEGRAM_BOT_TOKEN (+ TELEGRAM_BOT_USERNAME для виджета на фронте). -Доступен и в dev, и в prod (в prod — единственный метод входа). +Доступен во всех окружениях — наряду со входом по логину и паролю. """ from __future__ import annotations diff --git a/backend/app/core/config.py b/backend/app/core/config.py index 338cb3e..970079f 100644 --- a/backend/app/core/config.py +++ b/backend/app/core/config.py @@ -31,7 +31,7 @@ class Settings(BaseSettings): ) # ── Главный переключатель окружения: development | production ───────────── - # development — нативный dev (uvicorn + vite), БД в ./data/dev/, вход Telegram+ник. + # development — нативный dev (uvicorn + vite), БД в ./data/dev/, есть stub-вход по нику. # production — Docker на Pi; контейнер форсит это значение, игнорируя .env. app_env: str = "development" log_level: str = "INFO" @@ -68,8 +68,8 @@ class Settings(BaseSettings): # cookie_secure НЕ задаётся вручную — выводится из окружения (см. property ниже). cookie_domain: str | None = None - # Аутентификация. Методы входа определяются окружением (dev: telegram+stub, - # prod: только telegram) — отдельного переключателя провайдера нет. + # Аутентификация. Методы входа определяются окружением (auth/registry.py): везде + # логин/пароль + Telegram, в development ещё stub — отдельного переключателя нет. telegram_bot_token: str | None = None telegram_bot_username: str | None = None public_base_url: str | None = None diff --git a/backend/app/core/ratelimit.py b/backend/app/core/ratelimit.py index 941b5de..4c45a71 100644 --- a/backend/app/core/ratelimit.py +++ b/backend/app/core/ratelimit.py @@ -6,7 +6,7 @@ * рестарт (в т.ч. деплой) сбрасывает окно — злоумышленник получает новую квоту после перезапуска, но окно короткое, а рестарты редки; * при уходе от одного воркера лимит делится между процессами (каждый считает своё) — - тогда счётчики нужно вынести во внешний стор (Redis pub/sub, как отмечено в CLAUDE.md + тогда счётчики нужно вынести во внешний стор (Redis pub/sub, как отмечено в core/events.py про SSE-шину), общий для всех воркеров. Помимо пары «IP + логин» и лимита по IP есть IP-независимый лимит на аккаунт (`login-user:*` / `admin-login-user:*`), чтобы ротация X-Forwarded-For / многих адресов diff --git a/backend/app/core/security.py b/backend/app/core/security.py index fd456da..5a7dfc8 100644 --- a/backend/app/core/security.py +++ b/backend/app/core/security.py @@ -175,6 +175,6 @@ def is_session_revoked(payload: dict) -> bool: def client_ip(request: Request) -> str | None: """IP клиента для журнала аудита. - Одна точка на всё приложение: за VPS-привратником адрес придётся брать из - X-Forwarded-For, и менять это в двух десятках роутеров — не вариант.""" + Одна точка на всё приложение. Реальный адрес за VPS-привратником уже подставляет + uvicorn (--proxy-headers + --forwarded-allow-ips в entrypoint.sh) — отсюда он и берётся.""" return request.client.host if request.client else None diff --git a/backend/app/db/init_db.py b/backend/app/db/init_db.py deleted file mode 100644 index 90a009e..0000000 --- a/backend/app/db/init_db.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Инициализация схемы и справочников (для тестов и локального быстрого старта). - -В production схема создаётся миграциями Alembic; этот модуль удобен для тестов, -где БД поднимается из чистого состояния. -""" -from __future__ import annotations - -from sqlmodel import SQLModel - -from app.db.session import engine -from app.seed.reference_data import seed_reference_data -from sqlmodel import Session - -# Импорт моделей обязателен, чтобы они зарегистрировались в SQLModel.metadata. -import app.models # noqa: F401 - - -def create_db_and_seed() -> None: - SQLModel.metadata.create_all(engine) - with Session(engine) as session: - seed_reference_data(session) diff --git a/backend/app/routers/dev_auth.py b/backend/app/routers/dev_auth.py index 7f36f2f..600e8ab 100644 --- a/backend/app/routers/dev_auth.py +++ b/backend/app/routers/dev_auth.py @@ -1,8 +1,8 @@ """DEV-ТОЛЬКО роутер: вход по нику (stub) + тестовые пользователи. Этот файл и app/auth/dev_stub.py ФИЗИЧЕСКИ исключены из прод-образа (.dockerignore), -а подключается роутер лишь когда APP_ENV != production (см. app/main.py). Так код -входа по логину остаётся только на деве. +а подключается роутер лишь при APP_ENV=development (см. app/main.py). Так вход +без пароля остаётся только на деве (по логину и паролю входят везде). """ from __future__ import annotations diff --git a/deploy/vps/Caddyfile b/deploy/vps/Caddyfile index 08ad849..42f06d9 100644 --- a/deploy/vps/Caddyfile +++ b/deploy/vps/Caddyfile @@ -1,7 +1,7 @@ # Caddy на VPS (186.246.51.17) — единственная публичная точка входа. # Два домена, ОБА с твоими сертификатами; проксируют в SSH-туннели: # -# forbiddenstars.ru → 127.0.0.1:9000 ← Pi (autossh, постоянно) PROD +# forbiddenstars.ru → 127.0.0.1:9000 ← Pi (туннель-контейнер, ssh + restart) PROD # forbidden-stars.ru → 127.0.0.1:9001 ← ПК (по требованию) DEV # # Caddy сам терминирует TLS (он и есть edge: видит реального клиента), а вниз к diff --git a/docker-compose.yml b/docker-compose.yml index 5f62cb7..6d2528d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,8 +21,8 @@ services: app: build: . - # Образ из реестра (Gitea): собирается под arm64 на ПК (scripts/build-push.sh) и тянется - # на Pi через `docker compose pull`. build: оставлен как локальный фолбэк (сборка на Pi). + # Образ из реестра (Gitea): собирается под arm64 на ПК (scripts/build-push.ps1, в Linux — + # .sh) и тянется на Pi. build: нужен только этой сборке на ПК — на Pi не используется. image: ${IMAGE_REGISTRY:-gitea.arseniev.info/notbigghost}/forbidden-stars:${IMAGE_TAG:-latest} pull_policy: always # на Pi всегда тянем образ из реестра (без сборки) restart: unless-stopped diff --git a/h -u origin dev b/h -u origin dev deleted file mode 100644 index 36b4302..0000000 --- a/h -u origin dev +++ /dev/null @@ -1,29 +0,0 @@ -commit 65bebf8b85fcbf921cbbc4f2599953a9fa3ca9c6 (HEAD -> dev) -Author: NotBigGhost -Date: Wed Jun 17 04:45:33 2026 +0300 - - Добавление ssh-ключа к контейнеру tunnel - -commit 7e8b748caf5d9f4ab3d2d7f76d6a4b850b9b1366 -Author: NotBigGhost -Date: Wed Jun 17 04:27:11 2026 +0300 - - Правки локального размещения дева, перенос тунеля в контейнер - -commit b04fbb2e171a7fe3376f3633dfee9e29e4e4bb90 -Author: NotBigGhost -Date: Tue Jun 16 19:15:54 2026 +0300 - - Завершена настройка домена для дева, теста и прода. Соединение через ssh-туннель - -commit 56b5d09a4dc5e6f5d06e32c2635c84a66597e71f (origin/main, main) -Author: NotBigGhost -Date: Tue Jun 16 17:41:06 2026 +0300 - - v0.1 - макет интерфейса, аутентификация через логин, аккаунт админа, создание партии в 2 этапа, базовые настройки профиля и группы, переключение между группами, статистика - -commit 6ab74f01aaf1ac042a269c7660b3e564b93350c7 -Author: NotBigGhost -Date: Tue Jun 16 16:53:41 2026 +0300 - - first commit diff --git a/run.ps1 b/run.ps1 index c19cae2..430a30a 100644 --- a/run.ps1 +++ b/run.ps1 @@ -135,7 +135,7 @@ switch ($appEnv) { } "production" { Write-Host "production is not started by the launcher - prod is separate." -ForegroundColor Yellow - Write-Host "Deploy on Pi (from main branch): docker compose up -d --build" + Write-Host "Deploy: PC (main branch) scripts\build-push.ps1, then on Pi: docker compose up -d" exit 1 } default { diff --git a/run.sh b/run.sh index b959ef3..4057e1e 100644 --- a/run.sh +++ b/run.sh @@ -85,7 +85,7 @@ case "$app_env" in ;; production) echo "production лаунчером не запускается — прод обособлен." - echo "Деплой на Pi (из ветки main): docker compose up -d --build" + echo "Деплой: на ПК (ветка main) scripts/build-push.sh, затем на Pi: docker compose up -d" exit 1 ;; *) diff --git a/scripts/export-prod.sh b/scripts/export-prod.sh deleted file mode 100644 index 9a89e20..0000000 --- a/scripts/export-prod.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# Выгрузка ПРОДА: в целевую папку попадают только файлы, нужные для запуска -# прод-контейнера (без тестов, dev-входа и dev-лаунчера). -# -# Использование: scripts/export-prod.sh <целевая-папка> [git-ref] -# git-ref по умолчанию HEAD; для прод-ветки: scripts/export-prod.sh /srv/fs prod -set -euo pipefail - -DEST="${1:?Укажите целевую папку: scripts/export-prod.sh [ref]}" -REF="${2:-HEAD}" - -mkdir -p "$DEST" -# git archive уважает export-ignore из .gitattributes (тесты, dev-вход и т.п. отсеяны) -git archive --format=tar "$REF" | tar -x -C "$DEST" - -# dev-специфика в проде не нужна (лаунчер) -rm -f "$DEST/run.ps1" "$DEST/run.sh" - -echo "[export-prod] Прод выгружен в: $DEST" -echo " дальше: cp .env.example .env && docker compose up -d --build"