Repository navigation
Configuration
Almost nothing here is required. JWT_SECRET is the only variable you genuinely have to set; everything else has a working default, and most settings that matter day to day are changed in the UI rather than in .env.
-
JWT_SECRETis required. The backend refuses to start in production without it. -
ENCRYPTION_KEYis optional but recommended. It encrypts stored credentials — API keys and SSH passwords. If unset,JWT_SECRETis used instead, which means rotating that secret would make every stored credential unreadable.
Generate both:
echo "JWT_SECRET=$(openssl rand -base64 32)" >> .env
echo "ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env| Variable | Default | Description |
|---|---|---|
JWT_SECRET |
— | Required. Secret for JWT signing. Must be set in production. |
ENCRYPTION_KEY |
— | Optional. Dedicated key for AES-256-GCM credential encryption. Falls back to JWT_SECRET if not set. |
PORT |
3001 |
Backend port |
DATABASE_URL |
file:./data/ts6webui.db |
SQLite database path |
JWT_ACCESS_EXPIRY |
15m |
Access token lifetime |
JWT_REFRESH_EXPIRY |
7d |
How long a login lasts without being used; every refresh starts the period again. A whole number with s, m, h, d or w (12h, 30d); anything else keeps 7d and logs a warning at startup. Applies from backend 2.66.4 on - earlier versions ignored it and always used 7 days. |
FRONTEND_URL |
http://localhost:5173 (http://localhost:3000 in docker-compose.yml, https://ts6.example.com in the Coolify one) |
The address the web interface is opened at. CORS and the Single Sign-On redirect address are built from it, so behind a domain set it to your public origin — see Deployment. |
TRUST_PROXY |
1 |
How many reverse proxies stand between a visitor and the backend, so it knows which entry of X-Forwarded-For is the visitor (the sign-in rate limit counts per visitor address). 1 is the frontend container's nginx alone; a proxy of your own in front of it makes 2. A number from 0 to 10 (false = none) or a comma-separated list of proxy addresses, networks and the names loopback, linklocal, uniquelocal. true and anything unusable are refused with a warning in the log and the default applies. Too low shows your proxy instead of the visitor, too high believes an address the visitor wrote himself. Settings → Network shows what fits; details in Deployment. |
IP_BANS_DISABLED |
false |
Emergency switch for the IP bans of the Connection Journal: with true no address is turned away, whatever the ban list says, and a new web ban is refused. For the day you banned the address you come in from - set it, restart the backend, lift the ban, and unset it again. |
DB_BACKUP_KEEP |
5 |
Docker only. How many database copies the backend keeps in data/backups/; it takes one before each start that changes the database schema, and the oldest go first. A whole number of at least 1; anything else keeps 5 and logs a warning. See Deployment. |
MUSIC_DIR |
/data/music |
Root for the media library. New uploads and downloads are organized into music/ and video/ subfolders underneath it; files sitting directly in the root (from before that split, or a volume shared with another app) are still picked up by the library scan and the stream-source picker |
SIDECAR_URL |
— | Optional. Full URL of the WebRTC sidecar service (e.g. http://ts6-sidecar:9800). Set in Docker when the sidecar runs as a separate container. |
SIDECAR_BINARY_PATH |
sidecar |
Path to the sidecar binary, used only in local mode (running the sidecar as a subprocess instead of a separate container). |
FILES_MAX_UPLOAD_MB |
100 |
Largest single file the Files page will upload to a channel's file repository, in megabytes. Checked when the upload is agreed and again against the real body. The frontend image's nginx has a location of its own for upload bodies without a size cap, so raising this needs no rebuild; a reverse proxy of your own in front of it has to allow bodies this large too — see Deployment. |
YT_COOKIE_FILE |
— | Optional. Path to a Netscape-format cookies.txt for yt-dlp. Can also be managed under Settings → YouTube. A file mounted into the container has to be writable for uid 1000, because yt-dlp rewrites it after a run (Deployment). |
TS_ALLOW_SELF_SIGNED |
false |
Set to true/1 to accept self-signed TLS certificates when connecting to the TeamSpeak WebQuery API. This switches certificate checking off entirely (any certificate, any hostname) and for every connection, so leave it unset for a certificate from a public CA — see WebQuery over HTTPS. |
VOICE_DEBUG |
unset (off) | Legacy. Verbose voice-bot/audio-pipeline logging. Only read once, on first boot, to seed the database-backed setting if it has never been saved — after that Settings → Debug is authoritative and this variable is ignored. |
RANK_CHECK_DEBUG |
unset (off) | Legacy. As above, for per-client Rank Check detail. Promotions, errors and the per-run summary always log regardless. Prefer Settings → Debug, which toggles at runtime without a restart. |
QUERY_DEBUG |
unset (off) | Legacy. As above, for logging every ServerQuery command and its answer (credentials written as ***). Set to 1 to seed the switch; Settings → Debug → ServerQuery Debug is authoritative after that. |
NODE_ENV |
development |
Set to production in Docker; enables the startup guard that refuses a default JWT_SECRET. |
Looking for the stream's resolution, frame rate, bitrate or volume? Those are not environment variables. They live in the web interface under Settings → Streaming, which sets what
!streamuses when nobody names a quality — see Music Bots. The variables below are the sidecar's own fallbacks for when no preset is in play at all.
These are tuning knobs — none are required for a working setup. Defaults below are the sidecar's own built-in fallbacks, read from packages/sidecar/main.go. The shipped docker-compose.yml raises VIDEO_QUEUE_SIZE and AUDIO_QUEUE_SIZE to 8192/16384, sized for the server this fork was tuned against rather than as a hard requirement.
| Variable | Default | Description |
|---|---|---|
SIDECAR_PORT |
9800 |
HTTP API port. An internal interface of the stack: the shipped compose files do not publish it on the host (see Deployment) |
FFMPEG_PATH |
ffmpeg |
Path to the ffmpeg binary |
SIDECAR_DEBUG_LOGS |
unset (off) | Set to 1 for verbose per-packet debug logging |
STUN_SERVERS |
built-in public list | Comma-separated STUN server URLs, overriding the default list |
ICE_UDP_PORT_MIN / ICE_UDP_PORT_MAX
|
50000 / 50100
|
Fixed UDP port range for WebRTC ICE candidates, so it can be firewalled explicitly. Must match the published Docker port range. |
VIDEO_QUEUE_SIZE |
1024 (compose: 8192) |
Video RTP queue between the UDP reader and the pacing goroutine |
AUDIO_QUEUE_SIZE |
2048 (compose: 16384) |
Audio RTP queue |
VIDEO_RTP_READ_BUFFER |
4194304 (4 MiB) |
OS-level UDP socket read buffer for the video port |
AUDIO_RTP_READ_BUFFER |
1048576 (1 MiB) |
OS-level UDP socket read buffer for the audio port |
SYNC_PLAYOUT_BUFFER_MS |
50 |
Baseline playout buffer added on top of measured latency in the A/V sync pacing |
SYNC_VIDEO_BIAS_MS |
0 |
Extra holdback applied to video only, to fine-tune A/V sync |
SYNC_MAX_DELAY_MS |
500 |
Upper bound on the computed pacing delay — stops a single bad RTP timestamp from stalling playback and overflowing the queues |
VIDEO_WIDTH / VIDEO_HEIGHT / VIDEO_FRAMERATE
|
1280 / 720 / 30
|
Fallback dimensions and framerate when no preset is given (e.g. the idle black-screen source) |
VIDEO_BITRATE |
1500k |
Fallback video bitrate when none is passed by the app |
AUDIO_BITRATE |
128k |
Audio encode bitrate |
AUDIO_DELAY_MS |
0 |
Manual audio delay (adelay filter); expected to stay 0 under the current pacing logic |
VIDEO_CPU_USED |
4 |
libvpx -cpu-used — a speed/quality tradeoff (0 = slowest/best, 8 = fastest/worst), not a core count |
VIDEO_ENCODE_THREADS |
host CPU cores |
-threads passed to libvpx, which unlike most ffmpeg encoders doesn't auto-scale across cores |
VIDEO_BUFSIZE |
2× the target bitrate | ffmpeg -bufsize rate-control buffer. Scales with bitrate so high-bitrate streams aren't limited by too small a buffer; set explicitly to override |
Settings → Update Status checks this deployment's version against GitHub, using three unauthenticated API calls per check. GitHub limits unauthenticated calls to 60/hour for the whole server, shared with anything else on it that talks to GitHub — a handful of container restarts (each one checks once at boot) plus a couple of manual "Recheck Now" clicks is enough to exhaust it, after which every result reads unknown until the hourly reset. Without a token, "Recheck Now" is throttled to 20 uses/hour server-side specifically to avoid that.
A token raises the limit to 5,000/hour. It needs no repository access at all — it only ever reads this repo's public files.
To create one (GitHub → your profile → Settings → Developer settings → Personal access tokens):
-
Fine-grained token (
github_pat_...): generate one here. Set repository access to Public Repositories (read-only) — no specific repository or permission checkboxes needed. These tokens require an expiration date (GitHub caps it, typically at 1 year), so it will eventually need regenerating. -
Classic token (
ghp_...), simpler if you'd rather not think about renewal: generate one here. Leave every scope checkbox unchecked (public read access needs none of them) and set Expiration to No expiration.
Paste the token into Settings → Update Status → GitHub API Token → Save. It's stored encrypted the same way server connection credentials are.
Anything configured through the web interface — server connections, users, bot flows, music bots, debug toggles, the accent colour, the storage-notice switch, scheduled restarts, how long the Connection Journal keeps its entries — is stored in the database, not in environment variables. That means it survives container rebuilds, and that data/ is what you back up.
v0.21, last edited 23.10.2026
Getting started
Using the app
Running it
When something is odd
Contributing