diff --git a/config.example.toml b/config.example.toml
index d5624f5..1f02c92 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -1,127 +1,154 @@
-# Copy to config.toml and edit. config.toml is gitignored (holds the link password).
+# Copy to config.toml and edit. config.toml is gitignored (it holds the link password).
+# Only [uplink] and [server] are required; every other section is optional and off when
+# omitted. A few settings reload on OperServ REHASH (noted below); the rest need a restart.
[uplink]
host = "127.0.0.1"
port = 7000
-password = "changeme" # must match the block on the uplink IRCd
+password = "changeme" # must match the block on the uplink ircd
+# Link over TLS instead of plaintext, pinning the server's SPKI fingerprint (base64 of
+# SHA256 over its SubjectPublicKeyInfo) rather than a CA, so a self-signed link cert is
+# fine. Read the fingerprint with:
+# openssl s_client -connect HOST:PORT /dev/null | openssl x509 \
+# -pubkey -noout | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | base64
+# tls = true
+# spki_fingerprint = ""
[server]
name = "services.example.net"
-sid = "42S" # 3 chars, unique on the network
-description = "Federated Services"
-protocol = 1206 # InspIRCd link protocol version (1206 = insp4, 1205 = insp3)
+sid = "42S" # 3 chars, unique on the network
+description = "Network Services"
+protocol = 1206 # InspIRCd link protocol (1206 = insp4, 1205 = insp3)
+# scram_iterations = 210000 # PBKDF2 cost for new SCRAM verifiers; high by default
+# guest_nick = "Guest" # nick prefix after LOGOUT; must start with a letter
+# service_host = "" # host the pseudo-clients wear; empty = the server name
+# service_modes = "iHkB" # invisible, hideoper, servprotect (needs a U-line), bot
+# service_oper_type = "Network Service" # shown in /whois; empty = non-opers
+# services_channel = "#services" # channel every pseudo-client joins; empty = none
+# standard_replies = false # IRCv3 FAIL/WARN/NOTE; needs m_services_stdrpl on the ircd
-# Node-to-node replication. Omit this section (and [[peer]]) to run a single node.
-# [gossip]
-# bind = "0.0.0.0:16700" # where peers reach us; omit for a dial-only node
-# secret = "shared-secret" # both nodes must present the same secret
-#
-# [gossip.tls] # omit for a plaintext link; see scripts/gen-certs.sh
-# cert = "certs/node.crt"
-# key = "certs/node.key"
-# ca = "certs/ca.crt" # a peer must present a certificate signed by this CA
-#
-# [[peer]] # one block per other node
-# addr = "other-node:16700"
-# name = "other-node" # TLS name to expect; must match the peer certificate
-
-# Directory replication (gRPC) — lets a website mirror the account/channel
-# directory (identity + metadata only, never credentials; see proto/echo.proto).
-# Omit this section to run without it.
-# [grpc]
-# bind = "127.0.0.1:50051" # a private network hop is the expected deployment
-# token = "shared-secret" # every RPC must send `authorization: Bearer `
-#
-# [grpc.tls] # omit for plaintext (fine on a private/loopback hop)
-# cert = "certs/node.crt"
-# key = "certs/node.key"
-
-# Liveness + Prometheus metrics (plain HTTP, read-only, unauthenticated). Omit to
-# leave it off. Bind to localhost and point a monitor at it: GET /health for a
-# liveness probe (JSON), GET /metrics for a scrape (gauges: account/channel/oper
-# totals, per-service counters, and event.lamport — a monotonic log-progress gauge).
-# [health]
-# bind = "127.0.0.1:9099"
-
-# Which service modules to start. Omit this section for the full standard suite
-# (every service comes up by default). List names here to run a subset; add
-# "example" to also start the example template service.
+# Which service modules to start. Omit for the full standard suite; list names to run a
+# subset. Add "example" to also start the example template service.
# [modules]
# services = ["nickserv", "chanserv"]
# Services operators, in three tiers of increasing power:
-# operator — day-to-day moderation: view hidden info, network bans, kick,
-# kill, session limits, ignores, mode, spam filters, log search.
-# administrator — the above plus account/channel data: suspend, drop, set flags,
-# forbid, global notices, defcon, memo/info/bot administration.
-# root — the above plus daemon control: add/remove opers, set, rehash,
-# restart, shutdown, the activity feed.
-# Name a tier with `type`, or list individual privileges with `privs` (auspex,
-# oper, suspend, admin, root). Omit the whole block for a network with no opers.
+# operator day-to-day moderation: view hidden info, network bans, kick, kill,
+# session limits, ignores, mode, spam filters, log search.
+# administrator the above plus account and channel data: suspend, drop, set flags,
+# forbid, global notices, defcon, memo/info/bot administration.
+# root the above plus daemon control: add/remove opers, set, rehash,
+# restart, shutdown, the activity feed.
+# Name a tier with `type`, or list individual privileges with `privs` (auspex, oper,
+# suspend, admin, root). Repeat the block per operator. Omit for a network with no opers.
# [[oper]]
# account = "yournick"
# type = "root"
-# Staff audit feed: notable service actions (registrations, drops, vhosts,
-# suspensions, akicks, access and bot changes, oper host config) are announced
-# to this channel so operators can see who did what. Private material (memo
-# bodies, password/verifier data) and cosmetic self-service tweaks are never
-# surfaced. Omit the section to disable the feed.
+# Staff audit feed: notable service actions (registrations, drops, vhosts, suspensions,
+# akicks, access and bot changes) are announced here so operators see who did what. Memo
+# bodies and credential material are never shown. Omit to disable the feed.
# [log]
# channel = "#services"
-# Masks OperServ NOTIFY never announces, so they can't flood the feed. Three kinds:
-# "#channel" mute a channel (keep a broad `#*` watch out of "#staff")
-# "server:" mute everyone on a server (a relay whose users have clean
-# nicks; alias "via:", matching the "via " in the feed)
-# anything else mute a user — nick glob, user@host, or extban ("*/*" catches
-# PyLink relays that suffix nicks with /network)
-# Reloadable with OperServ REHASH.
-# notify_exclude = ["*/*", "server:chatnova.relay", "#staff"]
+# Masks OperServ NOTIFY never announces: "#channel" mutes a channel, "server:"
+# (alias "via:") mutes a server, anything else mutes a user (nick glob, user@host, or an
+# extban). Reloadable with OperServ REHASH.
+# notify_exclude = ["*/*", "server:relay.example.net", "#staff"]
-# Inactivity-expiry: accounts not identified to, and channels not joined, for
-# longer than the threshold are dropped on a periodic pass (opers, live
-# sessions, occupied channels, and NOEXPIRE-pinned records are spared). A zero
-# or omitted field leaves that kind never expiring. Omit the section to disable
-# expiry entirely.
-# [expire]
-# accounts_days = 90
-# channels_days = 30
-# warn_days sends the owner a heads-up email this many days before expiry (only
-# where an address is on file and [email] is configured); 0 or omitted = no
-# warning email.
-# warn_days = 7
+# Registration policy. Reloadable with OperServ REHASH.
+# [register]
+# confusable_check = true # refuse look-alike, mixed-script, or invisible names
+# vouch = false # invite-only: a new account waits for a NickServ VOUCH
-# Per-IP session limiting: the connection that puts an IP over `default_limit`
-# is killed on connect. OperServ EXCEPTION entries raise or lower the allowance
-# per IP-mask (an exception limit of 0 means unlimited). 0 or omitted = off.
-# [session]
-# default_limit = 3
+# Extbans echo accepts. Omit (or leave empty) to accept every extban the ircd offers.
+# [extban]
+# enabled = ["account", "realname", "country"]
-# Account authority. Omit this section (the default) and Echo owns accounts
-# itself: NickServ REGISTER / IDENTIFY / SET PASSWORD all work standalone, no
-# external service needed. Set external = true to hand identity to an outside
-# authority (e.g. your website): IRC can then only IDENTIFY — REGISTER, DROP,
-# SET PASSWORD/EMAIL, RESETPASS, CONFIRM, CERT and GROUP are refused, and the
-# authority pushes accounts in via the gRPC Accounts API (see [grpc]). Echo
-# still owns all channel/vhost/ban data, keyed by the account name.
+# Account authority. Default (omitted): echo owns accounts and REGISTER/IDENTIFY/SET all
+# work standalone. Set external = true to hand identity to an outside authority (your
+# website): IRC can then only IDENTIFY, and the authority pushes accounts in over the
+# gRPC Accounts API. echo still owns all channel, vhost, and ban data, keyed by account.
# [auth]
# external = true
-# DictServ: dictionary / thesaurus / reference lookups over the DICT protocol
-# (RFC 2229). Present = the service loads and channel bots answer !dict, !define,
-# !thes, !acronym, !law, !element, !bible, and friends (DictServ LOOKUP lists them
-# all); also queryable directly with /msg DictServ . Omit it and echo
-# makes no outbound lookups at all — this is opt-in because it reaches the network.
-# Lookups are globally rate-limited and the reply is truncated to one line. The log
-# channel is unaffected; keep this off if you don't want echo talking to dict.org.
+# Inactivity expiry: drop accounts not identified to, and channels not joined, for longer
+# than the threshold (opers, live sessions, occupied channels, and NOEXPIRE records are
+# spared). 0 or omitted leaves that kind never expiring. Omit the section to disable.
+# [expire]
+# accounts_days = 90
+# channels_days = 30
+# warn_days = 7 # email the owner this many days before expiry (needs [email])
+
+# Per-IP session limiting: the connection that puts an IP over default_limit is killed on
+# connect. OperServ EXCEPTION raises or lowers it per IP-mask. 0 or omitted = off.
+# [session]
+# default_limit = 3
+
+# Reply language. echo ships en, fr, de, es, es-ar, pt, pt-br.
+# [language]
+# default = "en"
+# dir = "lang"
+# available = ["en", "fr", "de", "es", "es-ar", "pt", "pt-br"]
+
+# Outbound email for registration confirmation and password recovery. Omit to disable.
+# [email]
+# from = "services@example.net"
+# command = "msmtp -t" # the message is piped on stdin, run via sh -c
+# brand = "Example Network" # display name in the template
+# accent = "#4f46e5" # any CSS colour
+# logo = "" # hosted image URL (no inline SVG or data URIs in email)
+# confirm_url = "" # e.g. https://example.net/confirm, adds a one-click link
+
+# Liveness and Prometheus metrics (plain HTTP, read-only, unauthenticated). GET /health
+# for a liveness probe, GET /metrics for a scrape (account/channel/oper totals, per-service
+# counters, event.lamport). Keep it on localhost. Omit to disable.
+# [health]
+# bind = "127.0.0.1:9099"
+
+# gRPC directory of accounts and channels for a website to mirror (identity and metadata
+# only, never credentials), plus the Accounts API to register and confirm accounts with
+# the token. See proto/echo.proto. Omit to disable.
+# [grpc]
+# bind = "127.0.0.1:50051"
+# token = "a-long-shared-secret" # every RPC sends `authorization: Bearer `
+# [grpc.tls] # optional; omit on a private or loopback hop
+# cert = "certs/node.crt"
+# key = "certs/node.key"
+
+# HTTP JSON-RPC endpoint for a staff web panel. Keep on localhost behind a proxy, or
+# terminate TLS here. Omit to disable.
+# [jsonrpc]
+# bind = "127.0.0.1:5601"
+# token = "a-long-shared-secret"
+# origins = ["https://example.net"] # browser origins allowed cross-site (CORS)
+# [jsonrpc.tls]
+# cert = "certs/node.crt"
+# key = "certs/node.key"
+
+# Passwordless web login: a member already signed in on the website connects with a
+# one-time kc_... token instead of a password, redeemed against this endpoint. Omit to disable.
+# [keycard]
+# url = "http://127.0.0.1:8000/accounts/api/login-token"
+# api_key = "shared-key" # matches the endpoint's X-API-Key
+
+# DictServ: dictionary, thesaurus, and reference lookups over DICT (RFC 2229). Opt-in
+# because it makes outbound requests. Lookups are rate-limited and truncated to one line.
+# Omit to make no outbound lookups at all.
# [dictserv]
# server = "dict.org:2628"
-# Registration policy. The look-alike guard refuses REGISTER of a nick or channel
-# that mixes alphabets (Cyrillic "аdmin"), is built from homoglyphs or styled
-# (fullwidth/math) letters imitating Latin, or hides invisible/bidi characters —
-# while genuine monolingual text (including accented French) passes. It's on by
-# default; turn it off for a community that legitimately uses mixed/non-Latin
-# names. Reloadable with OperServ REHASH.
-# [register]
-# confusable_check = false
+# Node-to-node replication (optional; most setups are a single node). Omit [gossip] and
+# [[peer]] to run standalone. See the Federation wiki page.
+# [gossip]
+# bind = "0.0.0.0:16700" # where peers reach us; omit for a dial-only node
+# secret = "shared-secret" # both nodes must present the same secret
+# [gossip.tls] # omit for a plaintext link; see scripts/gen-certs.sh
+# cert = "certs/node.crt"
+# key = "certs/node.key"
+# ca = "certs/ca.crt" # a peer must present a cert signed by this CA
+# [gossip.signing] # optional per-origin Ed25519 signing; key from --gen-gossip-key
+# key = "base64-secret-key" # this node's signing key
+# trust = { "42S" = "base64-pubkey", "43S" = "base64-peer-pubkey" }
+# [[peer]] # one block per other node
+# addr = "other-node:16700"
+# name = "other-node" # TLS name to expect; must match the peer's certificate