12 KiB
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)
- 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 # проверка
Лимиты памяти (memory cgroup)
Прошивка Raspberry Pi сама добавляет ядру cgroup_disable=memory. Без контроллера memory
Docker молча игнорирует mem_limit из docker-compose.yml (app 512m, tunnel 64m, backup
384m) и на каждый контейнер пишет «Your kernel does not support memory limit capabilities or
the cgroup is not mounted. Limitation discarded.». Тогда утечка или тяжёлый бэкап могут
занять всю RAM Pi, и OOM-killer прибьёт что попало.
Проверка — если есть вывод, лимиты не работают:
docker info 2>&1 | grep -i "no memory limit"
cat /sys/fs/cgroup/cgroup.controllers # в списке должно быть слово memory
Включить — дописать параметр в ту же единственную строку cmdline.txt (перевод строки
в этом файле ломает загрузку) и перезагрузить Pi. Прод на время перезагрузки недоступен,
контейнеры поднимутся сами:
sudo cp /boot/firmware/cmdline.txt /boot/firmware/cmdline.txt.bak
grep -q "cgroup_enable=memory" /boot/firmware/cmdline.txt || \
sudo sed -i '1 s/$/ cgroup_enable=memory/' /boot/firmware/cmdline.txt
cat /boot/firmware/cmdline.txt # одна строка, в конце cgroup_enable=memory
sudo reboot
После перезагрузки cgroup.controllers содержит memory, а docker info не пишет
No memory limit support. Если Pi не загрузился, верните бэкап: вставьте карту в ПК и на
разделе bootfs замените cmdline.txt содержимым cmdline.txt.bak.
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, у существующего админа само ничего не изменится.Смена пароля админа (плановая или при утечке) — осознанной командой, через панель его не сменить:
- поменять
ADMIN_PASSWORDв.envна Pi;docker compose up -d— пересоздастappс новым.env(контейнер читает.envтолько при создании; без этого шага команда ниже увидит старый пароль);docker compose exec app python -m app.bootstrap --reset-admin-password.Все админские сессии, в том числе чужие, если пароль утёк, после этого завершаются. Логин (
ADMIN_USERNAME) команда не меняет.
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 → на 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).docker compose upпишет «Your kernel does not support memory limit capabilities… Limitation discarded.» → лимиты памяти не работают: включите memory cgroup, раздел 1, «Лимиты памяти».