Self-hosted corporate messenger with voice calls. Rust backend + TypeScript (bun) frontend. See requirements.md and docs/design-notes.md.
Everything runs through the Makefile (make to list targets):
make dev # debug server on http://127.0.0.1:3000 (reads frontend/dist from disk)
make fe-dev # in another terminal: rebuild the frontend on change
make build # full release build — frontend embedded into one self-contained binary
make run # build frontend, then run the release server
make test # backend tests + frontend typecheck
make e2e # end-to-end auth/chat suite (spins up an ephemeral server)
make lint # clippy + rustfmt check + typecheck
make fmt # formatbackend/— Rust (axum). WS chat at/ws, health at/api/health. SQLite via sqlx with a batching writer (≤50ms / ≤16 messages per transaction). DB lives in the git-ignoredbackend/data/. The built frontend is embedded viarust-embed.frontend/— Svelte 5 + TypeScript, built with bun. Realtime over WS; stickers via self-hosted dotLottie (ThorVG WASM). Pure helpers are unit-tested (make fe-test).app/— Tauri v2 native shell (Windows/macOS/Linux/Android). Web-first: the web app never depends on Tauri. Built in CI; Android adds FCM push. See docs/ideas.md.
Everyone signs in by a personal link-token — no passwords. A token is
exchanged for an httpOnly cookie on first visit (/i/<token>). Identities
(principals) are either user (employee) or client (anonymous); admins
create them from the UI (the "Links" panel).
First run bootstraps an admin and surfaces its one-time login link: logged, and
saved to a git-ignored .env as ZENITHAR_ADMIN_LINK (in the server's working
dir, i.e. backend/.env for make dev/make run). Open http://<host>/i/<token>
once to sign in, then create the rest from the UI.
See docs/plans/phase-1-auth.md.
Phases 0–7 are in: realtime chat + rooms, passwordless link auth, attachments / voice / emoji, WebRTC voice calls with server-side recording, the REST integration API, and the production hardening (presence, WS reconnect, rate limits). On top of that: stickers, emoji reactions, saved images ("сохранёнки") + profiles, in-message links, a Telegram-style mobile UI with Android FCM push, a mic picker, and self-contained static-asset caching + compression.
Full checklist and roadmap in requirements.md; design decisions in docs/design-notes.md.
A REST API under /api/v1 lets integrations (CRM, bots) read and post to chats.
Authenticate with a permanent Bearer token (zk_…) created in the admin UI
(the "Integrations" section). An integration is a bot principal — a first-class
message author. Full reference: docs/plans/phase-6-api.md.
# create a client + login link, seeding the order as the first message
curl -X POST https://host/api/v1/clients \
-H "Authorization: Bearer zk_…" -H "Content-Type: application/json" \
-d '{"name":"Acme","order":"Order #123: laptop repair"}'
# → {"client_id":"…","room_id":"…","url":"/i/…"}
# post into a client's room by client_id
curl -X POST https://host/api/v1/clients/<client_id>/messages \
-H "Authorization: Bearer zk_…" -H "Content-Type: application/json" \
-d '{"body":"On it!"}'Other routes: GET /api/v1/me, GET /api/v1/rooms,
GET|POST /api/v1/rooms/{id}/messages (history is paginated via ?limit&before),
POST /api/v1/uploads (multipart, then pass the returned id in attachment_ids).
One self-contained container; state in a ./data volume. Full guide —
docker-compose, host networking for calls, Caddy + TLS, backups, env — in
docs/deploy.md.
mkdir -p data && docker compose up -d # then open the admin link from the logsZENITHAR_BIND— listen address (default127.0.0.1:3000, image0.0.0.0:3000)ZENITHAR_PORT— convenience override for just the HTTP/WS port (handy under host networking)ZENITHAR_DB— SQLite path (defaultdata/zenithar.db, relative tobackend/)ZENITHAR_ATTACHMENTS— uploads dir (default<data>/attachments)ZENITHAR_RECORDINGS— call recordings dir (default<data>/recordings)ZENITHAR_STUN— comma-separated STUN URLs for WebRTC ICE (empty = LAN/localhost)ZENITHAR_PUBLIC_IP— public IP(s) to advertise for calls when behind NAT/DMZ (NAT 1:1); see docs/deploy.mdZENITHAR_UDP_PORTS— media UDP port range for calls (one socket per participant, bound0.0.0.0), e.g.51000-51200(forward the whole range); empty = ephemeralZENITHAR_SECURE_COOKIES— set1/trueto mark auth cookiesSecure(behind TLS)ZENITHAR_FCM_CREDENTIALS— path to the Firebase service-account JSON for Android push (default/data/fcm-sa.json); absent → push disabled, server runs as usualRUST_LOG— log filter (defaultinfo)