В prod пароль админа задавался только при первом создании: обычный bootstrap его из .env не берёт, панель не меняет — комментарии отсылали друг к другу. Теперь `python -m app.bootstrap --reset-admin-password` применяет ADMIN_PASSWORD к существующему админу через user_service.set_password: пароль проверяется, token_version растёт, и все админские сессии (в том числе чужие) отзываются. Обычный старт по-прежнему пароль не трогает. Процедура на Pi — в deploy/pi/README.md (правка .env → docker compose up -d → exec команды), кратко — в README. #73 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
Публикация: домены, VPS, туннели
Приложение крутится дома (Pi — прод) и на твоём ПК (dev). Дома белого IP нет (CGNAT), поэтому наружу выставляем через VPS-привратник: на нём Caddy терминирует HTTPS твоими сертификатами и проксирует трафик в SSH reverse-туннели, которые приложение само открывает к VPS.
┌──────────── VPS 186.246.51.17 (Caddy) ───────────┐
forbiddenstars.ru ───►│ :443 (cert твой) → 127.0.0.1:9000 │
│ ▲ туннель-КОНТЕЙНЕР │
│ └── Pi : app:8000 PROD │
forbidden-stars.ru ──►│ :443 (cert твой) → 127.0.0.1:9001 │
│ ▲ ssh с ПК (по требованию) │
│ └── ПК dev : vite:5173 DEV │
└───────────────────────────────────────────────────┘
- PROD — Pi.
docker compose up -dподнимает три сервиса:app+tunnel+backup(образы из Gitea-реестра, собираются на ПКscripts/build-push.ps1). Уappпортов на хост нет — наружу его выставляет только туннель-контейнер (ssh -R 9000:app:8000к VPS). Работает постоянно: при обрывеsshзавершается, и Docker перезапускает контейнер (restart: unless-stopped). См.pi/. - DEV — ПК, нативно (
uvicorn+vite). По умолчанию только на localhost; приLOCAL_PUBLIC=vpsлаунчер (run.ps1/run.sh) дополнительно поднимает SSH-туннель с ПК (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 нет. - ПК, временный прод — файл
deploy/tunnel/id_tunnel, монтируется в туннель-контейнер. - ПК, dev —
run.ps1/run.shзовут системныйsshбез-i, то есть с ключом по умолчанию из~/.ssh. Он должен быть вauthorized_keys(можно тем же, чтоid_tunnel).
Настройка по шагам:
- VPS —
vps/README.md: Caddy, файрвол, пользовательtunnel, сертификаты,Caddyfile. - Pi (прод) —
pi/README.md: ключ туннеля вTUNNEL_KEY_B64,.env,docker compose up -d. - ПК (dev) — ключ по умолчанию в
~/.ssh(для dev-туннеля) и, если нужен временный прод, файлdeploy/tunnel/id_tunnel; pubkey — вauthorized_keysуtunnel@VPS. - Бэкапы —
backup/README.md: контейнерbackup(restic) делает снимки на Pi и на VPS (fsbackup@VPS, только SFTP), скрипты ПК скачивают их на ПК.
Секреты не в git: сертификаты/ключи (*.pem, *.key, id_tunnel*, id_backup*) живут на
VPS/Pi/ПК, в репозитории только Caddyfile, страница-заглушка, образы deploy/tunnel/ и
deploy/backup/ и шаблоны.
Telegram-вход требует HTTPS-домен: у BotFather
/setdomainукажи оба домена (forbiddenstars.ruиforbidden-stars.ru).
Реал-тайм (SSE)
Приложение шлёт события «данные изменились» через SSE: GET /api/events
(text/event-stream). Это обычный HTTP — проходит через Caddy и SSH-туннель без доп.
настроек, КРОМЕ одного: для пути /api/events в Caddyfile выделен отдельный handle
без encode и с flush_interval -1 (иначе сжатие/буферизация задержат поток).
После правки Caddyfile на VPS: sudo systemctl reload caddy (или restart).
Шина событий — in-memory, рассчитана на один процесс (uvicorn --workers 1, как в
контейнере). Если когда-нибудь поднимешь несколько воркеров/реплик — шину нужно вынести во
внешний брокер (Redis pub/sub), иначе события увидит только тот воркер, что принял мутацию.
SSE-поток сам не закрывается, поэтому uvicorn запускается с --timeout-graceful-shutdown
(10 с в entrypoint.sh, 2 с в run.*). Без него остановка ждёт закрытия всех соединений: в dev
--reload при открытой вкладке висит вечно, в контейнере остановку обрывает только SIGKILL
по stop_grace_period.