Files
ForbiddenStarsApp/deploy/pi/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

148 lines
9.3 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.
# Raspberry Pi — ПРОД (`forbiddenstars.ru`)
На Pi нужны только **ДВА файла**: `docker-compose.yml` и `.env`. Репозиторий, сборка и файл
ключа туннеля не нужны:
- образы (`app` + `tunnel` + `backup`) тянутся из Gitea-реестра (`pull_policy: always`);
- приватный ключ туннеля лежит в `.env` как `TUNNEL_KEY_B64` (base64).
`docker compose up` поднимает три контейнера: `app` (FastAPI+SPA, портов на хост нет),
`tunnel` (`ssh -R 9000:app:8000` к VPS, стартует после `healthy` у `app`) и `backup` (restic,
см. раздел «Бэкапы»). Публичная точка — VPS, домен `forbiddenstars.ru` (Pi за CGNAT — туннель
стучится наружу сам).
---
## 0. Предпосылки (один раз, не на Pi)
1. **VPS настроен**: Caddy `forbiddenstars.ru` → `127.0.0.1:9000`, пользователь `tunnel`,
сертификаты, страница-заглушка — см. `deploy/vps/README.md`.
2. **Образы собраны и запушены в реестр** (на ПК с Docker Desktop):
```powershell
docker login gitea.arseniev.info
.\scripts\build-push.ps1
```
Без этого `docker compose up` на Pi не найдёт образы в реестре.
## 1. Система и Docker на Pi
```bash
ssh pi@<ip-пая>
sudo apt update && sudo apt -y full-upgrade
sudo apt -y install curl openssh-client # base64/ssh-keygen уже есть в системе
curl -fsSL https://get.docker.com | sudo sh # Docker Engine + compose-плагин (arm64)
sudo usermod -aG docker $USER
sudo systemctl enable --now docker # автозапуск после ребута
newgrp docker # применить группу (или перезайти по SSH)
docker version && docker compose version # проверка
```
## 2. SSH-ключ для туннеля
**Что это.** Отдельная пара ключей **только для туннеля** — ею контейнер `tunnel` логинится на
`tunnel@VPS`, чтобы открыть `ssh -R`. Это не системный ключ Pi, ты создаёшь его сам. Распределение:
- **приватный** ключ → в `.env` как `TUNNEL_KEY_B64` (base64, одной строкой);
- **публичный** (`.pub`) → в `authorized_keys` пользователя `tunnel` на VPS.
Сгенерировать прямо на Pi:
```bash
ssh-keygen -t ed25519 -f ~/fs_tunnel -N "" # создаст ~/fs_tunnel (приватный) и ~/fs_tunnel.pub
```
Добавить публичный ключ на VPS (Pi ходит наружу — это работает даже за CGNAT):
```bash
ssh-copy-id -i ~/fs_tunnel.pub tunnel@186.246.51.17
# Если у tunnel нет пароля (только ключ) — добавь вручную через свой админ-доступ к VPS:
# cat ~/fs_tunnel.pub # скопируй строку
# на VPS: echo '<строка>' >> /home/tunnel/.ssh/authorized_keys
```
Закодировать приватный ключ в base64 **одной строкой** — это значение для `TUNNEL_KEY_B64`:
```bash
base64 -w0 ~/fs_tunnel; echo # выведет длинную строку без переносов — скопируй её целиком
```
> Альтернатива: если ключ уже есть на ПК (`deploy/tunnel/id_tunnel`) и его pubkey уже на VPS —
> не плоди новый, закодируй тот:
> `[Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path "deploy\tunnel\id_tunnel")))`
После того как base64 вставлен в `.env`, файлы ключа на Pi можно удалить — ключ теперь в `.env`,
а pubkey уже на VPS:
```bash
shred -u ~/fs_tunnel ~/fs_tunnel.pub # или просто rm
```
## 3. Два файла: docker-compose.yml + .env
```bash
mkdir -p ~/forbidden-stars && cd ~/forbidden-stars
# branch — main (или dev, если ещё не смёржено в main):
curl -fsSLO https://gitea.arseniev.info/NotBigGhost/ForbiddenStarsApp/raw/branch/main/docker-compose.yml
curl -fsSL https://gitea.arseniev.info/NotBigGhost/ForbiddenStarsApp/raw/branch/main/.env.example -o .env
nano .env
```
Заполнить в `.env`:
```
SECRET_KEY=... # python3 -c "import secrets;print(secrets.token_urlsafe(48))"
ADMIN_USERNAME=... # логин/пароль секретной админки
ADMIN_PASSWORD=...
TELEGRAM_BOT_TOKEN=... # бот @BotFather; затем /setdomain → forbiddenstars.ru
TELEGRAM_BOT_USERNAME=...
VPS_TUNNEL_HOST=186.246.51.17
TUNNEL_KEY_B64=... # длинная строка base64 из шага 2 (целиком, одной строкой)
IMAGE_REGISTRY=gitea.arseniev.info/notbigghost # уже значение по умолчанию
IMAGE_TAG=latest
```
`APP_ENV` (форсится в `production`) и `VPS_TUNNEL_PORT` (прод → 9000) не трогать.
Блок `BACKUP_*` можно оставить пустым: бэкапы включаются позже, по
[`deploy/backup/README.md`](../backup/README.md).
> В production приложение **не стартует**, если `SECRET_KEY` дефолтный или короче 32
> символов, а `ADMIN_PASSWORD` пустой или дефолтный (при `ADMIN_BOOTSTRAP_ENABLED=true`).
> `ADMIN_USERNAME`/`ADMIN_PASSWORD` применяются **только при первом создании** админа: если
> потом поменять их в `.env`, у существующего админа ничего не изменится, а штатной смены
> пароля админа на проде пока нет (задача #73).
## 4. Запуск
```bash
docker login gitea.arseniev.info # один раз, доступ к реестру
docker compose up -d # pull_policy: always → тянет образы из реестра, без сборки
docker compose ps # app healthy → поднимется tunnel
docker compose logs -f tunnel # ждём строку: [tunnel] -R 9000:app:8000 -> tunnel@...
```
На старте контейнер сам применит миграции, засидит справочники и создаст админа из `.env`
(если админа ещё нет).
## 5. Проверка
- Открой `https://forbiddenstars.ru` — должно отдать приложение (не заглушку).
- У @BotFather `/setdomain` → добавь `forbiddenstars.ru` (иначе Telegram-вход не заработает).
- Админка: удержать «Меню» 10 с → `/admin/login`, войти логином/паролём из `.env`.
## Обновление
```bash
# Pi: docker compose exec backup fs-backup run --tag before-update # по желанию, если бэкапы включены
# ПК: .\scripts\build-push.ps1
# Pi: docker compose up -d # always-pull подтянет свежие образы
```
Если в новой версии менялся `docker-compose.yml` или `.env.example`, сначала скачайте
свежий `docker-compose.yml` (шаг 3) и допишите новые переменные в `.env`.
## Автозапуск после перезагрузки
Уже обеспечен: `systemctl enable docker` + `restart: unless-stopped`. После `sudo reboot`
контейнеры поднимутся сами (используют локальный образ, без повторного pull).
## Бэкапы
Отдельный контейнер `backup` в том же `docker-compose.yml`: каждую ночь делает зашифрованный
снимок БД, `uploads` и `achievements` на Pi и на VPS. Дополнительных файлов на Pi не нужно,
всё настраивается блоком `BACKUP_*` в `.env`. Пока `BACKUP_PASSWORD` пуст, бэкапы выключены.
Пошаговая настройка, восстановление и действия при гибели Pi —
[`deploy/backup/README.md`](../backup/README.md).
```bash
docker compose exec backup fs-backup status # состояние
docker compose exec backup fs-backup list # хронология снимков
```
> Не выполняйте `docker compose down -v`: флаг `-v` удаляет тома с данными и локальными бэкапами.
## Если что-то не так
- `https://forbiddenstars.ru` отдаёт заглушку/502 → туннель не поднят: `docker compose logs tunnel`
(чаще: pubkey не в `authorized_keys` на VPS, пустой/битый `TUNNEL_KEY_B64`, либо на VPS занят
слот 9000 → на VPS `sudo fuser -k 9000/tcp`, затем `docker compose restart tunnel`).
- `pull` не проходит → проверь `docker login gitea.arseniev.info` и что реестр по HTTPS с валидным
сертификатом (иначе хост в `/etc/docker/daemon.json` → `insecure-registries`, `systemctl restart docker`).