7 Configuration
Jean edited this page 2026-07-21 14:39:14 +00:00

Configuration

echo reads one TOML file, passed as the first argument (echo config.toml). Copy config.example.toml to start. Keep config.toml out of version control; it holds the link password.

Two sections are required: [uplink] and [server]. Everything else is optional and off when omitted. A few settings reload on OperServ REHASH without a restart (noted below); the rest need a restart.

The smallest working config:

[uplink]
host = "127.0.0.1"
port = 7000
password = "changeme"

[server]
name = "services.example.net"
sid  = "42S"
description = "Network Services"

Contents


Where echo connects, and how it authenticates the link. Add a matching <link> block on the ircd for the same SID and password.

Key Default Description
host required Uplink address.
port required Uplink server-to-server port.
password required Link password; must match the ircd's <link> block.
tls false Connect over TLS instead of plaintext.
spki_fingerprint "" Required when tls = true. The server's SPKIFP, base64 SHA256 of its SubjectPublicKeyInfo. The link cert is pinned by this fingerprint rather than a CA, so a self-signed ircd cert is fine.
[uplink]
host = "irc.example.net"
port = 7000
password = "s3cret"
# tls = true
# spki_fingerprint = "Wh/ag/dtBCpXhiYPhRWV66kx3FutmTyNw1dysTbWvao="

Get the fingerprint of a running ircd with:

openssl s_client -connect irc.example.net:7000 </dev/null 2>/dev/null \
  | openssl x509 -pubkey -noout | openssl pkey -pubin -outform DER \
  | openssl dgst -sha256 -binary | base64

[server]

The services server's own identity, and how its pseudo-clients present themselves.

Key Default Description
name required Services server name (e.g. services.example.net).
sid required Three-character server ID, unique on the network.
description required Server description shown in /links and /map.
protocol 1206 InspIRCd link protocol version (1206 = insp4, 1205 = insp3).
scram_iterations high PBKDF2 cost baked into new SCRAM verifiers at registration. Lower it only if registration latency matters more than verifier strength.
guest_nick "Guest" Prefix a user is renamed to on LOGOUT (the ircd appends a number). Must start with a letter.
service_host server name Hostname the pseudo-clients wear, so NickServ shows as NickServ!services@<here>.
service_modes "iHkB" User modes the pseudo-clients are introduced with: invisible, hideoper, servprotect (unkillable, needs the services server U-lined), bot. Add T to have the ircd block all CTCP.
service_oper_type "Network Service" Oper type shown in /whois for the pseudo-clients. Empty leaves them non-opers.
services_channel "#services" Channel every pseudo-client joins at startup. Empty keeps them out of any channel.
standard_replies false Emit IRCv3 FAIL/WARN/NOTE standard replies instead of plain notices. Needs m_services_stdrpl on the ircd.

[modules]

Which services to start. Omit the section for the full suite; list names to run a subset. Add "example" to also start the template service.

Key Default Description
services full suite Lowercase service names to load.
[modules]
services = ["nickserv", "chanserv", "memoserv", "operserv"]

The full suite is nickserv, chanserv, botserv, hostserv, memoserv, operserv, statserv, groupserv, infoserv, reportserv, helpserv, chanfix, diceserv, gameserv, debugserv.

[[oper]]

Services operators. Repeat the block once per operator. Grant a tier with type, or list fine-grained privileges with privs. The three tiers stack in power:

  • operator is day-to-day moderation: view hidden info, network bans, kick, kill, session limits, ignores, mode, spam filters, log search.
  • administrator is that plus account and channel data: suspend, drop, set flags, forbid, global notices, defcon, memo/info/bot administration.
  • root is that plus daemon control: add or remove opers, SET, REHASH, RESTART, SHUTDOWN, the activity feed.
Key Default Description
account required The account name that holds these privileges.
type none Tier shortcut: operator, administrator, or root.
privs [] Fine-grained privileges: auspex, oper, suspend, admin, root.
[[oper]]
account = "alice"
type = "root"

[[oper]]
account = "bob"
privs = ["oper", "suspend"]

Runtime operators can also be added without editing the config, with OperServ OPER ADD (optionally time-limited).

[log]

The staff audit feed. Notable service actions (registrations, drops, vhosts, suspensions, akicks, access and bot changes) are announced to a channel so operators can see who did what. Memo bodies, credential material, and cosmetic self-service changes are never shown.

Key Default Description
channel required The log channel. Keep it operator-only; NOTIFY lines can carry real IPs.
notify_exclude [] Masks OperServ NOTIFY never announces. #channel mutes a channel, server:<glob> (or via:) mutes a server, anything else mutes a user (nick glob, user@host, or extban). Reloadable with REHASH.
[log]
channel = "#services"
notify_exclude = ["*/*", "server:relay.example.net", "#staff"]

[register]

Registration policy. Both settings reload with REHASH.

Key Default Description
confusable_check true Refuse look-alike or mixed-script registration names. Turn off for a community that legitimately uses non-Latin names.
vouch false Invite-only registration: a new account stays pending until an existing member runs NickServ VOUCH, instead of confirming by email.

[extban]

Which extbans echo recognizes. Omit the section (or leave the list empty) to accept every extban the ircd advertises.

Key Default Description
enabled all Extban names to accept (account, realname, country, ...).

[auth]

Who owns accounts. By default echo owns them and REGISTER / IDENTIFY / SET PASSWORD all work on their own. Set external = true to hand identity to an outside authority (a website): IRC can then only IDENTIFY, everything that changes an account is refused, and the authority pushes accounts in over the gRPC Accounts API. echo still owns all channel, vhost, and ban data, keyed by account name.

Key Default Description
external false Delegate account identity to an external authority.

[expire]

Drop inactive accounts and channels on a periodic pass. Operators, 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.

Key Default Description
accounts_days 0 (off) Expire accounts not identified to for this many days.
channels_days 0 (off) Expire channels not joined for this many days.
warn_days 0 (off) Email the owner this many days before expiry (needs [email] and an address on file).

[session]

Per-IP connection limiting. The connection that puts an IP over the limit is killed on connect. OperServ EXCEPTION raises or lowers it per IP-mask (a limit of 0 means unlimited).

Key Default Description
default_limit 0 (off) Connections allowed per IP.

[language]

Reply language. echo ships English plus French, German, Spanish (with an Argentine variant), and Portuguese (Portugal and Brazil).

Key Default Description
default "en" Language for users who haven't chosen one.
dir "lang" Directory of <code>.json catalogs.
available [] Codes a user may pick with NickServ SET LANGUAGE. Empty allows only the default.
[language]
default = "en"
available = ["en", "fr", "de", "es", "es-ar", "pt", "pt-br"]

[email]

Outbound email for registration confirmation and password recovery. Without it, those features are off.

Key Default Description
from required Sender address.
command required Shell command the message is piped to, e.g. sendmail -t or msmtp -t. Run via sh -c.
brand "Network Services" Display name in the email template.
accent "#4f46e5" Accent color (any CSS color).
logo "" Hosted logo image URL (email clients don't render inline SVG or data URIs).
confirm_url "" Base URL of a one-click confirmation endpoint. When set, confirmation emails include a <url>?account=<name>&code=<code> link alongside the CONFIRM command.
[email]
from = "services@example.net"
command = "msmtp -t"
brand = "Example Network"

[health]

A read-only, unauthenticated HTTP endpoint for monitoring. GET /health is a JSON liveness probe; GET /metrics is a Prometheus scrape (account, channel, and oper totals, per-service counters, and a monotonic log-progress gauge). Bind it to localhost.

Key Default Description
bind required HTTP listen address, e.g. 127.0.0.1:9099.

[grpc]

A gRPC directory of accounts and channels for a website to mirror (identity and metadata only, never credentials), and, with the token, to register or confirm accounts. Schema in proto/echo.proto. Off unless configured.

Key Default Description
bind required Listen address, e.g. 127.0.0.1:50051.
token required Bearer token every RPC must send (authorization: Bearer <token>).
[grpc.tls] cert / key none Optional server TLS (PEM chain and key). Omit on a private or loopback hop.
[grpc]
bind  = "127.0.0.1:50051"
token = "a-long-shared-secret"

[jsonrpc]

An HTTP JSON-RPC endpoint (for a staff web panel). Keep it on localhost behind a reverse proxy, or terminate TLS here.

Key Default Description
bind required HTTP listen address, e.g. 127.0.0.1:5601.
token required Bearer token every request must send.
origins [] Browser origins allowed cross-site (CORS). Empty means no browser access.
[jsonrpc.tls] cert / key none Terminate TLS here (enables HTTP/2). Omit if a proxy does TLS.

[keycard]

Passwordless web login: a member already signed in on the website connects with a one-time kc_... token in place of a password. echo redeems it against the website's localhost login endpoint.

Key Default Description
url required The website's login-token endpoint.
api_key required Matches the endpoint's X-API-Key.

[dictserv]

DictServ, dictionary and reference lookups over the DICT protocol (RFC 2229). Opt-in, because it makes outbound network requests. Lookups are globally rate-limited and truncated to one line.

Key Default Description
server "dict.org:2628" DICT endpoint.
[dictserv]
server = "dict.org:2628"

Running more than one node

Optional. A single node is the usual setup. If you run several, they replicate their event logs and share one account and channel database. [gossip] turns it on, [[peer]] names the others. See the wiki's federation notes for the trust model and certificate setup.

Section Key Description
[gossip] bind Where peers reach this node. Omit for a dial-only node.
[gossip] secret Shared secret every node presents.
[gossip.tls] cert / key / ca Mutual TLS; a peer must present a cert signed by ca.
[[peer]] addr / name One block per other node; name must match the peer's certificate.

Full example

Every section, with the optional ones commented out. Uncomment and edit what you need.

# ---- required ----

[uplink]
host = "127.0.0.1"
port = 7000
password = "changeme"      # must match the ircd's <link> block
# tls = false
# spki_fingerprint = ""    # base64 SHA256 of the server's SPKI; required when tls = true

[server]
name = "services.example.net"
sid  = "42S"               # three chars, unique on the network
description = "Network Services"
# protocol = 1206          # 1206 = insp4, 1205 = insp3
# scram_iterations = 210000
# guest_nick = "Guest"
# service_host = ""        # empty = the server name
# service_modes = "iHkB"
# service_oper_type = "Network Service"
# services_channel = "#services"
# standard_replies = false # needs m_services_stdrpl on the ircd

# ---- services and operators ----

# [modules]
# services = ["nickserv", "chanserv"]   # omit for the full suite

# [[oper]]
# account = "yournick"
# type = "root"                          # operator | administrator | root
# privs = []                             # or fine-grained: auspex, oper, suspend, admin, root

# [log]
# channel = "#services"
# notify_exclude = ["*/*", "server:relay.example.net", "#staff"]

# ---- policy ----

# [register]
# confusable_check = true
# vouch = false

# [extban]
# enabled = []               # empty = accept every extban the ircd offers

# [auth]
# external = false           # true = an outside authority owns accounts

# ---- lifecycle and limits ----

# [expire]
# accounts_days = 90
# channels_days = 30
# warn_days = 7

# [session]
# default_limit = 3

# ---- user-facing ----

# [language]
# default = "en"
# available = ["en", "fr", "de", "es", "es-ar", "pt", "pt-br"]

# [email]
# from = "services@example.net"
# command = "msmtp -t"
# brand = "Example Network"
# accent = "#4f46e5"
# logo = ""
# confirm_url = ""           # e.g. https://example.net/confirm

# ---- integrations ----

# [health]
# bind = "127.0.0.1:9099"

# [grpc]
# bind = "127.0.0.1:50051"
# token = "a-long-shared-secret"
# [grpc.tls]
# cert = "certs/node.crt"
# key  = "certs/node.key"

# [jsonrpc]
# bind = "127.0.0.1:5601"
# token = "a-long-shared-secret"
# origins = ["https://example.net"]

# [keycard]
# url = "http://127.0.0.1:8000/accounts/api/login-token"
# api_key = "shared-key"

# [dictserv]
# server = "dict.org:2628"

# ---- optional multi-node ----

# [gossip]
# bind = "0.0.0.0:16700"
# secret = "shared-secret"
# [gossip.tls]
# cert = "certs/node.crt"
# key  = "certs/node.key"
# ca   = "certs/ca.crt"
# [[peer]]
# addr = "other-node:16700"
# name = "other-node"