diff --git a/personal/projects/zulip-router.md b/personal/projects/zulip-router.md new file mode 100644 index 00000000..8605f05a --- /dev/null +++ b/personal/projects/zulip-router.md @@ -0,0 +1,94 @@ +--- +tags: [project, hermes, zulip, go, infra] +status: deployed +created: 2026-06-16 +--- + +# Zulip Router + +## Цель + +Event-роутер между Zulip и несколькими Hermes-инстансами. +Решает проблему конкурентного получения событий (Валера, Клавдий и другие боты видели все сообщения одновременно, `reset` срабатывал у всех). + +## Проблема (root cause) + +- Каждый Hermes-инстанс в polling-режиме регистрирует собственную event queue в Zulip +- `reset` — системная команда Hermes, обрабатывается до хука `whale-thread-guard` +- Нет механизма ownership топика между инстансами + +## Архитектура + +``` +Zulip Event Queue + ↓ poll (valera-bot credentials) +[zulip-router] + ├── @Валера → http://hermes-valera:8765/zulip-webhook + └── @Клавдий → http://hermes-klavdiy:8765/zulip-webhook + ↕ ownership: stream+topic → bot (TTL 24h, persisted to /data/ownership.json) +``` + +Hermes/Орёл — не трогаем, остаётся в polling-режиме. + +## Правила маршрутизации + +1. `@Валера` или `@Valera` в тексте → Валера (+ ownership обновляется) +2. `@Клавдий` или `@Klavdiy` в тексте → Клавдий (+ ownership обновляется) +3. Топик уже у кого-то → туда же (TTL refreshed) +4. Новый топик без @mention → Валера (default) +5. `reset` без @mention → уходит только к владельцу топика ✓ + +## Код + +`~/docker/zulip-router/` — Go, ~260 LOC + +| Файл | Назначение | +|------|------------| +| `main.go` | Poll loop, routing, dispatch | +| `config.go` | Config struct + YAML loading + env var expansion | +| `zulip.go` | Zulip API client (register_queue, get_events, long-poll) | +| `ownership.go` | Topic ownership store (RW mutex + JSON persistence) | +| `forwarder.go` | HTTP POST к Hermes webhook endpoints | + +## Deployment + +**Compose:** `~/docker/hermes/docker-compose.yml` — сервис `zulip-router` + +**Env vars** (`~/docker/hermes/.env`): +``` +ZULIP_ROUTER_API_KEY= +ZULIP_WEBHOOK_TOKEN=zr-secret-2026 +``` + +**Запуск после заполнения .env:** +```bash +cd ~/docker/hermes +docker compose up -d --force-recreate +``` + +**Перезапуск после изменений в профилях:** +```bash +docker compose restart hermes-valera hermes-klavdiy +``` + +## Hermes профили + +Валера и Клавдий переведены в webhook-режим: +```yaml +platforms: + zulip: + webhook_port: 8765 + webhook_path: /zulip-webhook + webhook_token: 'zr-secret-2026' +``` + +Токен должен совпадать с `ZULIP_WEBHOOK_TOKEN` в `.env`. + +## TODO + +- [ ] Заполнить ZULIP_ROUTER_API_KEY в ~/docker/hermes/.env +- [ ] `docker compose up -d --force-recreate` +- [ ] Проверить логи: `docker compose logs -f zulip-router` +- [ ] Протестировать: написать @Клавдий и @Валера в отдельных топиках +- [ ] Проверить ownership: обратиться в топик без @mention — должен отвечать последний бот +- [ ] При необходимости: добавить Hermes/Орёл в роутер (отдельная задача)