diff --git a/README.md b/README.md index 753a4c2..0dda0b6 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ signature.py HMAC подпись/проверка вебхуков (s2s + входящие gnexus-auth) worker/ Celery: celery_app, tasks, senders (s2s) alembic/ миграции (env.py читает DATABASE_URL из .env) -docs/ docs/04 — схема БД, docs/05 — контракт Ingestion API +docs/ docs/04 — схема БД, docs/05 — контракт Ingestion API, docs/06 — настройки+PWA, docs/07 — MCP, docs/08 — рантбук развёртывания (для ИИ-агента) frontend/ Vue 3 SPA админки (сборка кладётся в spa_static/ образа) docker/entrypoint.sh режимы api (миграции+uvicorn) / worker (celery) docker-compose.yml api, worker, postgres, redis diff --git a/docs/06-settings-pwa.md b/docs/06-settings-pwa.md index 13b9852..ca5ed83 100644 --- a/docs/06-settings-pwa.md +++ b/docs/06-settings-pwa.md @@ -109,6 +109,8 @@ `public` (base64url без-padding) — в «Настройки → Web Push → VAPID public key», `private` (DER в base64url) — в VAPID private key (write-only); воркер передаёт её через `pywebpush` → `Vapid.from_string` (raw/DER в base64url — сам pywebpush PEM-строки в этом формате не разбирает). `push_subject` — `mailto:you@gnexus.space`. +Пара секретная: сразу после генерации скопировать оба ключа в **gnexus-creds** (правило «секреты — в .env + gnexus-creds»; из UI приватный ключ назад не читается). Развёртывание на проде — шаг 6 рантбука docs/08. + > ⚠️ Смена приватного VAPID-ключа инвалидирует все подписки браузеров — после смены пользователи перезакажут подписку тумблером в разделе «Настройки». --- diff --git a/docs/07-mcp.md b/docs/07-mcp.md index 0029478..370fce4 100644 --- a/docs/07-mcp.md +++ b/docs/07-mcp.md @@ -52,7 +52,7 @@ | Правила | `rules_list` (enabled, include_archived) · `rule_create` · `rule_patch` · `rule_archive` · `rule_restore` | | Поток | `events_list` (status, source_name) · `event_get` · `deliveries_list` (status, channel) | | Push | `push_subscriptions_list` (user_id) · `push_subscription_delete` (жёстко — устройства пользователей) | -| Настройки | `settings_get` (секреты write-only: value=null, факт в set) · `settings_put` (правила docs/06: "" — не менять, null — сброс) | +| Настройки | `settings_get` (секреты write-only: value=null, факт в set) · `settings_put` (правила docs/06: "" — не менять, null — сброс; настройка push-канала — рецепт VAPID в docs/06 «Как задать VAPID», выполняется shell'ом в контейнере) | | Контроль | `send_test_event` (source_name, subject, action, payload, priority) — полный маршрут в воркере | ## Архив вместо удаления (семантика DELETE) diff --git a/docs/08-deploy-runbook.md b/docs/08-deploy-runbook.md new file mode 100644 index 0000000..ffc12cb --- /dev/null +++ b/docs/08-deploy-runbook.md @@ -0,0 +1,158 @@ +# 08 · Рантбук развёртывания Synapse (для ИИ-агента) + +Пошаговая инструкция для ИИ-агента, разворачивающего Synapse на проде. +Агент работает на целевом хосте с shell-доступом (docker, git). Всё, что +не определено этим документом и `.env`-примером — **подтверждать у владельца, +не выбирать молча** (см. открытые вопросы в CLAUDE.md). + +## 0. Подтвердить у владельца до старта + +1. **Хост** — VM на libvirt или существующий VPS (открытый вопрос №1 CLAUDE.md). +2. **Домен** — synapse.gnexus.space? (публичный вход через nginx gnexus.space, + внутренняя сеть 192.168.1.0/24). +3. **Telegram-бот** — новый или существующий токен (вопрос №2; слот + `telegram_bot_token` создаётся через админку, канал #29). +4. **OAuth-клиент в gnexus-auth** — зарегистрирован ли; если нет, кто создаёт + (владелец на сервере auth или агент с доступом к админке auth). Понадобятся: + redirect URI = `https://<домен>/auth/callback`, скоупы + `openid email profile roles permissions`, вебхук `auth.logout` / + `auth.global_logout` / `session.revoked` → `GAUTH_WEBHOOK_SECRET`. + +## 1. Предполёт (checklist) + +- [ ] docker + docker compose v2 на целевом хосте. +- [ ] git-доступ к `git.gnexus.space/git/root/gn-synapse.git`. +- [ ] DNS/hosts: домен Synapse резолвится; gnexus-auth доступен с хоста + (прод-URL — в `GAUTH_BASE_URL`, сейчас 192.168.1.167). +- [ ] Свободный порт на хосте (`SYNAPSE_PORT`, дефолт 8013). Публично + сервится nginx'ом gnexus.space на 80/443. +- [ ] На хосте есть место под том `postgres_data` (основное хранилище, см. + docs/04-database.md). + +## 2. Код и окружение + +```bash +git clone https://git.gnexus.space/git/root/gn-synapse.git gnsynapse +cd gnsynapse +cp .env.example .env +``` + +Полный список переменных — в `.env.example` (он же шаблон; там комментарии +по каждой). Обязательные к заполнению: + +| Переменная | Откуда значение | +|---|---| +| `POSTGRES_PASSWORD`, `DATABASE_URL` | `openssl rand -hex 16` в пароль (одинаковый в обоих полях) | +| `GAUTH_BASE_URL` | URL prod-gnexus-auth (192.168.1.167), согласовать схему (http/https) | +| `GAUTH_CLIENT_ID` / `GAUTH_CLIENT_SECRET` | зарегистрированный OAuth-клиент в gnexus-auth (шаг 0.4) | +| `GAUTH_REDIRECT_URI` | `https://<домен>/auth/callback` | +| `SPA_PUBLIC_URL` | пусто (тот же origin — SPA раздаётся этим же контейнером, docs/06) | +| `GAUTH_WEBHOOK_SECRET` | секрет вебхука клиента в gnexus-auth (показывается один раз) | +| `MCP_TOKEN` | `openssl rand -hex 32` (пусто → MCP выключен; токен = как API-ключ) | +| `SYNAPSE_PORT` | порт API на хосте (дефолт 8013) | +| `S2S_SECRET_*` | для каждой s2s-цели (Navi): `openssl rand -hex 16`; `channel_targets.config.token_ref` ссылается на имя переменной (docs/05) | + +Секреты (пароль БД, `GAUTH_CLIENT_SECRET`, `GAUTH_WEBHOOK_SECRET`, +`MCP_TOKEN`, `S2S_SECRET_*`) — скопировать в репозиторий **gnexus-creds**. + +## 3. Подъём + +```bash +docker compose up -d --build +``` + +- Entrypoint api сам применяет миграции Alembic. Проверка: + `docker compose exec api alembic current` → головная ревизия + (см. последнюю в `alembic/versions/`). +- `curl localhost:8013/api/healthz` → `{"status": "ok"}`. +- Воркер жив: `docker compose exec redis redis-cli ping` и логи + `docker compose logs worker | grep -i ready`. +- SPA открылась: `curl -s localhost:8013/ | head -c 200` (index.html). + +## 4. Публичный вход (nginx gnexus.space) + +Location на gnexus.space nginx → proxy на `:8013`, TLS по сертификату +домена. Важно для MCP и SSE-стримов: + +```nginx +location / { + proxy_pass http://:8013; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_buffering off; # SSE (/mcp) и стриминг без буферизации + proxy_read_timeout 300s; +} +``` + +Smoke: `https://<домен>/` открывает SPA, `GET /api/healthz` зелёный. +`GET /mcp` без токена → 401 (MCP включён) или 200 index (не включён) — не 500. + +## 5. SSO smoke + +1. Открыть `https://<домен>/` → редирект на gnexus-auth → логин владельца + (роль `admin`/`superadmin`) → колбэк → дашборд. +2. Пользователь роли `user` → попадает в личный раздел «Мои события», в + админку не пускается ни сервером, ни роутером. +3. Логаут (`POST /auth/logout` + вебхук из gnexus-auth) завершает сессию: + через ~45 сек SPA после 401 уходит на `/login` (single sign-out, CLAUDE.md). + +## 6. Push-канал (если включаем сразу) + +1. Сгенерировать пару VAPID по рецепту docs/06-settings-pwa.md («Как задать + VAPID»): `docker compose exec api python3 - </mcp \ + --header "Authorization: Bearer " +``` + +Приёмка конвейера целиком — через MCP-тулы (docs/07-mcp.md, каталог 31 тул): +`system_status` → `source_create` → `key_issue` (plaintext один раз — сразу +передать владельцу в его .env/gnexus-creds) → `type_register` → +`target_create` (internal_log; телеграм — см. вопрос №2) → `rule_create` → +`send_test_event` → `deliveries_list` (статус delivered). +Definition of Done (CLAUDE.md): приём 202 → доставка ≥ 2 каналов → +лог доставки в БД; s2s в Navi — по задаче #28. + +## 8. После деплоя + +- Задокументировать сервис в gnexus-book (через MCP-инструменты). +- Статусы задач gntodo (#27 тестирование и деплой → сделано; заметки о + нештатных решениях — в описания задач). +- Перед каждым будущим обновлением — `pg_dump` тома/БД (откат миграций + вниз не поддерживается всеми ревизиями — опора на дамп). + +## Ловушки (известные) + +- **Миграции**: применяются автоматически при старте api; если `alembic + current` не доехал до головы — смотреть `docker compose logs api`. +- **MCP_TOKEN пуст** → `/mcp` просто нет: GET отдаёт SPA, POST — 405. Это не + поломка — так выглядит выключенный MCP. +- **TLS gnexus-auth**: в LAN self-signed → `GAUTH_VERIFY_TLS=false`; в проде + с нормальным сертификатом — `true` обязательно. +- **Токены opaque**: каждый запрос API ходит «call home» в gnexus-auth — + недоступность auth-сервера = недоступен весь Synapse, держать в мониторинге. +- **Секреты s2s**: доставка в Navi требует и цели (`channel_targets.config` + с `token_ref`), и переменной `S2S_SECRET_` в .env с тем же суффиксом. +- **Порт**: `SYNAPSE_PORT` меняет только наружный порт хоста; внутри всё на 8000. + +## Откат + +```bash +docker compose exec postgres pg_dump -U synapse synapse > backup-$(date +%F).sql +git -C gnsynapse checkout <предыдущий коммит> +docker compose up -d --build # (восстановление из дампа при необходимости: + # docker compose exec -T postgres psql -U synapse synapse < backup-...sql) +``` \ No newline at end of file