Previously VoicX. See the rename and upgrade notes for existing installations.
Next-Generation High-Performance Real-Time Communication Platform
Ultra-low latency SFU voice & video engine, zero-trust E2EE chat messaging, PostgreSQL multi-tenant state persistence, and a 5-tier role-based permission system.
Architecture • Features • Quick Start • Permissions • ServerQuery API • Configuration
noXa is an enterprise-grade, self-hosted real-time communication platform written in Go. Designed for high concurrency and operational clarity, noXa couples a lightweight binary control protocol with a Pion WebRTC SFU engine for sub-100ms multi-party audio/video fan-out, end-to-end encrypted messaging, and granular administrative control.
Note
Zero-Trust Security: Direct messages are fully E2EE using X25519 Double-Ratchet key agreements. The server stores only channel history under persisted scope keys; direct message bodies never hit the server database in plaintext or unwrapped ciphertext.
graph TD
subgraph Clients["Clients"]
Wails["Wails Desktop Application\n(Windows / Linux / macOS)"]
WebUI["Web Browser Client\n(HTML5 / WebRTC / ES6)"]
Bot["ServerQuery Bot / CLI\n(TCP Telnet / SSH)"]
end
subgraph CoreServer["noXa Server Core"]
Control["TCP Control Listener\n:12333 (TLS / TOFU)"]
Keepalive["UDP Keepalive Worker Pool\n:12334"]
WebRTC["Pion WebRTC SFU Engine\n(DTLS-SRTP / ICE / Opus)"]
Query["ServerQuery Admin Protocol\n127.0.0.1:12335 (Raw) / :12339 (SSH opt-in)"]
FileXfer["File Transfer Service\n:12336 (TLS 1.3 / Token Authorized)"]
Health["Health & Metrics Service\n:12337 (/healthz, /readyz)"]
end
subgraph DataStore["Persistence & Messaging"]
Postgres[(PostgreSQL 16\nStore, State, Audit Logs)]
Redis[(Redis 7\nPub/Sub & Rate Limiting)]
end
Wails <-->|TLS Control JSON| Control
Wails <-->|UDP WebRTC Media| WebRTC
WebUI <-->|WebSockets / WebRTC| WebRTC
Bot <-->|TCP Text Commands| Query
Wails <-->|Token Upload/Download| FileXfer
Control --> Postgres
Control --> Redis
Query --> Postgres
WebRTC --> Control
| Protocol Port | Service Component | Wire Format / Transport | Security & Auth |
|---|---|---|---|
TCP :12333 |
Control Engine | Length-prefixed JSON frames over TLS 1.3 | Ed25519 Challenge / Argon2id / TOFU Pinning |
UDP :12334 |
Connection Probes | Datagram Ping/Pong Keepalive | Session Token Verification |
UDP Dynamic |
WebRTC SFU Engine | DTLS-SRTP (Opus audio, H.264/VP8 video) | ICE candidate negotiation & SRTP encryption |
TCP 127.0.0.1:12335 |
ServerQuery Protocol | Line-based ASCII / UTF-8 plaintext stream | Loopback by default; remote binding requires explicit opt-in, and SSH is preferred |
TCP :12336 |
File Transfer Engine | Binary frames over TLS 1.3 | TOFU-pinned certificate plus an ephemeral single-use token |
TCP :12337 |
Health & Prometheus | HTTP GET (/healthz, /readyz, /metrics) |
Liveness/readiness follow the listener bind; metrics are loopback-only unless explicitly enabled; pprof is disabled by default and loopback-only |
- Pion WebRTC SFU: Zero-copy packet fan-out supporting hundreds of concurrent speakers.
- Opus Codec Optimization: Dynamic SDP fmtp line rewriting per channel for variable bitrate (16–128 kbps), Forward Error Correction (FEC), and Discontinuous Transmission (DTX).
- Simulcast Video: Dynamic quality tier selection (
high,mid,lowRID layers) based on subscriber network conditions. - Priority Commander: Automatic audio ducking (−12 dB attenuation) across non-priority channels when a Priority Speaker talks.
- Whisper Routing: Point-to-point and cross-channel targeted voice transmission bypasses standard channel boundaries.
- True E2EE Direct Messaging: Signal-style X25519 prekey bundles with Double-Ratchet forward secrecy.
- Channel Scope Key Rotation: Channel message bodies are sealed with scope keys; server stores ciphertext and manages scope key generations.
- Rich Messaging Controls: Channel history search, pinned messages, emoji reactions, typing indicators, read receipts, and
@mentionnotifications. - Automated Moderation: Regex link whitelisting/blacklisting, duplicate message suppression, rate limiting, and word filtering.
flowchart TD
Tier1["Tier 1: Server Group (Lowest)"] --> Tier2["Tier 2: Client Permissions"]
Tier2 --> Tier3["Tier 3: Channel Client Overrides"]
Tier3 --> Tier4["Tier 4: Channel Permissions"]
Tier4 --> Tier5["Tier 5: Channel Group (Highest)"]
Tier5 --> Eval{"Evaluate Skip & Negate Flags"}
Eval --> Result["Final Granted / Denied Power"]
- 5 Evaluation Tiers: Server Group → Client → Channel Client → Channel → Channel Group.
- Skip & Negate Semantics: Prevent lower-level channel groups from overriding critical server-wide bans or moderation flags.
- Non-Admin Grant Capping: Delegated moderators can only assign permission values less than or equal to their own grant power.
- Detailed Audit Logging: Every group creation, assignment, permission mutation, kick, ban, and token redemption is appended to an immutable database audit log.
Tip
The fastest way to run noXa is using Docker Compose.
-
Clone the repository:
git clone https://github.com/arumes31/noxa.git cd noxa -
Create an explicit local-development environment, then launch PostgreSQL, Redis, and the server:
cp .env.example .env docker compose up -d
The sample environment is for host-local development. Before exposing a deployment, set
NOXA_DEV_MODE=false, replace the sample PostgreSQL credential, and setPOSTGRES_SSLMODEtorequire,verify-ca, orverify-full(or supplyNOXA_COMPOSE_DATABASE_URLwith that sslmode). Each Compose secret also supports an_FILEcounterpart; configure exactly one non-empty source. Set an_FILEvalue to a readable host path; Compose mounts it read-only at/run/secrets/.... Prefer a path outside the repository—docker/secrets/.emptyis only the checked-in empty fallback. On Linux, keep the source directory root-owned0700and each source file root-owned0444; Compose mounts individual files, never the directory. This permits the non-root service reader without exposing host traversal. Production startup rejects the sample credential and plaintext database transport. -
View initial startup log (includes the generated Admin Privilege Token):
docker compose logs -f noxa
Successful main builds publish signed stable releases, starting with v0.4.3,
and mark them Latest for the client updater. Each new commit advances the
highest stable patch tag; rerunning a published commit reuses its tag.
VERSION and the package declarations set the minimum version on that major/minor
release line. CI stamps the actual release patch into the binaries, Windows
package resources, and signed manifest. Prerelease tags do not advance the stable
sequence. Change the synchronized baseline declarations to start a new release line.
- Go:
>= 1.27.1(both Go modules declare this minimum) - Node.js:
>= 24 - Wails CLI:
go install github.com/wailsapp/wails/v2/cmd/wails@v2.16.0(matchclient/go.mod) - PostgreSQL:
>= 16
# Build the standalone server with automatic Git version metadata
make build
# Inspect the exact version embedded by this source state
make version
# Run migrations and start server
NOXA_DATABASE_URL="postgres://noxa:noxa@localhost:5432/noxa?sslmode=disable" ./bin/noxa-serverOn Windows PowerShell, use the equivalent native wrapper:
./scripts/build.ps1 version
./scripts/build.ps1 server# From the repository root
make client-build./scripts/build.ps1 clientStable versions come from vMAJOR.MINOR.PATCH tags. Untagged commits and
dirty trees receive deterministic commit/content metadata automatically; see
docs/versioning.md. Plain go build and wails build
also use Go's embedded VCS information, while the Make targets additionally
stamp the exact dirty-tree fingerprint into the binary.
noXa can be configured via environment variables or a YAML configuration file (config.yaml).
| Environment Variable | Default Value | Description |
|---|---|---|
NOXA_TCP_ADDR |
:12333 |
Primary control TCP listener address |
NOXA_UDP_ADDR |
:12334 |
UDP keepalive ping/pong listener address |
NOXA_GRPC_ADDR |
127.0.0.1:12338 |
Plaintext gRPC administration listener; loopback is mandatory |
NOXA_QUERY_ADDR |
127.0.0.1:12335 |
ServerQuery admin protocol binding address |
NOXA_QUERY_ALLOW_REMOTE |
false |
Explicitly permit a non-loopback raw ServerQuery bind; prefer SSH instead |
NOXA_QUERY_SSH_ENABLED |
false |
Enable the SSH-wrapped ServerQuery listener |
NOXA_QUERY_SSH_ADDR |
:12339 |
SSH ServerQuery listener address |
NOXA_FILE_ADDR |
:12336 |
File transfer upload/download listener address |
NOXA_HEALTH_ADDR |
:12337 |
Health/readiness HTTP listener; /dl bearer links on this listener are plaintext HTTP, so bind loopback or proxy it behind HTTPS |
NOXA_METRICS_ALLOW_REMOTE |
false |
Permit remote /metrics requests; without this opt-in, only IPv4/IPv6 loopback is accepted |
NOXA_PPROF_ENABLED |
false |
Enable runtime /debug/pprof/ diagnostics; every pprof endpoint remains GET-only and direct-loopback-only |
NOXA_SHUTDOWN_TIMEOUT |
30s |
Positive total grace period shared by all services during orderly shutdown |
NOXA_DATABASE_URL |
postgres://... |
PostgreSQL connection URL |
NOXA_REDIS_ADDR |
localhost:6379 |
Optional Redis address for pub/sub fanout |
NOXA_TLS_ENABLED |
true |
Enable TLS 1.3 encryption on control port |
NOXA_TLS_DIR |
./data/tls |
Directory storing the generated TLS certificate and key |
NOXA_TLS_CERT_FILE / NOXA_TLS_KEY_FILE |
empty | Custom certificate and key; both must be configured together |
NOXA_FILE_TLS_ENABLED |
true |
Enable TLS 1.3 on file transfers; disabling is development-only |
NOXA_FILE_ROOT |
./data/files |
Root storage path for uploaded channel files & avatars |
NOXA_FILE_MAX_CONNECTIONS |
128 |
Concurrent accepted file-transfer connections (1–10000) |
NOXA_PII_KEY_FILE |
./data/keys/pii.key |
AES-256-GCM master key file path for PII encryption |
NOXA_CHANNEL_TEMP_LIFETIME_SECONDS |
60 |
Grace period before an empty temporary channel is removed |
NOXA_CHAT_MASTER_KEY_FILE |
./data/keys/chat_master.key |
KEK file used to wrap persisted chat scope keys; back it up with PostgreSQL |
NOXA_CHAT_MASTER_KEY |
empty | Secret-injection override for the key file: one base64 32-byte key or a newline-separated id:base64 key ring; never commit it |
NOXA_CHAT_LEGACY_HISTORY |
encrypt |
One-time handling for legacy plaintext rows: encrypt or purge |
NOXA_CHAT_KEY_ROTATE_MIN_SECONDS |
60 |
Minimum interval used to coalesce scope-key rotations |
NOXA_CHAT_SEARCH_MAX_MESSAGES |
2000 |
Maximum history messages scanned by client-side search |
NOXA_CHAT_MAX_LENGTH |
4096 |
Maximum decrypted chat payload size in UTF-8 bytes |
NOXA_DEFAULT_GROUPS_ENABLED |
true |
Auto-create and assign the built-in Guest and Member groups |
NOXA_TURN_CREDENTIALS_TTL |
24h |
TURN credential lifetime; must be positive and at most 30 days |
NOXA_REDIS_DIAL_TIMEOUT / READ_TIMEOUT / WRITE_TIMEOUT |
5s / 3s / 3s |
Redis client timeouts when Redis is enabled |
NOXA_REDIS_TLS_ENABLED |
false |
Enable verified Redis TLS (TLS 1.2+); optional server name and CA file use NOXA_REDIS_TLS_SERVER_NAME / NOXA_REDIS_TLS_CA_FILE |
NOXA_CHAT_MASTER_KEY takes precedence over NOXA_CHAT_MASTER_KEY_FILE.
Use secret injection for the override; it accepts either a single base64 32-byte
key or a newline-separated id:base64 key ring for key rotation.
The desktop client accepts a hostname without a port in joins and bookmarks.
Publish an SRV record for service noxa, protocol tcp, pointing to the
server's control TCP port (including any externally mapped port):
_noxa._tcp.voice.example.com. 3600 IN SRV 0 5 23456 node.example.com.
node.example.com. 3600 IN A 203.0.113.10
Users enter voice.example.com to connect to node.example.com:23456.
The target must have an A and/or AAAA record. Lower priorities are tried first;
weights distribute the initial choice among targets with equal priority.
Unreachable targets are retried in resolver order within a 15-second connection
budget. Authentication and certificate trust failures stop the connection.
An explicit port, such as voice.example.com:23456, bypasses SRV. With no SRV
record, the client uses the entered hostname on port 12333. IP literals also
use 12333 when their port is omitted. DNS failures are reported; an SRV target
of . declares the service unavailable. SRV lookups time out after 5 seconds.
Bookmarks retain the entered hostname and resolve it again on reconnect.
Certificate pins stay tied to that hostname (with default port 12333), so
changing the SRV target does not bypass an existing pin.
This uses standard DNS SRV records (RFC 2782) and Go's priority/weight-ordered SRV resolver.
Production logging uses Zap sampling: for each repeated message in a sampling tick, it writes the first 100 entries and then every 10th entry thereafter. Development logging keeps Zap's unsampled development configuration.
The generated certificate under NOXA_TLS_DIR is the server's persistent
identity. Back up that directory with the server data volume; replacing or
losing it changes the fingerprint seen by every client.
On first connection, the desktop client pins the control certificate's SHA-256 fingerprint. Later changes fail closed. For a planned rotation:
- Generate or install the new certificate and record the fingerprint printed by the server at startup.
- Verify that fingerprint with users over a separate trusted channel.
- Reconnect. In the certificate-changed warning, compare the presented value with the verified value and choose Trust new fingerprint only when they match.
- Reconnect once more and confirm the connection-security message reports the
expected fingerprint. If verification fails, abort and restore the previous
certificate and key; do not delete
known_servers.jsonto bypass the check.
noXa exposes a line-based administrative text interface on 127.0.0.1:12335
for host-local automation. The raw protocol is plaintext: a non-loopback bind is
rejected unless NOXA_QUERY_ALLOW_REMOTE=true is set explicitly. For remote
administration, enable the SSH transport on port 12339 instead. Docker Compose
does not publish either administration port by default; publish 12339 when
enabling Query SSH.
| Command | Arguments | Description |
|---|---|---|
login |
<username> <password> |
Authenticate as ServerQuery administrator |
clientlist |
[-uid] [-times] [-voice] |
List all connected clients with state metadata |
channellist |
[-topic] [-flags] [-limits] |
List all active channels and configuration |
clientmove |
clid=<id> cid=<target_cid> |
Move a connected client to another channel |
clientkick |
clid=<id> reason=<text> |
Kick client from current channel or server |
banadd |
[uid=<uid>] [ip=<ip>] time=<sec> |
Create an identity or IP address ban rule |
permset |
permid=<key> val=<value> |
Modify permission value for a group or client |
tokenadd |
`tokentype=<0 | 1> id=<group_id>` |
Click to expand ServerQuery session example
$ telnet 127.0.0.1 12335
noXa ServerQuery
welcome to noXa ServerQuery
login admin secretpass
error id=0 msg=ok
channellist
cid=1 channel_name=Default\sChannel total_clients=3|cid=2 channel_name=Lounge total_clients=0
error id=0 msg=ok
clientmove clid=4 cid=2
error id=0 msg=oknoxa/
├── client/ # Desktop Client (Wails v2 / Go + ES6 UI)
│ ├── desktop_windows.go # Windows COM thread affinity & tray setup
│ ├── frontend/ # Single-page UI (Vite / ES6 / Modular CSS)
│ ├── hotkeys.go # Global hotkey registration engine
│ └── ptt_windows.go # Win32 Virtual Key low-level PTT observer
├── cmd/
│ ├── server/ # Standalone noXa Server entrypoint
│ └── migrate/ # Standalone DB migration utility
├── internal/
│ ├── auth/ # Ed25519 challenge & Argon2id authentication
│ ├── broadcast/ # Outbound message fanout & snapshot engine
│ ├── channels/ # Active channel tree manager
│ ├── e2ee/ # Signal-style X25519 Double-Ratchet crypto
│ ├── filetransfer/ # Token-authenticated file pipeline
│ ├── netproto/ # Binary frame codec & JSON message definitions
│ ├── permissions/ # 5-tier permission evaluation engine
│ ├── query/ # ServerQuery line-based admin protocol
│ ├── store/ # PostgreSQL data access layer & migrations
│ └── webrtc/ # Pion WebRTC SFU engine & Opus mixer
├── .github/
│ └── workflows/ # GitHub Actions (CI, Security, Docker, Lint)
├── Dockerfile # Multi-stage production container image
├── docker-compose.yml # Production Docker stack
└── README.md # System documentation
The transparent noXa logo is used on the login screen, in the application menu, and at the top of this README. The original turquoise artwork is preserved; the checkerboard background has been removed from the source JPEG.
| Asset | Location |
|---|---|
| Transparent logo (732 × 732) | client/frontend/public/branding/logo.png |
| Browser favicon (16–256 px) | client/frontend/public/favicon.ico |
| PNG icons (32, 180, 192, 512 px) | client/frontend/public/branding/ |
| Desktop app icon (1024 × 1024) | client/build/appicon.png |
| Windows app and installer icon | client/build/windows/icon.ico |
The system tray embeds the same mark (ICO on Windows, PNG on macOS/Linux). The frontend includes the favicon and Apple touch icon links. Vite copies the public assets into the frontend build; Wails uses the desktop assets when the application is rebuilt. Keep the transparent PNG as the branding source and resize it when replacing icons, rather than converting it back to JPEG.
The transparent noXa logo is used on the login screen, in the application menu, and at the top of this README. The original turquoise artwork is preserved; the checkerboard background has been removed from the source JPEG.
| Asset | Location |
|---|---|
| Transparent logo (732 × 732) | client/frontend/public/branding/logo.png |
| Browser favicon (16–256 px) | client/frontend/public/favicon.ico |
| PNG icons (32, 180, 192, 512 px) | client/frontend/public/branding/ |
| Desktop app icon (1024 × 1024) | client/build/appicon.png |
| Windows app and installer icon | client/build/windows/icon.ico |
The frontend includes the favicon and Apple touch icon links. Vite copies the public assets into the frontend build; Wails uses the desktop assets when the application is rebuilt. Keep the transparent PNG as the branding source and resize it when replacing icons, rather than converting it back to JPEG.
Production operators should adopt the repository's service-level objectives, practice the backup and restore drill, and keep the incident runbook available outside the deployment being operated. Update these documents when architecture, telemetry, or recovery procedures change.
noXa is engineered around a strict security posture:
- Challenge Authentication: Public key cryptography prevents password sniffing over untrusted networks.
- Strict TOFU Certificate Pinning: Clients pin self-signed TLS certificates on first connect.
- PII Storage Protection: Sensitive user metadata columns are encrypted at rest with AES-256-GCM authenticated data.
- Privilege Capping: Non-administrators cannot grant permissions exceeding their own delegated grant tier.
This project is licensed under the MIT License. See the LICENSE file for complete details.