Реализован контракт /api/bot/* из servaki-webhub-bot/SPEC.md:
- GET /api/bot/health (публичный) -> {ok, site}
- GET /api/bot/publications?status=&limit= -> список works
- POST /api/bot/publications {title,body,media[]} -> черновик works -> {id}
- POST /api/bot/publications/:id/publish -> published -> {id,url}
- DELETE /api/bot/publications/:id -> удаление (media cascade)
- POST /api/bot/materials (multipart file) -> media -> {id,url}
Аутентификация Authorization: Bearer <BOT_API_TOKEN> (middleware/botAuth,
сравнение постоянного времени; 503 без токена в env, 401 при несовпадении).
Маппинг: публикация<->works, материал<->media. Миграция bot_adapter.sql
разрешает media.type='file'. multer 2.x.
Фикс Яндекс-redirect и домена: nika-gallery.ru -> 1nika.ru в .env.example
(YANDEX_REDIRECT_URI, FRONTEND_URL, SITE_URL, NEXT_PUBLIC_*, SMTP_USER) и
nginx server_name. Секреты не в git (client_id/secret - на Я.Диске).
Добавлен docs/DEPLOY.md (VPS runbook: DNS, .env, SSL, миграции, бот).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
156 lines
7.0 KiB
Markdown
156 lines
7.0 KiB
Markdown
# Деплой nika-gallery (1nika.ru)
|
||
|
||
Полный runbook подъёма сайта Ники на VPS: Docker Compose (backend Express + Next.js
|
||
фронт + Postgres 16 + nginx), вход через Яндекс ID и подключение к единому боту флота
|
||
(`@servaki_online_bot`) через сайт-адаптер `/api/bot/*`.
|
||
|
||
> ⚠️ **Живой VPS-деплой — решение владельца** (выбор ноды и DNS). Этот документ —
|
||
> команды и чек-лист; агент репо готовит, но на боевой узел не раскатывает.
|
||
|
||
---
|
||
|
||
## 0. Что уже готово в репо
|
||
- `docker-compose.yml` — db / backend / frontend / nginx.
|
||
- `db/schema.sql` (+ `db/migrations/club_access.sql`, `db/migrations/bot_adapter.sql`).
|
||
- Вход через Яндекс ID (`backend/src/routes/oauth.js`), redirect на `1nika.ru`.
|
||
- Сайт-адаптер бота (`backend/src/routes/bot.js`, `middleware/botAuth.js`).
|
||
- nginx для `1nika.ru` / `www.1nika.ru`.
|
||
|
||
---
|
||
|
||
## 1. DNS
|
||
На домен **1nika.ru** (регистратор/DNS владельца) добавить A-записи на IP выбранной ноды:
|
||
|
||
```
|
||
1nika.ru. A <VPS_IP>
|
||
www.1nika.ru. A <VPS_IP>
|
||
```
|
||
|
||
Дождаться распространения (`dig +short 1nika.ru` должен вернуть `<VPS_IP>`).
|
||
|
||
---
|
||
|
||
## 2. Подготовка сервера
|
||
Нужны: Docker + docker compose plugin, открытые порты 80/443.
|
||
|
||
```bash
|
||
git clone http://192.168.1.120/german/nika-gallery.git /opt/nika-gallery
|
||
cd /opt/nika-gallery
|
||
git checkout main # либо feat/bot-adapter до мержа
|
||
cp .env.example .env
|
||
```
|
||
|
||
---
|
||
|
||
## 3. Заполнить `.env` (боевые значения — только на сервере, НЕ в git)
|
||
|
||
| Переменная | Значение | Откуда |
|
||
|---|---|---|
|
||
| `POSTGRES_PASSWORD` | сильный пароль | сгенерировать |
|
||
| `DATABASE_URL` | `postgresql://postgres:<POSTGRES_PASSWORD>@db:5432/nika_gallery` | собрать |
|
||
| `JWT_SECRET` / `JWT_REFRESH_SECRET` | `openssl rand -hex 32` (каждый свой) | сгенерировать |
|
||
| `YANDEX_CLIENT_ID` | `1f64f6ebcd9c45f9af78987204af8e36` | Я.Диск `YANDEX-OAUTH-APPS.secret.txt` [NIKA ART] |
|
||
| `YANDEX_CLIENT_SECRET` | `a2b737fc…` | там же |
|
||
| `YANDEX_REDIRECT_URI` | `https://1nika.ru/api/auth/yandex/callback` | (уже в .env.example) |
|
||
| `FRONTEND_URL` / `SITE_URL` | `https://1nika.ru` | — |
|
||
| `BOT_API_TOKEN` | `openssl rand -hex 32` | сгенерировать; **тот же** токен → в реестр бота (`sites.api_token`) + Я.Диск |
|
||
| `BOT_SITE_KEY` | `nika` | — |
|
||
| `NEXT_PUBLIC_API_URL` | `https://1nika.ru/api` | — |
|
||
| `OWNER_EMAIL`, `SMTP_*`, `TELEGRAM_*` | по факту | владелец |
|
||
|
||
⚠️ **Секреты не коммитить.** В git — только `.env.example` с пустыми полями.
|
||
`YANDEX_REDIRECT_URI` должен **точно** совпадать со значением в кабинете
|
||
`oauth.yandex.ru` (приложение NIKA ART) — иначе Яндекс вернёт `redirect_uri_mismatch`.
|
||
|
||
---
|
||
|
||
## 4. SSL-сертификат (Let's Encrypt)
|
||
nginx ждёт `nginx/certs/fullchain.pem` и `nginx/certs/privkey.pem`. Выпустить certbot'ом
|
||
(standalone до старта nginx, либо webroot):
|
||
|
||
```bash
|
||
# порт 80 должен быть свободен
|
||
docker run --rm -p 80:80 \
|
||
-v /opt/nika-gallery/letsencrypt:/etc/letsencrypt \
|
||
certbot/certbot certonly --standalone \
|
||
-d 1nika.ru -d www.1nika.ru --agree-tos -m <OWNER_EMAIL> --no-eff-email
|
||
|
||
mkdir -p /opt/nika-gallery/nginx/certs
|
||
cp /opt/nika-gallery/letsencrypt/live/1nika.ru/fullchain.pem nginx/certs/
|
||
cp /opt/nika-gallery/letsencrypt/live/1nika.ru/privkey.pem nginx/certs/
|
||
```
|
||
|
||
Продление — cron/systemd-timer с `certbot renew` + копированием в `nginx/certs/` и
|
||
`docker compose exec nginx nginx -s reload`.
|
||
|
||
---
|
||
|
||
## 5. Старт и миграции
|
||
`db/schema.sql` и `db/seed.sql` применяются автоматически при **первой** инициализации
|
||
тома Postgres (см. `docker-entrypoint-initdb.d` в compose). Миграции из `db/migrations/`
|
||
надо применить вручную (они аддитивны и идемпотентны).
|
||
|
||
```bash
|
||
docker compose build
|
||
docker compose up -d db
|
||
# дождаться healthy:
|
||
docker compose exec db pg_isready -U postgres
|
||
|
||
# Миграции (клуб + Яндекс ID, затем адаптер бота):
|
||
docker compose exec -T db psql -U postgres -d nika_gallery < db/migrations/club_access.sql
|
||
docker compose exec -T db psql -U postgres -d nika_gallery < db/migrations/bot_adapter.sql
|
||
|
||
docker compose up -d # поднять backend + frontend + nginx
|
||
```
|
||
|
||
Назначить владельца (Ника) ролью `owner`:
|
||
```bash
|
||
docker compose exec -T db psql -U postgres -d nika_gallery \
|
||
-c "UPDATE users SET role='owner' WHERE email='<nika_email>';"
|
||
```
|
||
|
||
---
|
||
|
||
## 6. Проверка
|
||
```bash
|
||
curl -s https://1nika.ru/api/health # {status:ok}
|
||
curl -s https://1nika.ru/api/bot/health # {ok:true, site:"nika"}
|
||
curl -s -o /dev/null -w '%{http_code}\n' \
|
||
https://1nika.ru/api/bot/publications # 401 (без токена)
|
||
curl -s -H "Authorization: Bearer $BOT_API_TOKEN" \
|
||
https://1nika.ru/api/bot/publications # [] или список
|
||
```
|
||
Вход через Яндекс: открыть `https://1nika.ru/api/auth/yandex` → авторизация →
|
||
редирект на `https://1nika.ru/club#token=…`.
|
||
|
||
---
|
||
|
||
## 7. Подключение к боту флота
|
||
После деплоя владелец в реестре бота (`sites` на finik,
|
||
`/var/lib/webhub-bot/state.db`) выставляет для `site_key='nika'`:
|
||
- `base_url = https://1nika.ru`
|
||
- `api_token = <BOT_API_TOKEN из .env>`
|
||
- `status = live` (сейчас `soon`).
|
||
|
||
Бот дергает `/api/bot/*` с заголовком `Authorization: Bearer <api_token>`. Контракт —
|
||
`servaki-webhub-bot/SPEC.md`.
|
||
|
||
---
|
||
|
||
## 8. Обновления
|
||
```bash
|
||
cd /opt/nika-gallery && git pull && docker compose build && docker compose up -d
|
||
```
|
||
(автоматизировано в `.gitea/workflows/deploy.yml` при push в `main`; требует
|
||
секретов раннера `DEPLOY_HOST`, `DEPLOY_KEY`).
|
||
|
||
## Приложение: карта ручек адаптера
|
||
| Метод | Путь | Auth | Ника |
|
||
|---|---|---|---|
|
||
| GET | `/api/bot/health` | — | `{ok,site}` |
|
||
| GET | `/api/bot/publications?status=&limit=` | Bearer | список works |
|
||
| POST | `/api/bot/publications` `{title,body,media[]}` | Bearer | works(draft) → `{id}` |
|
||
| POST | `/api/bot/publications/:id/publish` | Bearer | works.status=published → `{id,url}` |
|
||
| DELETE | `/api/bot/publications/:id` | Bearer | delete works (+media cascade) |
|
||
| POST | `/api/bot/materials` (multipart `file`) | Bearer | media → `{id,url}` |
|