Files
ForbiddenStarsApp/deploy/pi/README.md
T
NotBigGhostandClaude Opus 5 0ee7cd1274 Безопасность: команда ротации пароля администратора из .env
В prod пароль админа задавался только при первом создании: обычный bootstrap
его из .env не берёт, панель не меняет — комментарии отсылали друг к другу.
Теперь `python -m app.bootstrap --reset-admin-password` применяет ADMIN_PASSWORD
к существующему админу через user_service.set_password: пароль проверяется,
token_version растёт, и все админские сессии (в том числе чужие) отзываются.
Обычный старт по-прежнему пароль не трогает.

Процедура на Pi — в deploy/pi/README.md (правка .env → docker compose up -d →
exec команды), кратко — в README. #73

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

157 lines
10 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`, у существующего админа само ничего не изменится.
>
> **Смена пароля админа** (плановая или при утечке) — осознанной командой, через панель
> его не сменить:
> 1. поменять `ADMIN_PASSWORD` в `.env` на Pi;
> 2. `docker compose up -d` — пересоздаст `app` с новым `.env` (контейнер читает `.env`
> только при создании; без этого шага команда ниже увидит старый пароль);
> 3. `docker compose exec app python -m app.bootstrap --reset-admin-password`.
>
> Все админские сессии, в том числе чужие, если пароль утёк, после этого завершаются.
> Логин (`ADMIN_USERNAME`) команда не меняет.
## 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`).