Parvane — федеративная self-hosted платформа: мессенджер, облако, заметки, расписание (и в планах умный дом). Названа в честь Парванэ — мотылька-вестника из персидской мифологии.
Архитектура построена вокруг шины сообщений NATS и набора независимых Rust-сервисов («шардов»), каждый со своей встроенной базой SQLite. Постоянное хранение — ответственность шарда, шина передаёт события и ничего не хранит.
| Компонент | Назначение | Состояние |
|---|---|---|
parvane-types |
Общие типы событий, payload'ы, CRDT-типы, константы топиков | ✅ готов |
identity |
Выдача и проверка JWT, учётные записи | ✅ готов |
messenger |
Сообщения (текст + медиа), доставка, прочтение, офлайн-синхронизация | ✅ готов |
cloud |
Загрузка/скачивание файлов чанками, список файлов | ✅ готов |
notes |
Заметки, текстовый CRDT (RGA) | ✅ готов |
calendar |
События, CRDT (per-field LWW) | ✅ готов |
call |
Сигналинг звонков (WebRTC SDP/ICE) + история | ✅ готов |
gateway |
Единая доверенная точка входа (TCP/WS), JWT-auth, изоляция инбоксов, лимиты частоты | ✅ готов |
client (web/) |
Основной клиент — форк Telegram Web A (TS) | ✅ задеплоен на прод; мессенджер + звонки + E2E по умолчанию, паритет-фичи, русская локализация, 2FA через Telegram |
client (desktop/) |
Клиент — форк Telegram Desktop (C++/Qt) | ✅ паритет с вебом; ходит на прод по WSS (gateway), вход по нику, 2FA |
client (android/) |
Клиент — shim TDLib Client над parvane-core (JNI): свой Compose-клиент + форк Telegram X |
🟡 свой клиент: APK собирается, дымовой тест в эмуляторе зелёный (вход, E2E-текст); форк Telegram X собирается и стартует на шове, разработка приостановлена 8 сен 2026 (см. android/BUILD-android.md) |
smarthome |
Умный дом, RBAC по устройствам | ⛔ заморожен |
Клиентов три, все на общем контракте NATS/JSON: веб (основной, задеплоен),
десктоп (форк tdesktop) и Android (в работе). Принцип везде один — берём
зрелый клиент Telegram и подменяем его сетевой слой на наш (web: провайдер
вместо MTProto/GramJS; desktop: parvane-core вместо MTProto; android:
планируется shim TDLib поверх parvane-core). Прод-путь клиентов — через
gateway по WSS (wss://<host>/ws); прямой NATS остаётся для дева.
Parvane шифрует сквозно по умолчанию везде (в отличие от Telegram, где E2E — только «секретные чаты»):
- 1-на-1 текст и медиа — Olm (X3DH + Double Ratchet, Curve25519/AES-256/HMAC-SHA256) через vodozemac (крипто-библиотека, не Matrix-инфраструктура).
- Группы — Megolm (sender keys) + ротация ключа при удалении участника (forward secrecy).
- Медиа-блобы — AES-256-GCM локально; в
cloudлежит только шифртекст, ключ едет внутри E2E-сообщения. - Sealed sender — реальный отправитель спрятан внутри шифртекста, сервер видит
from=''. - Safety numbers (Signal-style) в профиле контакта — ручная проверка от MITM.
- Самоуничтожение (TTL) — таймер внутри E2E-контента; получатель ставит нативный
ttl_period. - Локальный персист истории — переписка (свои + принятые) переживает рестарт/релогин.
- Инфраструктура: TLS на NATS, пароли argon2id, JWT на Ed25519.
Паритет-фичи (нативный UI tdesktop кормится нашими данными): форматирование текста,
превью ссылок, реакции/пин/ответы/пересылка/поиск, @упоминания, папки,
черновики, админка групп (роли, add/kick, выйти), опросы (создание/
голоса/закрытие внутри E2E, сервер голосов не видит; агрегация на клиенте),
стикеры (локальные паки → нативная панель, отправка/приём через E2E) и
GIF-приём (attach .gif → автоплей). Пробелы: анимированные tgs/webm-стикеры,
кастом-эмодзи, вкладка GIFs (см. desktop/PARITY-telegram.md).
Тесты: unit-тесты бэкенда (cargo test --workspace, включая argon2 и
crypto vodozemac) + слой parvane-core клиента (transport, messenger, crypto/SAS,
call, group…) — все зелёные. Живые e2e (desktop/verify_*.sh, два реальных
экземпляра форка): E2E текст/группы/медиа, ротация ключей, safety numbers, TTL,
персист истории, @упоминания, папки, админка групп, опросы, стикеры — все проходят.
Основной клиент — веб (
web/telegram-tt, форк Telegram Web A на TypeScript): задеплоен на прод, ходит наgatewayпо WSS. Провайдерsrc/api/parvane/заменяет MTProto/GramJS; там же — русская локализация, «Избранное», контакты, папки, обои, QR, 2FA через Telegram-бота, лимиты частоты и сверка ключей.Десктоп (
desktop/, форк Telegram Desktop, C++/Qt) доведён до паритета с вебом и ходит на прод по тому же WSS (GatewayWsTransport), вход по нику, Telegram-подтверждение/2FA. Лицензия унаследована от tdesktop — GPLv3.Android (
android/) — шов TDLib: классorg.drinkless.tdlib.Clientподменён shim'ом поверхparvane-core(JNI,libparvane_jni.so), объектыTdApiсинтезируются из событий Parvane. Свой минимальный Compose-клиент (android/app) работает end-to-end (вход по нику/паролю, E2E-текст с десктопом/вебом); форк Telegram X (setup-tgx.sh, оверлей) собирается и стартует на том же шове — доводка приостановлена 8 сен 2026. Подробности и как возобновить —android/BUILD-android.md.Прежний самодельный Tauri-клиент (React 18, Gruvbox-TUI) архивирован в ветке
tauri.Состояние (общее для веба и десктопа): мессенджер и звонки работают end-to-end поверх шардов. Сделано:
Текст 1-на-1 (логин через
identity, отправка/приём/sync) + форматирование (жирный/курсив/моно/код/цитата/спойлер/ссылка — round-trip черезentities).Медиа: голосовые (с реальной формой волны), видео-кружочки, фото, видео, файлы — в личке и в группах.
Реакции, закреп, ответы, редактирование, удаление, пересылка, поиск, аватары/имена, typing, online/last-seen.
Группы и каналы (роли, права постинга, медиа/реакции/пин).
Звонки: реальный WebRTC (tg_owt) — аудио и видео, групповые (mesh); свой аудио-модуль на PulseAudio (прибилженный tg_owt без ALSA/Pulse давал тишину); защита от MITM (подпись SDP Ed25519 + SAS-эмодзи); нативный экран звонка (
Ui::GL::Window+ родные виджетыcalls.style), входящий с «Ответить/Отклонить», рингтоны; STUN/TURN (свой серверbackend/infra/turn).Безопасность инфраструктуры: TLS на NATS (
PARVANE_NATS_TLS_CA), пароли argon2id.E2E-шифрование по умолчанию (текст/медиа/группы, Olm+Megolm, sealed sender, ротация ключей, safety numbers, TTL, локальный персист истории) — см. раздел выше.
Опросы (нативные CreatePollBox/лента; вопрос/варианты/голоса едут внутри E2E-контента, агрегация на каждом клиенте, переживают рестарт) и стикеры (локальные паки
~/.local/share/ParvaneStickers/<Пак>/*.webp|png→ нативная панель; отправка/приём через E2E-блоб вcloud), GIF-приём с автоплеем.Проверено двумя реальными экземплярами (
desktop/run-live-demo.sh,desktop/verify_*.sh). Батч паритета завершён; дальше — рескин (Фаза 5), Календарь/Дневник (Фаза 6), мелкие пробелы (анимированные стикеры, кастом-эмодзи, GIF-панель). Ботов/Stories/Premium — не делаем.
┌─────────────────────────────────────────────────────────────────┐
│ Клиенты: web (Telegram Web A, TS) · desktop (tdesktop, C++) · │
│ android (форк TDLib-клиента, в работе) │
│ У каждого сетевой слой Telegram заменён на наш (провайдер/ │
│ parvane-core); события Parvane маппятся в родные объекты клиента │
└────────────────────────────┬────────────────────────────────────┘
прод: WSS через gateway (wss://host/ws)
дев: NATS напрямую (TCP :4222) или gateway TCP :9223
│
┌─────▼──────────────────────────────────┐
│ gateway (JWT-auth, изоляция инбоксов, │
│ лимиты частоты; WS :9222 / TCP :9223) │
└─────┬───────────────────────────────────┘
│ NATS Core (TCP :4222)
┌─────▼──────────────────────────────────┐
│ NATS (Core) │
│ fire-and-forget, без JetStream │
└─────┬────┬──────┬──────┬───────┬───────┘
identity msg file note cal call
│ │ │ │ │ │
┌───▼──┐┌─▼────┐┌──▼───┐┌──▼──┐┌──▼───┐┌──▼──┐
│iden- ││mes- ││cloud ││notes││cal- ││call │
│tity ││senger││ ││ ││endar ││ │
└──┬───┘└─┬────┘└──┬───┘└──┬──┘└──┬───┘└──┬──┘
SQLite SQLite SQLite SQLite SQLite SQLite
Медиа-блобы (голос/видео/фото/файлы) хранит шард
cloud; сообщение вmessengerнесёт лишьfile_id+ метаданные. Звонки:callрелеит WebRTC-сигналинг в персональный инбокс получателяcall.user.<id>.
Три слоя:
- Шина — Core NATS. Только доставка событий, без хранения.
- Шарды — независимые Rust-сервисы. Каждый владеет своей SQLite и своей доменной логикой. Шарды не ходят в БД друг друга — только обмениваются событиями через шину.
- Клиенты — форки зрелых клиентов Telegram с заменённым сетевым слоем.
Веб (
web/, форк Telegram Web A) — провайдерsrc/api/parvane/вместо MTProto/GramJS. Десктоп (desktop/, форк tdesktop) — модульparvane-core(WSS/gateway или cnats) вместо MTProto, события маппятся в TL-объекты (MTPMessage/MTPUser) для штатного UI. Android (android/, в работе) — shim TDLib поверхparvane-core. Прод-подключение — черезgatewayпо WSS.
- Язык: Rust (edition 2021)
- Async: Tokio
- Шина: Core NATS через крейт
async-nats - БД: SQLite через
sqlx(runtime-tokio, без макросаquery!) - Миграции:
sqlx::migrate! - JWT:
jsonwebtoken(HS256) - Сериализация:
serde+serde_json - Логи:
tracing+tracing-subscriber - Ошибки:
anyhowв бинарниках
- База: Telegram Web A (
web/telegram-tt), TypeScript + собственный фреймворк Teact - Транспорт: провайдер
src/api/parvane/— WSS доgateway, заменяет MTProto/GramJS - Шов: события Parvane ↔ объекты api-слоя Telegram Web A; UI без изменений
- Своё: русская локализация, «Избранное», контакты, папки, обои, QR, 2FA, лимиты частоты, сверка ключей безопасности
- Деплой: статика за Caddy,
wss://<host>/ws(см.backend/infra/deploy)
- База: Telegram Desktop (
tdesktop), C++ / Qt 6 (см.desktop/UPSTREAM) - Транспорт:
parvane-core— WSS доgateway(GatewayWsTransport, прод) либо cnats/NATS напрямую (дев); заменяет MTProto - Шов: события Parvane ↔ TL-объекты (
MTPMessage/MTPUser); UX tdesktop без изменений - Лицензия: GPLv3 (с OpenSSL-исключением), унаследована от tdesktop
- Шов:
android/libtd—TdApi.java(бандл Telegram X, TDLib d1085f9),Client.kt(create/send/execute/close, авторизация: ник = поле «телефон» → пароль →identity.token.issue),ParvaneStore.kt(синтез Chat/User/Message),ParvaneCore.kt+jni/parvane_jni.cpp→libparvane_jni.so(сессия WSS, E2E, sealed-отправка, verifySender, sync/инбокс, resolve/search) - Свой клиент
android/app(Compose): вход, чаты, текст, «новый чат по нику»; APK arm64 ~11 МБ; дымовой тест в эмуляторе x86_64smoke_emulator.sh— зелёный - Telegram X: оверлей
setup-tgx.shна внешний клон; собирается (arm64/x64), стартует, экран входа по нику; дальше не доведено (падение хостового эмулятора на экране пароля; на телефоне не проверялось) - Лицензия: свой клиент — как проект; форк Telegram X — GPLv3
Прежний Tauri-клиент (React 18 + Babel-standalone, Gruvbox-TUI, Rust IPC-мост с 17 командами) сохранён в ветке
tauri.
Четыре самостоятельных каталога — у каждого свой CLAUDE.md с подробными
знаниями о содержимом (корневой CLAUDE.md — общие правила и маршрутизация).
Parvane/
├── README.md · CLAUDE.md · ARCHITECTURE.md · ROADMAP.md · specs/
├── backend/ ← БЭКЕНД: Rust-шарды на NATS + инфраструктура
│ ├── Cargo.toml ← workspace (шарды); target/ — артефакты (не в git)
│ ├── shared/
│ │ ├── parvane-types/ ← общие типы, топики, topic_contract (единый ACL)
│ │ └── parvane-e2e/ ← Rust staticlib (vodozemac) для клиентов C++
│ ├── shards/ ← identity, messenger, cloud, call, preview, push,
│ │ gateway, notes (RGA CRDT), calendar (LWW CRDT)
│ └── infra/
│ ├── nats/ ← server.conf (dev) / server.prod.conf — ACL по ролям
│ ├── deploy/ ← docker compose + deploy.sh (прод за Caddy)
│ ├── telegram-bot/ ← бот подтверждения регистрации/2FA (на VPS)
│ ├── turn/ ← TURN/STUN для звонков (pion, Go)
│ └── tls/ ← самоподписанные серты для TLS на NATS (dev)
├── web/ ← ОСНОВНОЙ клиент — форк Telegram Web A (TS)
│ ├── telegram-tt/
│ │ └── src/api/parvane/ ← провайдер: WSS/gateway вместо MTProto, E2E, локализация
│ ├── dev/cdp.mjs ← headless-драйвер Chromium
│ └── WEB-ROADMAP.md · WEB-A4-MATRIX.md
├── desktop/ ← клиент — форк Telegram Desktop (C++/Qt)
│ ├── UPSTREAM ← тег + commit снапшота tdesktop
│ ├── BUILD-parvane.md ← рецепт сборки (п.7 — запуск против прода по WSS)
│ ├── ARCHITECTURE-parvane.md ← шов врезки Parvane в tdesktop
│ ├── PARITY-telegram.md ← карта паритета с Telegram
│ ├── parvane-core/ ← транспорт (WSS/cnats) + E2E + cloud + звонки, тесты
│ ├── tdesktop/ ← вендоренный снапшот форка
│ │ └── Telegram/SourceFiles/parvane/ ← parvane_client.{h,cpp}, intro_parvane
│ └── verify_*.sh ← e2e-скрипты (два реальных экземпляра)
├── android/ ← Android: шов TDLib над parvane-core (приостановлен)
│ ├── BUILD-android.md ← тулчейн, стадии 1–3, как возобновить
│ ├── libtd/ ← TdApi.java + Client.kt (shim) + ParvaneStore + ParvaneCore
│ ├── app/ ← свой Compose-клиент (APK, дымовой тест зелёный)
│ ├── jni/ ← CMake ядра (без cnats, WSS-only) + parvane_jni.cpp
│ ├── tgx-overlay/ · setup-tgx.sh · tgx_*.sh ← форк Telegram X (оверлей, эмулятор)
│ └── build-openssl.sh · build-core.sh · smoke_emulator.sh
├── scripts/ ← сквозные e2e (Playwright, весь стек), демо, бэкапы
└── local-workdirs/ ← локальные профили/демо/логи (не в git)
Основной клиент — форк Telegram Web A в web/telegram-tt. Сетевой слой
(MTProto/GramJS) заменён провайдером src/api/parvane/, который ходит на
gateway по WSS и маппит события Parvane в объекты api-слоя Telegram Web A —
штатный UI без изменений. Там же: русская локализация, «Избранное», контакты,
папки, обои, QR, 2FA через Telegram-бота, лимиты частоты, сверка ключей.
cd web/telegram-tt
npm ci
npm run dev # локально; адрес gateway задаётся в настройках/окружении
npm run check:ts # типы; тесты — vitest; e2e — scripts/run_web_*_e2e.shДеплой на прод (статика за Caddy + wss://<host>/ws) — backend/infra/deploy.
Клиент — форк Telegram Desktop в desktop/. Сетевой слой MTProto заменён
модулем desktop/parvane-core; на проде транспорт — WSS до gateway
(GatewayWsTransport), в деве — cnats/NATS напрямую. События Parvane маппятся
в TL-объекты, которые потребляет штатный UI tdesktop. Подробности шва —
desktop/ARCHITECTURE-parvane.md, паритет — desktop/PARITY-telegram.md.
| Файл | Роль |
|---|---|
SourceFiles/parvane/parvane_client.{h,cpp} |
сессия шины, реестр пиров (address↔id, FNV-1a), отправка/приём, sync-таймер |
SourceFiles/intro/intro_parvane.cpp |
логин через identity.token.issue, синтез self как MTPUser |
apiwrap.cpp |
ApiWrap::sendMessage → Parvane::MirrorOutgoing → msg.chat.send |
main/main_session.cpp |
Parvane::AfterSessionReady — post-session хуки + старт sync |
Приём — pull: msg.sync.request → msg.sync.response → синтез MTPMessage →
Data::Session::addNewMessage. Триггеры: подписка на msg.chat.delivered
(«синкнись») + периодический base::Timer.
Рецепт сборки (тулчейн Qt6/OpenSSL/FFmpeg, шаги CMake/ninja) — в
desktop/BUILD-parvane.md. Бинарь — desktop/build-probe/bin/Telegram.
Сборка требует -j6 (иначе OOM на 16 ГБ).
# headless-запуск (нужны живые identity + messenger, см. «Запуск»)
cd desktop/build-probe/bin
QT_QPA_PLATFORM=offscreen PARVANE_AUTOLOGIN='alice@local:test' \
./Telegram -workdir /tmp/parvane-fork
# логи tdesktop пишутся в <workdir>/log.txt, НЕ в stdoutОтладочные env-хуки: PARVANE_AUTOLOGIN=user:password,
PARVANE_AUTOSEND=peer:текст, PARVANE_NATS_URL.
cd desktop
./verify_phase3b.sh # отправка
./verify_phase3c.sh # приём
./verify_phase3d.sh # стартовый/периодический sync + список диалогов
./verify_two_instances.sh # два реальных экземпляра форка одновременно (alice ↔ bob)
./verify_polls.sh # опросы: создание → E2E-доставка → голос → агрегация → рестарт
./verify_stickers.sh # стикеры: локальный пак → панель → E2E-отправка → приёмПользователи для e2e переопределяются:
A_USER=palice@local B_USER=pbob@local ./verify_polls.sh(парольtest; регистрация —nats req identity.user.register '{"user":"…","password":"test"}').
Любое событие на шине — JSON со стандартной обёрткой ParvaneEvent<T>:
{
"id": "0192...-uuid-v7",
"from": "alice@local",
"ts": 1718000000,
"token": "<JWT>",
"payload": { ... }
}id— UUID v7 (лексикографически сортируется по времени — используется в sync).from— отправительuser@server.ts— unix-время отправителя.token— JWT от identity. Пустая строка только дляidentity.token.issue.payload— доменная нагрузка, тип зависит от топика.
Соглашение: {домен}.{ресурс}.{действие}.
| Топик | Payload | Тип |
|---|---|---|
identity.token.issue |
IssueRequest → IssueResponse |
request/reply |
identity.token.verify |
VerifyRequest → VerifyResponse |
request/reply |
msg.chat.send |
SendPayload { to, content: MessageContent } |
publish |
msg.chat.delivered |
DeliveredPayload |
publish (от шарда) |
msg.chat.read |
ReadPayload |
publish |
msg.sync.request |
SyncRequestPayload → SyncResponsePayload |
request/reply |
file.upload.chunk |
UploadChunkPayload |
publish |
file.upload.complete |
UploadCompletePayload → UploadCompleteResponse |
request/reply |
file.download.request |
DownloadRequest → чанки DownloadResponse |
request/reply |
file.list.request |
FileListPayload → FileListResponse |
request/reply |
note.create / note.update / note.delete |
NoteCreate/Update/DeletePayload |
publish |
note.sync.request |
NoteSyncRequestPayload { known } → NoteSyncResponsePayload (diff) |
request/reply |
cal.event.create / cal.event.update / cal.event.delete |
CalSetPayload / CalDeletePayload |
publish |
cal.sync.request |
CalSyncRequestPayload { known } → CalSyncResponsePayload (diff) |
request/reply |
call.signal |
CallSignalPayload (invite/answer/reject/ice/hangup) |
publish |
call.user.<id> |
CallSignal — релей в инбокс получателя |
publish (от шарда) |
call.history.request |
{} → CallHistoryResponse |
request/reply |
msg.chat.send несёт content: MessageContent — тегированный enum по полю kind:
kind |
Поля |
|---|---|
text |
text |
voice |
file_id, duration_secs, mime, size_bytes |
video_note |
file_id, duration_secs, mime, size_bytes |
photo |
file_id, width, height, mime, size_bytes, caption? |
video |
file_id, duration_secs, width, height, mime, size_bytes, caption? |
file |
file_id, filename, mime, size_bytes, caption? |
Медиа-поток: клиент грузит файл в cloud (file.upload.chunk × N → file.upload.complete
⇒ file_id), затем шлёт сообщение со ссылкой. Получатель скачивает через file.download.request.
Backend релеит WebRTC-сигналы и ведёт историю. Вызывающий шлёт call.signal
→ шард call проверяет JWT, пишет запись (ringing) и публикует сигнал в инбокс
call.user.<callee>. Статусы: ringing → answered → ended (или missed / rejected).
Реальный медиа-поток идёт P2P через WebRTC — мимо шины.
Двухуровневая:
- NATS ACL (
backend/infra/nats/server.conf) — каждый компонент подключается своим пользователем и имеет права только на нужные топики. - JWT внутри события — шард извлекает
tokenиз события, спрашиваетidentity.token.verify, получаетuser(subject токена) и сверяет сfrom. Доменные правила применяются поверх: только владелец может редактировать свои заметки/события.
Identity генерирует keypair (Ed25519) при первом старте (хранит в SQLite), JWT
подписывается им (TTL 24 часа). Пароли — argon2id. Регистрация и вход
разделены (identity.user.register / identity.token.issue).
Протокол синхронизации (шарды отдают только расхождение, а не весь набор):
- messenger (append-only): курсор = max
idу клиента → шард возвращает сообщения сid > cursor(UUID v7 лексикографически сортируем по времени). Новое дописывается с дедупом поid. (Форк-клиент сейчас делает полный ресинкsince=0с дедупом по UUID; дисковый курсор — в планах.) - notes / calendar (изменяемые): diff по контрольным суммам. Клиент шлёт
манифест
{id → checksum}(FNV-1a, считается одинаково на шарде и в клиенте —parvane_types::content_checksum/event_checksum). Шард возвращает только заметки/события, чья сумма разошлась или которых клиент не знает, плюс tombstone'ы удалённых. Неизменившееся не передаётся вовсе.
Сохранение заметки — одна операция NoteOp::Replace { text }: клиент источник
истины для тела, шард атомарно сносит и пересобирает RGA-узлы. Это делает
сохранение детерминированным независимо от состояния клиентского кеша.
Текстовый CRDT. Каждый символ — узел с уникальным OpId{seq, site} и ссылкой
after. Вставки и удаления (tombstone) коммутируют. Видимый текст — preorder-обход,
сиблинги упорядочены по OpId убыванием. Обход итеративный (не рекурсивный — длинный
текст = переполнение стека при рекурсии).
Событие — набор полей, каждое со своим LWW-регистром (value, Stamp{ts, site}).
Конкурентные правки разных полей сливаются; одного поля — побеждает больший ts
(site разрывает ничью). Правка новее delete-штампа «воскрешает» событие.
- Rust ≥ 1.80 (бэкенд-шарды)
- nats-server ≥ 2.10
- nats CLI (для ручного тестирования)
- Node ≥ 20 + npm — для веб-клиента (
web/telegram-tt,npm ci && npm run dev) - Тулчейн tdesktop (Qt 6, OpenSSL, FFmpeg, CMake/ninja) — для десктоп-клиента,
см.
desktop/BUILD-parvane.md - Android NDK +
cargo-ndk+ Rust android-таргеты — дляparvane-coreпод Android, см.android/BUILD-android.md(Android-клиент в работе)
# nats-server
curl -sL https://github.com/nats-io/nats-server/releases/download/v2.10.24/nats-server-v2.10.24-linux-amd64.tar.gz \
| tar -xz -C /tmp && mv /tmp/nats-server-*/nats-server ~/.local/bin/
# nats CLI
curl -sL https://github.com/nats-io/natscli/releases/download/v0.1.6/nats-0.1.6-linux-amd64.zip -o /tmp/nats.zip \
&& cd /tmp && unzip -q nats.zip && mv nats-*/nats ~/.local/bin/cd backend # Cargo workspace живёт здесь
# Все шарды
cargo build
# Один шард
cargo build -p messenger
# Клиент (форк tdesktop) — рецепт в desktop/BUILD-parvane.md (нужен -j6)В отдельных терминалах (или через nohup ... &):
# 1. Шина
nats-server
# 2. Identity (нужен всем шардам для проверки JWT); шарды — из backend/
cd backend
cargo run -p identity
# 3. Доменные шарды
PARVANE_DB_PATH=/tmp/messenger.db cargo run -p messenger
PARVANE_DB_PATH=/tmp/cloud.db cargo run -p cloud
PARVANE_DB_PATH=/tmp/notes.db cargo run -p notes
PARVANE_DB_PATH=/tmp/calendar.db cargo run -p calendar
PARVANE_DB_PATH=/tmp/call.db cargo run -p call
# 4. Клиент (форк tdesktop) — см. desktop/BUILD-parvane.md
cd ../desktop/build-probe/bin && QT_QPA_PLATFORM=offscreen \
PARVANE_AUTOLOGIN='alice@local:test' ./Telegram -workdir /tmp/parvane-fork| Переменная | По умолчанию | Назначение |
|---|---|---|
PARVANE_NATS_URL |
nats://localhost:4222 |
адрес шины |
PARVANE_DB_PATH |
./<shard>.db |
путь к SQLite |
PARVANE_LOG_LEVEL |
info |
уровень логов |
⚠️ PARVANE_DB_PATHотносителен рабочему каталогу запуска. Используй абсолютный путь или/tmp/<shard>.db.
cd backend # все cargo-команды — из backend/
cargo test --workspace # все 47
cargo test -p notes # RGA CRDT
cargo test -p calendar # LWW CRDT
cargo test -p messenger # логика мессенджера
cargo test -p call # переходы статуса звонка# Получить JWT
TOKEN=$(nats req identity.token.issue '{"user":"alice@local","password":"secret"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")
# ── Мессенджер ─────────────────────────────────────────────────
# Отправить текст alice → bob
nats pub msg.chat.send \
"{\"id\":\"$(uuidgen)\",\"from\":\"alice@local\",\"ts\":$(date +%s),\
\"token\":\"$TOKEN\",\"payload\":{\"to\":\"bob@local\",\
\"content\":{\"kind\":\"text\",\"text\":\"привет\"}}}"
# Синхронизация пропущенных
TOKEN_BOB=$(nats req identity.token.issue '{"user":"bob@local","password":"pass"}' \
| python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")
nats req msg.sync.request \
"{\"id\":\"$(uuidgen)\",\"from\":\"bob@local\",\"ts\":$(date +%s),\
\"token\":\"$TOKEN_BOB\",\"payload\":{\"last_seen_id\":\"00000000-0000-0000-0000-000000000000\"}}"
# ── Notes ───────────────────────────────────────────────────────
NOTE_ID=$(uuidgen)
nats pub note.create \
"{\"id\":\"$(uuidgen)\",\"from\":\"alice@local\",\"ts\":$(date +%s),\
\"token\":\"$TOKEN\",\"payload\":{\"note_id\":\"$NOTE_ID\",\"title\":\"Заметка\"}}"
nats req note.sync.request \
"{\"id\":\"$(uuidgen)\",\"from\":\"alice@local\",\"ts\":$(date +%s),\
\"token\":\"$TOKEN\",\"payload\":{}}"
# ── Calendar ────────────────────────────────────────────────────
EV_ID=$(uuidgen); NOW=$(date +%s)
nats pub cal.event.create \
"{\"id\":\"$(uuidgen)\",\"from\":\"alice@local\",\"ts\":$NOW,\
\"token\":\"$TOKEN\",\"payload\":{\"event_id\":\"$EV_ID\",\
\"fields\":{\"title\":\"Встреча\",\"start\":\"$NOW\"},\
\"stamp\":{\"ts\":$NOW,\"site\":\"alice@local\"}}}"
# ── Cloud ───────────────────────────────────────────────────────
nats req file.list.request \
"{\"id\":\"$(uuidgen)\",\"from\":\"alice@local\",\"ts\":$(date +%s),\
\"token\":\"$TOKEN\",\"payload\":{}}"
# ── Звонки ──────────────────────────────────────────────────────
# Подписаться на инбокс получателя в отдельном терминале:
nats sub "call.user.bob@local"
# Отправить invite:
nats pub call.signal \
"{\"id\":\"$(uuidgen)\",\"from\":\"alice@local\",\"ts\":$(date +%s),\
\"token\":\"$TOKEN\",\"payload\":{\"to\":\"bob@local\",\
\"signal\":{\"type\":\"invite\",\"call_id\":\"$(uuidgen)\",\
\"media\":\"audio\",\"sdp\":\"<offer>\"}}}"
# История:
nats req call.history.request \
"{\"id\":\"$(uuidgen)\",\"from\":\"alice@local\",\"ts\":$(date +%s),\
\"token\":\"$TOKEN\",\"payload\":{}}"
⚠️ Проверка JWT через identity занимает ~0.5 с. При скриптовом тестировании ставь паузу ≥ 1.5 с передsync.request.
- Порядок старта шарда:
tracing→ SQLite + миграции → NATS → подписки. - Ошибки — через
tracing::error!. Паника в проде запрещена. - Все топики — константы в
parvane-types::topics, не строки в коде шардов. - Чистая доменная логика (CRDT) — в отдельных модулях без async/IO, покрыта тестами.
sqlx::query_asвместоsqlx::query!(не требуетDATABASE_URLна этапе компиляции).
- Федерация (
fed.*, leaf nodes) — «North Star», пока не реализована. smarthomeзаморожен.- Свои исходящие sealed после релогина — восстанавливаются из локального журнала истории (на сервере их как «своих» нет — by design sealed sender).
- Android-клиент в работе:
parvane-coreсобирается под NDK; JNI-shim TDLib, установка SDK/JDK и сам UI — впереди (см.android/BUILD-android.md). - Календарь/Дневник (шарды
notes/calendarесть, к UI клиентов не подключены) — впереди. notes/calendarшарды на прод не разворачиваются (пока не нужны клиентам).