From 2f2720802d8282007f220fb10b2387d86062bce0 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Tue, 7 Apr 2026 22:13:56 +0400 Subject: [PATCH] feat: TOML config file with federation peers + --config flag The relay now supports loading configuration from a TOML file via --config . CLI flags override TOML values. All fields have serde defaults so a minimal config only needs what you want to change. Example relay.toml: listen_addr = "0.0.0.0:4433" [[peers]] url = "193.180.213.68:4433" fingerprint = "1a:39:38:..." label = "Pangolin EU" Federation hint on startup now shows TOML format with TLS fingerprint (not Ed25519 identity fingerprint), since TLS fingerprint is what peers actually verify. Configured peers are logged on startup. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/wzp-relay/src/config.rs | 29 +++++++++++++++++- crates/wzp-relay/src/main.rs | 55 +++++++++++++++++++++++++++------- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/crates/wzp-relay/src/config.rs b/crates/wzp-relay/src/config.rs index 01d9e14..a771b42 100644 --- a/crates/wzp-relay/src/config.rs +++ b/crates/wzp-relay/src/config.rs @@ -3,8 +3,24 @@ use serde::{Deserialize, Serialize}; use std::net::SocketAddr; -/// Configuration for the relay daemon. +/// A federated peer relay. #[derive(Clone, Debug, Serialize, Deserialize)] +pub struct PeerConfig { + /// Address of the peer relay (e.g., "193.180.213.68:4433"). + pub url: String, + /// Expected TLS certificate fingerprint (hex, with colons). + pub fingerprint: String, + /// Optional human-readable label. + #[serde(default)] + pub label: Option, +} + +/// Configuration for the relay daemon. +/// +/// All fields have defaults, so a minimal TOML file only needs the +/// fields you want to override (e.g., just `[[peers]]`). +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(default)] pub struct RelayConfig { /// Address to listen on for incoming connections (client-facing). pub listen_addr: SocketAddr, @@ -44,6 +60,9 @@ pub struct RelayConfig { pub ws_port: Option, /// Directory to serve static files from (HTML/JS/WASM for web clients). pub static_dir: Option, + /// Federation peer relays. + #[serde(default)] + pub peers: Vec, } impl Default for RelayConfig { @@ -62,6 +81,14 @@ impl Default for RelayConfig { trunking_enabled: false, ws_port: None, static_dir: None, + peers: Vec::new(), } } } + +/// Load relay configuration from a TOML file. +pub fn load_config(path: &str) -> Result { + let content = std::fs::read_to_string(path)?; + let config: RelayConfig = toml::from_str(&content)?; + Ok(config) +} diff --git a/crates/wzp-relay/src/main.rs b/crates/wzp-relay/src/main.rs index 3388754..8f2c6b7 100644 --- a/crates/wzp-relay/src/main.rs +++ b/crates/wzp-relay/src/main.rs @@ -24,11 +24,34 @@ use wzp_relay::room::{self, RoomManager}; use wzp_relay::session_mgr::SessionManager; fn parse_args() -> RelayConfig { - let mut config = RelayConfig::default(); let args: Vec = std::env::args().collect(); + + // Check for --config first to use as base + let mut config_file = None; + let mut i = 1; + while i < args.len() { + if args[i] == "--config" { + i += 1; + config_file = args.get(i).cloned(); + } + i += 1; + } + + let mut config = if let Some(ref path) = config_file { + wzp_relay::config::load_config(path) + .unwrap_or_else(|e| { + eprintln!("failed to load config from {path}: {e}"); + std::process::exit(1); + }) + } else { + RelayConfig::default() + }; + + // CLI flags override config file values let mut i = 1; while i < args.len() { match args[i].as_str() { + "--config" => { i += 1; } // already handled "--listen" => { i += 1; config.listen_addr = args.get(i).expect("--listen requires an address") @@ -90,9 +113,10 @@ fn parse_args() -> RelayConfig { std::process::exit(0); } "--help" | "-h" => { - eprintln!("Usage: wzp-relay [--listen ] [--remote ] [--auth-url ] [--metrics-port ] [--probe ]... [--probe-mesh] [--mesh-status]"); + eprintln!("Usage: wzp-relay [--config ] [--listen ] [--remote ] [--auth-url ] [--metrics-port ] [--probe ]... [--probe-mesh] [--mesh-status]"); eprintln!(); eprintln!("Options:"); + eprintln!(" --config Load configuration from TOML file (peers, listen, etc.)"); eprintln!(" --listen Listen address (default: 0.0.0.0:4433)"); eprintln!(" --remote Remote relay for forwarding (disables room mode)"); eprintln!(" --auth-url featherChat auth endpoint (e.g., https://chat.example.com/v1/auth/validate)"); @@ -258,18 +282,27 @@ async fn main() -> anyhow::Result<()> { let relay_fp = relay_seed.derive_identity().public_identity().fingerprint; info!(addr = %config.listen_addr, fingerprint = %relay_fp, "WarzonePhone relay starting"); - // Print federation hint with our public IP + listen port - let listen_port = config.listen_addr.port(); - let public_ip = detect_public_ip(); - if let Some(ip) = &public_ip { - info!("federation: to peer with this relay, add to peers config:"); - info!(" - url: \"{ip}:{listen_port}\""); - info!(" fingerprint: \"{relay_fp}\""); - } - let (server_config, cert_der) = wzp_transport::server_config_from_seed(&relay_seed.0); let tls_fp = wzp_transport::tls_fingerprint(&cert_der); info!(tls_fingerprint = %tls_fp, "TLS certificate (deterministic from relay identity)"); + + // Print federation hint with our public IP + listen port + TLS fingerprint + let listen_port = config.listen_addr.port(); + let public_ip = detect_public_ip(); + if let Some(ip) = &public_ip { + info!("federation: to peer with this relay, add to relay.toml:"); + info!(" [[peers]]"); + info!(" url = \"{ip}:{listen_port}\""); + info!(" fingerprint = \"{tls_fp}\""); + } + + // Log configured peers + if !config.peers.is_empty() { + info!(count = config.peers.len(), "federation peers configured"); + for p in &config.peers { + info!(url = %p.url, label = ?p.label, " peer"); + } + } let endpoint = wzp_transport::create_endpoint(config.listen_addr, Some(server_config))?; // Forward mode