A [modules] block selects which services start (default: NickServ + ChanServ); main.rs constructs each compiled-in module only when named, so a node can run a subset and optional modules ship dormant. Add fedserv-example, a minimal read-only Service that depends on fedserv-api alone -- the template a new pseudo-client is copied from, enabled by adding "example" to the config. MODULES.md documents writing and registering a service or protocol module. |
||
|---|---|---|
| api | ||
| chanserv | ||
| example | ||
| inspircd | ||
| nickserv | ||
| proto | ||
| scripts | ||
| src | ||
| testing | ||
| .gitignore | ||
| build.rs | ||
| Cargo.lock | ||
| Cargo.toml | ||
| config.example.toml | ||
| MODULES.md | ||
| README.md | ||
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: theService,Protocol,StoreandNetViewtraits a module implements or is handed, plus the normalizedNetEvent/NetActionvocabulary 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 networkstate, the account store, and the log. The store is event-sourced: every change is anEventappended to a per-node log, and state is a fold over that log. A service touches it only through theStore/NetViewtraits, so the log, gossip and credential material stay out of a module's reach.src/gossip.rsnode-to-node replication over the logs.inspircd/(fedserv-inspircd) the ircd link layer. AProtocolimpl 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.nickserv/,chanserv/(fedserv-nickserv,fedserv-chanserv) the pseudo-clients, each a crate implementingService. OperServ and others follow the same shape.
Writing your own module — a service or a new ircd protocol — is documented in
MODULES.md; 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).