Skip to content

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_env tool 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.