Configuration Reference
Every operator-specific value lives in configuration — an env file or a config
file. Nothing is hardcoded. Copy each component's .env.example to .env to
start.
Every service shares two behaviors:
-
Fail-fast validation. A typo'd value — a bad integer, an out-of-range port, a malformed key or URL — produces a startup error. The error names the exact variable; the service never falls back silently. If a service exits right after a config change, read its first log lines. Before you restart, dry-run the change with check-config mode. This mode prints the same problem list and exits 0 or 1, without booting anything. (The MCP's
server_set_envtool runs this check for you before every restart.)docker compose run --rm panel node src/config.js --check -
LOG_FORMAT=json. This opts in to structured logs: one{ts, level, svc, msg}JSON object per line, for log shippers like Loki, ELK, or CloudWatch. Leave it unset to keep the normal human-readable log lines.
Panel (panel/.env)
| Key | Default | Description |
|---|---|---|
DATA_DIR |
./data |
Where the panel stores its Corestore and keys. Gitignored. |
RELAY_ONLY |
false |
Route traffic through DHT relays to hide the panel's origin IP. |
SERVICE_NAME |
Aliran |
Your service name. The app shows it while a viewer pairs with the service pairing code, before the viewer signs in. Public display text only. |
ARGON2_MEM_KIB |
65536 |
Argon2id memory cost, in KiB (64 MiB). The viewer's device pays this at every typed login, so size it to the weakest client hardware, not the server. Stored per user record — a change applies to new enrollments and password rotations only. |
ARGON2_TIME |
2 |
Argon2id time cost, in iterations. |
MAX_DEVICES_DEFAULT |
2 |
Default number of concurrent devices allowed per user. |
SESSION_TTL_DAYS |
30 |
Lifetime of the panel-signed session token, in days. Long by design. The device's enrollment does not expire with it: the device keeps its slot until an admin revokes it, logout-all clears it, or a new device past MAX_DEVICES_DEFAULT evicts the least recently used one. |
POW_DIFFICULTY |
16 |
Login proof-of-work difficulty, in bits. |
LOCKOUT_THRESHOLD |
10 |
Failed login attempts allowed before lockout. |
LOCKOUT_SECONDS |
900 |
Lockout duration, in seconds. |
LEGACY_PUBLISHER |
1 |
Accept unnamed broadcaster registrations signed with the shared init publisher key. Set this to 0 once every broadcaster site is enrolled with add-publisher. This restricts registration to named, scoped identities only. |
ADMIN_ENABLED |
false |
Serve the admin HTTP API from the panel process. |
ADMIN_HOST |
127.0.0.1 |
Admin API bind address. Put TLS in front if this isn't loopback. |
ADMIN_PORT |
3210 |
Admin API port. |
ADMIN_SESSION_TTL_HOURS |
12 |
Admin session token lifetime, in hours. |
ESCROW_EXPORT |
0 |
Serve POST /api/identity/escrow, which exports the panel keys encrypted. Off by default: it lowers identity theft from "shell access on the box" to "an admin session plus a password". The route answers 404 while this is off. The CLI export (admin-cli export-escrow) needs shell access, so it always works. See key escrow. |
ESCROW_ARGON2_MEM_MIB / ESCROW_ARGON2_OPS |
256 / 3 |
Argon2id cost for an exported escrow file. That file lives outside every control this box has, and the passphrase is its only protection — raise this if the box can take it. You pay the cost once per export, in a worker thread. The values travel inside each file, so an older file always opens with its own. |
ANALYTICS_RETENTION_DAYS |
90 |
How many days of aggregate-only analytics rollups the panel keeps, under DATA_DIR/analytics/. Set this to 0 to disable collection entirely — the panel writes no files, and its endpoints answer empty. |
REPORTS_RETENTION_DAYS |
30 |
How many days of viewer problem reports the panel keeps, under DATA_DIR/reports/. Set this to 0 to disable the feature entirely — the panel writes no files, and it stops serving the report RPC method. |
REPORTS_MAX_PER_WINDOW / REPORTS_WINDOW_SECONDS |
5 / 600 |
How many reports one reporter can send in the given window, in seconds. |
REPORTS_ALERT_COUNT / REPORTS_ALERT_WINDOW_MIN |
3 / 10 |
How many distinct reporters on one channel, within the given window in minutes, open a correlation alert. |
REPORTS_STORM_SAMPLE |
20 |
How many full report records the panel stores once an alert is open for a channel. Reports after that only increment a tally. |
REPORTS_GLOBAL_PER_MIN |
120 |
Panel-wide ingest limit. Past this rate, the panel acknowledges reports, counts them as shed, and drops them. |
REPORTS_WEBHOOK_URL |
(empty) | Webhook for ops notifications. One request body works for ntfy, Slack, and Discord. This is a secret — the credential lives in the URL path. Set it on the box by hand; never set it through the MCP. See recipes. |
REPORTS_TELEGRAM_BOT_TOKEN / REPORTS_TELEGRAM_CHAT_ID |
(empty) | Telegram bot target. Set both, or neither. The token is a secret. |
SOURCES_SYNC_INTERVAL_MS |
86400000 |
Default pull interval per source, in milliseconds (one day). Override per source with intervalMs. |
SOURCES_TICK_MS |
300000 |
How often the scheduler checks which sources are due for a pull. This is a check, not a fetch, so 5 minutes is cheap and it lets a sub-hour per-source intervalMs (for example, a 30-minute event playlist) work with no extra tuning. |
SOURCES_BOOT_DELAY_MS |
15000 |
Delay before the first due-check after the panel boots. |
SOURCES_FETCH_TIMEOUT_MS |
30000 |
HTTP timeout for one source pull. |
SOURCES_MAX_BYTES |
5242880 |
Maximum feed size the panel accepts. Enforced while streaming the download. |
SOURCES_MAX_CHANNELS |
500 |
Maximum entries the panel imports from one source. The panel truncates the feed beyond this. |
EVENTS_EPOCH_DAYS |
7 |
How often the ephemeral events drive mints a fresh epoch. That drive carries the channels of any source marked ephemeral, instead of the signed catalog. Its own index is append-only, so rotation is what bounds it: a new drive is minted, the current index and shards are copied in, and the meta/eventsKey pointer is flipped — one appended block per epoch. There is no on/off knob here; the flag lives per source, and a deployment with no ephemeral source never mints a drive at all. |
EVENTS_GRACE_HOURS |
6 |
How long a retired events epoch keeps being served after a rotation, so a viewer that was offline across the pointer flip still finds its lineup. It is purged outright afterwards. Must be under one whole epoch, or the panel refuses to boot. |
LIVENESS_INTERVAL_MINUTES |
10 |
How often the panel probes redirect-channel URLs and flips isLive, so a dead provider link dims in the viewer's list instead of spinning. Set this to 0 to disable the probe entirely — dead redirect URLs then stay listed as live. Event playlists are the population this exists for: between events they legitimately 404. |
LIVENESS_SCOPE |
events |
Which channels the probe is allowed to judge. events (the default) sweeps only the Live Events drive — the population with no other authority over whether it is live, and the one this probe was calibrated for. all also sweeps ordinary catalog redirect channels, which is what the probe did before this setting existed. Leave it at events unless your lineup really is hand-maintained redirect URLs with no rebuilding pipeline behind it: the panel probes from one datacenter address, and the FAST platforms an ordinary lineup is largely built on (Pluto, Samsung TV+, Rakuten, Plex) refuse exactly that traffic — so all marks channels offline that play perfectly well for your viewers. A source rebuilt daily by its own pipeline already drops channels that died upstream. Narrowing the scope does not undim anything — it stops new verdicts; flags an earlier all sweep left behind stay until they are re-stamped. |
LIVENESS_TIMEOUT_MS |
8000 |
HTTP timeout for one liveness probe. |
LIVENESS_FAILS_TO_FLIP |
3 |
Consecutive failed sweeps before a channel is marked offline (~30 minutes at the default interval). One blip must not dim a channel. |
LIVENESS_SUCCESSES_TO_FLIP |
1 |
Consecutive successful sweeps before a channel comes back. 1 is the default, so a started event undims on the very next sweep. Raise it — to 3, matching LIVENESS_FAILS_TO_FLIP — if a flapping provider is oscillating your lineup, at the cost of roughly 20 more minutes of a live match reading offline. Worth it on a lineup of stable channels; usually not on a sports one. |
LIVENESS_BOOT_DELAY_MS |
90000 |
Delay before the first liveness sweep after the panel boots. |
SWARM_RCVBUF_MB / SWARM_SNDBUF_MB |
2 / 2 |
Swarm UDP socket buffers, in MB (0 uses the OS default). Every client replicates the catalog over this one swarm. This only takes effect if the host allows it — the OS clamps it to net.core.{r,w}mem_max. The optional deploy/sysctl/install.sh script raises that ceiling. See the KB. |
BOOTSTRAP |
(empty) | Custom DHT bootstrap nodes. Optional. |
BACKUP_DIR |
./backups |
Where the dashboard's Backup page looks for recovery archives. The shipped compose file mounts the host ./backups here read-only and sets this to /backups — leave it unset there. The page can only list: a cold backup stops the service, so archives are made on the box with deploy/backup.sh. |
CONFIG_SNAPSHOT_KEEP |
20 |
How many on-box config snapshots the service keeps under DATA_DIR/config-snapshots/. It takes one by itself before a destructive change and before every restore or import; the oldest past the cap are removed. |
Broadcaster (broadcaster/.env)
| Key | Default | Description |
|---|---|---|
DATA_DIR |
./data |
Where the broadcaster stores its Corestore and drive keys. |
PANEL_PUBKEY |
(required) | The panel to register the stream with. |
PUBLISHER_KEY |
(required) | The publisher secret that signs register RPCs. Use a per-site key from the panel's add-publisher command — recommended when you run more than one broadcaster — or the shared legacy key from init. |
PUBLISHER_NAME |
(empty) | The enrolled publisher identity that matches PUBLISHER_KEY. When you set this, registrations verify against this site's own key and are limited to its admin-assigned channel scopes; the catalog also gets origin:<name> attribution. Leave it empty to use the legacy shared-key path. |
STREAM_ID |
(optional) | The catalog id for the legacy env-configured channel. In a multi-channel setup, add channels through the control API or UI instead. |
INPUT |
(with STREAM_ID) |
One of: test, a file path, a pull URL (rtsp://, rtmp://, srt://, or http…m3u8), or a push listener type (rtmp, srt, or udp). The control API accepts these as typed objects. |
RTMP_PORT |
1935 |
When INPUT=rtmp, the port OBS pushes to. |
PUBLIC_HOST |
(empty) | The hostname shown in operator-facing push URLs (rtmp://<PUBLIC_HOST>:<port>/…). |
INGEST_PORT_BASE / INGEST_PORT_MAX |
5000 / 5999 |
The port range the broadcaster auto-allocates for push ingest. Each channel gets a validated, unique port in this range. |
HLS_TIME |
2 |
Segment duration, in seconds. Shorter means a faster time to first frame. |
HLS_LIST_SIZE |
8 |
Rolling playlist window, in segments. Use 12 (24 s at HLS_TIME=2) as the standard setting: it gives the viewer apps' ~10 s live offset a comfortable margin, and large swarms more blocks to share. Do not go below 8 — the viewer apps sit ~10 s behind the live edge. |
FFMPEG_MAX_RSS_MB |
150 |
Memory threshold, in MB, that recycles a running ffmpeg process. The broadcaster restarts ffmpeg once its VmRSS plus VmSwap crosses this. This bounds a slow memory buildup that some live-HLS upstreams cause in the demuxer. It uses the same watchdog backoff as a stalled-edge respawn, and does not rotate the feed. 0 disables this check. Linux only — it reads /proc. |
FEED_BUFFER |
disk |
disk gives the feed a stable identity and a warm DHT topic, so joins are faster. ram keeps the disk byte-flat, but discovery starts cold on every restart. See the KB. |
RESUME_PACE |
true |
Paces the boot auto-resume. Between channel starts, the broadcaster waits until its event loop catches up, so the control API and swarm stay responsive during a mass restart. Without this, recreating the whole fleet blacks out /api for minutes. The pace is adaptive: it barely waits on an idle box, and backs off on a loaded one. Set false for the old back-to-back resume. |
RESUME_PACE_TARGET_MS |
50 |
The event-loop lag, in ms, the pacer waits to fall below before starting the next channel. A higher value resumes faster, but leaves less API headroom during the resume. |
RESUME_PACE_MAX_MS |
1000 |
The cap on the per-channel wait. This keeps a permanently busy loop making forward progress. It bounds the total pacing overhead to roughly channels × this value. |
RESUME_PACE_MIN |
8 |
Below this channel count, the broadcaster does not pace at all — a handful of channels has no resume storm to manage. |
RESUME_CONCURRENCY |
12 |
How many channel starts run at once during boot. A channel start is mostly I/O wait, so running several at once cuts total recovery time roughly in proportion. Measured on 83 channels / 4 vCPU: 451 s sequential versus 40 s at concurrency 12, with a CPU peak of 77%. The adaptive pace still throttles the launch rate under load, so a high value self-limits on a constrained host. Set 1 for strictly sequential starts. Lower this value to keep the startup burst further from the host's CPU ceiling. |
SLATE_ENABLED |
true |
Loops a pre-rendered "SOURCE OFFLINE" slate when a source is dead. This keeps the channel live with a message, instead of going blank during watchdog backoff. Set false to revert to the blank-during-backoff behavior. See the KB. |
SLATE_DIR |
(image broadcaster/slate) |
Where the rendered slate .ts files live. The image builds these in at build time. Point this at the data volume instead — for example, /data/slate — to serve your own files. |
SLATE_AFTER |
3 |
Consecutive failed respawns, per configured source, before a channel gives up and shows the slate. A channel with fallback sources tries every URL first. A slated channel is remuxed with -c copy, which costs close to 0 CPU — cheaper than the live pull it replaces. |
SLATE_RETRY_MS |
30000 |
How often a slated channel drops the slate to re-probe its real source. This is also how the channel returns automatically: a working source clears the failure streak and stays; a still-dead source re-slates on the next failure. A lower value recovers faster. A higher value glitches the slate bars less often during a long outage, at the cost of the bars showing a little longer after the source is already back. Raise it — for example, to 60000 — for smoother bars. Values below about 20000 are discouraged. See the KB. |
SWARM_MAX_PEERS |
(unset) | An optional connection budget, per channel (each channel runs its own swarm; hyperswarm's own default is also 64, per channel). Connections beyond this budget are dropped at accept time. Leave headroom for non-viewer peers, such as repeaters and the panel's blobsKey probe. |
SWARM_RCVBUF_MB / SWARM_SNDBUF_MB |
2 / 2 |
Swarm UDP socket buffers, in MB (0 uses the OS default). UDX carries every peer stream over one socket pair, so this buffer is what overflows under fan-out — silently, as kernel packet drops. This only takes effect if the host allows it — setsockopt clamps it to net.core.{r,w}mem_max (212992 on stock Linux). The optional deploy/sysctl/install.sh script raises that ceiling. See the KB. |
CONTROL_ENABLED |
false |
Serve the channel control HTTP API. |
CONTROL_HOST |
127.0.0.1 |
Control API bind address. Put TLS in front if this isn't loopback. |
CONTROL_PORT |
3310 |
Control API port. |
CONTROL_SESSION_TTL_HOURS |
12 |
Control session token lifetime, in hours. |
LOCKOUT_THRESHOLD / LOCKOUT_SECONDS |
10 / 900 |
Control login lockout threshold and duration. |
ANALYTICS_RETENTION_DAYS |
90 |
How many days of aggregate-only analytics rollups — per-channel peers, egress, and respawns — the broadcaster keeps, under DATA_DIR/analytics/. Set this to 0 to disable collection entirely. |
ARGON2_MEM_KIB / ARGON2_TIME |
65536 / 2 |
Argon2id cost for control-admin passwords. |
BACKUP_DIR |
./backups |
Where the dashboard's Backup page looks for recovery archives. The shipped compose file mounts the host ./backups here read-only and sets this to /backups — leave it unset there. The page can only list: a cold backup stops the service, so archives are made on the box with deploy/backup.sh. |
CONFIG_SNAPSHOT_KEEP |
20 |
How many on-box config snapshots the service keeps under DATA_DIR/config-snapshots/. It takes one by itself before a destructive change and before every restore or import; the oldest past the cap are removed. |
Repeater (repeater/.env)
The repeater is a keyless regional relay appliance — see the repeater appliance page.
| Variable | Default | Description |
|---|---|---|
PANEL_PUBKEY |
— (required) | The panel whose public catalog names the channels. |
CHANNELS |
all |
all, an explicit list (ch1,ch2), or a category filter (category:news[,sports]). |
RETENTION_SECONDS |
300 |
The live window the repeater keeps per channel. This may exceed the origin's own HLS window. |
SWARM_MAX_PEERS |
256 |
Connection budget. A repeater exists to absorb fan-out, so this is set high. |
SWARM_RCVBUF_MB / SWARM_SNDBUF_MB |
4 / 4 |
Swarm UDP socket buffers, in MB (0 uses the OS default). This is higher than the broadcaster's default, because absorbing fan-out is this box's entire job — it is the most likely service to hit a buffer wall. Pair this with the optional deploy/sysctl/install.sh script; otherwise the OS silently clamps the request. See the KB. |
DATA_DIR |
./data |
Ciphertext block store. This is a disposable cache. |
STATUS_INTERVAL_SECONDS |
60 |
Per-channel status log cadence. 0 turns it off. |
STATUS_PORT |
0 (off) |
An opt-in health and metrics HTTP server: GET /healthz plus a Prometheus GET /metrics. Off by default — a stock repeater opens no listening sockets at all. |
STATUS_HOST |
127.0.0.1 |
Status-server bind address. Its endpoints are unauthenticated — widen this beyond loopback only on a network you control. |
BOOTSTRAP |
public DHT | Custom DHT bootstrap nodes. |
Library (library/.env) — VOD
The library is the standalone VOD service — see the VOD library page and the reference.
| Variable | Default | Description |
|---|---|---|
DATA_DIR |
./data |
The titles registry, per-title encryption keys (under secrets/, mode 0600), and the encrypted title stores. Disk use equals the sum of title sizes; only delete-title reclaims it. |
PANEL_PUBKEY |
— (required to register) | The panel to register titles with. |
PUBLISHER_NAME / PUBLISHER_KEY |
— | The library's enrolled publisher identity. Always enroll the library as its own publisher, scoped to its title ids — for example, add-publisher library1 --scopes 'vod-*'. Never reuse the live fleet's key. |
HLS_TIME |
4 |
VOD segment length, in seconds. A shorter value gives finer seek and demand-paging, at the cost of more per-request overhead. Override this per title in the API. |
INGEST_CONCURRENCY |
1 |
Parallel ingest jobs. A transcode burst uses 0.5–1 core; the default runs jobs strictly one at a time. |
SWARM_MAX_PEERS |
256 |
Connection budget. One swarm carries every title — the same seeder economics as the repeater. |
SWARM_RCVBUF_MB / SWARM_SNDBUF_MB |
4 / 4 |
Swarm UDP socket buffers, in MB (0 uses the OS default). A seeder is send-dominant, so the same rationale — and the same sysctl pairing — applies as for the repeater. |
CONTROL_ENABLED |
0 |
The control API and a minimal UI on CONTROL_HOST:CONTROL_PORT (127.0.0.1:3320). This is the only way to add titles. Manage admins with library-cli add-admin. |
CONTROL_SESSION_TTL_HOURS |
12 |
Control session lifetime, in hours. |
LOCKOUT_THRESHOLD / LOCKOUT_SECONDS |
10 / 900 |
Control login rate limit. |
ARGON2_MEM_KIB / ARGON2_TIME |
65536 / 2 |
Control-admin password hashing cost. |
BOOTSTRAP |
public DHT | Custom DHT bootstrap nodes. |
BACKUP_DIR |
./backups |
Where the dashboard's Backup page looks for recovery archives. The shipped compose file mounts the host ./backups here read-only and sets this to /backups — leave it unset there. The page can only list: a cold backup stops the service, so archives are made on the box with deploy/backup.sh. |
CONFIG_SNAPSHOT_KEEP |
20 |
How many on-box config snapshots the service keeps under DATA_DIR/config-snapshots/. It takes one by itself before a destructive change and before every restore or import; the oldest past the cap are removed. |
Client
The client reads build-time config from client/config, or a runtime
service descriptor:
| Key | Description |
|---|---|
panelPubKey |
The operator's panel public key. This pins trust and locates the service. |
name / branding |
The app name, logo, and color palette. |
bootstrap |
Custom DHT bootstrap nodes. Optional. |
CDN channels are catalog config, not client config
To make a channel play a CDN/HLS link, add a per-channel redirect entry in the admin panel. Do not configure this on the client. See content-management.md.
Never commit secrets
Keys under DATA_DIR must never be committed. See .gitignore.