Реализован контракт /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>
7.0 KiB
Деплой 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.
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):
# порт 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/
надо применить вручную (они аддитивны и идемпотентны).
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:
docker compose exec -T db psql -U postgres -d nika_gallery \
-c "UPDATE users SET role='owner' WHERE email='<nika_email>';"
6. Проверка
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.ruapi_token = <BOT_API_TOKEN из .env>status = live(сейчасsoon).
Бот дергает /api/bot/* с заголовком Authorization: Bearer <api_token>. Контракт —
servaki-webhub-bot/SPEC.md.
8. Обновления
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} |