weavesignal/crates/control/control.example.toml
The WeaveSignal contributors 466b32ab6a WeaveSignal 1.0.0 — release one
The first public release. What it does: https://weavesignal.top; what changed: CHANGELOG.md.
2026-11-04 12:15:42 +00:00

183 lines
11 KiB
TOML

# SPDX-License-Identifier: AGPL-3.0-only
# SPDX-FileCopyrightText: 2026 The WeaveSignal contributors
# WeaveSignal control plane settings. `weavesignal-control sample-config` prints this file with a
# fresh station identifier. Secrets are not kept here: the database URL comes from the
# WEAVESIGNAL_DATABASE_URL environment variable.
[station]
# The station's identifier (ADR-0047). Keep it for the station's life.
id = "00000000-0000-0000-0000-000000000000"
name = "Kettle FM"
[listen]
# The address internal services bind. Loopback: the reverse proxy is the one public listener.
internal = "127.0.0.1"
# Every listening port is a setting (ADR-0075). Each line shows the default and the suggested
# alternatives; any free port may be used. `weavesignal-control check-ports` checks them all.
[ports]
proxy_https = 443 # tcp, public: the reverse proxy. Or 8443.
proxy_http = 80 # tcp, public: redirects and plain-HTTP listener streams. Or 8080.
control = 7310 # tcp, loopback: the control plane's API. Or 17310, 27310.
chat = 7311 # tcp, loopback: the chat service. Or 17311, 27311.
listeners = 7312 # tcp, loopback: icecast.server's listener streams. Or 17312, 27312.
srt_ingest = 7313 # udp, loopback: the programme; the desk listens, Liquidsoap connects. Or 17313, 27313.
playout_feed = 7315 # tcp, loopback: playout's programme to the listener process (and 7316). Or 17315, 27315.
desk_feed = 7317 # tcp, loopback: playout's programme back to the desk, the video's sound (ADR-0172). Or 17317, 27317.
livekit_http = 7880 # tcp, loopback: LiveKit's signalling, behind the proxy. Or 17880, 27880.
livekit_media = 7882 # udp, public: LiveKit's one media port. Or a port in 52000-60000 (not 50000-51000, Cloudron's TURN relays), or 443.
livekit_tcp = 7881 # tcp, public: LiveKit's ICE over TCP. Or 17881, 27881.
turn_udp = 3478 # udp, public: TURN. Or 3479, 13478 (Cloudron's own TURN may hold 3478).
turn_tls = 5349 # tcp, public: TURN over TLS, on by default (ADR-0080). Or 443 where the proxy does not hold it, or 15349.
# The live room. LiveKit's API secret comes from WEAVESIGNAL_LIVEKIT_API_SECRET, never this file.
[room]
public_url = "wss://station.example" # LiveKit as browsers reach it, through the proxy (the client adds /rtc)
api_key = "weavesignal"
api_url = "http://127.0.0.1:7880" # LiveKit's server API on this machine (drops a caller)
name = "studio"
desk_identity = "desk" # the desk's identities; nobody else may join under this prefix
token_ttl_s = 600 # a room token can be used to join for 10 minutes (at most 4 hours)
# The desk's control socket (ADR-0088): the studio app's desk and screener drive the desk
# through the control plane. Leave `socket` out and the app says the desk is not set up.
[desk]
# socket = "/run/weavesignal/desk.sock"
poll_ms = 250 # how often the desk is asked for its status
# Playout's control socket (Liquidsoap's server socket, `control_socket` in its data): the cart
# wall fires through it. Leave `socket` out and the app says playout is not set up.
[playout]
# socket = "/run/weavesignal-playout/playout.sock"
poll_ms = 250 # how often playout is asked what is sounding
# The cart wall. The media worker (this program, `media-worker`) turns each upload into FLAC in
# `media_dir`, which playout reads too (its `carts.dir`), and stores the gain that brings it to
# the loudness target.
[carts]
media_dir = "/var/lib/weavesignal/carts"
loudness_lufs = -23.0 # EBU R128, as the desk's meters
ceiling_dbtp = -1.0 # a cart's gain never pushes its true peak above this
max_upload_mb = 64
max_seconds = 600
worker_timeout_s = 120
# demo_pack = "/opt/weavesignal/carts-demo" # loaded as the banks when the station has none
# The show on air and what is coming up, until the schedule arrives (M4); callers banned
# before, until accounts and the moderation log arrive (M6).
[studio]
show = ""
rundown = [] # [[studio.rundown]] at = "14:00", title = "News", detail = "...", hard_post = true, key = "F2"
banned = [] # [[studio.banned]] identity = "...", date = "2026-09-12", reason = "..."
listener_delay_ms = 15000 # how far behind listeners hear; shown as a setting until measured (ADR-0103)
# The video slate's still (ADR-0010): a picture uploaded in the studio, made into a clean PNG by
# the media worker, which the desk (its `video.still_file`) shows on the slate.
[video]
still_file = "/var/lib/weavesignal/carts/stills/slate.png"
still_max_upload_mb = 24
still_max_width = 1280 # the picture is fitted inside this, never enlarged
still_max_height = 720
# demo_still = "/opt/weavesignal/stills-demo/kettle-fm.png" # the station's, once, when it has none (ADR-0200)
# Joining from a browser (M3): the player page's call-in and the station's invite links.
[join]
stream_url = "" # the listener stream as the player reaches it, through the proxy
call_in = true # the player page offers "call in" (ADR-0006)
# What a caller agrees to before they can call: {station} becomes the station's name, {kept} how
# long its recordings are kept (from the Jurisdiction setting), {contact} the line below. Changing
# the words changes their version, and the consent log keeps the words each caller agreed to.
consent_text = "You'll be live on {station}, and we may keep your call in our archive and podcast {kept}. To have it taken down, contact {contact}."
consent_contact = "" # an email address or a link; call-in stays closed until it is set
call_pass_ttl_s = 14400 # a caller's pass: the wait, the call and a rejoin (4 hours)
invite_ttl_s = 14400 # an invite link works for 4 hours unless it asks for less (at most a day)
calls_per_address_per_hour = 6
calls_per_minute = 30
forwarded_for = false # true only behind the station's own proxy, which sets X-Forwarded-For
# Call-in's challenge (ADR-0006): a proof of work the caller's browser does while they type, in
# place of a CAPTCHA; nothing to see or solve. Each bit doubles the work; 0 turns it off.
challenge_bits = 18 # about 260,000 hashes: well under a second on a recent phone
challenge_bits_busy = 21 # while the station's minute is at least half used (a flood)
challenge_ttl_s = 1800 # a lobby left open longer fetches a new challenge
# The public pages (the player with call-in, the invite page, the echo test): the directory of
# apps/web's build, served at / with their caching and Content-Security-Policy. Unset, only the
# API is served. The server image has them at /opt/weavesignal/web.
[web]
# dir = "/opt/weavesignal/web"
# Search engines (ADR-0133). false: every response says `X-Robots-Tag: noindex`, the pages carry
# a robots meta tag and /robots.txt disallows everything. true: the public address (the player,
# feeds, cards) may be listed; the API, the invite page and the echo test never are.
listed = false
# Show cards and podcast feeds (M8). Every card prints the station's address (ADR-0056), and
# the feeds, the card images and the episode files are reached under it: /feeds/<show>.xml,
# /cards/ and /media/episodes/. Leave `public_url` empty and cards and feeds are off.
[publishing]
public_url = "" # "https://" and the station's public host
place = "" # where the station is, printed on the cards
zone = "Europe/London" # air times on cards, and the series' weekly rule
language = "en-gb"
owner_name = "" # the feeds' owner; empty, the station's name
owner_email = "" # for itunes:owner and podcast:locked; empty, neither is written
copyright = ""
category = "Music" # Apple's podcast category
subcategory = ""
locked = true # podcast:locked: other platforms may not import the feeds
cards_dir = "/var/lib/weavesignal/show-cards" # not the cart wall's `carts.media_dir`
episodes_dir = "/var/lib/weavesignal/episodes"
[log]
# "json" (one object per line) or "pretty". RUST_LOG overrides the filter.
format = "json"
filter = "info"
# Retention (ADRs 0113-0116). How long each kind of record is kept is the station's setting
# (the Jurisdiction drop-down in the studio app's Settings; No retention until chosen). Here:
# where recordings' files live, and how often the sweeper deletes what has expired and is not
# held.
[retention]
recordings_dir = "/var/lib/weavesignal/recordings"
sweep_every_s = 3600
# HLS content steering among the station's edges (M7, ADR-0051): the multivariant playlist names
# /api/v1/steering in EXT-X-CONTENT-STEERING, and each viewer is given the healthy edges in an
# order of their own, by weight, keeping their edge from one reload to the next. With no edges,
# steering is off. Bunny (ADR-0017) or the origin can sit after the pool with `weight = 0`.
[steering]
ttl_s = 300 # how long a player keeps its manifest; the specification recommends 300
check_every_s = 5 # each edge's health check
check_timeout_ms = 2000
down_after = 2 # failed checks in a row before an edge leaves the order; one pass brings it back
clone_from = "." # the pathway the playlist declares; edges with a `host` are sent as its clones
edges = []
# [[steering.edges]]
# pathway = "edge-1" # letters, digits, ".", "-", "_"
# host = "edge-1.edges.example" # replaces the playlist's host for this edge
# weight = 1 # its share of new viewers; 0 = after the pool
# check_url = "http://edge-1.edges.example/health" # plain HTTP; empty, never checked
# Staff sign-in codes by email (ADR-0127). Off: staff sign in with a password only, which works
# everywhere. "command" pipes each email to a sendmail-compatible program; "outbox" writes each to
# a file in `outbox_dir` (development and tests). With mail on, the control plane will not start
# without WEAVESIGNAL_CONTROL_EMAIL_KEY (at least 32 characters, e.g. `openssl rand -hex 32`; it
# keys the hash of staff email addresses, which are never stored; keep it with the database's
# backups). An admin then sets each person's address under Settings, Staff.
[mail]
mode = "off"
from = "weavesignal@station.example"
command = ["/usr/sbin/sendmail", "-t", "-i"]
outbox_dir = "/var/lib/weavesignal/staff-outbox"
# The public API (ADR-0195). Limits per minute (0 turns one off): requests from one address to the
# open routes (now-playing and /api/v1/public/), requests with one API token, and now-playing
# streams one address may hold open. An address is the proxy's last X-Forwarded-For entry, counted
# only with `join.forwarded_for = true`. Webhooks must be https on a public address unless
# `webhooks_allow_private` is on (an integration on the station's own network). API tokens and
# webhook secrets are keyed by WEAVESIGNAL_CONTROL_TOKEN_KEY (at least 32 characters), or, unset, by a
# key derived from the station key; changing the key revokes every token.
[api]
public_per_minute = 120
token_per_minute = 600
streams_per_address = 4
webhooks_allow_private = false