echo/README.md
Jean a803177f8b
Nest the ircd link under modules/protocol/
The protocol link is a different kind of module from the service
pseudo-clients, so give it its own home: modules/inspircd ->
modules/protocol/inspircd. A future ircd link is a new crate alongside
it. Workspace member, dependency path, the crate's own api path, and the
docs are updated to match. No code change.
2026-07-14 14:28:18 +00:00

5.1 KiB

fedserv

A federated IRC services daemon in Rust. Protocol-agnostic core, InspIRCd link first.

Clean-room, inspired by the ideas behind modern event-log/gossip services, not derived from any project's source.

Design

The core lives in src/. The pluggable parts are separate crates in a Cargo workspace, each depending only on the fedserv-api SDK crate.

  • api/ (fedserv-api) the module SDK: the Service, Protocol, Store and NetView traits a module implements or is handed, plus the normalized NetEvent/NetAction vocabulary and the read views. It has no storage or runtime dependencies, so a third-party module builds against it alone.
  • src/engine/ the services engine: live network state, the account store, and the log. The store is event-sourced: every change is an Event appended to a per-node log, and state is a fold over that log. A service touches it only through the Store/NetView traits, so the log, gossip and credential material stay out of a module's reach.
  • src/gossip.rs node-to-node replication over the logs.
  • modules/ the loadable modules, each its own crate depending only on the fedserv-api SDK:
    • modules/protocol/inspircd/ (fedserv-inspircd) the ircd link layer. A Protocol impl maps raw server-to-server lines to and from the normalized model, so the engine never touches a raw line and a new ircd is one new crate under modules/protocol/.
    • modules/nickserv/, modules/chanserv/, modules/botserv/, modules/memoserv/, modules/statserv/, modules/hostserv/, modules/operserv/ and diceserv, infoserv, reportserv, groupserv, chanfix, helpserv — the pseudo-clients, each a crate implementing Service. OperServ holds the network-operator tools (AKILL network bans to start); the rest follow the same shape.

Writing your own module — a service or a new ircd protocol — is documented in MODULES.md; modules/example/ is a minimal service to copy from.

Replication

Each log entry carries an origin, a per-origin seq, and a Lamport clock. Peers connect, exchange version vectors, and send each other the entries the other lacks; a freshly committed entry is also pushed immediately, so a registration propagates in milliseconds. Ingest is idempotent, so re-delivery and reconnect after a split both converge. The log is compacted to one entry per account as it grows. An account registered on any node works on all of them.

Directory API (gRPC)

src/grpc.rs exposes the account/channel directory over gRPC (see proto/fedserv.proto) so a website can mirror it without touching IRC: a one-shot Snapshot for the initial load, then Subscribe for a live stream of every change from that point on. It subscribes to the same committed-entry channel gossip does, so a change reaches a subscriber in the same push, no polling. Deliberately excludes anything credential-shaped (password hashes, SCRAM verifiers, cert fingerprints) and the finer channel-ops-list events (access/akick/mlock/entrymsg) — identity and metadata only. Bearer-token authenticated, optional server TLS. Omit [grpc] in config.toml to run without it.

The same port also serves Accounts, the write side: Register, Authenticate, SetPassword, SetEmail, Confirm, Drop, ForceLogout, GroupNick, UngroupNick — a trusted caller (e.g. a website's own backend, already having done its own login/session check) managing accounts the same way an IRC user does through NickServ, minus the command syntax. The bearer token is the authorization: unlike the NickServ commands these mirror, most calls do not re-check the account's own password (an admin-level override, the same trust model a JSON-RPC integration to another services package would use) — Register and Authenticate are the two exceptions, since the password is the actual input there. A write committed this way replicates to every other fedserv node exactly like an IRC-originated one does — gossip doesn't know or care where it came from.

Config

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

[server]
name = "services.example"
sid = "42S"

[gossip]                 # omit to run a single, non-replicating node
bind = "0.0.0.0:16700"
secret = "shared-secret"

[gossip.tls]             # omit for a plaintext link
cert = "certs/nodeA.crt"
key  = "certs/nodeA.key"
ca   = "certs/ca.crt"    # a peer must present a certificate signed by this CA

[[peer]]
addr = "nodeB.example:16700"
name = "nodeB"           # TLS name to expect; must match the peer certificate

Generate certificates with scripts/gen-certs.sh.

Run

cp config.example.toml config.toml   # edit uplink + link password
cargo run -- config.toml

Status

Links to an InspIRCd uplink and runs NickServ (REGISTER, IDENTIFY, LOGOUT, CERT, and SASL PLAIN / SCRAM-SHA-256/512 / EXTERNAL) and ChanServ over an event-sourced account store replicated between nodes by gossip, with an optional mutually authenticated TLS peer link, plus a gRPC directory API for websites to mirror the account/channel directory (bearer-token authenticated, optional TLS).