modules: [modules] config + example service crate + SDK guide

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.
This commit is contained in:
Jean Chevronnet 2026-07-13 01:48:09 +00:00
parent 596630df53
commit e0bb3a1fea
No known key found for this signature in database
9 changed files with 254 additions and 7 deletions

View file

@ -16,6 +16,28 @@ pub struct Config {
// directory. Absent = the RPC server does not start.
#[serde(default)]
pub grpc: Option<Grpc>,
// Which service modules to start. Absent = the built-in NickServ + ChanServ.
#[serde(default)]
pub modules: Modules,
}
// The service modules to bring up at burst. Each name maps to a compiled-in
// module crate the daemon knows how to construct (see main.rs). Names not built
// in are ignored.
#[derive(Debug, Deserialize)]
pub struct Modules {
#[serde(default = "default_services")]
pub services: Vec<String>,
}
impl Default for Modules {
fn default() -> Self {
Modules { services: default_services() }
}
}
fn default_services() -> Vec<String> {
vec!["nickserv".to_string(), "chanserv".to_string()]
}
#[derive(Debug, Deserialize, Clone)]

View file

@ -15,6 +15,7 @@ use tokio::sync::Mutex;
use engine::Engine;
use fedserv_chanserv::ChanServ;
use fedserv_example::ExampleServ;
use fedserv_inspircd::InspIrcd;
use fedserv_nickserv::NickServ;
@ -39,16 +40,28 @@ async fn main() -> Result<()> {
ts,
));
let services: Vec<Box<dyn engine::service::Service>> = vec![
Box::new(NickServ {
// Bring up the service modules named in [modules] (default NickServ +
// ChanServ). Each keeps a fixed uid suffix so its identity is stable no
// matter which others are enabled.
let enabled = |name: &str| cfg.modules.services.iter().any(|s| s == name);
let mut services: Vec<Box<dyn engine::service::Service>> = Vec::new();
if enabled("nickserv") {
services.push(Box::new(NickServ {
uid: format!("{}AAAAAA", cfg.server.sid),
guest_nick: cfg.server.guest_nick.clone(),
guest_seq: (ts % 100_000) as u32,
}),
Box::new(ChanServ {
}));
}
if enabled("chanserv") {
services.push(Box::new(ChanServ {
uid: format!("{}AAAAAB", cfg.server.sid),
}),
];
}));
}
if enabled("example") {
services.push(Box::new(ExampleServ {
uid: format!("{}AAAAAC", cfg.server.sid),
}));
}
let (gossip_tx, _) = tokio::sync::broadcast::channel::<engine::db::LogEntry>(1024);
let mut db = engine::db::Db::open("fedserv.db.jsonl", &cfg.server.sid);
db.scram_iterations = cfg.server.scram_iterations;