docs: rework README + manual — refresh the feature set (services interface, rustls TLS backend, WebSocket, PROXY v1/v2, metrics + JSON-RPC endpoints), correct command/module/cap counts, and document the tls_backend/metrics_bind/rpc/sts config keys in configuration.md and the example

This commit is contained in:
Jean Chevronnet 2026-08-19 14:37:22 +00:00
parent b106b66de5
commit ebd6e29589
No known key found for this signature in database
GPG key ID: 439666D63A9477E4
9 changed files with 121 additions and 98 deletions

107
README.md
View file

@ -2,58 +2,55 @@
# echoIRCd # echoIRCd
**A from-scratch, memory-safe IRCv3 server written in Rust.** **A memory-safe IRCv3 server written in Rust.**
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](Cargo.toml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](Cargo.toml)
[![Language: Rust](https://img.shields.io/badge/rust-stable-orange.svg)](https://www.rust-lang.org) [![Language: Rust](https://img.shields.io/badge/rust-stable-orange.svg)](https://www.rust-lang.org)
[![unsafe: forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](src/lib.rs)
[![IRCv3](https://img.shields.io/badge/IRCv3-supported-blueviolet.svg)](https://ircv3.net) [![IRCv3](https://img.shields.io/badge/IRCv3-supported-blueviolet.svg)](https://ircv3.net)
[![dependencies: 2](https://img.shields.io/badge/dependencies-openssl%20%2B%20mio-lightgrey.svg)](Cargo.toml)
</div> </div>
## About ## About
echoIRCd is a full IRC + IRCv3 server built from the ground up in safe Rust echoIRCd is a full IRC + IRCv3 server. A single lock-free **core thread** owns all
(`#![forbid(unsafe_code)]`) with just two dependencies — `openssl` for TLS and state; a **pool of epoll reactor threads** (one per core) drives the connections
`mio` for the socket engine. A single lock-free core thread owns all state; a around it — TLS crypto and all — with no async runtime. It ships **100+ commands**,
**pool of epoll reactor threads** (one per core) drives the connections around it the **complete channel & user mode set**, **30+ IRCv3 capabilities**,
— TLS crypto and all — without an async runtime. It ships **100+ commands**, the server-to-server linking, a services interface, TLS, WebSocket, GeoIP, layered
**complete channel & user mode set**, anti-spam, a Prometheus metrics endpoint, and a JSON-RPC control plane — with every
**28 IRCv3 capabilities**, server-to-server linking, a services interface, TLS, operational limit exposed as a config key.
WebSocket, GeoIP, layered anti-spam, and a JSON-RPC control plane — with every
operational limit configurable and nothing hardcoded.
## Features ## Features
- **Full IRC core** — registration, channels (`JOIN`/`PART`/`KICK`/`INVITE`/ - **Full IRC core** — registration, channels (`JOIN`/`PART`/`KICK`/`INVITE`/
`KNOCK`/`CYCLE`/`REMOVE`/`TOPIC`), messaging (`PRIVMSG`/`NOTICE`/`TAGMSG`, CTCP), `KNOCK`/`CYCLE`/`REMOVE`/`TOPIC`), messaging (`PRIVMSG`/`NOTICE`/`TAGMSG`, CTCP),
and info (`WHO`/`WHOIS`/`WHOWAS`/`LIST`/`STATS`/`MAP`/`LUSERS`/`MOTD`). and info (`WHO`/`WHOIS`/`WHOWAS`/`LIST`/`STATS`/`MAP`/`LUSERS`/`MOTD`).
- **Complete mode set** — prefixes `qaohv` (+ a network-staff `!` prefix), list - **Complete mode set** — prefixes `qaohv` (plus a network-staff `!` prefix), list
modes `beIgXw`, keyed/limit/flood/redirect/history/anticaps params, the full flag modes `beIgXw`, keyed/limit/flood/redirect/history/anticaps params, the full flag
set, all the standard user modes, and matching + acting **extbans** set, all the standard user modes, and matching + acting **extbans**.
(`g y r j s G b`, `m c n`). - **IRCv3** — message-tags (+msgid), server-time, labeled-response, batch,
- **IRCv3** — 28 capabilities including `message-tags`+`msgid`, `server-time`, echo-message, account-tag, **CHATHISTORY**, **multiline**, **message-redaction**,
`labeled-response`, `batch`, `echo-message`, `account-tag`, **CHATHISTORY**, **read-marker**, **relaymsg**, SASL, standard-replies, and `WATCH`/`MONITOR`/
**multiline**, **message-redaction**, **read-marker**, **relaymsg**, and `SILENCE`/caller-id.
`WATCH`/`MONITOR`/`SILENCE`/callerid.
- **Operators**`OPER`/`KILL`/`WALLOPS`/`GLOBOPS`, the `SA*`/`CHG*`/`SET*` - **Operators**`OPER`/`KILL`/`WALLOPS`/`GLOBOPS`, the `SA*`/`CHG*`/`SET*`
override toolbox, x-lines (`K`/`G`/`Z`/`E`/`SHUN`/`QLINE`/`CBAN`) persisted to override toolbox, x-lines (`K`/`G`/`Z`/`E`/`SHUN`/`QLINE`/`CBAN`/`RLINE`)
disk, staff prefix (`operprefix`/`OJOIN`), rank-gated `hidelist`/`hidemode`, and persisted to disk, staff prefix (`operprefix`/`OJOIN`), oper levels, rank-gated
a reload-safe `REHASH`. `hidelist`/`hidemode`, and a reload-safe `REHASH`.
- **Services & accounts** — SASL PLAIN/EXTERNAL relayed over S2S, the `SVS*` / - **Services & accounts** — SASL PLAIN/EXTERNAL relayed over the link, the `SVS*` /
`ENCAP` / `METADATA` interface, account-gated modes, and optional ircd-side `ENCAP` / `METADATA` interface, account-gated modes, and optional ircd-side
account registration. account registration (`REGISTER`/`VERIFY`).
- **Server-to-server linking**`UID`/`FJOIN` netburst, cross-server users and - **Server-to-server linking**`UID`/`FJOIN` netburst, cross-server users and
channels, multi-hop routing, nick-collision handling and clean netsplit. channels, multi-hop routing, TS-based nick-collision handling, and clean
- **Security & anti-spam** — TLS with cert fingerprints, keyed host cloaking, netsplit/rejoin.
DNSBL, connection/message flood limits, mixed-script & gibberish detection, - **Security & anti-spam** — TLS with client-cert fingerprints, keyed host
CAPTCHA / PONG-cookie / arithmetic gates, and DCC filtering. cloaking, DNSBL, per-IP connection/message flood limits, mixed-script & random
- **GeoIP** — a native MaxMind `.mmdb` reader with a `G:<cc>` geoban, `GEOIP` (drone) detection, CAPTCHA / PONG-cookie / arithmetic gates, and DCC filtering.
command, and WHOIS country line. - **Transports** — plaintext, TLS (OpenSSL or rustls backend), a native WebSocket
- **Transports & control** — a native WebSocket layer (`ws://` / `wss://`), a layer (`ws://` / `wss://`), and the PROXY protocol (v1/v2) behind a load balancer.
from-scratch forward-confirmed DNS resolver, and a token-authenticated JSON-RPC - **GeoIP** — a MaxMind `.mmdb` reader with a `G:<cc>` geoban, `GEOIP` command, and
control plane over HTTP. a WHOIS country line.
- **Control & observability** — a token-authenticated JSON-RPC plane over HTTP, and
an optional OpenMetrics/Prometheus endpoint.
## Quick start ## Quick start
@ -74,14 +71,17 @@ The full manual lives in [`docs/`](docs/):
- [Building & running](docs/building.md) · [Configuration](docs/configuration.md) · [Architecture](docs/architecture.md) - [Building & running](docs/building.md) · [Configuration](docs/configuration.md) · [Architecture](docs/architecture.md)
- [Channel & user modes](docs/modes.md) · [Operators](docs/operators.md) · [Server linking & services](docs/linking.md) - [Channel & user modes](docs/modes.md) · [Operators](docs/operators.md) · [Server linking & services](docs/linking.md)
- [IRCv3](docs/ircv3.md) · [Anti-abuse & flood protection](docs/anti-abuse.md) · [Deployment](docs/deployment.md) - [IRCv3](docs/ircv3.md) · [Anti-abuse & flood protection](docs/anti-abuse.md) · [Deployment](docs/deployment.md)
- [Module developer API](docs/api/) — write your own commands, modes, and modules.
## Configuration ## Configuration
Configuration is a plain `key = value` file; see Configuration is a plain `key = value` file; see
[`echoircd.conf.example`](echoircd.conf.example) for the full, documented set of [`echoircd.conf.example`](echoircd.conf.example) for the full, annotated set of
keys. Your live `echoircd.conf` is gitignored — it holds secrets (oper password, keys. Every operational limit is a config key with a built-in default, and most
cloak key, link password), so never commit it. Generate a TLS certificate into settings apply on `REHASH` without a restart. Your live `echoircd.conf` is
`tls/` with the one-liner in the example config. gitignored — it holds secrets (oper password, cloak key, link password), so never
commit it. Generate a TLS certificate into `tls/` with the one-liner in the example
config.
## Architecture ## Architecture
@ -92,13 +92,13 @@ anywhere. The I/O edge feeds it events over channels:
- **A pool of `mio` epoll reactors** drives client sockets — an acceptor - **A pool of `mio` epoll reactors** drives client sockets — an acceptor
round-robins each connection onto a worker (one per core by default), and each round-robins each connection onto a worker (one per core by default), and each
worker frames lines and runs **TLS handshakes and record crypto non-blocking** worker frames lines and runs **TLS handshakes and record crypto non-blocking**
in-thread. So the socket work and the crypto spread across cores while the state in-thread. Socket work and crypto spread across cores while the state core stays
core stays single-threaded and lock-free. (Proxied TLS and server links keep a single-threaded and lock-free. (Proxied TLS and server links keep a thread each;
thread each; there are few of them.) there are few of them.)
- **Resilience is built in.** Slow work (KDF hashing, DNS, disk snapshots) runs - **Resilience is built in.** Slow work (KDF hashing, DNS, disk snapshots) runs off
off the core so a flood can't freeze it; each event and each connection's I/O is the core so a flood can't freeze it; each event and each connection's I/O is
panic-isolated so one bad client can't crash the server; a watchdog flags a panic-isolated so one bad client can't crash the server; a watchdog flags a stuck
stuck core; and half-open/stalled connections are reaped on a timer. core; and half-open/stalled connections are reaped on a timer.
**Why a raw reactor and not async?** IRC is one large shared mutable graph, and **Why a raw reactor and not async?** IRC is one large shared mutable graph, and
almost every command mutates it and then broadcasts. With one thread owning all of almost every command mutates it and then broadcasts. With one thread owning all of
@ -106,9 +106,9 @@ it, handlers are plain synchronous code — no locks, no `.await`, no `Send + 's
bounds. A multi-threaded async runtime would force that shared state behind mutexes bounds. A multi-threaded async runtime would force that shared state behind mutexes
or an actor mailbox, and a channel broadcast is serialized anyway, so you'd pay for or an actor mailbox, and a channel broadcast is serialized anyway, so you'd pay for
parallelism the workload can't use. `mio` is the same readiness layer async runtimes parallelism the workload can't use. `mio` is the same readiness layer async runtimes
build on, so you keep the scaling without the runtime. What *does* parallelize — build on, so you keep the scaling without the runtime. What *does* parallelize — the
the socket syscalls and TLS crypto — runs in the reactor pool; scaling past one socket syscalls and TLS crypto — runs in the reactor pool; scaling past one machine
machine is done by linking servers, not threading one harder. is done by linking servers, not threading one harder.
Memory safety is structural: `Uid` handles instead of raw pointers, an `Extensible` Memory safety is structural: `Uid` handles instead of raw pointers, an `Extensible`
typemap instead of `void*` module data (freed on drop), and compiled-in trait typemap instead of `void*` module data (freed on drop), and compiled-in trait
@ -117,13 +117,13 @@ objects instead of a fragile plugin ABI. **Full design notes:**
## Extending ## Extending
Three small extension points, each one file + one table line — full reference and Three small extension points, each one file + one table line — full reference and a
a tutorial in [`docs/api/`](docs/api/): tutorial in [`docs/api/`](docs/api/):
- **Commands** (`src/command.rs`, `src/coremods/`) — a handler with `name`, - **Commands** (`src/command.rs`, `src/coremods/`) — a handler with `name`,
`min_params`, `before_reg`, `handle(&mut Server, uid, params)`. `min_params`, `before_reg`, `handle(&mut Server, uid, params)`.
- **Modes** (`src/mode.rs`) — channel/user modes as `ChanMode` / `UserMode` - **Modes** (`src/mode.rs`) — channel/user modes as `ChanMode` / `UserMode` handler
handler objects; adding one never touches the parser. objects; adding one never touches the parser.
- **Modules** (`src/module.rs`, `src/modules/`) — lifecycle hooks; pre-hooks can - **Modules** (`src/module.rs`, `src/modules/`) — lifecycle hooks; pre-hooks can
**Deny** a register/command/message, notify-hooks fire after. **Deny** a register/command/message, notify-hooks fire after.
@ -136,7 +136,4 @@ a tutorial in [`docs/api/`](docs/api/):
## License ## License
echoIRCd is released under the [MIT License](Cargo.toml). It is original Rust — echoIRCd is released under the [MIT License](Cargo.toml).
no code is copied or translated from any other project, enforced on every edit by
`scripts/native-rust-guard.sh` (no `unsafe`, no C/FFI, dependencies limited to
`openssl` + `mio`).

View file

@ -1,15 +1,14 @@
# echoIRCd documentation # echoIRCd documentation
echoIRCd is a from-scratch IRC + IRCv3 server written in safe Rust echoIRCd is a memory-safe IRC + IRCv3 server written in Rust. A single lock-free
(`#![forbid(unsafe_code)]`) with two dependencies — `openssl` for TLS and `mio` core thread owns all state; a pool of epoll reactor threads drives the connections
for the socket engine. A single lock-free core thread owns all state; a pool of around it — TLS crypto and all — with no async runtime.
reactor threads drives the connections around it.
This folder is the reference manual. Start with whichever fits what you're doing: This folder is the reference manual. Start with whichever fits what you're doing:
| Doc | What's in it | | Doc | What's in it |
|-----|--------------| |-----|--------------|
| [Building & running](building.md) | Prerequisites, debug/release builds, tests, the originality guard, project layout. | | [Building & running](building.md) | Prerequisites, debug/release builds, tests, project layout. |
| [Configuration](configuration.md) | The config file format and a grouped reference of every setting. | | [Configuration](configuration.md) | The config file format and a grouped reference of every setting. |
| [Architecture](architecture.md) | The single-threaded core + reactor-pool design, the I/O models, resilience, and memory safety. Read this to understand *why* it's built the way it is. | | [Architecture](architecture.md) | The single-threaded core + reactor-pool design, the I/O models, resilience, and memory safety. Read this to understand *why* it's built the way it is. |
| [Channel & user modes](modes.md) | Every prefix, list, parameter, and flag mode, plus extbans. | | [Channel & user modes](modes.md) | Every prefix, list, parameter, and flag mode, plus extbans. |
@ -23,7 +22,7 @@ This folder is the reference manual. Start with whichever fits what you're doing
## At a glance ## At a glance
- **Full IRC core** — registration, channels, messaging, and the informational - **Full IRC core** — registration, channels, messaging, and the informational
command set (~130 commands total). command set (100+ commands total).
- **Complete mode set** — the standard prefixes plus a staff prefix, list modes, - **Complete mode set** — the standard prefixes plus a staff prefix, list modes,
keyed/limit/flood/redirect/history/anticaps parameters, the full flag set, and keyed/limit/flood/redirect/history/anticaps parameters, the full flag set, and
matching + acting extbans. See [modes](modes.md). matching + acting extbans. See [modes](modes.md).
@ -33,10 +32,13 @@ This folder is the reference manual. Start with whichever fits what you're doing
- **Operators & services** — a rich oper toolbox with oper levels, X-lines - **Operators & services** — a rich oper toolbox with oper levels, X-lines
persisted to disk, and a services interface (SASL over links, the `SVS*` / persisted to disk, and a services interface (SASL over links, the `SVS*` /
`ENCAP` / `METADATA` set, account-gated modes). `ENCAP` / `METADATA` set, account-gated modes).
- **Transports** — plaintext, TLS (with client-cert fingerprints), a native - **Transports** — plaintext, TLS (OpenSSL or rustls backend, with client-cert
WebSocket layer, and the PROXY protocol behind a load balancer. fingerprints), a native WebSocket layer, and the PROXY protocol behind a load
balancer.
- **Security** — keyed host cloaking, DNSBL, GeoIP, layered connection/message - **Security** — keyed host cloaking, DNSBL, GeoIP, layered connection/message
flood limits, and script/gibberish spam detection. flood limits, and script/gibberish spam detection.
- **Control & observability** — a token-authenticated JSON-RPC plane over HTTP and
an optional OpenMetrics/Prometheus endpoint. See [configuration](configuration.md).
## Design in one paragraph ## Design in one paragraph

View file

@ -2,7 +2,7 @@
This is the reference for extending echoIRCd. The server has five extension This is the reference for extending echoIRCd. The server has five extension
points, all ordinary Rust trait objects compiled into the binary — there is no points, all ordinary Rust trait objects compiled into the binary — there is no
plugin ABI, no dynamic loading, and no `unsafe`: plugin ABI and no dynamic loading:
| You want to… | Implement | Registered in | Reference | | You want to… | Implement | Registered in | Reference |
|--------------|-----------|---------------|-----------| |--------------|-----------|---------------|-----------|
@ -41,9 +41,9 @@ Modules in this codebase follow a few hard rules — match them:
literal default. literal default.
4. **Register in the table**, don't touch the parser or the dispatcher. Adding a 4. **Register in the table**, don't touch the parser or the dispatcher. Adding a
command / mode / module is one new file plus one line in a registration table. command / mode / module is one new file plus one line in a registration table.
5. **Original Rust only.** No `unsafe`, no C/FFI, no new dependencies, and no code 5. **Stay self-contained.** A module shouldn't pull in a heavy new dependency —
copied or translated from another project. `scripts/native-rust-guard.sh` the primitives you'll reach for (an HTTP client, a regex engine, base64, the
enforces this on every edit. hashing/KDF helpers) already live in the tree; reuse them.
## Write your first module in five steps ## Write your first module in five steps
@ -92,11 +92,10 @@ pub fn default_modules() -> Vec<Box<dyn Module>> {
**4. (Only if it adds a command)** expose a `commands()` function from your module **4. (Only if it adds a command)** expose a `commands()` function from your module
and chain it into `module_commands()` — see [commands](commands.md). and chain it into `module_commands()` — see [commands](commands.md).
**5. Build, test, and check:** **5. Build and test:**
```sh ```sh
cargo build && cargo test cargo build && cargo test
bash scripts/native-rust-guard.sh src/modules/hello.rs
``` ```
That's it — `hello` is now a first-class part of the server. That's it — `hello` is now a first-class part of the server.

View file

@ -91,7 +91,7 @@ cares which one a connection uses:
| Transport | Model | Notes | | Transport | Model | Notes |
|-----------|-------|-------| |-----------|-------|-------|
| **Plaintext clients** | reactor pool | The common case; one worker frames many sockets. | | **Plaintext clients** | reactor pool | The common case; one worker frames many sockets. |
| **Direct TLS clients** | reactor pool | The handshake and record crypto run **non-blocking inside the worker** (OpenSSL driven off a `mio` socket). TLS work spreads across cores like everything else. | | **Direct TLS clients** | reactor pool | The handshake and record crypto run **non-blocking inside the worker**, driven off a `mio` socket by the configured TLS backend (OpenSSL by default, or rustls via `tls_backend = rustls`). TLS work spreads across cores like everything else. |
| **Proxied TLS** (a PROXY header before the handshake) | thread per connection | Reading the pre-handshake header wants the simpler blocking path; there are few of these. | | **Proxied TLS** (a PROXY header before the handshake) | thread per connection | Reading the pre-handshake header wants the simpler blocking path; there are few of these. |
| **Server links** | thread per connection | A handful of long-lived peers; not worth multiplexing. | | **Server links** | thread per connection | A handful of long-lived peers; not worth multiplexing. |
@ -131,11 +131,12 @@ kernel-level filtering.
## Memory safety ## Memory safety
Safety is structural, not just `#![forbid(unsafe_code)]`: Safety is structural, not just a matter of avoiding raw pointers:
- **Handles, not pointers.** Users and channels are referenced by `Uid` / - **Handles, not pointers.** Users and channels are referenced by `Uid` /
channel-key handles looked up in maps, so there are no dangling references and channel-key handles looked up in maps, so there are no dangling references and
no use-after-free. no use-after-free — a `Uid` is monotonic and never reused, so a stale handle
resolves to nothing rather than to the wrong user.
- **A typemap, not `void*`.** Modules attach per-user / per-channel / per-server - **A typemap, not `void*`.** Modules attach per-user / per-channel / per-server
state through an `Extensible` typemap keyed by Rust type; it's dropped state through an `Extensible` typemap keyed by Rust type; it's dropped
automatically with its owner, so module state can't leak or be freed twice. automatically with its owner, so module state can't leak or be freed twice.
@ -143,11 +144,6 @@ Safety is structural, not just `#![forbid(unsafe_code)]`:
compiled-in trait objects. There is no dynamic-loading FFI boundary to get compiled-in trait objects. There is no dynamic-loading FFI boundary to get
wrong. wrong.
The two dependencies (`openssl`, `mio`) keep their own `unsafe` internal, so the
daemon itself never writes any. The `scripts/native-rust-guard.sh` check enforces
all of this on every edit: no `unsafe`, no C/FFI, dependencies limited to those
two, and no code copied or translated from another project.
## Tuning knobs ## Tuning knobs
| Setting | Effect | | Setting | Effect |

View file

@ -3,10 +3,10 @@
## Prerequisites ## Prerequisites
- A stable **Rust** toolchain (`cargo`, `rustc`). - A stable **Rust** toolchain (`cargo`, `rustc`).
- **OpenSSL** development headers (the `openssl` crate links against the system - **OpenSSL** development headers for the default TLS backend (the `openssl` crate
library) — e.g. `libssl-dev` on Debian/Ubuntu. links against the system library) — e.g. `libssl-dev` on Debian/Ubuntu. The
optional pure-Rust rustls backend (`tls_backend = rustls`) needs no system
That's it. There are exactly two dependencies: `openssl` and `mio`. library.
## Build ## Build
@ -52,20 +52,8 @@ cargo test --test integration # just the end-to-end suite
The integration suite spawns the real binary on ephemeral ports and drives it as The integration suite spawns the real binary on ephemeral ports and drives it as
a client — covering reactor-pool cross-worker delivery, TLS-in-reactor handshakes, a client — covering reactor-pool cross-worker delivery, TLS-in-reactor handshakes,
the stalled-handshake reap, the accept-rate limiter, and nick collisions. It the stalled-handshake reap, the accept-rate limiter, and nick collisions. It
tracks and kills its child processes by PID, never by name. tracks and kills its child processes by PID, never by name. The parser and the S2S
convergence logic also carry property-based (`proptest`) suites.
## The originality guard
Every source edit is checked by `scripts/native-rust-guard.sh`, which enforces the
project's invariants:
- no `unsafe` (the crate is `#![forbid(unsafe_code)]`),
- no C / FFI,
- dependencies limited to `openssl` + `mio`,
- and no code copied or translated from any other project — everything is
original Rust.
Run it on a file directly with `bash scripts/native-rust-guard.sh <file>`.
## Project layout ## Project layout
@ -82,7 +70,7 @@ src/
command.rs the Command trait command.rs the Command trait
module.rs the module lifecycle-hook trait module.rs the module lifecycle-hook trait
coremods/ built-in commands (registration, channels, messaging, oper, …) coremods/ built-in commands (registration, channels, messaging, oper, …)
modules/ optional, pluggable modules (~70 of them) modules/ optional, pluggable modules (70+ of them)
tests/ end-to-end integration tests tests/ end-to-end integration tests
deploy/ production systemd units + firewall script deploy/ production systemd units + firewall script
docs/ this manual docs/ this manual

View file

@ -29,6 +29,8 @@ Most settings apply on `REHASH` without a restart.
| `bind` | Plaintext client listener, `ip:port` (e.g. `0.0.0.0:6667`). | | `bind` | Plaintext client listener, `ip:port` (e.g. `0.0.0.0:6667`). |
| `bind_tls` | TLS client listener (e.g. `0.0.0.0:6697`). | | `bind_tls` | TLS client listener (e.g. `0.0.0.0:6697`). |
| `tls_cert` / `tls_key` | PEM certificate + private key for TLS (and `wss://`). | | `tls_cert` / `tls_key` | PEM certificate + private key for TLS (and `wss://`). |
| `tls_sni` | Serve a different cert for a given hostname (`<host> <cert> <key>`, repeatable). |
| `tls_backend` | `openssl` (default) or `rustls` (pure-Rust, no system OpenSSL). |
| `bind_server` | Server-to-server link listener (see [linking](linking.md)). | | `bind_server` | Server-to-server link listener (see [linking](linking.md)). |
Generate a self-signed cert to start: Generate a self-signed cert to start:
@ -148,6 +150,14 @@ See [anti-abuse](anti-abuse.md) for how these layer together.
| `log_json` | Append the notice/log stream to a file as JSON lines. | | `log_json` | Append the notice/log stream to a file as JSON lines. |
| `chanlog` | Mirror the oper server-notice stream into a channel. | | `chanlog` | Mirror the oper server-notice stream into a channel. |
## Control & observability
| Key | Meaning |
|-----|---------|
| `metrics_bind` | Bind an OpenMetrics/Prometheus scrape endpoint (`ip:port`, plaintext HTTP GET). Off unless set — expose it privately or behind a proxy. |
| `rpc` + `rpc_bind` | Enable the JSON-RPC control plane and bind its HTTP listener. Both required to turn it on. |
| `rpc_user` / `rpc_token` | Credentials for the control plane — sent as HTTP Basic (`user:token`) or Bearer. Bind privately; the token is a shared secret. |
## Transports ## Transports
| Key | Meaning | | Key | Meaning |

View file

@ -96,7 +96,7 @@ A connection from a trusted proxy must lead with a PROXY (v1 or v2) header. This
applies to the plaintext and TLS client listeners; for browser clients over applies to the plaintext and TLS client listeners; for browser clients over
WebSocket, use `ws_proxyranges` with `X-Forwarded-For` instead. WebSocket, use `ws_proxyranges` with `X-Forwarded-For` instead.
## Log shipping ## Log shipping & metrics
| Setting | Sends the notice/log stream to | | Setting | Sends the notice/log stream to |
|---------|--------------------------------| |---------|--------------------------------|
@ -107,6 +107,13 @@ WebSocket, use `ws_proxyranges` with `X-Forwarded-For` instead.
The core also surfaces its own health: `slow_command_ms` raises a notice when an The core also surfaces its own health: `slow_command_ms` raises a notice when an
event runs long, and `watchdog_ms` logs if the core is stuck. event runs long, and `watchdog_ms` logs if the core is stuck.
For a metrics pipeline, bind the OpenMetrics/Prometheus endpoint with
`metrics_bind = 127.0.0.1:9109` and scrape it (counters for commands / messages /
connects, gauges for users / channels / servers / links). For scripted control,
the JSON-RPC plane (`rpc` / `rpc_bind` / `rpc_token`) exposes admin operations over
HTTP. Bind both privately — on loopback or behind the reverse proxy, never on a
public interface.
## TLS certificates ## TLS certificates
Point `tls_cert` / `tls_key` at your PEM files (the same pair serves `bind_tls` Point `tls_cert` / `tls_key` at your PEM files (the same pair serves `bind_tls`

View file

@ -29,6 +29,9 @@ informed as they change. This page groups what's supported.
| `multi-prefix` | See all of a member's status prefixes at once. | | `multi-prefix` | See all of a member's status prefixes at once. |
| `userhost-in-names` | Full `nick!user@host` in `NAMES`. | | `userhost-in-names` | Full `nick!user@host` in `NAMES`. |
| `invite-notify` | Channel ops see invites to their channel. | | `invite-notify` | Channel ops see invites to their channel. |
| `draft/pre-away` | Send `AWAY` during registration so away state is set before the first `JOIN`. |
| `no-implicit-names` | Suppress the automatic `NAMES` reply on `JOIN` (the client asks when it wants it). |
| `draft/channel-rename` | `RENAME` a channel in place, keeping membership. |
## Authentication ## Authentication
@ -60,6 +63,7 @@ control who may message them.
| `draft/metadata-2` | `METADATA` key/value data on users and channels. | | `draft/metadata-2` | `METADATA` key/value data on users and channels. |
| `draft/extended-isupport` | Re-request the current ISUPPORT tokens on demand. | | `draft/extended-isupport` | Re-request the current ISUPPORT tokens on demand. |
| `draft/json-log` | Stream the server log to an oper as JSON. | | `draft/json-log` | Stream the server log to an oper as JSON. |
| `sts` | Strict Transport Security — tell a client to upgrade to TLS and pin that for a duration (opt-in via `sts_duration` / `sts_port` / `sts_preload`). |
| `EXTJWT` | A short-lived, server-signed HS256 token a client can present elsewhere. | | `EXTJWT` | A short-lived, server-signed HS256 token a client can present elsewhere. |
| network icon / profile link | Advertise a network icon and per-account profile URLs. | | network icon / profile link | Advertise a network icon and per-account profile URLs. |

View file

@ -22,8 +22,16 @@ tls_key = ./tls/key.pem
# The cert is re-read on REHASH, so a renewed cert applies without a restart. # The cert is re-read on REHASH, so a renewed cert applies without a restart.
# SNI: serve a different cert for a given hostname. Repeatable. # SNI: serve a different cert for a given hostname. Repeatable.
# tls_sni = irc.example.net ./tls/example.crt ./tls/example.key # tls_sni = irc.example.net ./tls/example.crt ./tls/example.key
# TLS backend: openssl (default) or rustls (pure-Rust, no system OpenSSL needed).
# tls_backend = rustls
# STS (Strict Transport Security): tell CAP 302 clients on the plaintext port to
# upgrade to TLS and pin it. Off unless sts_duration > 0. Set sts_port to your TLS
# port. Only enable once TLS is solid — clients will refuse plaintext for the pin.
# 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
motd = Welcome to echoIRCd — a from-scratch IRC daemon in Rust. motd = Welcome to echoIRCd.
motd = Edit the MOTD in your echoircd.conf. motd = Edit the MOTD in your echoircd.conf.
# --- server-to-server linking --- # --- server-to-server linking ---
@ -192,6 +200,18 @@ amu_target = both
# --- log_json: append the server-notice / log stream to a file as JSONL --- # --- log_json: append the server-notice / log stream to a file as JSONL ---
# log_json = /var/log/echoircd/events.jsonl # log_json = /var/log/echoircd/events.jsonl
# --- metrics: OpenMetrics/Prometheus scrape endpoint (plaintext HTTP GET). Off
# unless bound; expose it only on a private/loopback address or behind a proxy. ---
# metrics_bind = 127.0.0.1:9109
# --- rpc: token-authenticated JSON-RPC control plane over HTTP (admin tooling).
# Off unless rpc = yes AND rpc_bind is set. Bind privately; the token is a
# shared secret sent as HTTP Basic (rpc_user:rpc_token) or Bearer. ---
# rpc = yes
# rpc_bind = 127.0.0.1:8080
# rpc_user = admin
# rpc_token = CHANGE_THIS_RPC_TOKEN
# --- PROXY protocol: trust the HAProxy/nginx PROXY header (v1 or v2) from these # --- PROXY protocol: trust the HAProxy/nginx PROXY header (v1 or v2) from these
# sources (glob or CIDR, repeatable), so the real client IP is used instead of # sources (glob or CIDR, repeatable), so the real client IP is used instead of
# the proxy's. A connection from a trusted proxy MUST lead with a PROXY header. # the proxy's. A connection from a trusted proxy MUST lead with a PROXY header.