# 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@ 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 прибьёт что попало. Проверка — если есть вывод, лимиты не работают: ```bash docker info 2>&1 | grep -i "no memory limit" cat /sys/fs/cgroup/cgroup.controllers # в списке должно быть слово memory ``` Включить — дописать параметр **в ту же единственную строку** `cmdline.txt` (перевод строки в этом файле ломает загрузку) и перезагрузить Pi. Прод на время перезагрузки недоступен, контейнеры поднимутся сами: ```bash 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: ```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`). - `docker compose up` пишет «Your kernel does not support memory limit capabilities… Limitation discarded.» → лимиты памяти не работают: включите memory cgroup, раздел 1, «Лимиты памяти».