echoIRCd/echoircd.conf.example

513 lines
30 KiB
Text

###############################################################################
# echoIRCd configuration
#
# Syntax: section { field value; field value; ... }
# * one field per `;` * comments: # // and /* ... */
# * booleans are yes / no * quote values with spaces or #, e.g. "a b"
# * repeat a block (oper, listen, link) or a field (alias, motd) as needed
#
# Every option below is shown with its BUILT-IN DEFAULT; uncomment a line only to
# change it. Nothing is hardcoded — all values are read at runtime and most apply
# again on `./echoircd rehash` (SIGHUP) without a restart.
#
# Copy this file to `echoircd.conf` and fill in your own values. NOTE:
# echoircd.conf is gitignored because it holds secrets (oper password, cloak key,
# link password). Never commit your real config.
#
# (The legacy flat `key = value` format is still accepted; a top-level `name {`
# selects this block format.)
###############################################################################
# ═══ server identity ═════════════════════════════════════════════════════════
server {
name "irc.example.net"; # this server's unique name on the network
network "ExampleNet"; # network name shown to clients
sid "0AA"; # 3-char server id for S2S (digit + 2 alnum)
description "echoIRCd server"; # shown in LINKS / WHOIS 312
# Write this server's PID here on boot so `echoircd rehash` (and `kill -HUP`)
# can find and signal it to reload config in place. Omit to disable.
pidfile "echoircd.pid";
}
# ═══ listeners ═══════════════════════════════════════════════════════════════
# Repeatable. `ip "*"` (or "[::]") binds IPv4+IPv6 at once (dual-stack); an IPv4
# client on it is normalized back to its real v4 address. type: client (default)
# | server (S2S) ; tls yes = direct TLS ; wss yes = WebSocket-over-TLS.
listen { ip "0.0.0.0"; port 6667; } # plaintext clients
listen { ip "0.0.0.0"; port 6697; tls yes; } # TLS clients
listen { ip "0.0.0.0"; port 7000; type server; } # server-to-server links
# listen { ip "[::]"; port 6667; } # dual-stack on one line
# listen { ip "0.0.0.0"; port 7799; wss yes; } # wss:// (browser IRC clients)
# listen { ip "127.0.0.1"; port 8097; ws yes; } # ws:// (plaintext WebSocket)
# ═══ TLS ═════════════════════════════════════════════════════════════════════
# Generate a cert/key first, e.g.:
# openssl req -x509 -newkey rsa:2048 -keyout tls/key.pem -out tls/cert.pem \
# -days 3650 -nodes -subj "/CN=irc.example.net"
# The cert is re-read on REHASH, so a renewed cert applies without a restart.
tls {
cert "./tls/cert.pem";
key "./tls/key.pem";
# backend openssl; # openssl (default) | rustls (pure-Rust, no system OpenSSL)
# sni "irc.example.net ./tls/example.crt ./tls/example.key"; # per-host cert (repeatable)
}
# ═══ MOTD ════════════════════════════════════════════════════════════════════
motd {
"Welcome to echoIRCd.";
"Edit the MOTD in your echoircd.conf.";
}
# opermotd { "Welcome to the staff team."; } # shown to opers via /OPERMOTD
# ═══ per-SNI branding (one daemon, multiple network identities) ══════════════
# A client that connected via this TLS SNI host is shown `servername`/`network`
# instead of the globals — welcome burst, ISUPPORT NETWORK=, and the source of
# every numeric it receives. Hosts with no brand block use the global server{}.
# Needs TLS+SNI; plaintext/IP connections fall back to the globals. Repeatable.
# brand { host "irc.example.org"; servername "irc.example.org"; network "ExampleNet"; }
# ═══ operators ═══════════════════════════════════════════════════════════════
# The password accepts plaintext, sha256:<hex>, pbkdf2:… or a bcrypt $2b$ hash.
# Generate a bcrypt hash with: printf '%s' 'yourpassword' | ./echoircd mkpasswd
# Add fingerprint "<sha256>" to also (or only, with no password) require the
# client's TLS certificate. A block with neither password nor fingerprint is
# refused (it would let anyone oper up). type references an opertype below.
oper {
name "admin";
password "CHANGE_THIS_PASSWORD";
type netadmin;
# fingerprint "<sha256-cert-fp>"; # require this TLS client cert too (2FA)
# level 0; # operlevel (KILL protection tiers)
}
# Oper types (optional). No type = full access. A type is a named role (the WHOIS
# "is a <title>") built from reusable capability classes; five ship built in:
# helpop, globop, admin, servadmin, netadmin. Running a command your type doesn't
# grant is refused; its modes/snomasks/vhost are applied on oper-up.
# class { name "helpdesk"; commands "CHECK"; snomasks "c"; } # privs "..."
# opertype { name "helpdesk"; classes "helpdesk"; modes "+ih"; title "Help_Desk"; level 15; }
# ═══ server-to-server linking ════════════════════════════════════════════════
# The password is a shared secret. services yes marks the peer as a U-lined
# services server (also handles SASL if it is the sasl_server below).
# link {
# name "peer.example.net";
# ip 203.0.113.5;
# port 7000;
# password "CHANGE_THIS_LINK_SECRET";
# autoconnect yes; # dial it on boot / after a netsplit (default no)
# services no; # yes = U-lined services server
# }
# The linked server that handles SASL (client AUTHENTICATE is relayed to it).
# Unset = SASL disabled. SASL EXTERNAL also needs the client on TLS with a client
# cert (its fingerprint is forwarded to services).
# services { sasl_server "services.example.net"; }
# ═══ WEBIRC gateways ═════════════════════════════════════════════════════════
# Trusted web gateways (CGI:IRC / kiwiirc) send WEBIRC to declare the real
# client's host+ip. mask restricts which source IP may use the password.
# webirc { password "CHANGE_THIS_WEBIRC_SECRET"; name "mygateway"; mask "203.0.113.9"; }
# ═══ host cloaking (+x) ══════════════════════════════════════════════════════
cloak {
# Long random hex secret; keep it private. Changing it re-cloaks everyone.
key "CHANGE_THIS_TO_A_LONG_RANDOM_HEX_STRING";
# method — how +x cloaks are built (repeatable/ordered; first match wins).
# Default = hmac-sha256. Methods: hmac-sha256 (keyed subnet-preserving host
# hash) | account (<account_prefix>/<account>) | fingerprint
# (<cert_prefix>/<cert hash>) | static (everyone shows static_host).
# method account;
# method hmac-sha256;
# account_prefix "account"; # default: account
# cert_prefix "cert"; # default: cert
# static_host "users.example.net";
}
# ═══ DNS / ident ═════════════════════════════════════════════════════════════
dns {
# reverse-DNS on connect (the "Looking up your hostname..." notices).
resolve_hosts yes; # yes (default) resolves + reports; no = bare IP
# whether a resolved name is used in the hostmask (only if resolve_hosts=yes).
use_resolved_host yes; # yes (default) shows the domain; no keeps the IP
# ident (RFC1413): off by default; a connect class can also enable it.
# useident yes; # look up every client's ident (adds connect latency)
# requireident yes; # refuse clients whose ident can't be confirmed
# ident_timeout 5; # seconds to wait for the ident reply
}
# ═══ DNS blocklists (DNSBL) ══════════════════════════════════════════════════
# Repeatable. On a listing, dnsbl_action decides: mark (notice only, default) |
# kill (disconnect) | kline/gline/zline (ban for dnsbl_duration + disconnect).
# dnsbl {
# dnsbl "dnsbl.dronebl.org";
# dnsbl "rbl.efnetrbl.org";
# dnsbl_action "mark";
# dnsbl_reason "Your host is listed in a DNS blocklist";
# dnsbl_duration 86400; # ban seconds for kline/gline/zline (default 1 day)
# }
# Per-blocklist form (one value string; overrides the globals):
# dnsbl { dnsbl "domain=torexit.dan.me.uk name=TorExit action=zline duration=1w reason=Tor-not-allowed"; }
# ═══ limits (advertised in ISUPPORT and enforced) ════════════════════════════
limits {
# maxnick 30; # nick length (NICKLEN)
# maxchannel 50; # channels a user may join (CHANLIMIT)
# maxbans 100; # ban-list entries per channel (MAXLIST)
# maxinvites 100; # pending invites tracked per user
# modes 20; # mode changes per MODE line (MODES)
# max_line 16384; # max bytes in one line / receive queue (16 KiB)
# max_sendq 1048576; # queued output before a slow client is dropped (1 MiB)
# whowas_maxentries 256; # historical nick records retained
# maxwatch 128; # WATCH entries per user
# maxmonitor 128; # MONITOR entries per user
# maxsilence 32; # SILENCE entries per user
# maxaccept 64; # /ACCEPT (caller-id) entries per user
# maxsignore 64; # server-ignore entries per user
# metadata_maxkeys 32; # IRCv3 METADATA keys per target
# metadata_maxvalue 512; # METADATA value length
# multiline_maxbytes 4096; # draft/multiline: max bytes advertised + enforced
# multiline_maxlines 24; # draft/multiline: max lines
# chathistory_limit 256; # CHATHISTORY messages kept per conversation
# chathistory_maxage 604800; # max age (secs) a client may request (7 days)
# dccallow_maxentries 20; # /DCCALLOW list entries per user
# http_max_concurrent 32; # in-flight outbound HTTP requests (API modules)
}
# ═══ timeouts (seconds) ══════════════════════════════════════════════════════
timeouts {
# registration_timeout 60; # drop clients that never register (NICK+USER)
# ping_frequency 90; # send a PING after this much idle time
# ping_timeout 60; # then drop if no PONG within this much longer
# slow_command_ms 200; # snotice when one core event takes >= this (0=off)
}
# ═══ flood control ═══════════════════════════════════════════════════════════
flood {
# flood_messages 8; # messages allowed per window before dropping (opers exempt)
# flood_seconds 4; # the flood window
# joinflood_duration 60; # +j (join flood) default window
# nickflood_duration 60; # +F (nick flood) default window
# connflood "5 10"; # refuse > N connections per S seconds from one IP
# --- blockamsg: block mass /amsg and /ame ---
# blockamsg yes;
# blockamsg_delay 3; # same text to a different target list within N s = block
# blockamsg_action block; # block | kill | gline | kline | zline
# blockamsg_duration 900; # ban seconds for a *line action
}
# ═══ connection policy ═══════════════════════════════════════════════════════
connections {
# --- conn_waitpong: hold registration until the client echoes a PING cookie
# (filters bots that never PONG; real clients auto-reply). ---
# conn_waitpong yes;
# conn_waitpong_killonbadreply yes; # drop on wrong pong (default: keep waiting)
# conn_waitpong_exempt_localhost4 yes; # exempt 127.0.0.0/8
# conn_waitpong_exempt_localhost6 yes; # exempt ::1
# --- connectban: z-line an IP range that opens too many connections ---
# connectban yes;
# connectban_threshold 10; # connections from a range before it's banned
# connectban_duration 21600; # ban seconds (default 6h)
# connectban_bootwait 120; # grace secs after startup (reconnect storm)
# connectban_gcinterval 3600; # wipe the tally this often
# connectban_ipv4cidr 32; # range width for IPv4 counting (/32)
# connectban_ipv6cidr 128; # range width for IPv6 counting (/128)
# connectban_banmessage "Too many connections from your address";
# connectban_exempt "10.0.0.0/8"; # never ban this glob/CIDR (repeatable)
# --- accept-rate: drop connection-churn floods at the accept edge ---
# accept_rate 0; # max NEW connections/sec per source IP (0 = off)
# accept_burst 0; # instantaneous burst per IP (0 = same as accept_rate)
# --- PROXY protocol: trust HAProxy/nginx PROXY header from these sources
# (glob/CIDR, repeatable) so the real client IP is used. ---
# proxy 127.0.0.1;
# proxy 10.0.0.0/8;
}
# --- connectclass: per-class connection policy. Each value matches connecting
# clients by IP/host mask (glob OR CIDR) + optional TLS/port; first match
# wins, else the global limits apply. One quoted value string per class:
# allow=<mask[,mask]> deny=yes parent=<name> requiressl=yes|trusted
# password=<pw> hash=<algo> port=<p[,p]> localmax=<n> globalmax=<n>
# limit=<n> maxchans=<n> pingfreq=<s> timeout=<s> modes=<+modes>
# recvq=<bytes> softsendq=<bytes> hardsendq=<bytes> fakelag=no
# penaltythreshold=<n> commandrate=<s> useident=yes requireident=yes
# resolvehostnames=no maxconnwarn=yes
# classes {
# connectclass "trusted allow=10.0.0.0/8 maxchans=200 pingfreq=120 fakelag=no";
# connectclass "secure allow=* requiressl=yes password=sha256:<hex> hash=sha256";
# connectclass "vpn allow=* parent=trusted localmax=2 maxchans=20 modes=+ix";
# connectclass "banned allow=1.2.3.0/24 deny=yes";
# connectclass_required yes; # refuse clients that match no allow class (default no)
# }
# ═══ STS (Strict Transport Security) ═════════════════════════════════════════
# Tell CAP-302 clients on the plaintext port to upgrade to TLS and pin it. Off
# unless sts_duration > 0. Only enable once TLS is solid — clients will then
# refuse plaintext for the pinned duration.
# sts {
# sts_duration 2592000; # seconds clients should stick to TLS (0 = off)
# sts_port 6697; # the TLS port to upgrade to
# sts_preload no; # advertise preload eligibility
# }
# ═══ oper / staff behaviour ══════════════════════════════════════════════════
opers {
# operprefix yes; # give every oper a `!` prefix (mode y, above owner)
# ojoin yes; # /OJOIN <#chan> — join as network staff with `!`
# ojoin_op yes; # also grant channel op on OJOIN (default yes)
# oper_svslogin yes; # allow services to SVSLOGIN opers to an oper block
# maphide yes; # hide LINKS / MAP from non-opers
# hideservices yes; # hide U-lined services from MAP/LINKS/STATS for non-opers
# rline_matchonnickchange yes; # re-check /RLINE regex bans on nick change
# --- hidewhois: hide sensitive WHOIS lines from ordinary users ---
# hidewhois yes;
# hidewhois_opers yes; # opers still see everything (default yes)
# hidewhois_selfview yes; # a user sees their own full WHOIS (default yes)
# hidewhois_hide_server yes; # hide 312 server line (default yes)
# hidewhois_hide_idle yes; # hide 317 idle (default yes)
# hidewhois_hide_secure yes; # hide 671 secure-connection (default yes)
}
# --- hidemode / hidelist: restrict who sees a mode change / list mode, by rank
# (owner|admin|op|halfop|voice). Opers/setter/links always see it. Repeatable.
# channelvis {
# hidemode "b op"; # hide ban changes below op
# hidelist "b op"; # only ops may view the ban list
# }
# ═══ channels ════════════════════════════════════════════════════════════════
channels {
# announce_channels yes; # snotice opers when a brand-new channel is created
# chancreate yes; # (alias of announce_channels)
# channames_deny ""; # forbid these chars in NEW channel names (control codes, etc.)
# permchannels_database "permchannels.db"; # +P permanent channels (default <conf>.permchannels)
# markread_database "markread.db"; # draft/read-marker persistence (default <conf>.markread)
# --- restrictchans: only opers may CREATE channels (all may still join) ---
# restrictchans yes;
# restrictchan "#public-*"; # glob whitelist ordinary users may create (repeatable)
# --- denychans: forbid joining channels matching a glob ---
# badchan "#evil* reason=Off-limits redirect=#lobby allowopers=yes";
# goodchan "#evilgenius"; # whitelist back out of a broad badchan (repeatable)
}
# ═══ users: on-connect behaviour ═════════════════════════════════════════════
users {
# connbanner "This network is for authorized users only."; # NOTICE at connect (repeatable)
# conn_umodes "+ix"; # user modes auto-set on every client at connect (alias autoumodes)
# autojoin "#lobby,#help"; # channels every client auto-joins (alias conn_join; repeatable)
# seenicks yes; # snotice opers on every nick change (default no)
# --- applied on successful OPER ---
# opermodes "+ws"; # extra user modes set on oper-up (alias oper_umodes)
# operjoin "#opers"; # channels an oper auto-joins on oper-up (repeatable)
# --- self-service vhosts: /VHOST <user> <pass> sets your displayed host ---
# vhost "alice s3cret alice.staff.example"; # (repeatable)
}
# ═══ account registration (IRCv3 draft/account-registration → HTTP API) ══════
# Bridges REGISTER / VERIFY to an HTTP backend (POST form-encoded + X-API-Key).
# accounts {
# account_registration yes; # master switch (default no)
# acctregister_registerurl "https://accounts.example/register"; # required
# acctregister_verifyurl "https://accounts.example/verify"; # required for VERIFY
# acctregister_apikey "CHANGE_THIS_API_KEY"; # sent as X-API-Key
# acctregister_autologin yes; # log in on successful register (default yes)
# acctregister_beforeconnect yes; # allow REGISTER pre-registration (default yes)
# acctregister_emailrequired yes; # require an email address (default yes)
# acctregister_requiretls yes; # only over TLS (default yes)
# acctregister_ratecount 3; # max register attempts per IP ...
# acctregister_ratetime 3600; # ... per this many seconds
# }
# ═══ human-verification gates (anti-bot) ═════════════════════════════════════
# recaptcha: hand an unverified user a token+URL; they send CAPTCHA <token>.
# cloudflare_challenge: same idea via VERIFYCHALLENGE <token>. JWT-only by default.
# verification {
# recaptcha yes;
# recaptcha_secret "CHANGE_THIS_HS256_SECRET"; # signs the IP-bound token
# recaptcha_url "https://example.org/captcha?token=";
# recaptcha_issuer "echoIRCd"; # JWT issuer (default echoIRCd)
# recaptcha_ttl 1800; # token lifetime secs (default 1800)
# recaptcha_message "Please verify you are human:";
# recaptcha_whitelistports 6697; # listener ports exempt (repeatable)
# cloudflare_challenge yes;
# cloudflare_secret "CHANGE_THIS_HS256_SECRET";
# cloudflare_url "https://example.org/challenge?token=";
# cloudflare_issuer "echoIRCd";
# cloudflare_ttl 1800;
# cloudflare_message "Solve the challenge:";
# cloudflare_whitelistports 6697;
# }
# ═══ anti-spam / anti-drone ══════════════════════════════════════════════════
antiabuse {
# --- antirandom: score random-looking nick/ident/realname (spam drones) ---
# antirandom yes;
# antirandom_threshold 10; # score at/above this acts (default 10)
# antirandom_checkfull yes; # also score ident + realname (default yes)
# antirandom_action kill; # block | kill | gline | kline | zline
# antirandom_duration 86400; # ban seconds for a *line action
# antirandom_reason "Random-looking connection rejected";
# antirandom_showfailed no; # snotice opers on a hit (default no)
# --- hashident: replace ident with a stable opaque token per IP ---
# hashident yes;
# hashident_key "CHANGE_THIS_SECRET"; # HMAC key; makes the mapping unforgeable
# --- solvemsg: unvouched users answer one arithmetic question before their
# PMs are delivered (opers & logged-in accounts exempt). ---
# solvemsg yes;
# --- antimixedutf8: block spam mixing look-alike scripts within words ---
# antimixedutf8 yes;
# amu_threshold 8;
# amu_minlen 10;
# amu_action block; # block | kill | gline | kline | zline
# amu_target both; # both | channel | private
# amu_reason "Mixed-script spam blocked";
# --- +G censor words: badword "<find> [replacement]" (omit = block) ---
# badword "examplebadword ***";
}
# ═══ access restrictions ═════════════════════════════════════════════════════
restrictions {
# restrictmsg yes; # only opers/services may be PM'd by ordinary users
# disabled_commands "KNOCK"; # refuse these commands to ordinary users (repeatable)
# restrictcommand "LIST connectdelay=60 exemptidentified=yes exempttls=no reason=Please-wait";
# --- securelist: delay /LIST for new connections (defeats list-spam bots) ---
# securelist yes;
# securelist_waittime 60; # seconds connected before /LIST works
# securelist_exemptregistered yes; # logged-in users are exempt (default yes)
# securelist_exception "*!*@trusted.example"; # exempt host glob (repeatable)
# securelist_showmsg yes; # tell the early lister to wait (default yes)
# securelist_fakechans 5; # size of the throwaway fake list shown
# securelist_fakechanprefix "#"; # prefix for the fake channels
# securelist_fakechantopic ""; # topic shown on the fake channels
# --- autodrop: silently drop a pre-registration client that sends any of
# these (HTTP scanners blurt GET/POST before NICK/USER): ---
# autodrop_commands "GET POST HEAD CONNECT PUT DELETE OPTIONS TRACE PATCH";
}
# ═══ reputation & security groups ════════════════════════════════════════════
# reputation: per-address scoring + y: score extban (MODE #c +b y:<100 / y:>500).
# reputation {
# reputation_database "reputation.db"; # default <conf>.reputation
# reputation_minchanmembers 3; # only bump if in a channel this big
# reputation_scorecap 10000; # max score
# reputation_whois all; # all | opers | self | none
# reputationexpire "2 1h"; # score<=2 decays after 1h (repeatable; * = any)
# reputationexpire "* 90d";
# }
# security groups — use as an extban: MODE #c +b g:<name>. criteria: public tls
# insecure account unregistered oper exclude-oper bot webirc mask=<glob>
# exclude=<glob> scoremin=<n> scoremax=<n>.
# securitygroups {
# securitygroup "trusted account tls public";
# securitygroup "newbies scoremax=10 public";
# }
# ═══ logging & observability ═════════════════════════════════════════════════
logging {
# --- syslog: mirror the server-notice / log stream to the system logger ---
# syslog yes;
# syslog_target "/dev/log"; # a Unix socket path, or host:port for UDP
# syslog_facility daemon; # kern user mail daemon auth ... local0..local7
# syslog_tag echoircd;
# --- log_json: append the log stream to a file as JSONL ---
# log_json "/var/log/echoircd/events.jsonl";
# --- snoop_stderr: also echo the server-notice stream to stderr ---
# snoop_stderr yes;
# --- metrics: OpenMetrics/Prometheus scrape endpoint (plaintext HTTP GET);
# bind privately or behind a proxy. ---
# metrics_bind "127.0.0.1:9109";
# --- chanlog: mirror the oper snotice stream into a channel. Add snomask
# letters after the channel to log only those (x x-lines, d dnsbl,
# c connects, o oper, q quit, k kill …); none = everything. Repeatable. ---
# chanlog "#snotices"; # everything
# chanlog "#bans xdk"; # only x-lines, dnsbl hits, kills
}
# ═══ extra features / modules ════════════════════════════════════════════════
modules {
# abbreviation yes; # a unique command prefix resolves to its command (WHOI→WHOIS)
# --- command aliases: /NS ... → PRIVMSG <target> (services shortcuts) ---
# alias "NS NickServ";
# alias "CS ChanServ";
# --- customprefix: reconfigure built-in prefix tiers, or add new ones ---
# Built-ins (oper founder admin op halfop voice): bare token = sigil;
# ranktoset/ranktounset = min rank to grant/revoke; depriv=no locks self-removal.
# customprefix "op * ranktoset=admin ranktounset=admin depriv=no";
# New tier: letter+prefix required; rank default 1 (voice=10 halfop=20 op=30
# admin=40 founder=50 oper=60).
# customprefix "helper letter=V prefix=? rank=25 ranktoset=op ranktounset=op";
# --- customtitle: /TITLE <name> <pass> grants a WHOIS title (+ optional vhost) ---
# customtitle "staff s3cret staff.example.net Network Staff";
# --- randquote: greet each connecting user with a random line (repeatable) ---
# randquote "The best way out is always through. — Robert Frost";
# --- showfile: serve a text file as its own command (read fresh each use) ---
# showfile "RULES /etc/echoircd/rules.txt";
# --- filter (oper /FILTER): pattern engine for rules added after it ---
# filter_engine glob; # glob (wildcards, default) | regex
# --- geoip: MaxMind .mmdb country lookup. Enables +b G:<cc>, GEOIP, WHOIS country ---
# geoip_database "/etc/echoircd/GeoLite2-Country.mmdb";
# --- network_icon: advertise a network icon via draft/ICON ISUPPORT ---
# network_icon "https://example.org/icon.png";
# --- profilelink: a profile URL in WHOIS for logged-in users ---
# profilelink_baseurl "https://example.org/profile/";
# --- relaymsg (draft/relaymsg): opers can /RELAYMSG under a spoofed nick (bridges) ---
# relaymsg_separators "/";
# relaymsg_ident "relay";
# relaymsg_host "relay.example.com"; # default: the server name
# --- extjwt: /EXTJWT issues a signed token proving IRC identity to a service ---
# extjwt_secret "CHANGE_THIS_HS256_SECRET"; # required to enable
# extjwt_duration 30; # token lifetime, seconds
# extjwt_chunk 200; # token line-chunk size (bytes) when splitting
# extjwt_service "myservice CHANGE_THIS_SERVICE_SECRET"; # per-service key (repeatable)
# --- filehost: advertise a file-upload service + hand logged-in users a token ---
# filehost_website "https://files.example.net";
# filehost_jwt_secret "CHANGE_THIS_HS256_SECRET";
# filehost_jwt_issuer "echoIRCd";
# filehost_requiressl yes; # only issue upload tokens to TLS users (default yes)
# filehost_token_expiry 3600; # upload-token lifetime, secs
# filehost_auth_message "Upload here:";
# --- dccallow: block unwanted DCC unless the recipient ran /DCCALLOW +<nick> ---
# dccallow_blockfile "*.exe"; # (repeatable)
# dccallow_blockchat yes; # also gate DCC CHAT
# --- HTTP client (API modules): verify upstream TLS certificates ---
# http_tls_verify yes;
}
# ═══ WebSocket tuning (only if a ws/wss listener is defined) ══════════════════
# websocket {
# ws_origin "https://x.example"; # allowed Origin globs (repeatable); empty = any
# ws_proxyranges "127.0.0.1"; # proxies whose X-Real-IP/XFF we trust (repeatable)
# ws_trust_proxy no; # trust those headers from ANY peer (allows spoofing)
# ws_allowmissingorigin yes; # allow clients that send no Origin header
# ws_defaultmode text; # frame mode with no subprotocol: text|binary|reject
# ws_nativeping yes; # liveness via WebSocket pings (no = IRC PING)
# ws_handshake_timeout 10; # seconds to complete the HTTP Upgrade
# ws_ping_interval 60; # seconds between WebSocket keepalive pings
# ws_timeout 120; # drop after this many seconds of silence
# }