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

View file

@ -1,15 +1,14 @@
# echoIRCd documentation
echoIRCd is a from-scratch IRC + IRCv3 server written in safe Rust
(`#![forbid(unsafe_code)]`) with two dependencies — `openssl` for TLS and `mio`
for the socket engine. A single lock-free core thread owns all state; a pool of
reactor threads drives the connections around it.
echoIRCd is a memory-safe IRC + IRCv3 server written in Rust. A single lock-free
core thread owns all state; a pool of epoll reactor threads drives the connections
around it — TLS crypto and all — with no async runtime.
This folder is the reference manual. Start with whichever fits what you're doing:
| 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. |
| [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. |
@ -23,7 +22,7 @@ This folder is the reference manual. Start with whichever fits what you're doing
## At a glance
- **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,
keyed/limit/flood/redirect/history/anticaps parameters, the full flag set, and
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
persisted to disk, and a services interface (SASL over links, the `SVS*` /
`ENCAP` / `METADATA` set, account-gated modes).
- **Transports** — plaintext, TLS (with client-cert fingerprints), a native
WebSocket layer, and the PROXY protocol behind a load balancer.
- **Transports** — plaintext, TLS (OpenSSL or rustls backend, with client-cert
fingerprints), a native WebSocket layer, and the PROXY protocol behind a load
balancer.
- **Security** — keyed host cloaking, DNSBL, GeoIP, layered connection/message
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

View file

@ -2,7 +2,7 @@
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
plugin ABI, no dynamic loading, and no `unsafe`:
plugin ABI and no dynamic loading:
| You want to… | Implement | Registered in | Reference |
|--------------|-----------|---------------|-----------|
@ -41,9 +41,9 @@ Modules in this codebase follow a few hard rules — match them:
literal default.
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.
5. **Original Rust only.** No `unsafe`, no C/FFI, no new dependencies, and no code
copied or translated from another project. `scripts/native-rust-guard.sh`
enforces this on every edit.
5. **Stay self-contained.** A module shouldn't pull in a heavy new dependency —
the primitives you'll reach for (an HTTP client, a regex engine, base64, the
hashing/KDF helpers) already live in the tree; reuse them.
## 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
and chain it into `module_commands()` — see [commands](commands.md).
**5. Build, test, and check:**
**5. Build and test:**
```sh
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.

View file

@ -91,7 +91,7 @@ cares which one a connection uses:
| Transport | Model | Notes |
|-----------|-------|-------|
| **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. |
| **Server links** | thread per connection | A handful of long-lived peers; not worth multiplexing. |
@ -131,11 +131,12 @@ kernel-level filtering.
## 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` /
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
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.
@ -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
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
| Setting | Effect |

View file

@ -3,10 +3,10 @@
## Prerequisites
- A stable **Rust** toolchain (`cargo`, `rustc`).
- **OpenSSL** development headers (the `openssl` crate links against the system
library) — e.g. `libssl-dev` on Debian/Ubuntu.
That's it. There are exactly two dependencies: `openssl` and `mio`.
- **OpenSSL** development headers for the default TLS backend (the `openssl` crate
links against the system library) — e.g. `libssl-dev` on Debian/Ubuntu. The
optional pure-Rust rustls backend (`tls_backend = rustls`) needs no system
library.
## 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
a client — covering reactor-pool cross-worker delivery, TLS-in-reactor handshakes,
the stalled-handshake reap, the accept-rate limiter, and nick collisions. It
tracks and kills its child processes by PID, never by name.
## 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>`.
tracks and kills its child processes by PID, never by name. The parser and the S2S
convergence logic also carry property-based (`proptest`) suites.
## Project layout
@ -82,7 +70,7 @@ src/
command.rs the Command trait
module.rs the module lifecycle-hook trait
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
deploy/ production systemd units + firewall script
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_tls` | TLS client listener (e.g. `0.0.0.0:6697`). |
| `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)). |
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. |
| `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
| 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
WebSocket, use `ws_proxyranges` with `X-Forwarded-For` instead.
## Log shipping
## Log shipping & metrics
| 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
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
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. |
| `userhost-in-names` | Full `nick!user@host` in `NAMES`. |
| `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
@ -60,6 +63,7 @@ control who may message them.
| `draft/metadata-2` | `METADATA` key/value data on users and channels. |
| `draft/extended-isupport` | Re-request the current ISUPPORT tokens on demand. |
| `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. |
| network icon / profile link | Advertise a network icon and per-account profile URLs. |