Rename the project to Echo (was fedserv)
Rebrand the daemon and workspace: the binary is now `echo`, every crate is `echo-*` (`echo-api` and the module crates), Rust imports use `echo_*`, the gRPC proto is `proto/echo.proto` with package `echo.v1`, and the log filter, gossip peer-name default, and on-disk store default (`echo.db.jsonl`) follow. Docs updated throughout. No behaviour change; crate directories and the config schema are untouched.
This commit is contained in:
parent
e7037572e5
commit
993f5b2eea
159 changed files with 660 additions and 660 deletions
|
|
@ -36,7 +36,7 @@ pub struct Config {
|
|||
// Per-IP session limiting. Absent = unlimited.
|
||||
#[serde(default)]
|
||||
pub session: Option<Session>,
|
||||
// Account authority. Absent = built-in (fedserv owns accounts). With
|
||||
// Account authority. Absent = built-in (echo owns accounts). With
|
||||
// `external = true`, an outside authority (e.g. the website) owns identity
|
||||
// and pushes accounts in; IRC can only authenticate.
|
||||
#[serde(default)]
|
||||
|
|
@ -109,10 +109,10 @@ pub struct Oper {
|
|||
|
||||
impl Config {
|
||||
// The account -> privileges table (casefolded keys) built from [[oper]].
|
||||
pub fn opers(&self) -> std::collections::HashMap<String, fedserv_api::Privs> {
|
||||
pub fn opers(&self) -> std::collections::HashMap<String, echo_api::Privs> {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
for o in &self.oper {
|
||||
map.insert(o.account.to_ascii_lowercase(), fedserv_api::Privs::from_names(&o.privs));
|
||||
map.insert(o.account.to_ascii_lowercase(), echo_api::Privs::from_names(&o.privs));
|
||||
}
|
||||
map
|
||||
}
|
||||
|
|
@ -234,7 +234,7 @@ pub struct Peer {
|
|||
}
|
||||
|
||||
fn default_peer_name() -> String {
|
||||
"fedserv".to_string()
|
||||
"echo".to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ pub use event::Event;
|
|||
pub(crate) use event::{apply, Scope};
|
||||
|
||||
// Error kinds, the emailed-code purpose, and the module-facing views live in the
|
||||
// fedserv-api SDK crate; re-exported so the engine keeps naming them locally and
|
||||
// echo-api SDK crate; re-exported so the engine keeps naming them locally and
|
||||
// modules importing `crate::engine::db::{ChanError, ...}` are unaffected.
|
||||
pub use fedserv_api::{
|
||||
pub use echo_api::{
|
||||
AccountView, AjoinView, AkillView, BotView, Caps, GroupView, HelpView, IgnoreView, MemoView, NewsView, Privs, ReportView, SuspensionView, ChanAccessView, ChanAkickView, ChanError, ChanSetting, ChannelView, CertError, CodeKind, Kicker, RegError, Store, TriggerView, VhostView,
|
||||
};
|
||||
|
||||
|
|
@ -538,7 +538,7 @@ impl ChannelInfo {
|
|||
self.access
|
||||
.iter()
|
||||
.find(|a| a.account.eq_ignore_ascii_case(account))
|
||||
.and_then(|a| fedserv_api::level_caps(&a.level).auto)
|
||||
.and_then(|a| echo_api::level_caps(&a.level).auto)
|
||||
}
|
||||
|
||||
/// The matching auto-kick entry for `hostmask` (nick!user@host), if any.
|
||||
|
|
|
|||
|
|
@ -376,7 +376,7 @@ impl Db {
|
|||
self.net.groups.iter().find(|g| key(&g.name) == k).map(|g| GroupView {
|
||||
name: g.name.clone(),
|
||||
founder: g.founder.clone(),
|
||||
members: g.members.iter().map(|m| fedserv_api::GroupMemberView { account: m.account.clone(), flags: m.flags.clone() }).collect(),
|
||||
members: g.members.iter().map(|m| echo_api::GroupMemberView { account: m.account.clone(), flags: m.flags.clone() }).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -409,7 +409,7 @@ impl Db {
|
|||
pub fn channel_caps(&self, channel: &str, account: &str) -> Caps {
|
||||
let Some(c) = self.channels.get(&key(channel)) else { return Caps::default() };
|
||||
if c.founder.eq_ignore_ascii_case(account) {
|
||||
return fedserv_api::level_caps("founder");
|
||||
return echo_api::level_caps("founder");
|
||||
}
|
||||
let mut caps = Caps::default();
|
||||
for a in &c.access {
|
||||
|
|
@ -418,7 +418,7 @@ impl Db {
|
|||
None => a.account.eq_ignore_ascii_case(account),
|
||||
};
|
||||
if applies {
|
||||
caps = caps.union(fedserv_api::level_caps(&a.level));
|
||||
caps = caps.union(echo_api::level_caps(&a.level));
|
||||
}
|
||||
}
|
||||
caps
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use super::*;
|
||||
|
||||
fn tmp(name: &str) -> PathBuf {
|
||||
let p = std::env::temp_dir().join(format!("fedserv-log-{name}.jsonl"));
|
||||
let p = std::env::temp_dir().join(format!("echo-log-{name}.jsonl"));
|
||||
let _ = std::fs::remove_file(&p);
|
||||
p
|
||||
}
|
||||
|
|
@ -12,8 +12,8 @@
|
|||
|
||||
#[test]
|
||||
fn formats_unix_time_as_utc() {
|
||||
assert_eq!(fedserv_api::human_time(0), "1970-01-01 00:00:00 UTC");
|
||||
assert_eq!(fedserv_api::human_time(1783844590), "2026-07-12 08:23:10 UTC");
|
||||
assert_eq!(echo_api::human_time(0), "1970-01-01 00:00:00 UTC");
|
||||
assert_eq!(echo_api::human_time(1783844590), "2026-07-12 08:23:10 UTC");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ use tokio::sync::mpsc;
|
|||
use crate::proto::{NetAction, NetEvent, RegReply};
|
||||
use db::{Db, LogEntry, RegError};
|
||||
use scram::Verifier;
|
||||
use fedserv_api::Privs;
|
||||
use echo_api::Privs;
|
||||
use service::{Sender, Service, ServiceCtx};
|
||||
use state::Network;
|
||||
|
||||
|
|
@ -468,14 +468,14 @@ impl Engine {
|
|||
if let Some(lead) = self.expire_warn.filter(|_| self.db.email_enabled()) {
|
||||
if let Some(ttl) = self.account_ttl {
|
||||
for (account, email, left) in self.db.accounts_to_warn(now, ttl, lead) {
|
||||
let mail = fedserv_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), "account", &account, &human_duration(left));
|
||||
let mail = echo_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), "account", &account, &human_duration(left));
|
||||
self.emit_irc(NetAction::SendEmail { to: email, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||
self.db.mark_account_warned(&account);
|
||||
}
|
||||
}
|
||||
if let Some(ttl) = self.channel_ttl {
|
||||
for (channel, email, left) in self.db.channels_to_warn(now, ttl, lead) {
|
||||
let mail = fedserv_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), "channel", &channel, &human_duration(left));
|
||||
let mail = echo_api::email::expiry_warning(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), "channel", &channel, &human_duration(left));
|
||||
self.emit_irc(NetAction::SendEmail { to: email, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||
self.db.mark_channel_warned(&channel);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ impl Engine {
|
|||
if status == AuthorityStatus::Ok && !self.db.is_verified(name) {
|
||||
if let Some(addr) = addr {
|
||||
let code = self.db.issue_code(name, db::CodeKind::Confirm);
|
||||
let mail = fedserv_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), name, &code);
|
||||
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), name, &code);
|
||||
self.emit_irc(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +177,7 @@ impl Engine {
|
|||
if ok && !self.db.is_verified(account) {
|
||||
if let (Some(addr), RegReply::NickServ { agent, uid, .. }) = (addr, &reply) {
|
||||
let code = self.db.issue_code(account, db::CodeKind::Confirm);
|
||||
let mail = fedserv_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), account, &code);
|
||||
let mail = echo_api::email::confirm(self.db.email_brand(), self.db.email_accent(), self.db.email_logo(), account, &code);
|
||||
out.push(NetAction::SendEmail { to: addr, subject: mail.subject, text: mail.text, html: Some(mail.html) });
|
||||
out.push(NetAction::Notice { from: agent.clone(), to: uid.clone(), text: "A confirmation code has been emailed to you. Confirm with \x02CONFIRM <code>\x02.".to_string() });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Sender, ServiceCtx, and the Service trait a pseudo-client implements all live
|
||||
// in the fedserv-api SDK crate; re-exported so the engine and the service
|
||||
// in the echo-api SDK crate; re-exported so the engine and the service
|
||||
// modules keep using `crate::engine::service::{...}`.
|
||||
pub use fedserv_api::{Sender, Service, ServiceCtx};
|
||||
pub use echo_api::{Sender, Service, ServiceCtx};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
|
||||
// The read-only network view a module sees; re-exported so the engine keeps
|
||||
// naming it locally.
|
||||
pub use fedserv_api::{IncidentView, NetView, SeenView};
|
||||
pub use echo_api::{IncidentView, NetView, SeenView};
|
||||
|
||||
// The most recent moderation/action incidents kept for LOGSEARCH.
|
||||
const INCIDENT_CAP: usize = 10_000;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -265,10 +265,10 @@ async fn send(tx: &mpsc::Sender<String>, msg: &Msg) -> Result<(), ()> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::engine::db::Db;
|
||||
use fedserv_nickserv::NickServ;
|
||||
use echo_nickserv::NickServ;
|
||||
|
||||
fn engine(origin: &str, tag: &str) -> (Shared, Outbound) {
|
||||
let path = std::env::temp_dir().join(format!("fedserv-gossip-{tag}.jsonl"));
|
||||
let path = std::env::temp_dir().join(format!("echo-gossip-{tag}.jsonl"));
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let (tx, _) = broadcast::channel(1024);
|
||||
let mut db = Db::open(&path, origin);
|
||||
|
|
@ -411,18 +411,18 @@ mod tests {
|
|||
// Generate a CA + node cert with openssl into a temp dir; None if unavailable.
|
||||
fn gen_certs() -> Option<String> {
|
||||
use std::process::Command;
|
||||
let dir = std::env::temp_dir().join(format!("fedserv-tls-{}", std::process::id()));
|
||||
let dir = std::env::temp_dir().join(format!("echo-tls-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).ok()?;
|
||||
let d = dir.to_str()?.to_string();
|
||||
let run = |args: &[&str]| Command::new("openssl").args(args).status().map(|s| s.success()).unwrap_or(false);
|
||||
let ok = run(&["req", "-x509", "-newkey", "ec", "-pkeyopt", "ec_paramgen_curve:P-256", "-nodes",
|
||||
"-keyout", &format!("{d}/ca.key"), "-out", &format!("{d}/ca.crt"), "-days", "1", "-subj", "/CN=fedserv-ca"])
|
||||
"-keyout", &format!("{d}/ca.key"), "-out", &format!("{d}/ca.crt"), "-days", "1", "-subj", "/CN=echo-ca"])
|
||||
&& run(&["req", "-newkey", "ec", "-pkeyopt", "ec_paramgen_curve:P-256", "-nodes",
|
||||
"-keyout", &format!("{d}/node.key"), "-out", &format!("{d}/node.csr"), "-subj", "/CN=fedserv"]);
|
||||
"-keyout", &format!("{d}/node.key"), "-out", &format!("{d}/node.csr"), "-subj", "/CN=echo"]);
|
||||
if !ok {
|
||||
return None;
|
||||
}
|
||||
std::fs::write(format!("{d}/ext"), "subjectAltName=DNS:fedserv,IP:127.0.0.1\n").ok()?;
|
||||
std::fs::write(format!("{d}/ext"), "subjectAltName=DNS:echo,IP:127.0.0.1\n").ok()?;
|
||||
run(&["x509", "-req", "-in", &format!("{d}/node.csr"), "-CA", &format!("{d}/ca.crt"),
|
||||
"-CAkey", &format!("{d}/ca.key"), "-CAcreateserial", "-out", &format!("{d}/node.crt"),
|
||||
"-days", "1", "-extfile", &format!("{d}/ext")])
|
||||
|
|
@ -441,7 +441,7 @@ mod tests {
|
|||
a.lock().await.test_register("alice");
|
||||
|
||||
let (sside, cside) = tokio::io::duplex(64 * 1024);
|
||||
let name = ServerName::try_from("fedserv").unwrap();
|
||||
let name = ServerName::try_from("echo").unwrap();
|
||||
let (server, client) = tokio::join!(acceptor.accept(sside), connector.connect(name, cside));
|
||||
let sa = tokio::spawn(session(server.expect("tls accept"), a.clone(), "s3cret".into(), "A".into(), atx));
|
||||
let sb = tokio::spawn(session(client.expect("tls connect"), b.clone(), "s3cret".into(), "B".into(), btx));
|
||||
|
|
|
|||
12
src/grpc.rs
12
src/grpc.rs
|
|
@ -1,6 +1,6 @@
|
|||
// Directory replication over gRPC: lets a website (Django, or anything else
|
||||
// with a protoc-generated client) mirror the account/channel directory this
|
||||
// node owns. See proto/fedserv.proto for the wire contract and exactly which
|
||||
// node owns. See proto/echo.proto for the wire contract and exactly which
|
||||
// fields are exposed — no credentials of any kind cross this API.
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -17,7 +17,7 @@ use crate::engine::db::{Account, ChannelInfo, Db, Event, LogEntry};
|
|||
use crate::engine::{AuthorityStatus, Engine};
|
||||
|
||||
pub mod pb {
|
||||
tonic::include_proto!("fedserv.v1");
|
||||
tonic::include_proto!("echo.v1");
|
||||
}
|
||||
|
||||
use pb::accounts_server::{Accounts, AccountsServer};
|
||||
|
|
@ -414,12 +414,12 @@ pub async fn run(engine: Shared, cfg: GrpcCfg, outbound: broadcast::Sender<LogEn
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::engine::db::Db;
|
||||
use fedserv_nickserv::NickServ;
|
||||
use echo_nickserv::NickServ;
|
||||
use tokio_stream::StreamExt;
|
||||
use tonic::metadata::MetadataValue;
|
||||
|
||||
fn engine_with(tag: &str) -> (Shared, broadcast::Sender<LogEntry>) {
|
||||
let path = std::env::temp_dir().join(format!("fedserv-grpc-{tag}.jsonl"));
|
||||
let path = std::env::temp_dir().join(format!("echo-grpc-{tag}.jsonl"));
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let (tx, _) = broadcast::channel(1024);
|
||||
let mut db = Db::open(&path, "A");
|
||||
|
|
@ -670,12 +670,12 @@ mod tests {
|
|||
}
|
||||
|
||||
// Full round trip through the real confirmation-email pipeline: register with
|
||||
// email confirmation on, capture the code fedserv actually emails out (never
|
||||
// email confirmation on, capture the code echo actually emails out (never
|
||||
// returned by the RPC — proving the caller can't skip owning the inbox), then
|
||||
// confirm with it.
|
||||
#[tokio::test]
|
||||
async fn confirm_completes_with_the_real_emailed_code() {
|
||||
let path = std::env::temp_dir().join("fedserv-grpc-acct-confirm.jsonl");
|
||||
let path = std::env::temp_dir().join("echo-grpc-acct-confirm.jsonl");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let (tx, _) = broadcast::channel(1024);
|
||||
let mut db = Db::open(&path, "A");
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ pub async fn run(engine: Shared, cfg: JsonRpcCfg) {
|
|||
.with_state(state);
|
||||
|
||||
match tls {
|
||||
// TLS terminated here → HTTP/2 (ALPN) plus HTTP/1.1, encrypted to fedserv.
|
||||
// TLS terminated here → HTTP/2 (ALPN) plus HTTP/1.1, encrypted to echo.
|
||||
Some(t) => {
|
||||
// The dependency tree carries more than one rustls crypto provider, so
|
||||
// pin one before building any TLS config (ignored if already set).
|
||||
|
|
@ -155,7 +155,7 @@ mod tests {
|
|||
use crate::engine::db::Db;
|
||||
|
||||
fn engine_with_stat(tag: &str) -> Shared {
|
||||
let path = std::env::temp_dir().join(format!("fedserv-jsonrpc-{tag}.jsonl"));
|
||||
let path = std::env::temp_dir().join(format!("echo-jsonrpc-{tag}.jsonl"));
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let mut e = Engine::new(vec![], Db::open(&path, "42S"));
|
||||
e.bump("botserv.messages");
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ fn dispatch_email(email: &Option<crate::config::Email>, to: String, subject: Str
|
|||
let headers = format!("From: {}\r\nTo: {to}\r\nSubject: {subject}\r\nMIME-Version: 1.0\r\n", email.from);
|
||||
let msg = match html {
|
||||
Some(html) => {
|
||||
let b = "fedserv-alt-boundary-x9";
|
||||
let b = "echo-alt-boundary-x9";
|
||||
format!(
|
||||
"{headers}Content-Type: multipart/alternative; boundary=\"{b}\"\r\n\r\n\
|
||||
--{b}\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n{text}\r\n\
|
||||
|
|
|
|||
38
src/main.rs
38
src/main.rs
|
|
@ -1,6 +1,6 @@
|
|||
// Core lives in src/; the pseudo-clients and the ircd link are external module
|
||||
// crates (fedserv-nickserv, fedserv-chanserv, fedserv-inspircd), each depending
|
||||
// only on the fedserv-api SDK.
|
||||
// crates (echo-nickserv, echo-chanserv, echo-inspircd), each depending
|
||||
// only on the echo-api SDK.
|
||||
mod config;
|
||||
mod engine;
|
||||
mod gossip;
|
||||
|
|
@ -15,28 +15,28 @@ use std::time::{SystemTime, UNIX_EPOCH};
|
|||
use tokio::sync::Mutex;
|
||||
|
||||
use engine::Engine;
|
||||
use fedserv_botserv::BotServ;
|
||||
use fedserv_chanserv::ChanServ;
|
||||
use fedserv_memoserv::MemoServ;
|
||||
use fedserv_statserv::StatServ;
|
||||
use fedserv_hostserv::HostServ;
|
||||
use fedserv_operserv::OperServ;
|
||||
use fedserv_diceserv::DiceServ;
|
||||
use fedserv_infoserv::InfoServ;
|
||||
use fedserv_reportserv::ReportServ;
|
||||
use fedserv_groupserv::GroupServ;
|
||||
use fedserv_chanfix::ChanFix;
|
||||
use fedserv_helpserv::HelpServ;
|
||||
use fedserv_example::ExampleServ;
|
||||
use fedserv_inspircd::InspIrcd;
|
||||
use fedserv_nickserv::NickServ;
|
||||
use echo_botserv::BotServ;
|
||||
use echo_chanserv::ChanServ;
|
||||
use echo_memoserv::MemoServ;
|
||||
use echo_statserv::StatServ;
|
||||
use echo_hostserv::HostServ;
|
||||
use echo_operserv::OperServ;
|
||||
use echo_diceserv::DiceServ;
|
||||
use echo_infoserv::InfoServ;
|
||||
use echo_reportserv::ReportServ;
|
||||
use echo_groupserv::GroupServ;
|
||||
use echo_chanfix::ChanFix;
|
||||
use echo_helpserv::HelpServ;
|
||||
use echo_example::ExampleServ;
|
||||
use echo_inspircd::InspIrcd;
|
||||
use echo_nickserv::NickServ;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
.with_env_filter(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||||
.unwrap_or_else(|_| "fedserv=debug".into()),
|
||||
.unwrap_or_else(|_| "echo=debug".into()),
|
||||
)
|
||||
.init();
|
||||
|
||||
|
|
@ -131,7 +131,7 @@ async fn main() -> Result<()> {
|
|||
}));
|
||||
}
|
||||
let (gossip_tx, _) = tokio::sync::broadcast::channel::<engine::db::LogEntry>(1024);
|
||||
let mut db = engine::db::Db::open("fedserv.db.jsonl", &cfg.server.sid);
|
||||
let mut db = engine::db::Db::open("echo.db.jsonl", &cfg.server.sid);
|
||||
db.scram_iterations = cfg.server.scram_iterations;
|
||||
db.set_outbound(gossip_tx.clone());
|
||||
db.set_email_enabled(cfg.email.is_some());
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// The normalized protocol vocabulary lives in the fedserv-api SDK crate;
|
||||
// The normalized protocol vocabulary lives in the echo-api SDK crate;
|
||||
// re-exported so the engine keeps referring to it as `crate::proto::*`. The
|
||||
// concrete ircd link (InspIRCd) is an external module crate, not part of core.
|
||||
pub use fedserv_api::{NetAction, NetEvent, Protocol, RegReply};
|
||||
pub use echo_api::{NetAction, NetEvent, Protocol, RegReply};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue