Running it
Two programs on your box. Your members run the third.
Two Rust servers, a desktop app, and four services around them.
There's no packaged release yet, and the source isn't published yet. Putting it up is on the board.
The programs
Yours to run.
Rust. One live connection per client carries the lot: messages, edits, reactions, pins, typing, presence, voice state, membership, roles, threads. Sign-in goes through your own Authentik.
The voice server, written in Rust from the protocol up. Nobody upstream can turn it off, because it's yours.
A Tauri + Svelte desktop app for Linux and Windows. Nothing is baked in. Members type your server's address at login and everything else comes from the server.
Around them sit four services: PostgreSQL, MinIO for files, Authentik for identity, LiveKit for screen share. There's no Redis. The one optional outside service is Tenor for GIFs, and the picker doesn't even show up until you add a key.
Day to day
The boring bits, which are the ones that matter at 2 am.
- Settings
- No
.envfile. Every instance setting is a row in the database, edited in the operator app, type-checked, validated as a batch, secrets echoed back as***. The process itself reads a handful of environment variables. Those are the database URL, the at-rest key (as encrypted systemd credentials), the admin build path, the log level and the metrics bind. - What it runs on
- Linux. We build and run the server and the voice server there, and the deploy path assumes systemd. Windows is the desktop app only; there's no Windows build of either server. Neither program needs a grunty box; the hardware question is really your database and file store.
The numbers
Measured on the boxes running our own instance, which today serves only us, so treat these as floors. The server sat at 12 MB of memory freshly started; the voice server at 15 MB after six days up. The binaries are 59 MB and 9.6 MB.
- Fresh installs
- Every instance-specific address seeds as the literal
<stub>, so a fresh install never probes somebody else's servers by accident. A missing key aborts the boot. - Storage limits
- Three caps, all rows in the config editor: bytes per file, files per message, and total attachment bytes per server. The per-server cap is off until you set it; past it an upload is refused rather than the disk filling quietly. Each server's stored bytes come back from the API, but the operator app's server table doesn't show the figure yet.
- Tokens at rest
- The refresh token Authentik issues for each member is encrypted in the database, every row under its own key derived from the at-rest key. Session tokens are stored only as hashes, so the database never holds one anybody could use. Rotating that key costs what losing it costs. Those tokens are gone and everyone signs in again.
- Deploys
- One command ships the API, gates on the health check's body (not its status code), and puts the old binary back if the new one isn't healthy. The voice-server deploy refuses to ship anything that would change the certificate your users have pinned, and backs the certificate identity up off-box.
- Health
- The API says
ok,degraded(storage down, database up) or 503, from real probes. Babel goes red the moment it can move audio but can't enforce permissions, including when its secret's been rotated under it. - Alerting
- Voice is checked every five minutes. You get one notification when it goes down and one when it's back. The alert path runs on a different box from the voice server, so an outage can't silence its own alarm.
- Voice
- The one process on the public internet runs unprivileged and sandboxed under systemd. It holds a single secret, an encrypted database URL only that host can unseal. Control and audio share one TLS connection on one port.
- Voice trust
- Babel mints its own certificate on first start. Clients pin the fingerprint on first connect and refuse a silent change. Joining voice mints a single-use token, dead in sixty seconds. No certificate authority needed.
- TLS
- The API terminates none. Put Caddy, nginx or haproxy in front with one vhost and a path split for LiveKit. The client assumes https unless you type http, and refuses a screen-share server that isn't on wss unless it's local.
- LiveKit's config, generated
- The server renders a ready-to-deploy LiveKit config from the settings already in your database, the same rows it signs tokens with. Regenerate it after a settings change and the SFU and the server can't disagree. No page in the operator app links it yet; it's one request away.
- Admin
- The admin side is a separate web app on its own hostname. It has every dependency's health and latency on one page, users and servers, the audit log across all servers, instance reports, nine maintenance jobs (six with a dry run), and the config editor. The maintenance page shows you what a cleanup would remove before it removes it.
- Apps
- Each app keeps its own tables and its own migrations, applied at start-up against its own history and held apart from the core schema. Installing or removing one touches nothing the rest of the server owns.
- Identity drift
- A maintenance job asks Authentik who it has and compares it with who's here, both directions. It reports; it removes nothing. It needs an API token in the config editor, and says so plainly when there isn't one.
- Logs
- The app keeps its recent lines in memory, the interface and the desktop side in one list. An admin can read them in that tab or export them to a file. Tokens, credentials and addresses are stripped on the way in, so the copy that leaves the machine never held them.
- Diagnostics
- Instance admins get a Diagnostics tab inside the same desktop app everyone runs. It shows what the app is running and where it's pointed, the state of the connection and how often it has reconnected, counters from both ends of a call and a share, and a measured offset between picture and sound. Members never see the tab.
- Breaking it on purpose
- The Diagnostics tab goes past counters. A spectrum view of the mic or the playout path, with a distortion figure measured on the playout side; a rate-this-call button that bundles your score with the measured numbers, scrubbed and ready to paste; a plot of clock drift against the correction being applied; and buttons that disrupt the screen-share connection — forced reconnects, a simulated node failure — so you can watch it recover before a real night depends on it.
- Looking inside
- The operator app can join any voice channel from the browser, so you can tell a broken screen-share server from a broken client without a member on the phone. The health page carries the voice server's own counters beside the rooms LiveKit has up. Open connection counts and the database's slowest queries are on the API, with nothing rendering them yet.
- Operator reach
- Instance admin reaches past every server. An operator can list and edit every account, delete any server, and mint a token for any voice channel, including one they have never joined. Those routes skip the membership checks, because an operator has to be able to look at a room they were never invited to. Every one of those actions is written down. Who did it, what it touched, when.
- Edge
- After any proxy or firewall change, one script proves the public path from outside your network and matches it against the LAN path. That's the breakage a localhost check can't see.
- API
- Every instance serves its own OpenAPI spec and a Swagger UI to anyone with an account. The operator app's types are generated from it.
- Metrics
- Prometheus on a separate port. A public bind refuses to start without a bearer token.
- Throttles
- GIF search is capped at two a second per member, so one person can't burn through your Tenor quota. Comms edits are capped the same way, at ten new nets and thirty permission changes a minute. A stuck client can't rewrite your nets in a loop. Both count per account rather than per address.
Where it's at
No package yet, and what's coming for whoever hosts it.
It runs on our own hardware today, from source. There's a deployment guide for standing up a fresh instance, and every trap we know about is written in it. But some of the deploy tooling still carries our own hostnames, so it's not a clean install on somebody else's box yet.
On the board, no order promised: packages and a container image for the server and the voice server, a tracked service unit, a first-admin bootstrap so a fresh install has someone who can log in, each of those hostnames turned into an override, server backup and export, data export, and account deletion. The roadmap has each one and where it stands. No dates.