A privacy-focused voice communication server and client, inspired by TeamSpeak, built entirely in Go.
GoSpeak uses a selective forwarding architecture: the control plane handles signalling, the voice plane relays encrypted audio packets, and the screen plane relays encrypted screen-share frames.
- Real-time voice chat: Opus codec at 48 kHz, 20ms frames via PortAudio
- Encrypted voice: AES-128-GCM with authenticated headers; server relays without decoding (see Security for key model caveats)
- Authenticated TLS 1.3 control plane: system PKI for public certificates and explicit TOFU fingerprint pinning for self-signed servers
- Channel system: hierarchical channels with sub-channels, temporary channels, max-user limits
- Role-based access control: Admin, Moderator, User roles with granular permissions
- Token-based authentication: 256-bit random tokens, SHA-256 hashed storage
- Text chat: per-channel messaging
- Basic screen sharing: opt-in per-channel viewing with a dedicated encrypted relay plane
- Desktop GUI: native cross-platform UI built with Fyne
- Server bookmarks: save and manage server connections
- YAML configuration: server channels, client settings, bookmarks
- Admin tools: create/delete channels, manage tokens, kick/ban, import/export config
- Global hotkeys: configurable push-to-mute/deafen (Windows; F11/F12 default)
- Voice Activity Detection: energy-based VAD with configurable threshold
- Containerized builds: reproducible multi-stage Podman/Docker builds for Linux and Windows
Note: Voice packets and screen-share frames are encrypted with AES-128-GCM keys generated by the server and distributed to authorized clients over TLS. The server relays both without needing to decode them, but a compromised or modified server could still decrypt them because it generates the keys. See Security — Threat Model for details.
docker compose up --buildThe server listens on:
- TCP :9600: TLS control plane
- UDP :9601: Encrypted voice
- Screen sharing: disabled by default;
-screen-shareenables the encrypted TCP relay on:9603 - Metrics: disabled by default; enable with
-metrics :9602and keep the plaintext endpoint on a trusted network
The bundled monitoring overlay enables metrics only on the Compose network and does not publish port 9602 on the host:
GOSPEAK_METRICS_ADDR=:9602 \
GRAFANA_ADMIN_PASSWORD='<choose-password>' \
docker compose -f compose.yaml -f compose.monitoring.yaml up -dGrafana is then available only on 127.0.0.1:3000. Prometheus is not published
on the host. The bundled dashboard shows current and startup-lifetime high-water
utilization for pre-auth admission, authentication, and account provisioning.
Authentication usage is failed attempts plus in-flight checks against the
30-attempt, one-minute source window; provisioning usage is successful creations
plus in-flight reservations against the 120-account, one-hour source window.
The rejection chart distinguishes source exhaustion, tracker capacity, and
in-flight window transitions without exposing source identities as labels. The
75% yellow and 90% red thresholds are dashboard guidance, not configured alerts;
high-water values reset when the server restarts.
On first run, GoSpeak writes an admin bootstrap credential to
bootstrap-admin.token in the data directory. For this Compose setup, read it
with cat ./data/bootstrap-admin.token. Use it for the first admin login and
save the personal token returned by the server. Interrupted first logins can be
retried for the same administrator. The server removes the bootstrap file once
that personal token is used; the credential is never written to normal logs.
# Extract binaries from container build
docker compose --profile build run builder
# Run server
./bin/gospeak-server -open -screen-share # -open allows token-less connectionsDownload the appropriate binary for your platform from Releases, or build from source:
./bin/gospeak-client-win.exe # Windows
./bin/gospeak-client-lin # LinuxEnter the server address, your username, and (optionally) an invite token to connect. On first login the server issues a personal token; keep it to reconnect with the same username.
For a self-signed server, the client displays its SHA-256 public-key fingerprint before sending credentials. Verify that fingerprint with the server operator through a trusted channel, then choose Trust and Connect. The saved pin is checked for both control and screen connections. A later identity change is a hard connection failure and requires an explicit Re-trust and Connect confirmation after the new fingerprint has been verified. Publicly trusted certificates are validated with the operating system's CA store and hostname checks without a TOFU prompt.
Existing bookmark files remain compatible. They gain a trusted_server_pins section after the first self-signed server is trusted; no pin is silently created during migration.
┌──────────────────────────────────────────────────────────────────────┐
│ GoSpeak Server │
│ │
│ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐ ┌──────────┐ │
│ │ Control Plane │ │ Voice SFU │ │ Screen Relay │ │ SQLite │ │
│ │ TCP/TLS 1.3 │ │ UDP Relay │ │ TCP/TLS │ │ Store │ │
│ │ :9600 │ │ :9601 │ │ :9603 │ │ │ │
│ └──────────────┘ └───────────────┘ └──────────────┘ └──────────┘ │
└──────────────────────────────────────────────────────────────────────┘
│ │ │
JSON/TLS AES-128-GCM AES-128-GCM
│ │ │
┌─────────────────────────────────────────────────────┐
│ GoSpeak Client │
│ │
│ ┌──────────┐ ┌──────────┐ ┌────────┐ ┌────────┐ │
│ │ Fyne GUI │ │ Engine │ │ Audio │ │ Crypto │ │
│ │ │ │ Control │ │ Opus │ │ AES │ │
│ │ │ │ + Voice │ │ + VAD │ │ GCM │ │
│ └──────────┘ └──────────┘ └────────┘ └────────┘ │
└─────────────────────────────────────────────────────┘
| Document | Description |
|---|---|
| Architecture | Package structure, data models, server/client lifecycle |
| Protocol | Control plane messages, voice packet format, wire protocol |
| Security | Encryption details, key distribution, RBAC, threat model |
| Audio Pipeline | Capture/playback, Opus codec, VAD, jitter buffer |
| Building | Container builds, local dev setup, build targets |
| Deployment | Rocky Linux 10 cloud-init example |
| Flag | Default | Description |
|---|---|---|
-control |
:9600 |
TCP/TLS bind address |
-voice |
:9601 |
UDP voice bind address |
-screen |
:9603 |
TCP/TLS screen-share relay bind address |
-db |
gospeak.db |
SQLite database path |
-data |
. |
Data directory for generated TLS files and the first-run bootstrap-admin.token |
-open |
false |
Allow connections without a token |
-screen-share |
false |
Enable per-channel screen sharing |
-channels-file |
YAML file for initial channel setup | |
-cert / -key |
(empty) | Custom matching TLS pair, including self-signed certificates; provide both. When both are empty, GoSpeak loads or creates server.crt and server.key in -data. On the first new TLS connection within 30 days of expiry, it renews the automatic certificate without changing the private key or TOFU identity |
-metrics |
(empty) | Prometheus /metrics and /healthz HTTP bind address; opt in with a trusted bind such as 127.0.0.1:9602 |
-export-users |
false |
Export all users as YAML and exit |
-export-channels |
false |
Export all channels as YAML and exit |
-log-level |
info |
Log level |
-log-format |
text |
Log format: text or json |
channels:
- name: General
description: Main voice channel
max_users: 50
- name: Gaming
description: Gaming channels
allow_sub_channels: true
channels:
- name: FPS
- name: MMOThe parser requires a channels sequence in one YAML document of at most 512 KiB and rejects unknown fields, aliases and merges, duplicate sibling names, and configurations above 8 levels or 256 channels. Imports create missing channels atomically; channels that already exist under the same parent keep their current settings. Older examples used children:; that field was ignored instead of creating subchannels, so replace it with channels: before upgrading.
Upgrades also reject an existing database that already contains channels with the same name under one parent. GoSpeak does not guess which channel to keep because the rows may have different settings or references. Back up the database, inspect the conflicting (parent_id, name) rows, and resolve them before restarting the upgraded server.
| Component | Technology |
|---|---|
| Language | Go 1.24 |
| GUI | Fyne v2 |
| Audio I/O | PortAudio via gordonklaus/portaudio |
| Voice Codec | Opus via hraban/opus |
| Encryption | AES-128-GCM (stdlib crypto/aes), Argon2id (golang.org/x/crypto) |
| Database | SQLite via modernc.org/sqlite (pure Go) |
| TLS | Go stdlib crypto/tls (TLS 1.3) |
| Config | gopkg.in/yaml.v3 |
| Containers | Podman / Docker with multi-stage builds |
See CONTRIBUTING.md for development guidelines.
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
This means you can use, modify, and distribute GoSpeak freely, but:
- Any modified version must also be open-sourced under AGPL-3.0
- If you run a modified version as a network service, you must provide the source code to users
- Commercial use requires a separate license: contact the author for licensing
See LICENSE for the full license text.
Copyright (c) 2026 Nicolas Haas