- deploy/pi/README: пошагово от чистого Pi (Docker → SSH-ключ туннеля на самом Pi → 2 файла → up), с объяснением назначения ключа и кодирования в base64 (TUNNEL_KEY_B64). - .env.example/README: PowerShell-команда base64 через Resolve-Path (абсолютный путь — иначе [IO.File]::ReadAllBytes искал относительно домашнего каталога .NET и падал). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Raspberry Pi — ПРОД (forbiddenstars.ru)
На Pi нужны только ДВА файла: docker-compose.yml и .env. Репозиторий, сборка и файл
ключа туннеля не нужны:
- образы (
app+tunnel) тянутся из Gitea-реестра (pull_policy: always); - приватный ключ туннеля лежит в
.envкакTUNNEL_KEY_B64(base64).
docker compose up поднимает два контейнера: app (FastAPI+SPA, портов на хост нет) и
tunnel (ssh -R 9000:app:8000 к VPS). Публичная точка — VPS, домен forbiddenstars.ru
(Pi за CGNAT — туннель стучится наружу сам).
0. Предпосылки (один раз, не на Pi)
- VPS настроен: Caddy
forbiddenstars.ru→127.0.0.1:9000, пользовательtunnel, сертификаты, страница-заглушка — см.deploy/vps/README.md. - Образы собраны и запушены в реестр (на ПК с Docker Desktop):
Без этого
docker login gitea.arseniev.info .\scripts\build-push.ps1docker 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) не трогать.
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.
Обновление
# ПК: .\scripts\build-push.ps1
# Pi: docker compose up -d # always-pull подтянет свежий образ
Автозапуск после перезагрузки
Уже обеспечен: systemctl enable docker + restart: unless-stopped. После sudo reboot
контейнеры поднимутся сами (используют локальный образ, без повторного pull).
Бэкап (опционально)
Бэкап-скриптам нужен сам docker-compose.yml (он на Pi) + скрипты. Скопируй рядом
scripts/backup.sh и scripts/restore.sh, настрой BACKUP_* в .env и cron. Подробно —
комментарии в scripts/backup.sh и deploy/vps/README.md §8.
Если что-то не так
https://forbiddenstars.ruотдаёт заглушку/502 → туннель не поднят:docker compose logs tunnel(чаще: pubkey не вauthorized_keysна VPS, пустой/битыйTUNNEL_KEY_B64, либо на VPS занят слот 9000 → на VPSsudo fuser -k 9000/tcp, затемdocker compose restart tunnel).pullне проходит → проверьdocker login gitea.arseniev.infoи что реестр по HTTPS с валидным сертификатом (иначе хост в/etc/docker/daemon.json→insecure-registries,systemctl restart docker).