add Anope->Echo migration importer (echo import)
All checks were successful
CI / check (push) Successful in 3m41s

New 'echo import <anope.json> <out.log> [node]' subcommand builds a fresh event
log from an Anope db_json: accounts (identity only — Anope's one-way password
hashes can't become SCRAM verifiers, so migrated users auth by certfp/external
authority), grouped nicks, cert fingerprints, vhosts, channels (+desc/topic/
assigned bot), access mapped to op/voice by each channel's AUTOOP/AUTOVOICE
thresholds, param-less mode locks, BotServ bots (service pseudo-clients skipped),
and memos. Read-only on the source; self-verifies by replaying the produced log.
Verified against the live backup (11 accounts, 10 channels, 10 bots) + a unit
test over a synthetic fixture. The prerequisite for a safe cutover.
This commit is contained in:
Jean Chevronnet 2026-07-16 13:05:45 +00:00
parent b23bf5d8a9
commit 062eae703e
No known key found for this signature in database
3 changed files with 366 additions and 0 deletions

View file

@ -7,6 +7,7 @@ mod gossip;
mod grpc;
mod jsonrpc;
mod link;
mod migrate;
mod proto;
use anyhow::Result;
@ -40,6 +41,30 @@ async fn main() -> Result<()> {
)
.init();
// One-off migration: `echo import <anope.json> <out.log> [node-name]` builds a
// fresh event log from an Anope db_json and exits, touching nothing else.
let argv: Vec<String> = std::env::args().collect();
if argv.get(1).map(String::as_str) == Some("import") {
let (Some(src), Some(out)) = (argv.get(2), argv.get(3)) else {
eprintln!("usage: echo import <anope.json> <out.log> [node-name]");
std::process::exit(2);
};
let node = argv.get(4).map(String::as_str).unwrap_or("services");
let s = migrate::import_anope(src, out, node)?;
println!(
"imported: {} accounts, {} grouped nicks, {} certs, {} vhosts, {} channels, {} access, {} mlocked, {} bots, {} memos",
s.accounts, s.grouped_nicks, s.certs, s.vhosts, s.channels, s.access, s.mlocked, s.bots, s.memos
);
for note in &s.skipped {
println!(" skipped: {note}");
}
println!(
"verified (log replays to): {} accounts, {} channels, {} bots",
s.loaded_accounts, s.loaded_channels, s.loaded_bots
);
return Ok(());
}
let path = std::env::args().nth(1).unwrap_or_else(|| "config.toml".to_string());
let cfg = config::Config::load(&path)?;
let ts = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs();