Files
obsidian-vault/personal/tech/zulip-message-history.md
T

250 lines
12 KiB
Markdown
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Zulip Message History — SQLite (поиск истории переписки)
_Создано: 2026-09-29_
## Что это
Вся история переписки Zulip (134 734 сообщения, 197 тредов, 11 стримов) выгружена в
**один SQLite-файл**. Zulip как транспорт выводится из эксплуатации — история остаётся
в этом файле.
| Параметр | Значение |
|---|---|
| **Файл** | `~/Documents/zulip_message_history.sqlite` |
| Размер | 81.9 MB на диске (78.1 MB логических данных) |
| Сообщений | **134 734** (134 688 в стримах + 46 личек) |
| Стримов | 11 |
| Тредов (`topics`) | 197 |
| Источник | `zulip-database-1` (Postgres 14, `~/Developer/zulip/docker-compose.yml`) |
| Снимок на | 2026-09-29 |
| Бэкап | `~/backup/zulip-final/` (см. §Бэкапы) |
## Схема
```sql
streams (id, name) -- 11 каналов
topics (id, stream_id → streams.id, name) -- 197 тредов
messages (id, topic_id → topics.id, kind, sender,
sender_email, date_sent, content) -- 134 734
messages_fts (fts5: content, content='messages') -- полнотекстовый поиск
```
- `kind` = `stream` | `dm`
- `content` — **markdown-исходник** Zulip (не HTML-рендер); HTML был в `rendered_content`
и в дамп не попал осознанно
- `date_sent` — ISO-8601 UTC (`2026-09-29 06:29:41.829084+00`)
### Индексы
| Индекс | Назначение |
|---|---|
| `messages_fts` (FTS5) | поиск по тексту через `MATCH` |
| `idx_messages_topic` | сообщения одного треда |
| `idx_messages_date` | диапазон по датам |
| `idx_topics_stream` | треды одного стрима |
## Как искать историю (главное)
```bash
DB=~/Documents/zulip_message_history.sqlite
```
### 1. Поиск по тексту
```bash
sqlite3 "$DB" "
SELECT s.name AS stream, t.name AS topic, m.sender, m.date_sent,
substr(m.content, 1, 3000) AS content
FROM messages_fts f
JOIN messages m ON m.id = f.rowid
JOIN topics t ON t.id = m.topic_id
JOIN streams s ON s.id = t.stream_id
WHERE messages_fts MATCH 'docker jellyfin'
ORDER BY m.date_sent DESC LIMIT 30;"
```
### 2. Список тредов с количеством сообщений
```bash
sqlite3 -column -header "$DB" "
SELECT s.name AS stream, t.name AS topic, count(*) AS msgs
FROM messages m
JOIN topics t ON t.id = m.topic_id
JOIN streams s ON s.id = t.stream_id
GROUP BY t.id ORDER BY msgs DESC;"
```
### 3. Прочитать тред целиком
```bash
sqlite3 "$DB" "
SELECT m.date_sent || ' | ' || m.sender || char(10) || m.content || char(10) || '==='
FROM messages m
WHERE m.topic_id = (SELECT id FROM topics WHERE name = 'Home assistant setup' LIMIT 1)
ORDER BY m.date_sent;"
```
### 4. Найти сообщения конкретного человека по ключевым словам
```bash
sqlite3 "$DB" "
SELECT t.name, m.date_sent, substr(m.content,1,500)
FROM messages m JOIN topics t ON t.id = m.topic_id
WHERE m.sender = 'Alex' AND m.content LIKE '%план%'
ORDER BY m.date_sent DESC LIMIT 20;"
```
### ⚠️ FTS5-синтаксис
Токенизатор — `unicode61` (дефолт): поиск по **целым словам**, стемминга нет.
| Хочу | Запрос |
|---|---|
| слово `docker` | `MATCH 'docker'` |
| префикс `dock…` | `MATCH 'dock*'` |
| фраза | `MATCH '"zulip router"'` |
| AND | `MATCH 'docker jellyfin'` |
| OR | `MATCH 'docker OR jellyfin'` |
| НЕ | `MATCH 'docker NOT podman'` |
`собрал` **не** найдёт `собрать` — стемминга нет. Для поиска по подстроке внутри слова
нужен `trigram`-токенизатор (не включён; пересборка FTS-таблицы).
## Стримы и треды (снимок)
| Стрим | Сообщений | Тредов |
|---|---|---|
| personal | 111 534 | 95 |
| master | 7 630 | 29 |
| projects | 7 524 | 20 |
| executor | 7 355 | 32 |
| Zulip | 432 | 1 |
| daily-brief | 182 | 12 |
| general | 15 | 1 |
| inbox | 14 | 3 |
| journal | 1 | 1 |
| focus | 1 | 1 |
| Direct messages | 46 | 2 |
Крупнейшие треды (personal): `Home assistant setup` 12 196 · `truenas jellyfin` 12 059 ·
`Балда/валера` 9 454 · `ZONT Config compiler` 6 585 · `✔ media toolbox setup` 6 198.
## 🔴 Ограничение — база не живая
Файл — **снимок на 2026-09-29**. После сноса Zulip новые сообщения сюда не попадают.
Живой поиск свежей переписки — только в Mattermost (`hermes-maintenance`,
`a6nyh77eyjbn5c1owh1i8eebee`). Разделять явно: «историческое (до 29.09)» → SQLite;
«что обсуждали вчера» → Mattermost.
## Как это было сделано (воспроизводимо)
1. **Экспорт из Postgres в CSV.** Ключ — `\copy` (не `COPY`), потому что `COPY` через
`docker exec` требует прав на файл внутри контейнера, а `\copy` пишет в stdout.
Колонки: `id, kind, stream, topic, dm_user_id, sender, sender_email, date_sent, content`.
```bash
docker exec zulip-database-1 psql -U zulip -d zulip -c "\copy (
SELECT m.id, 'stream' AS kind, s.name AS stream, m.subject AS topic, 0 AS dm_user_id,
u.full_name AS sender, u.email AS sender_email, m.date_sent, m.content
FROM zerver_message m
JOIN zerver_recipient r ON r.id = m.recipient_id
JOIN zerver_stream s ON s.id = r.type_id
JOIN zerver_userprofile u ON u.id = m.sender_id
WHERE r.type = 2 ORDER BY m.id
) TO STDOUT WITH (FORMAT csv, HEADER false)" > /tmp/zulip-export/messages.csv
```
Личные сообщения (`r.type = 1`) — отдельным запросом в `dms.csv`, топик = `DM user #<id>`.
В БД лички в `zerver_recipient.type_id = 1`, у них нет `subject` — отсюда синтетический топик.
2. **Сборка SQLite** — скрипт `build_sqlite.py` (Python stdlib `csv` + `sqlite3`), создаёт
таблицы, наполняет `streams`/`topics`/`messages`, строит индексы и наполняет FTS5.
3. **Верификация** — `PRAGMA integrity_check`, `PRAGMA foreign_key_check`,
`count(*)` против Postgres, FTS-запрос по реальному слову.
### 🔴 Питфоллы, которые стоили времени
- **TSV рвётся на многострочном контенте.** Первый заход был с `-A -F$'\t'` → вместо
134 688 строк получилось **486 751** (переносы строк внутри сообщений режут TSV).
Решение: `WITH (FORMAT csv)` — кавычки экранируют переносы корректно. `wc -l` на CSV
всё равно покажет завышенное число переносов — это ожидаемо, **считать строки нужно
парсером, а не `wc`**.
- **`rendered_content` — это HTML.** Для читаемой истории нужен `content` (markdown).
HTML-дамп бесполезен без рендера.
- **`content` может содержать что угодно** — включая NUL и гигантские блоки кода.
В `build_sqlite.py` нужен `csv.field_size_limit(50MB)`, иначе падает на больших сообщениях.
- **CSV-колонок было 9, а проверял на 8** — первая сборка дала 0 сообщений и
134 734 ошибки «bad field count». Читать позиционно по факту заголовка, не по памяти.
## Бэкапы
| Что | Где |
|---|---|
| SQLite (история) | `~/Documents/zulip_message_history.sqlite` |
| CSV-дампы | `/tmp/zulip-export/messages.csv` (56 MB), `dms.csv` (18 KB) |
| Compose + env Zulip | `~/Developer/zulip/docker-compose.yml`, `.env.zulip` |
## Что НЕ выгружалось (осознанно, по указанию Alex)
Только переписка + структура тредов. Вне выгрузки:
- вложения/аплоады (`volume zulip_zulip-data`, 38.1 MB — `uploads/`, `certs/`)
- аватары, реакции, подписки, метаданные организаций
- настройки стримов, права, история изменений сообщений
Если понадобится картинка из старого треда — её в SQLite нет, она была в volume.
## Снос Zulip — чек-лист
⚠️ **Пока не выполнено.** Файл сделан как страховка перед сносом.
```bash
cd ~/Developer/zulip
docker compose down
docker volume rm zulip_zulip-data zulip_zulip-pgdata zulip_zulip-rabbitmq zulip_zulip-redis
docker rmi zulip/docker-zulip:11.6-0 # ~3.84 GB
```
Стек: `zulip-zulip-1` (`zulip/docker-zulip:11.6-0`, порты 8000/8443), `zulip-database-1`
(`zulip/zulip-postgresql:14`), `zulip-rabbitmq-1`, `zulip-memcached-1`, `zulip-redis-1`.
Объём volumes ≈ 1.1 GB (pgdata 503 MB на диске, БД 439 MB; data 38 MB).
### ⚠️ Зависимости, которые сломаются
| Компонент | Что будет |
|---|---|
| `zulip-router` (Go, `~/Developer/zulip-router/`) | мёртвый — поллит Zulip Events API. Убрать из compose, иначе restart-loop |
| Bot webhook-роутинг (Eagle/Кит/Валера/Клавдий, `host.docker.internal:8644-8645`) | маршрутизация в Zulip перестанет работать |
| Hermes `platforms: zulip` в `~/.hermes/config.yaml` | платформа отвалится; транспорт уже переехал на Mattermost |
ℹ️ Скиллы на Zulip больше не ссылаются — этап 3 закрыт, битых инструкций после сноса не будет
(кроме двух файлов `docker-balda-service`, см. §«Оставлено как есть»).
## Связанные документы
- [[plans/zulip-decommission]] — план и статус вывода Zulip из эксплуатации (этап 3 = этот файл)
- [[projects/zulip-router]] — архитектура event-роутера (Go), конфиг ботов
- [[tech/hermes-whale-mattermost-transport]] — текущий транспорт Hermes
- [[tech/hermes-self-improvement-review]] — фоновый review скиллов/памяти (в т.ч. как его выключить)
- [[how-to/hermes-eagle-mac]] — Hermes на Mac, Zulip Docker стек, RabbitMQ/Khepri pitfall
- [[tech/docker-mac-disk-issues]] — почему Zulip тормозил (переполнение диска VM)
## Как это используется агентами (скилл вместо скиллов)
Три скилла, работавшие с Zulip Postgres, **снесены** — их место занял один:
| Было | Стало |
|---|---|
| `zulip-db-forensics` | `message-history-search` |
| `zulip-thread-handoff` | ↑ |
| `zulip-bot-routing` | ↑ |
`~/.hermes/skills/note-taking/message-history-search/SKILL.md` — единственная точка входа
для «ищи в базе», «вспоминай», «читай историю». На неё ссылаются `SOUL.md` (оба файла!) и
`USER.md`, поэтому агент находит её без подсказки.
**Триггер в SOUL/USER:** «ищи в базе» / «чё сделано» → `sqlite3` СРАЗУ, без `session_search`
и без теории. `session_search` индексирует файлы сессий Hermes, **не** историю чата —
для старых тредов он пуст.