[2026-06-16] eagle: personal/projects/zulip-router.md
This commit is contained in:
@@ -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=<valera-bot api key из ~/.hermes/profiles/valera/config.yaml>
|
||||
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/Орёл в роутер (отдельная задача)
|
||||
Reference in New Issue
Block a user