IRC services for InspIRCd networks, written in Rust. NickServ, ChanServ, and a dozen more, backed by an append-only event log instead of a database. https://echo.dev.tronic.pro
Find a file
Jean ad61addb84
BotServ: community !votekick / !voteban
SET <#channel> VOTEKICK <n> enables in-channel voting: members type
!votekick <nick> (or !voteban), each voter counts once, and when n votes
are reached within the window the assigned bot kicks (or bans then kicks)
the target. Votes are ephemeral and lapse after two minutes. Crowd
moderation with no op online.
2026-07-13 19:32:25 +00:00
api BotServ: community !votekick / !voteban 2026-07-13 19:32:25 +00:00
botserv BotServ: community !votekick / !voteban 2026-07-13 19:32:25 +00:00
chanserv Wire NickServ + ChanServ into the shared stats registry 2026-07-13 19:15:40 +00:00
example modules: [modules] config + example service crate + SDK guide 2026-07-13 01:48:09 +00:00
inspircd BotServ: DONTKICKVOICES, plus neutral code comments 2026-07-13 18:19:37 +00:00
memoserv BotServ: DONTKICKVOICES, plus neutral code comments 2026-07-13 18:19:37 +00:00
nickserv Wire NickServ + ChanServ into the shared stats registry 2026-07-13 19:15:40 +00:00
proto Shared stats registry + gRPC Stats API 2026-07-13 19:13:31 +00:00
scripts Add mutual-TLS to the gossip link 2026-07-12 07:31:16 +00:00
src BotServ: community !votekick / !voteban 2026-07-13 19:32:25 +00:00
testing Add SASL SCRAM-SHA-256 and SCRAM-SHA-512 2026-07-12 03:59:24 +00:00
.gitignore chanserv: op, deop, voice, devoice, kick, ban, unban 2026-07-12 11:21:35 +00:00
build.rs grpc: directory replication API for websites to mirror accounts/channels 2026-07-12 23:10:21 +00:00
Cargo.lock BotServ BADWORDS: user-configurable regex kicker 2026-07-13 16:16:44 +00:00
Cargo.toml BotServ BADWORDS: user-configurable regex kicker 2026-07-13 16:16:44 +00:00
config.example.toml engine: services-operator privilege foundation 2026-07-13 03:42:59 +00:00
MODULES.md modules: [modules] config + example service crate + SDK guide 2026-07-13 01:48:09 +00:00
README.md modules: [modules] config + example service crate + SDK guide 2026-07-13 01:48:09 +00:00

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.
  • 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.
  • nickserv/, chanserv/ (fedserv-nickserv, fedserv-chanserv) the pseudo-clients, each a crate implementing Service. 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).