Правки локального размещения дева, перенос тунеля в контейнер
This commit is contained in:
+25
-13
@@ -8,28 +8,40 @@ HTTPS твоими сертификатами и проксирует трафи
|
||||
```
|
||||
┌──────────── VPS 186.246.51.17 (Caddy) ───────────┐
|
||||
forbiddenstars.ru ───►│ :443 (cert твой) → 127.0.0.1:9000 │
|
||||
│ ▲ autossh (постоянно) │
|
||||
│ ▲ туннель-КОНТЕЙНЕР │
|
||||
│ └── Pi : app:8000 PROD │
|
||||
forbidden-stars.ru ──►│ :443 (cert твой) → 127.0.0.1:9001 │
|
||||
│ ▲ ssh (по требованию) │
|
||||
│ └── ПК : 5173(dev)/8080(test)│
|
||||
│ ▲ контейнер (test) ИЛИ │
|
||||
│ ▲ ssh с ПК (dev) │
|
||||
│ ├── ПК test : app:8000 │
|
||||
│ └── ПК dev : vite:5173 │
|
||||
└───────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- **PROD** — Pi, `forbiddenstars.ru`, туннель постоянный (autossh-служба). См. [`pi/`](pi/README.md).
|
||||
- **DEV/TEST** — ПК, `forbidden-stars.ru`, туннель по требованию: в `.env` поставь
|
||||
`LOCAL_PUBLIC=vps` и запусти `run.ps1` (dev → туннель на vite:5173; test → на :8080).
|
||||
Dev и test делят порт `9001` — по очереди.
|
||||
- Разделение окружений сохраняется на уровне приложения (свои `APP_ENV`, БД, контейнеры);
|
||||
домены разные, поэтому прод и dev/test могут работать одновременно.
|
||||
- **PROD** — Pi. `docker compose up -d` поднимает два сервиса: `app` + `tunnel`. У `app`
|
||||
**портов на хост нет** — наружу его выставляет только туннель-контейнер
|
||||
(`ssh -R 9000:app:8000` к VPS). Постоянно, Docker сам переподключает. См. [`pi/`](pi/README.md).
|
||||
- **TEST** — ПК. То же самое: `docker compose -f docker-compose.test.yml up` поднимает
|
||||
`app` + `tunnel` (`ssh -R 9001:app:8000`). Портов на хост нет — тест виден только на
|
||||
`forbidden-stars.ru`. Обычно запускается лаунчером при `APP_ENV=test`.
|
||||
- **DEV** — ПК, нативно (`uvicorn`+`vite`). По умолчанию только на localhost; при
|
||||
`LOCAL_PUBLIC=vps` лаунчер (`run.ps1`) дополнительно поднимает SSH-туннель с ПК
|
||||
(`ssh -R 9001:localhost:5173`) → дев виден на `forbidden-stars.ru`.
|
||||
- DEV и TEST делят слот **9001** (`forbidden-stars.ru`) → поднимай что-то **одно за раз**.
|
||||
PROD на отдельном слоте **9000** (`forbiddenstars.ru`) — работает независимо.
|
||||
|
||||
Ключ туннеля — **`deploy/tunnel/id_tunnel`** (приватный, в git не идёт). Его публичную
|
||||
часть добавь в `authorized_keys` пользователя `tunnel` на VPS. Один и тот же ключ годится
|
||||
для контейнерного туннеля (Pi/ПК) и для dev-туннеля `run.ps1`.
|
||||
|
||||
Настройка по шагам:
|
||||
1. **VPS** — [`vps/README.md`](vps/README.md): Caddy, файрвол, пользователь `tunnel`, сертификаты, `Caddyfile`.
|
||||
2. **Pi (прод)** — [`pi/README.md`](pi/README.md): Docker-приложение + autossh-служба туннеля.
|
||||
3. **ПК (dev/test)** — ключ для `tunnel@VPS` (`ssh-copy-id`), затем `LOCAL_PUBLIC=vps` + `run.ps1`.
|
||||
2. **Pi (прод)** — [`pi/README.md`](pi/README.md): ключ в `deploy/tunnel/id_tunnel`, `.env`, `docker compose up -d`.
|
||||
3. **ПК (dev/test)** — тот же ключ в `deploy/tunnel/id_tunnel` (для test-контейнера) и/или
|
||||
ключ по умолчанию для dev (`run.ps1`); pubkey — в `authorized_keys` у `tunnel@VPS`.
|
||||
|
||||
Секреты не в git: сертификаты/ключи (`*.pem`, `*.key`, SSH-ключи) живут на VPS/Pi/ПК,
|
||||
в репозитории только `Caddyfile` и юнит-шаблон.
|
||||
Секреты не в git: сертификаты/ключи (`*.pem`, `*.key`, `id_tunnel*`) живут на VPS/Pi/ПК,
|
||||
в репозитории только `Caddyfile`, `deploy/tunnel/` (образ туннеля) и шаблоны.
|
||||
|
||||
> Telegram-вход требует HTTPS-домен: у BotFather `/setdomain` укажи оба домена
|
||||
> (`forbiddenstars.ru` и `forbidden-stars.ru`).
|
||||
|
||||
+37
-29
@@ -1,38 +1,46 @@
|
||||
# Raspberry Pi — ПРОД (`forbiddenstars.ru`)
|
||||
|
||||
Pi запускает приложение в Docker и держит постоянный SSH reverse-туннель к VPS.
|
||||
Снаружи Pi недоступен (CGNAT) — публичная точка только VPS, Pi сам стучится наружу.
|
||||
Pi запускает приложение **и туннель** в Docker: `docker compose up` поднимает два сервиса —
|
||||
`app` (FastAPI + SPA) и `tunnel` (ssh-контейнер). У `app` **портов на хост нет**, наружу он
|
||||
доступен только через туннель-контейнер (`ssh -R 9000:app:8000` к VPS). Публичная точка —
|
||||
VPS, домен `forbiddenstars.ru`. Снаружи Pi недоступен (CGNAT) — туннель сам стучится наружу.
|
||||
|
||||
## 1. Приложение (Docker)
|
||||
Из ветки `main`:
|
||||
## 1. Ключ для туннеля
|
||||
Туннель-контейнер ходит на `tunnel@VPS` по ключу из `deploy/tunnel/id_tunnel` (gitignored).
|
||||
Создай ключ и добавь его pubkey в `authorized_keys` пользователя `tunnel` на VPS:
|
||||
```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
|
||||
# (или вручную: содержимое deploy/tunnel/id_tunnel.pub -> /home/tunnel/.ssh/authorized_keys на VPS)
|
||||
```
|
||||
|
||||
## 2. .env
|
||||
```bash
|
||||
cp .env.example .env # заполни секреты; APP_ENV контейнер форсит в production
|
||||
```
|
||||
Проверь, что заданы параметры VPS (их читает сервис `tunnel`):
|
||||
```
|
||||
VPS_TUNNEL_HOST=186.246.51.17
|
||||
VPS_TUNNEL_USER=tunnel
|
||||
```
|
||||
|
||||
## 3. Запуск
|
||||
```bash
|
||||
docker compose up -d --build # поднимет app + tunnel
|
||||
docker compose ps # оба up; tunnel стартует после healthy app
|
||||
docker compose logs -f tunnel # ждём строку: [tunnel] -R 9000:app:8000 -> tunnel@...
|
||||
```
|
||||
Открой `https://forbiddenstars.ru` — должен отдать прод.
|
||||
|
||||
## Обновление
|
||||
```bash
|
||||
git pull
|
||||
cp .env.example .env # заполни секреты; APP_ENV контейнер форсит в production
|
||||
docker compose up -d --build
|
||||
curl http://localhost:8000/api/health # → {"status":"ok"}
|
||||
```
|
||||
БД на томе `db-data`, миграции и бутстрап админа — автоматически (`entrypoint.sh`).
|
||||
|
||||
## 2. Ключ для туннеля (без пароля)
|
||||
```bash
|
||||
sudo apt -y install autossh
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_tunnel -N ""
|
||||
ssh-copy-id -i ~/.ssh/id_tunnel.pub tunnel@186.246.51.17
|
||||
# (или вручную добавь ~/.ssh/id_tunnel.pub в /home/tunnel/.ssh/authorized_keys на VPS)
|
||||
```
|
||||
Проверка туннеля вручную:
|
||||
```bash
|
||||
autossh -M 0 -N -R 9000:localhost:8000 -i ~/.ssh/id_tunnel tunnel@186.246.51.17
|
||||
# в другом окне/браузере открой https://forbiddenstars.ru — должно работать. Ctrl+C.
|
||||
```
|
||||
|
||||
## 3. Туннель как служба (автозапуск + переподключение)
|
||||
```bash
|
||||
sudo cp deploy/pi/forbidden-stars-tunnel.service /etc/systemd/system/
|
||||
sudoedit /etc/systemd/system/forbidden-stars-tunnel.service # поправь User= и путь к ключу
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now forbidden-stars-tunnel
|
||||
systemctl status forbidden-stars-tunnel
|
||||
```
|
||||
## Бэкап БД
|
||||
`scripts/backup.sh` (консистентная онлайн-копия SQLite; по cron на хосте). Том БД — `db-data`.
|
||||
|
||||
Готово: `https://forbiddenstars.ru` отдаёт прод. Логи туннеля: `journalctl -u forbidden-stars-tunnel -f`.
|
||||
Логи: `docker compose logs -f app` / `... tunnel`. Если `https://forbiddenstars.ru` отдаёт
|
||||
502 — значит туннель не поднят: смотри `docker compose logs tunnel` (чаще всего ключ не в
|
||||
`authorized_keys` на VPS или не заданы `VPS_TUNNEL_*` в `.env`).
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
# SSH reverse-туннель ПРОДА: VPS:9000 → Pi localhost:8000 (приложение в Docker).
|
||||
# Caddy на VPS отдаёт forbiddenstars.ru через этот туннель.
|
||||
#
|
||||
# Установка:
|
||||
# sudo cp deploy/pi/forbidden-stars-tunnel.service /etc/systemd/system/
|
||||
# # ПОПРАВЬ под себя: User=, путь к ключу (-i ...), при необходимости IP VPS.
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl enable --now forbidden-stars-tunnel
|
||||
# systemctl status forbidden-stars-tunnel
|
||||
[Unit]
|
||||
Description=Forbidden Stars: SSH reverse tunnel (prod) to VPS
|
||||
After=network-online.target docker.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
# Замени pi на своего пользователя Raspberry Pi (и путь к ключу ниже).
|
||||
User=pi
|
||||
Environment=AUTOSSH_GATETIME=0
|
||||
ExecStart=/usr/bin/autossh -M 0 -N \
|
||||
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 -o ExitOnForwardFailure=yes \
|
||||
-i /home/pi/.ssh/id_tunnel \
|
||||
-R 9000:localhost:8000 tunnel@186.246.51.17
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,2 @@
|
||||
# Приватный ключ монтируется в рантайме, в образ его НЕ кладём.
|
||||
id_tunnel*
|
||||
@@ -0,0 +1,8 @@
|
||||
# Крошечный контейнер SSH reverse-туннеля к VPS.
|
||||
# Держит -R <VPS_TUNNEL_PORT>:app:8000 на VPS, отдавая приложение по сети compose.
|
||||
# Переподключение обеспечивает Docker (restart: unless-stopped) при выходе ssh.
|
||||
FROM alpine:3.20
|
||||
RUN apk add --no-cache openssh-client
|
||||
COPY tunnel.sh /usr/local/bin/tunnel.sh
|
||||
RUN sed -i 's/\r$//' /usr/local/bin/tunnel.sh && chmod +x /usr/local/bin/tunnel.sh
|
||||
ENTRYPOINT ["/usr/local/bin/tunnel.sh"]
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/sh
|
||||
# SSH reverse-туннель из контейнера на VPS:
|
||||
# VPS:127.0.0.1:$VPS_TUNNEL_PORT -> $UPSTREAM (сервис app по сети compose)
|
||||
# Ключ монтируется снаружи в /key/id_tunnel (см. docker-compose*.yml).
|
||||
set -eu
|
||||
|
||||
: "${VPS_TUNNEL_HOST:?VPS_TUNNEL_HOST is required}"
|
||||
VPS_TUNNEL_USER="${VPS_TUNNEL_USER:-tunnel}"
|
||||
: "${VPS_TUNNEL_PORT:?VPS_TUNNEL_PORT is required}"
|
||||
UPSTREAM="${UPSTREAM:-app:8000}"
|
||||
|
||||
# Копируем ключ и выставляем строгие права (с хоста права могут не годиться, особенно с Windows).
|
||||
mkdir -p /root/.ssh
|
||||
cp /key/id_tunnel /root/.ssh/id_tunnel
|
||||
chmod 600 /root/.ssh/id_tunnel
|
||||
|
||||
echo "[tunnel] -R ${VPS_TUNNEL_PORT}:${UPSTREAM} -> ${VPS_TUNNEL_USER}@${VPS_TUNNEL_HOST}"
|
||||
exec ssh -N \
|
||||
-o BatchMode=yes \
|
||||
-o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
|
||||
-o ExitOnForwardFailure=yes \
|
||||
-o StrictHostKeyChecking=accept-new \
|
||||
-o UserKnownHostsFile=/root/.ssh/known_hosts \
|
||||
-i /root/.ssh/id_tunnel \
|
||||
-R "${VPS_TUNNEL_PORT}:${UPSTREAM}" \
|
||||
"${VPS_TUNNEL_USER}@${VPS_TUNNEL_HOST}"
|
||||
+28
-13
@@ -54,21 +54,36 @@ install -m 600 -o tunnel -g tunnel /dev/null /home/tunnel/.ssh/authorized_keys
|
||||
Pi (`deploy/pi/README.md`) и ПК (через `ssh-copy-id`).
|
||||
|
||||
## 5. Сертификаты
|
||||
|
||||
Caddy читает **PEM** (текст с `-----BEGIN CERTIFICATE-----`). Расширение неважно — `.crt`
|
||||
от GlobalSign обычно УЖЕ PEM. Проверь первой строкой: `head -1 файл.crt`.
|
||||
- если `-----BEGIN CERTIFICATE-----` → это PEM, конвертировать НЕ нужно;
|
||||
- если бинарь/абракадабра → это DER, сконвертируй: `openssl x509 -inform DER -in файл.crt -out файл.pem`.
|
||||
|
||||
На каждый домен нужны два файла:
|
||||
- `fullchain.pem` = твой сертификат (leaf) **+** промежуточный(е) GlobalSign, в одном файле,
|
||||
именно в таком порядке (сначала leaf, потом промежуточный).
|
||||
- `privkey.pem` = приватный ключ (`-----BEGIN PRIVATE KEY-----`, без пароля; обычно это `.key`).
|
||||
|
||||
Удобно собрать прямо на VPS — залей свои файлы и склей:
|
||||
```bash
|
||||
mkdir -p /etc/caddy/certs/forbiddenstars.ru /etc/caddy/certs/forbidden-stars.ru
|
||||
```
|
||||
Залей свои файлы с локальной машины (с каждого домена — свой `fullchain.pem` + `privkey.pem`):
|
||||
```bash
|
||||
# на локальной машине:
|
||||
scp fullchain.pem privkey.pem root@186.246.51.17:/etc/caddy/certs/forbiddenstars.ru/
|
||||
scp fullchain.pem privkey.pem root@186.246.51.17:/etc/caddy/certs/forbidden-stars.ru/
|
||||
```
|
||||
- `fullchain.pem` = твой сертификат + промежуточные GlobalSign **одним файлом** (сначала leaf, затем chain).
|
||||
- `privkey.pem` = приватный ключ (без пароля).
|
||||
```bash
|
||||
# на VPS — права:
|
||||
chown -R caddy:caddy /etc/caddy/certs && chmod -R 640 /etc/caddy/certs
|
||||
mkdir -p /etc/caddy/certs/forbidden-stars.ru /root/certs-tmp
|
||||
# с локальной машины (пример для домена forbidden-stars.ru):
|
||||
scp forbidden-stars.crt intermediate.crt forbidden-stars.key root@186.246.51.17:/root/certs-tmp/
|
||||
# на VPS — собрать fullchain (leaf + промежуточный) и положить ключ:
|
||||
cat /root/certs-tmp/www_forbidden_stars_ru_2026_12_31.crt /root/certs-tmp/intermediate_pem_globalsign_ssl_dv_free_1.crt \
|
||||
> /etc/caddy/certs/forbidden-stars.ru/fullchain.pem
|
||||
cp /root/certs-tmp/forbidden-stars.key /etc/caddy/certs/forbidden-stars.ru/privkey.pem
|
||||
# то же для forbiddenstars.ru (свои crt/intermediate/key), затем права
|
||||
# (папкам нужен x-бит → 750, файлам 640; иначе пользователь caddy не прочитает):
|
||||
chown -R caddy:caddy /etc/caddy/certs
|
||||
find /etc/caddy/certs -type d -exec chmod 750 {} \;
|
||||
find /etc/caddy/certs -type f -exec chmod 640 {} \;
|
||||
rm -rf /root/certs-tmp
|
||||
```
|
||||
> Если GlobalSign дал не отдельный промежуточный, а готовый bundle/chain — используй его
|
||||
> как «промежуточную» часть. Если дал один файл, где leaf и chain уже вместе, — это и есть
|
||||
> `fullchain.pem` (склейка не нужна).
|
||||
|
||||
## 6. Caddyfile
|
||||
Скопируй `deploy/vps/Caddyfile` из репозитория в `/etc/caddy/Caddyfile`, затем:
|
||||
|
||||
Reference in New Issue
Block a user