12 KiB
Executable File
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/ (см. §Бэкапы) |
Схема
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|dmcontent— 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 |
треды одного стрима |
Как искать историю (главное)
DB=~/Documents/zulip_message_history.sqlite
1. Поиск по тексту
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. Список тредов с количеством сообщений
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. Прочитать тред целиком
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. Найти сообщения конкретного человека по ключевым словам
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.
Как это было сделано (воспроизводимо)
-
Экспорт из Postgres в CSV. Ключ —
\copy(неCOPY), потому чтоCOPYчерезdocker execтребует прав на файл внутри контейнера, а\copyпишет в stdout. Колонки:id, kind, stream, topic, dm_user_id, sender, sender_email, date_sent, content.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— отсюда синтетический топик. -
Сборка SQLite — скрипт
build_sqlite.py(Python stdlibcsv+sqlite3), создаёт таблицы, наполняетstreams/topics/messages, строит индексы и наполняет FTS5. -
Верификация —
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 — чек-лист
⚠️ Пока не выполнено. Файл сделан как страховка перед сносом.
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, не историю чата —
для старых тредов он пуст.