From d81f5d8964eb28adf8ee12af0b026c846579a5ca Mon Sep 17 00:00:00 2001 From: NotBigGhost Date: Mon, 29 Jun 2026 22:00:10 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=BA=D0=B8=20Pi:=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D0=B0=D1=8F=20=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D1=8F=20=D1=81=20=D0=BD=D1=83=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=20=D0=B4=D0=B5=D0=BF=D0=BB=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=B8=D0=B7=202=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2;=20?= =?UTF-8?q?=D0=BA=D0=BB=D1=8E=D1=87=20=D1=82=D1=83=D0=BD=D0=BD=D0=B5=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B8=D1=80=D1=83=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=BD=D0=B0=20Pi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .env.example | 2 +- deploy/pi/README.md | 117 +++++++++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/.env.example b/.env.example index f877d0c..82f46cf 100644 --- a/.env.example +++ b/.env.example @@ -31,7 +31,7 @@ VPS_TUNNEL_USER=tunnel # (без файла deploy/tunnel/id_tunnel). Нужен ТОЛЬКО для прод-контейнера на Pi; для dev/test # на ПК ключ берётся из файла. Сгенерируй ключ на ПК и закодируй БЕЗ переносов строк: # Git Bash / Linux: base64 -w0 deploy/tunnel/id_tunnel -# PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes("deploy/tunnel/id_tunnel")) +# PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path "deploy/tunnel/id_tunnel"))) # Pubkey (deploy/tunnel/id_tunnel.pub) добавь в authorized_keys у tunnel@VPS. TUNNEL_KEY_B64= diff --git a/deploy/pi/README.md b/deploy/pi/README.md index 7c9d0ee..24cd037 100644 --- a/deploy/pi/README.md +++ b/deploy/pi/README.md @@ -7,48 +7,70 @@ `docker compose up` поднимает два контейнера: `app` (FastAPI+SPA, портов на хост нет) и `tunnel` (`ssh -R 9000:app:8000` к VPS). Публичная точка — VPS, домен `forbiddenstars.ru` -(Pi за CGNAT, туннель стучится наружу сам). - -> Предпосылки: VPS настроен (Caddy `forbiddenstars.ru` → :9000, юзер `tunnel`) — см. -> `deploy/vps/README.md`. +(Pi за CGNAT — туннель стучится наружу сам). --- -## A. На ПК (один раз + при каждом релизе) +## 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 не найдёт образы в реестре. -**Ключ туннеля и его base64 для `.env`** (один раз): -```bash -ssh-keygen -t ed25519 -f deploy/tunnel/id_tunnel -N "" -ssh-copy-id -i deploy/tunnel/id_tunnel.pub tunnel@186.246.51.17 # pubkey → authorized_keys на VPS -# base64 приватного ключа БЕЗ переносов строк (значение для TUNNEL_KEY_B64 в .env): -# Git Bash / Linux: base64 -w0 deploy/tunnel/id_tunnel -# PowerShell: [Convert]::ToBase64String([IO.File]::ReadAllBytes("deploy/tunnel/id_tunnel")) -``` - -**Сборка и пуш образов под arm64** (при каждом релизе): -```powershell -docker login gitea.arseniev.info -.\scripts\build-push.ps1 # *nix: bash scripts/build-push.sh -``` - -## B. На Pi - -**1. Docker (один раз):** +## 1. Система и Docker на Pi ```bash +ssh pi@ sudo apt update && sudo apt -y full-upgrade -curl -fsSL https://get.docker.com | sudo sh +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) +sudo systemctl enable --now docker # автозапуск после ребута +newgrp docker # применить группу (или перезайти по SSH) +docker version && docker compose version # проверка ``` -**2. Положить два файла** (любой каталог, напр. `~/forbidden-stars`): +## 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 -# docker-compose.yml из Gitea (raw); branch — main (или dev, если ещё не смёржено): +# branch — main (или dev, если ещё не смёржено в main): curl -fsSLO https://gitea.arseniev.info/NotBigGhost/ForbiddenStarsApp/raw/branch/main/docker-compose.yml -# .env: можно взять шаблон оттуда же и заполнить -curl -fsSL https://gitea.arseniev.info/NotBigGhost/ForbiddenStarsApp/raw/branch/main/.env.example -o .env +curl -fsSL https://gitea.arseniev.info/NotBigGhost/ForbiddenStarsApp/raw/branch/main/.env.example -o .env nano .env ``` Заполнить в `.env`: @@ -56,24 +78,28 @@ nano .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_TOKEN=... # бот @BotFather; затем /setdomain → forbiddenstars.ru TELEGRAM_BOT_USERNAME=... VPS_TUNNEL_HOST=186.246.51.17 -TUNNEL_KEY_B64=... # base64 ключа из шага A (одной строкой) +TUNNEL_KEY_B64=... # длинная строка base64 из шага 2 (целиком, одной строкой) IMAGE_REGISTRY=gitea.arseniev.info/notbigghost # уже значение по умолчанию IMAGE_TAG=latest ``` -`APP_ENV` менять не нужно (контейнер форсит `production`); `VPS_TUNNEL_PORT` — тоже (прод → 9000). +`APP_ENV` (форсится в `production`) и `VPS_TUNNEL_PORT` (прод → 9000) не трогать. -**3. Запуск:** +## 4. Запуск ```bash -docker login gitea.arseniev.info # один раз (доступ к реестру) -docker compose up -d # pull_policy: always → сам тянет образы, без сборки +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@... +docker compose logs -f tunnel # ждём строку: [tunnel] -R 9000:app:8000 -> tunnel@... ``` На старте контейнер сам применит миграции, засидит справочники и создаст админа из `.env`. -Открой `https://forbiddenstars.ru`. У @BotFather `/setdomain` → добавь `forbiddenstars.ru`. + +## 5. Проверка +- Открой `https://forbiddenstars.ru` — должно отдать приложение (не заглушку). +- У @BotFather `/setdomain` → добавь `forbiddenstars.ru` (иначе Telegram-вход не заработает). +- Админка: удержать «Меню» 10 с → `/admin/login`, войти логином/паролём из `.env`. ## Обновление ```bash @@ -82,18 +108,17 @@ docker compose logs -f tunnel # ждём: [tunnel] -R 9000:app:8000 -> tu ``` ## Автозапуск после перезагрузки -Обеспечен: `systemctl enable docker` + `restart: unless-stopped`. После `sudo reboot` -контейнеры поднимаются сами (используют локальный образ, без повторного pull). +Уже обеспечен: `systemctl enable docker` + `restart: unless-stopped`. После `sudo reboot` +контейнеры поднимутся сами (используют локальный образ, без повторного pull). ## Бэкап (опционально) -Бэкап-скриптам нужен доступ к Docker и к самому `docker-compose.yml` (он уже на Pi). Скопируй -рядом ещё `scripts/backup.sh` и `scripts/restore.sh` (или склонируй репозиторий для бэкапов). -Затем — отдельный ключ `backup@VPS`, `BACKUP_VPS_HOST` в `.env`, cron. Подробнее — комментарии -в `scripts/backup.sh` и `deploy/vps/README.md` §8. Восстановление: `scripts/restore.sh <архив>`. +Бэкап-скриптам нужен сам `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 - → `sudo fuser -k 9000/tcp` на VPS, затем `docker compose restart 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`).