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

9.3 KiB
Raw Blame History

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):
    docker login gitea.arseniev.info
    .\scripts\build-push.ps1
    
    Без этого docker compose up на Pi не найдёт образы в реестре.

1. Система и Docker на Pi

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:

ssh-keygen -t ed25519 -f ~/fs_tunnel -N ""    # создаст ~/fs_tunnel (приватный) и ~/fs_tunnel.pub

Добавить публичный ключ на VPS (Pi ходит наружу — это работает даже за CGNAT):

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:

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:

shred -u ~/fs_tunnel ~/fs_tunnel.pub   # или просто rm

3. Два файла: docker-compose.yml + .env

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.

В production приложение не стартует, если SECRET_KEY дефолтный или короче 32 символов, а ADMIN_PASSWORD пустой или дефолтный (при ADMIN_BOOTSTRAP_ENABLED=true). ADMIN_USERNAME/ADMIN_PASSWORD применяются только при первом создании админа: если потом поменять их в .env, у существующего админа ничего не изменится, а штатной смены пароля админа на проде пока нет (задача #73).

4. Запуск

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.

Обновление

# 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.

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).