A modern, self-hostable, privacy-first voice platform for groups that take communication seriously.
Raddir is a TeamSpeak-inspired voice communication platform with true end-to-end encryption. The server cannot read your voice or text content — only metadata (who, when, where) is visible. Licensed under AGPL-3.0; all third-party dependencies are permissively licensed (MIT/ISC/Apache 2.0).
- Low-latency voice channels — Opus codec (48kHz, 128kbps, stereo, FEC, DTX), mediasoup SFU routing
- Push-to-talk & voice activation — Configurable PTT keybind or VAD with adjustable threshold
- Per-user volume control — 0–200% per user, plus a master output volume slider
- Mic test mode — Real-time level meter with VAD threshold visualization to dial in sensitivity
- Audio processing — Toggleable noise suppression, echo cancellation, and auto gain control
- Audio device selection — Choose input/output devices from settings
- Mute & deafen — Mute stops transmission; deafen mutes all incoming audio
- Speaking indicators — Green ring on avatars for yourself and other users
- Webcam support — Configurable resolution (480p/720p/1080p), fps (5–60), and bitrate (500–6000 kbps) via video settings tab; permission-gated per role
- Screen sharing — Share entire screen or individual window via native OS picker; configurable fps (1–60) and bitrate (1000–8000 kbps)
- E2EE video — Same AES-256-GCM Insertable Streams pipeline as voice; all simulcast layers encrypted; server cannot decode video frames
- Simulcast — 3-layer webcam (quarter/half/full resolution), 2-layer screen share; viewers receive low quality in grid, high quality when maximized
- Producer limits — Server-configurable max concurrent webcams (default 5) and screen shares (default 1) per channel; enforced server-side
- Video grid — Responsive tile layout above chat; click to maximize any feed; screen shares get priority sizing
- Video indicators — Camera and monitor icons in user list show who is sharing
- End-to-end encrypted voice — AES-256-GCM via Insertable Streams; server is cryptographically blind
- End-to-end encrypted video — Same Insertable Streams E2EE as voice; webcam and screen share frames are AES-256-GCM encrypted
- End-to-end encrypted text chat — Messages encrypted client-side before transmission
- Persistent identity keys — ECDSA P-256, device-bound keypair stored via Electron
safeStorage - User verification — Signal-style safety numbers and fingerprints; verified users show a badge
- Identity export/import — Backup your identity as a passphrase-encrypted file (PBKDF2 + AES-256-GCM)
- ECDH P-256 key exchange — Per-channel shared secrets derived via HKDF
- Built-in TLS — Server always runs HTTPS/WSS; self-signed cert auto-generated, Let's Encrypt supported
- Scoped certificate trust — Electron only trusts self-signed certs for the specific server you connect to, not globally
- No telemetry — Zero tracking, no analytics, no phone-home
- No accounts required — Connect with just a nickname; identity stays on your device
- Self-hosting first — Single binary, works fully offline / LAN, no cloud dependency
- TLS out of the box — Self-signed (default), Let's Encrypt (automatic ACME), or custom certificates
- Optional server password — Protect your server with
RADDIR_PASSWORD; leave empty for open access - Admin token authentication — Set
RADDIR_ADMIN_TOKENand provide it on connect to get ephemeral admin privileges for that session (not persisted to DB); all mutating REST API routes require it - Invite system — Admins generate invite codes that encode the server address (routing hint) and a token into a shareable blob. Recipients paste the code on the connect screen to redeem an unbound session credential — no identity key is needed at this stage. The server password is never included
- Session credentials — On first WebSocket connect, the credential is bound to the user's public key. Subsequent connections must present the same key. Credentials can be revoked server-side
- Encrypted credential storage — Passwords, admin tokens, and session credentials are encrypted at rest using Electron's OS-level
safeStorageAPI (DPAPI on Windows, Keychain on macOS, libsecret on Linux). Falls back to plaintext in browser mode - Rate limiting — IP-based sliding-window rate limiting on WebSocket auth and public invite endpoints; per-connection post-auth signaling rate limits by message category
- CORS policy — Restricted to Electron (
file://,app://) and localhost dev origins - Server customization — Set a custom server name, description, and icon via the admin panel; changes broadcast live to all connected clients
- Role-based permissions — Admin, Member, Guest roles with granular permission control and role colors
- Live permission updates — Role and channel override changes take effect immediately for all connected clients without reconnecting
- Permission-gated UI — Buttons for restricted actions (video, screen share) show as disabled with a tooltip explaining the missing permission
- Channel permission overrides — Per-channel permission tweaks per role
- Effective permissions viewer — See computed permissions for any user
- Admin panel — General settings, channels, users (kick/ban/move/role assignment), invite codes, roles, overrides, and effective permissions
- Move users — Admins can move users between channels (requires
moveUserspermission) - Ban system — Ban users by identity; persisted across reconnects
- Electron desktop app — Native window, clean frameless UI
- Appearance settings — Dark, light, or system theme (follows OS preference)
- Server browser — Save multiple servers with name, address, password, and admin token
- Paste invite — Paste an invite code to auto-add a server and receive a session credential
- Smart server URLs — Type
your-server:4000instead ofwss://your-server:4000/ws - Channel tree — Hierarchical channels with inline user avatars and speaking rings
- User verification UI — Click any user to verify via safety number; verified badge in channel tree and user list
- Your identity card — Click yourself to see your fingerprint with a copy button
- E2EE text chat — Per-channel encrypted chat with in-memory message history across channel switches
- User avatars — Upload a profile picture from settings; displayed in channel tree, user list, and chat messages (rounded, with speaking glow)
- Settings panel — Profile, audio, video, keybinds, appearance, and identity tabs
- Identity management — View fingerprint, export/import encrypted identity backup, manage verified users
- Join/leave sounds — Audio cues when users enter or leave channels
- Reconnect overlay — Automatic reconnection handling with visual feedback
- Kick/ban notifications — Toast notifications when kicked or banned
- Channel tree model — Persistent server → channel hierarchy, TeamSpeak-style
- SQLite database — Zero-config persistence via better-sqlite3
- mediasoup SFU — Scales to hundreds of users with configurable worker count
- Docker ready — Multi-stage Dockerfile, Docker Hub image, Portainer-compatible
- Electron builds — NSIS installer + portable exe (Windows), DMG (macOS), AppImage + deb (Linux)
- pnpm monorepo — Shared types between server and client
- Node.js ≥ 20
- pnpm ≥ 9
# Install dependencies
pnpm install
# Build shared types
pnpm build:shared
# Start the server (dev mode with hot reload)
pnpm dev:server
# Start the client (Electron + Vite + React)
pnpm dev:clientThe server starts on https://localhost:4000 with WebSocket signaling at wss://localhost:4000/ws. A self-signed TLS certificate is auto-generated on first start.
pnpm --filter @raddir/client dev:browserOpens at http://localhost:5173.
The server image is available on Docker Hub:
docker pull zahli/raddir-server:latestOr build locally:
docker build -f Dockerfile.server -t raddir-server .services:
raddir:
image: zahli/raddir-server:latest
container_name: raddir
restart: unless-stopped
ports:
- "4000:4000"
- "40000-40100:40000-40100/udp"
environment:
- RADDIR_HOST=0.0.0.0
- RADDIR_PORT=4000
- RADDIR_DB_PATH=/data/raddir.db
- RADDIR_RTC_MIN_PORT=40000
- RADDIR_RTC_MAX_PORT=40100
- RADDIR_ANNOUNCED_IP= # Set to your server's public IP
- RADDIR_ADMIN_TOKEN= # Optional
- RADDIR_PASSWORD= # Optional
- RADDIR_TRUST_PROXY=false # Set true only behind a reverse proxy
- RADDIR_TLS_MODE=selfsigned
- RADDIR_LOG_LEVEL=info
volumes:
- raddir-data:/data
volumes:
raddir-data:
driver: localFor Let's Encrypt, set RADDIR_TLS_MODE=letsencrypt, RADDIR_TLS_DOMAIN, RADDIR_TLS_EMAIL, and expose port 80.
# Windows (NSIS installer + portable exe)
pnpm electron:build:win
# macOS (DMG, x64 + arm64)
pnpm electron:build:mac
# Linux (AppImage + deb)
pnpm electron:build:linuxOutput goes to packages/client/release/.
Note: You can only build for your current OS natively. For cross-platform builds, use CI (e.g., GitHub Actions).
Configuration is loaded in order: environment variables → config file → defaults.
| Variable | Default | Description |
|---|---|---|
RADDIR_HOST |
0.0.0.0 |
Listen address |
RADDIR_PORT |
4000 |
HTTPS/WSS port |
RADDIR_RTC_MIN_PORT |
40000 |
WebRTC UDP port range start |
RADDIR_RTC_MAX_PORT |
49999 |
WebRTC UDP port range end |
RADDIR_ANNOUNCED_IP |
(empty) | Public IP for WebRTC (required for remote access) |
RADDIR_DB_PATH |
./data/raddir.db |
SQLite database path |
RADDIR_MEDIA_WORKERS |
(CPU count) | Number of mediasoup workers |
RADDIR_CONFIG_PATH |
./raddir.config.json |
Path to optional JSON config file |
| Variable | Default | Description |
|---|---|---|
RADDIR_PASSWORD |
(empty) | Server password (leave empty for open access) |
RADDIR_ADMIN_TOKEN |
(empty) | Admin authentication token — grants ephemeral admin privileges for the session (not persisted) |
RADDIR_OPEN_ADMIN |
false |
Allow admin API without a token (not recommended for public servers) |
RADDIR_TRUST_PROXY |
false |
Trust X-Forwarded-For header for rate limiting. Only enable if behind a reverse proxy |
The server always runs HTTPS/WSS. Three modes are available:
| Variable | Default | Description |
|---|---|---|
RADDIR_TLS_MODE |
selfsigned |
TLS mode: selfsigned, letsencrypt, or custom |
RADDIR_TLS_DOMAIN |
(empty) | Domain name (required for letsencrypt) |
RADDIR_TLS_EMAIL |
(empty) | Contact email (required for letsencrypt) |
RADDIR_TLS_CERT |
(empty) | Path to PEM cert file (required for custom) |
RADDIR_TLS_KEY |
(empty) | Path to PEM key file (required for custom) |
Self-signed (default): Auto-generates an RSA-2048 certificate on first start, valid 10 years, persisted in the data directory. The Electron client accepts self-signed certs only for the specific server host you connect to (scoped trust, not global).
Let's Encrypt: Obtains a free certificate via ACME HTTP-01 challenge. Requires a domain pointing to the server and port 80 open (temporarily, during cert issuance). Certificates auto-renew every 12 hours if expiring within 30 days — hot-swapped without restart.
Custom: Bring your own PEM certificate and key files (e.g., from a reverse proxy or corporate CA).
| Variable | Default | Description |
|---|---|---|
RADDIR_LOG_LEVEL |
info |
Log level: debug, info, warn, error |
| Port | Protocol | Purpose |
|---|---|---|
4000 |
TCP | HTTPS API + WSS signaling |
80 |
TCP | ACME challenge (Let's Encrypt only, temporary) |
40000-49999 |
UDP | WebRTC media (voice) |
For Docker Compose, the default UDP range is narrowed to 40000-40100 for practicality. Adjust RADDIR_RTC_MIN_PORT / RADDIR_RTC_MAX_PORT as needed.
packages/
├── shared/ # Protocol types, permission enums, crypto types
├── server/ # Node.js backend: Fastify (HTTPS) + mediasoup SFU + SQLite
└── client/ # Electron + React + Vite desktop app
Mic → Opus encode → [E2EE: AES-256-GCM encrypt] → DTLS-SRTP → mediasoup SFU → DTLS-SRTP → [E2EE: AES-256-GCM decrypt] → Opus decode → Speaker
Camera/Screen → VP8/VP9 encode → [Simulcast: 3 layers] → [E2EE: AES-256-GCM encrypt per layer] → DTLS-SRTP → mediasoup SFU → DTLS-SRTP → [E2EE: AES-256-GCM decrypt] → VP8/VP9 decode → Display
The SFU selects which simulcast layer to forward based on the 10 unencrypted VP8/VP9 header bytes (payload descriptor + keyframe indicator). The actual video content is always encrypted.
| What | Transport Encrypted? | E2E Encrypted? | Server can read? |
|---|---|---|---|
| Voice audio | ✅ DTLS-SRTP | ✅ AES-256-GCM | ❌ No |
| Webcam video | ✅ DTLS-SRTP | ✅ AES-256-GCM | ❌ No |
| Screen share video | ✅ DTLS-SRTP | ✅ AES-256-GCM | ❌ No |
| Text chat | ✅ WSS (TLS) | ✅ AES-256-GCM | ❌ No |
| Signaling / metadata | ✅ WSS (TLS) | — | ✅ Yes (routing) |
| Identity keys | ✅ WSS (TLS) | — | ✅ Public keys only |
| User avatars / server icon | ✅ WSS (TLS) | — | ✅ Yes (intentionally public) |
| Roles / permissions | ✅ WSS (TLS) | — | ✅ Yes (server authorization) |
| Telemetry | — | — | ❌ None sent |
The server sees metadata (who connects, when, to which channel). It cannot see or hear content. This is the same trust model as Signal.
Users can verify each other using safety numbers (12-digit numeric codes derived from SHA-256 of both public keys) and fingerprints (hex digest of a single public key). Verified users display a green shield badge in the channel tree and user list. Verification state is persisted locally.
Your cryptographic identity can be exported as a passphrase-encrypted JSON file (PBKDF2 key derivation + AES-256-GCM). This allows restoring your identity on a new device or after a reinstall. The salt and IV are stored in cleartext in the file — this is standard and safe, as they are not secrets; only the passphrase protects the key material.
Audio only:
| Users | CPU | RAM | Bandwidth |
|---|---|---|---|
| 1–25 | 1 core | 256 MB | 5 Mbps |
| 25–100 | 2 cores | 512 MB | 20 Mbps |
| 100–500 | 4 cores | 1 GB | 100 Mbps |
With video (simulcast):
| Users sharing video | Bandwidth (grid view, low layer) | Bandwidth (1 maximized, rest low) |
|---|---|---|
| 5 webcams | ~15 Mbps | ~20 Mbps |
| 10 webcams | ~60 Mbps | ~70 Mbps |
| 1 screen share | ~5 Mbps | ~25 Mbps |
Simulcast reduces grid-view bandwidth by ~90% compared to sending full resolution to every viewer. Use the server admin panel to set producer limits appropriate for your bandwidth.
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-only).
You are free to use, modify, and distribute this software under the terms of the AGPL-3.0. If you run a modified version of Raddir as a network service, you must make the source code available to its users.