Status: implemented, pre-alpha
The daemon can still run from its loopback-safe defaults and existing
environment variables. A deployment should instead select one versioned TOML
file with IRC_CONFIG and use environment variables only for deliberate
runtime overrides.
The merge order is:
- compiled loopback-safe defaults;
- the complete TOML file selected by
IRC_CONFIG; and - explicitly present
IRC_*overrides.
An unset environment variable leaves the file value intact. An empty variable is still an explicit override and will normally fail validation; edit the file to remove an optional listener. The loader never silently falls back when a selected file is absent or invalid.
Every file must contain version = 1. The loader rejects unknown keys,
unsupported versions, invalid TOML and UTF-8, unsafe listener combinations,
and files larger than 64 KiB. Listener addresses, names, origins, registration
mode, retention ranges, timeouts, and connection bounds pass through the same
validation used by programmatic and environment-only configurations.
Validate the merged policy without opening sockets, creating a database, or reading certificate files:
IRC_CONFIG=/etc/telex/server.toml \
telex-ircd config checkThis command is suitable for an installation or service preflight. A successful check does not prove that the configured ports are free or that certificate, key, database, and directory permissions are correct. Normal server startup checks those runtime resources before advertising readiness.
Offline administrative commands use the selected file's database path without
requiring valid listener settings. They acquire the same exclusive database
lease as the daemon and therefore require it to be stopped. IRC_DATABASE can
override only that path:
IRC_CONFIG=/etc/telex/server.toml \
telex-ircd content epoch
IRC_CONFIG=/etc/telex/server.toml \
IRC_DATABASE=/srv/recovery/irc.db \
telex-ircd content epochdatabase backup <new-file> creates a read-only online snapshot without
migrating the selected source; database prepare intentionally migrates,
validates, and checkpoints an existing selected restore candidate. Their
operational boundary and safe swap procedure are in the database
runbook.
The complete public shape is represented by the generalized example profile. All sections except the top-level version are optional; omitted settings retain their safe defaults.
version = 1
[server]
name = "irc.example.net"
network = "ExampleNet"
[storage]
database = "/var/lib/telex/irc.db"
[listeners]
irc = "0.0.0.0:6697"
websocket = "0.0.0.0:8097"
control = "0.0.0.0:8098"
allow_insecure_remote = false
[tls]
certificate_chain = "/etc/letsencrypt/live/irc.example.net/fullchain.pem"
private_key = "/etc/letsencrypt/live/irc.example.net/privkey.pem"
[admission]
authentication_required = true
account_registration = "invite"
initial_channel = "#general"
[websocket]
origins = ["https://chat.example.net"]
[operators]
accounts = ["operator"]
[history]
legacy_replay_limit = 50
[retention]
channel_days = 90
direct_message_days = 180
channel_min_days = 1
channel_max_days = 400
allow_indefinite = false
prune_interval_seconds = 3600
[limits]
registration_timeout_seconds = 30
heartbeat_idle_seconds = 180
heartbeat_timeout_seconds = 90
max_connections = 128
[limits.protocol]
per_minute = 600
burst = 60
[limits.admission]
per_minute = 120
burst = 64
[limits.authentication]
per_minute = 120
burst = 64
[limits.invitation]
per_minute = 5
burst = 5
[limits.messages]
per_minute = 120
burst = 20
[limits.history]
per_minute = 60
burst = 20
[limits.administration]
per_minute = 60
burst = 20The scopes, rejection behavior, anonymity tradeoff, and aggregate counters are defined in runtime rate limits.
Relative storage.database and TLS paths are resolved against the directory
containing the TOML file. Relative environment overrides remain relative to the
daemon's working directory. Bare-metal profiles should normally use absolute
paths.
The configuration contains policy and paths, not passwords, invitation bearer tokens, account credentials, or TLS private-key contents. Keep the referenced private-key file readable only by the eventual service account.
| TOML setting | Environment override |
|---|---|
server.name |
IRC_SERVER_NAME |
server.network |
IRC_NETWORK_NAME |
storage.database |
IRC_DATABASE |
listeners.irc |
IRC_LISTEN |
listeners.websocket |
IRC_WEBSOCKET_LISTEN |
listeners.control |
IRC_CONTROL_LISTEN |
listeners.allow_insecure_remote |
IRC_ALLOW_INSECURE_REMOTE |
tls.certificate_chain |
IRC_TLS_CERT |
tls.private_key |
IRC_TLS_KEY |
admission.authentication_required |
IRC_REQUIRE_AUTHENTICATION |
admission.account_registration |
IRC_ACCOUNT_REGISTRATION |
admission.initial_channel |
IRC_INITIAL_CHANNEL |
websocket.origins |
IRC_WEBSOCKET_ORIGINS as a comma-separated list |
operators.accounts |
IRC_OPERATOR_ACCOUNTS as a comma-separated list |
history.legacy_replay_limit |
IRC_LEGACY_REPLAY_LIMIT |
retention.channel_days |
IRC_CHANNEL_RETENTION_DAYS |
retention.direct_message_days |
IRC_DM_RETENTION_DAYS |
retention.channel_min_days |
IRC_CHANNEL_RETENTION_MIN_DAYS |
retention.channel_max_days |
IRC_CHANNEL_RETENTION_MAX_DAYS |
retention.allow_indefinite |
IRC_ALLOW_INDEFINITE_RETENTION |
retention.prune_interval_seconds |
IRC_RETENTION_PRUNE_INTERVAL_SECS |
limits.registration_timeout_seconds |
IRC_REGISTRATION_TIMEOUT_SECS |
limits.heartbeat_idle_seconds |
IRC_HEARTBEAT_IDLE_SECS |
limits.heartbeat_timeout_seconds |
IRC_HEARTBEAT_TIMEOUT_SECS |
limits.max_connections |
IRC_MAX_CONNECTIONS |
limits.protocol.per_minute |
IRC_PROTOCOL_RATE_PER_MINUTE |
limits.protocol.burst |
IRC_PROTOCOL_RATE_BURST |
limits.admission.per_minute |
IRC_ADMISSION_RATE_PER_MINUTE |
limits.admission.burst |
IRC_ADMISSION_RATE_BURST |
limits.authentication.per_minute |
IRC_AUTHENTICATION_RATE_PER_MINUTE |
limits.authentication.burst |
IRC_AUTHENTICATION_RATE_BURST |
limits.invitation.per_minute |
IRC_INVITATION_RATE_PER_MINUTE |
limits.invitation.burst |
IRC_INVITATION_RATE_BURST |
limits.messages.per_minute |
IRC_MESSAGE_RATE_PER_MINUTE |
limits.messages.burst |
IRC_MESSAGE_RATE_BURST |
limits.history.per_minute |
IRC_HISTORY_RATE_PER_MINUTE |
limits.history.burst |
IRC_HISTORY_RATE_BURST |
limits.administration.per_minute |
IRC_ADMINISTRATION_RATE_PER_MINUTE |
limits.administration.burst |
IRC_ADMINISTRATION_RATE_BURST |
RUST_LOG remains a process logging setting rather than server policy. No
configuration source enables remote plaintext control traffic; invitation and
account credentials require TLS outside loopback regardless of the development
IRC override.
operators.accounts contains at most 32 human account names. Names are
validated with the ordinary account-name rules, normalized to ASCII lowercase,
and rejected if two entries become equal. These names authorize /operator/;
they are not stored global capabilities and cannot be added or removed by the
panel. The named human account must already exist and remain active. Changing
the list requires a configuration update and daemon restart, while capability
changes made through the panel take effect immediately. An empty list disables
operator login without removing the page assets. See the
operator panel.
websocket.origins is also the exact browser-origin allowlist for authenticated
avatar reads from the control listener. This lets a static web client on HTTPS
443 retrieve images from the TLS control port without opening general control
API CORS. Each listed origin must therefore be one you trust to receive an
account password in tab memory. See account avatars.
admission.initial_channel is optional and must be one valid IRC channel name.
When set, it is atomically assigned only to newly created human accounts; each
authenticated device autojoins it until the account parts it. Adding or
changing the setting does not backfill existing accounts. Keep this room public
and unkeyed. The exact account/device behavior is documented in initial
channel.
limits.heartbeat_idle_seconds controls how long a registered IRC connection
may send no client traffic before Telex sends PING. The default is 180
seconds. The client must return that exact token in PONG within
limits.heartbeat_timeout_seconds, which defaults to 90 seconds. Ordinary
traffic postpones the next heartbeat; unregistered connections remain governed
only by the separate registration deadline. Heartbeats apply to both IRC/TLS
and IRC-over-WebSocket, bypass command throttles, create no stored event or
cursor, and are not logged individually. Keep any TCP proxy idle timeout longer
than the combined heartbeat window.