удаление test-контура (дополнение)

This commit is contained in:
2026-09-14 20:50:40 +03:00
parent dafbf5bad4
commit 101c457f7e
27 changed files with 195 additions and 375 deletions
+9 -17
View File
@@ -1,6 +1,6 @@
# Публикация: домены, VPS, туннели
Приложение крутится дома (Pi — прод) и на твоём ПК (dev/test). Дома белого IP нет
Приложение крутится дома (Pi — прод) и на твоём ПК (dev). Дома белого IP нет
(CGNAT), поэтому наружу выставляем через **VPS-привратник**: на нём Caddy терминирует
HTTPS твоими сертификатами и проксирует трафик в SSH reverse-туннели, которые
приложение само открывает к VPS.
@@ -11,10 +11,8 @@ HTTPS твоими сертификатами и проксирует трафи
│ ▲ туннель-КОНТЕЙНЕР │
│ └── Pi : app:8000 PROD │
forbidden-stars.ru ──►│ :443 (cert твой) → 127.0.0.1:9001 │
│ ▲ контейнер (test) ИЛИ │
│ ▲ ssh с ПК (dev) │
│ ├── ПК test : app:8000 │
│ └── ПК dev : vite:5173 │
│ ▲ ssh с ПК (по требованию) │
│ └── ПК dev : vite:5173 DEV │
└───────────────────────────────────────────────────┘
```
@@ -23,32 +21,26 @@ HTTPS твоими сертификатами и проксирует трафи
хост нет** — наружу его выставляет только туннель-контейнер (`ssh -R 9000:app:8000` к VPS).
Работает постоянно: при обрыве `ssh` завершается, и Docker перезапускает контейнер
(`restart: unless-stopped`). См. [`pi/`](pi/README.md).
- **TEST** — ПК. То же самое: `docker compose -f docker-compose.test.yml up` собирает образы
локально и поднимает `app` + `tunnel` (`ssh -R 9001:app:8000`) + `backup` (без расписания и
без VPS). Портов на хост нет — тест виден только на `forbidden-stars.ru`. Обычно
запускается лаунчером при `APP_ENV=test`.
- **DEV** — ПК, нативно (`uvicorn`+`vite`). По умолчанию только на localhost; при
`LOCAL_PUBLIC=vps` лаунчер (`run.ps1` / `run.sh`) дополнительно поднимает SSH-туннель с ПК
(`ssh -R 9001:localhost:5173`) → дев виден на `forbidden-stars.ru`.
- DEV и TEST делят слот **9001** (`forbidden-stars.ru`) → поднимай что-то **одно за раз**.
PROD на отдельном слоте **9000** (`forbiddenstars.ru`) — работает независимо.
(`ssh -R 9001:localhost:5173`) → дев виден на `forbidden-stars.ru` (слот **9001**).
- PROD на отдельном слоте **9000** (`forbiddenstars.ru`) — работает независимо от dev.
Временный прод на ПК (`docker-compose.temp.yml`) тоже занимает **9000** — одновременно с Pi нельзя.
Ключи туннеля (приватные, в git не идут; публичные части — в `authorized_keys` пользователя
`tunnel` на VPS):
- **Pi** — `TUNNEL_KEY_B64` (base64 приватного ключа) в `.env`; файла ключа на Pi нет.
- **ПК, test и временный прод** — файл `deploy/tunnel/id_tunnel`, монтируется в туннель-контейнер.
- **ПК, временный прод** — файл `deploy/tunnel/id_tunnel`, монтируется в туннель-контейнер.
- **ПК, dev** — `run.ps1`/`run.sh` зовут системный `ssh` без `-i`, то есть с ключом по
умолчанию из `~/.ssh`. Он должен быть в `authorized_keys` (можно тем же, что `id_tunnel`).
Настройка по шагам:
1. **VPS** — [`vps/README.md`](vps/README.md): Caddy, файрвол, пользователь `tunnel`, сертификаты, `Caddyfile`.
2. **Pi (прод)** — [`pi/README.md`](pi/README.md): ключ туннеля в `TUNNEL_KEY_B64`, `.env`, `docker compose up -d`.
3. **ПК (dev/test)** — ключ в `deploy/tunnel/id_tunnel` (для test-контейнера) и/или ключ по
умолчанию в `~/.ssh` (для dev-туннеля); pubkey — в `authorized_keys` у `tunnel@VPS`.
3. **ПК (dev)** — ключ по умолчанию в `~/.ssh` (для dev-туннеля) и, если нужен временный прод,
файл `deploy/tunnel/id_tunnel`; pubkey — в `authorized_keys` у `tunnel@VPS`.
4. **Бэкапы** — [`backup/README.md`](backup/README.md): контейнер `backup` (restic) делает
снимки на Pi и на VPS (`fsbackup@VPS`, только SFTP), скрипты ПК скачивают их и проверяют
восстановление на тест-клоне.
снимки на Pi и на VPS (`fsbackup@VPS`, только SFTP), скрипты ПК скачивают их на ПК.
Секреты не в git: сертификаты/ключи (`*.pem`, `*.key`, `id_tunnel*`, `id_backup*`) живут на
VPS/Pi/ПК, в репозитории только `Caddyfile`, страница-заглушка, образы `deploy/tunnel/` и
+31 -33
View File
@@ -16,7 +16,7 @@
4. [Сборка и публикация образов](#шаг-4-сборка-и-публикация-образов) — ПК
5. [Pi: включить бэкапы](#шаг-5-pi-включить-бэкапы) — Pi
6. [ПК: доступ к Pi и выгрузка бэкапов](#шаг-6-пк-доступ-к-pi-и-выгрузка-бэкапов) — ПК
7. [Учебное восстановление на тест-клоне](#шаг-7-учебное-восстановление-на-тест-клоне) — ПК
7. [Проверка скачанного архива](#шаг-7-проверка-скачанного-архива) — ПК
8. [Восстановление прода](#8-восстановление-прода) — Pi
9. [Катастрофа: Pi умер](#9-катастрофа-pi-умер) — новый Pi
10. [Повседневные действия](#10-повседневные-действия)
@@ -562,43 +562,42 @@
---
## Шаг 7. Учебное восстановление на тест-клоне
## Шаг 7. Проверка скачанного архива
**Где:** ПК с Docker Desktop. **Зачем:** убедиться, что бэкап действительно
восстанавливается, **до** того как это понадобится по-настоящему. Прод не затрагивается.
**Где:** ПК. **Зачем:** убедиться, что БД в скачанном снимке целая и в ней те данные, что
ожидаются, **до** того как это понадобится по-настоящему. Прод не затрагивается.
> Данные тест-клона на ПК будут заменены данными из архива. Прежние данные тест-клона
> сохраняются в его собственный снимок `pre-restore`.
> Отдельного тестового контейнера для учебного восстановления больше нет. Сам механизм
> `restore`/`import` отрабатывает только на Pi ([раздел 8](#8-восстановление-прода)); здесь
> проверяется содержимое архива.
1. Восстановите скачанный архив в тест-клон (подставьте имя своего файла):
1. Распакуйте последний скачанный архив во временную папку:
```powershell
.\scripts\fs-backup.ps1 restore-test -File backups\fs_20260914_0400_3f2a9c1d.tar
$f = (Get-ChildItem backups\fs_*.tar | Sort-Object LastWriteTime | Select-Object -Last 1).FullName
$d = Join-Path $env:TEMP "fs-check"; New-Item -ItemType Directory -Force $d | Out-Null
tar -xf $f -C $d
```
В первый раз Docker соберёт образы тест-клона — это несколько минут.
2. Откройте `%TEMP%\fs-check\forbidden_stars.db` в [DB Browser for SQLite](https://sqlitebrowser.org)
(вкладка «Выполнить SQL») и выполните:
2. Проверьте, что приложение тест-клона поднялось:
```sql
PRAGMA integrity_check;
SELECT (SELECT count(*) FROM users WHERE role = 'player') AS players,
(SELECT count(*) FROM matches) AS matches;
```
3. Закройте DB Browser и удалите временную папку — данные в ней не зашифрованы:
```powershell
docker compose -f docker-compose.test.yml ps
docker compose -f docker-compose.test.yml logs --tail 20 app
Remove-Item -Recurse -Force (Join-Path $env:TEMP "fs-check")
```
3. Посмотрите на сайт: в `.env` на ПК временно поставьте `APP_ENV=test` и запустите
`.\run.ps1`. Тест-клон откроется на `https://forbidden-stars.ru`: проверьте топ,
профили, историю партий. Потом верните `APP_ENV=development`.
**Что должно получиться:**
- в выводе `restore-test`:
- `Развёрнутые данные в порядке: игроков N, партий M.` — те же числа, что в `list` на Pi;
- `Данные восстановлены.`;
- `Done. The test clone now runs on the restored data.`;
- `docker compose ... ps` показывает `app` в состоянии `Up … (healthy)`;
- на сайте тест-клона — данные прода на момент снимка.
> Этим же способом можно восстановить в тест-клон старые архивы `fs_*.tar.gz` прежнего
> `scripts/backup.sh`: `.\scripts\fs-backup.ps1 restore-test -File backups\fs_20260710_140914.tar.gz`.
- `PRAGMA integrity_check` → `ok`;
- `players` и `matches` совпадают со столбцами `Игроков` / `Партий` этого снимка в `list` на Pi;
- в папке рядом с БД есть `uploads\…` (фото партий) и, если заводились, `achievements\…`.
---
@@ -651,8 +650,9 @@
Если локальный репозиторий повреждён или пуст, смотрите копию на VPS:
`docker compose exec backup fs-backup list vps`.
2. **По желанию, но рекомендуется:** сначала отрепетируйте на ПК:
`.\scripts\fs-backup.ps1 pull -Snapshot <ID>`, затем `restore-test` ([шаг 7](#шаг-7-учебное-восстановление-на-тест-клоне)).
2. **По желанию, но рекомендуется:** перед восстановлением проверьте выбранный снимок на ПК:
`.\scripts\fs-backup.ps1 pull -Snapshot <ID>`, затем [шаг 7](#шаг-7-проверка-скачанного-архива).
Заодно у вас останется копия этого снимка вне Pi.
3. Остановите приложение. Сайт покажет страницу «Технические шоколадки»:
@@ -806,7 +806,7 @@
- **Перед каждым обновлением прода** — `now -Tag before-update` (метка — латиница, цифры, `.`, `_`, `-`).
- **Раз в месяц:**
- скачать снимок на ПК (`pull`);
- раз в пару месяцев сделать учебное восстановление (`restore-test`);
- проверить скачанный архив ([шаг 7](#шаг-7-проверка-скачанного-архива));
- удалить с ПК старые архивы — они не зашифрованы.
- **Иногда:** посмотреть `docker compose ps`. Статус `unhealthy` у `backup` означает, что
бэкапы перестали проходить (причину покажет `fs-backup status`).
@@ -899,7 +899,7 @@ docker volume rm <имя тома>
| `WARNING: UNPROTECTED PRIVATE KEY FILE!` | у файла ключа слишком открытые права (ключ создан в Git Bash/WSL или скопирован) | `icacls <путь к ключу> /inheritance:r /grant:r "$($env:USERNAME):(R)"` |
| `Checksum mismatch … run pull again` | файл повредился при передаче | повторить `pull` (битый файл уже удалён) |
| `Already downloaded: …` | этот снимок уже скачан | ничего не делать; нужен новый — сначала `now`, потом `pull` |
| `restore-test`: `Import failed … The test clone data was not changed` | архив повреждён или неполный | скачать заново (`pull`); текст ошибки выше в выводе |
| шаг 7: `integrity_check` не `ok` или счётчики не совпадают с `list` | архив повреждён или скачан не тот снимок | удалить файл из `backups\` и скачать заново (`pull -Snapshot <ID>`); если повторяется — `fs-backup verify` на Pi |
---
@@ -935,11 +935,9 @@ docker volume rm <имя тома>
|---|---|
| `status`, `list [-Repo vps]`, `now [-Tag имя]`, `verify` | то же, что на Pi, но с ПК |
| `pull [-Snapshot ID] [-Repo vps]` | скачать снимок в `backups\` со сверкой sha256 |
| `restore-test -File <архив>` | учебное восстановление в локальный тест-клон |
| `-Target test` | выполнить `status`/`list`/`now`/`verify`/`pull` на локальном тест-клоне |
В bash-версии те же команды пишутся так: `list vps`, `now --tag имя`,
`pull <ID> --repo vps`, `restore-test <архив>`, `--test` первым аргументом.
`pull <ID> --repo vps`.
### Переменные `.env`
@@ -987,7 +985,7 @@ docker volume rm <имя тома>
- [ ] На Pi первый бэкап прошёл в `local` и `vps`, `status` без ошибок (шаг 5)
- [ ] `docker compose ps` показывает `backup` `(healthy)` (шаг 5)
- [ ] С ПК `status`, `list`, `pull` работают без пароля (шаг 6)
- [ ] Учебное восстановление на тест-клоне прошло, данные на месте (шаг 7)
- [ ] Скачанный архив проверен: БД целая, числа совпадают с `list` (шаг 7)
**Через сутки**
- [ ] В `list` появился снимок с меткой `scheduled` в 04:00
+1 -1
View File
@@ -10,7 +10,7 @@ VPS_TUNNEL_USER="${VPS_TUNNEL_USER:-tunnel}"
UPSTREAM="${UPSTREAM:-app:8000}"
# Источник приватного ключа: либо TUNNEL_KEY_B64 (base64 в .env — прод: только compose+env),
# либо смонтированный файл /key/id_tunnel (dev/test, где репозиторий есть на хосте).
# либо смонтированный файл /key/id_tunnel (временный прод на ПК, где репозиторий есть на хосте).
mkdir -p /root/.ssh
KEY=/root/.ssh/id_tunnel
if [ -n "${TUNNEL_KEY_B64:-}" ]; then
+5 -5
View File
@@ -2,7 +2,7 @@
# Два домена, ОБА с твоими сертификатами; проксируют в SSH-туннели:
#
# forbiddenstars.ru → 127.0.0.1:9000 ← Pi (autossh, постоянно) PROD
# forbidden-stars.ru → 127.0.0.1:9001 ← ПК (по требованию) DEV/TEST
# forbidden-stars.ru → 127.0.0.1:9001 ← ПК (по требованию) DEV
#
# Caddy сам терминирует TLS (он и есть edge: видит реального клиента), а вниз к
# приложению передаёт X-Forwarded-Proto=https / X-Forwarded-For / Host —
@@ -38,10 +38,10 @@
-Server
}
# Content-Security-Policy подготовлена, но ВЫКЛЮЧЕНА до проверки на test-клоне: строгая
# политика легко ломает SPA (инлайновые стили Vite), Telegram-виджет входа (скрипт с
# telegram.org + iframe oauth.telegram.org) и EventSource (/api/events). Раскомментировать
# после проверки на forbidden-stars.ru, что вход и реал-тайм работают (#61).
# Content-Security-Policy подготовлена, но ВЫКЛЮЧЕНА до проверки: строгая политика легко
# ломает SPA (инлайновые стили Vite), Telegram-виджет входа (скрипт с telegram.org + iframe
# oauth.telegram.org) и EventSource (/api/events). Раскомментировать после проверки,
# что вход и реал-тайм работают (#61).
# header Content-Security-Policy "default-src 'self'; script-src 'self' https://telegram.org https://oauth.telegram.org; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self'; frame-src https://oauth.telegram.org; font-src 'self' data:; base-uri 'self'; form-action 'self'; frame-ancestors 'none'"
# HTML-документ (навигации, Accept: text/html) НЕ кэшируем. Иначе браузер отдаёт старый
+5 -5
View File
@@ -1,11 +1,11 @@
# VPS (186.246.51.17) — реверс-прокси Caddy + точка входа SSH-туннелей
Единственная публичная точка. На VPS: Caddy терминирует HTTPS твоими сертификатами
для двух доменов и проксирует трафик в SSH reverse-туннели от Pi (прод) и ПК (dev/test).
для двух доменов и проксирует трафик в SSH reverse-туннели от Pi (прод) и ПК (dev).
```
forbiddenstars.ru → 127.0.0.1:9000 ← Pi (туннель-контейнер, постоянно) PROD
forbidden-stars.ru → 127.0.0.1:9001 ← ПК (контейнер test или ssh dev, по требованию) DEV/TEST
forbiddenstars.ru → 127.0.0.1:9000 ← Pi (туннель-контейнер, постоянно) PROD
forbidden-stars.ru → 127.0.0.1:9001 ← ПК (ssh из лаунчера, по требованию) DEV
```
> Туннель `ssh -R` по умолчанию слушает на loopback VPS (127.0.0.1) — ровно туда смотрит
@@ -106,8 +106,8 @@ systemctl reload caddy
> файл читается на каждый запрос).
## 7. Проверка
1. Подними туннель прода на Pi (см. `deploy/pi/README.md`) и/или dev/test на ПК
(лаунчер `run.ps1`: dev — при `LOCAL_PUBLIC=vps`, test — при `APP_ENV=test`).
1. Подними туннель прода на Pi (см. `deploy/pi/README.md`) и/или dev на ПК
(лаунчер `run.ps1` при `LOCAL_PUBLIC=vps`).
2. Открой `https://forbiddenstars.ru` и `https://forbidden-stars.ru`.
3. Пока соответствующий туннель не поднят — Caddy отдаёт страницу-заглушку «Технические шоколадки»
(HTTP 503), это ожидаемо.