diff --git a/crates/wzp-relay/src/config.rs b/crates/wzp-relay/src/config.rs index 7dfb077..b9d7a6a 100644 --- a/crates/wzp-relay/src/config.rs +++ b/crates/wzp-relay/src/config.rs @@ -122,3 +122,56 @@ pub fn load_config(path: &str) -> Result { let config: RelayConfig = toml::from_str(&content)?; Ok(config) } + +/// Load config from path, or create an example config file if it doesn't exist. +pub fn load_or_create_config(path: &str) -> Result { + let p = std::path::Path::new(path); + if p.exists() { + return load_config(path); + } + // Create parent directory if needed + if let Some(parent) = p.parent() { + std::fs::create_dir_all(parent)?; + } + // Write example config + let example = EXAMPLE_CONFIG; + std::fs::write(p, example)?; + eprintln!("Created example config at {path} — edit it and restart."); + let config: RelayConfig = toml::from_str(example)?; + Ok(config) +} + +/// Example TOML configuration written when --config points to a non-existent file. +pub const EXAMPLE_CONFIG: &str = r#"# WarzonePhone Relay Configuration +# See docs/ADMINISTRATION.md for full reference. + +# Listen address for client connections +listen_addr = "0.0.0.0:4433" + +# Maximum concurrent sessions +# max_sessions = 100 + +# Prometheus metrics endpoint (uncomment to enable) +# metrics_port = 9090 + +# featherChat auth endpoint (uncomment to enable) +# auth_url = "https://chat.example.com/v1/auth/validate" + +# Federation: peer relays we connect to (outbound) +# [[peers]] +# url = "relay-b.example.com:4433" +# fingerprint = "aa:bb:cc:dd:..." +# label = "Relay B" + +# Federation: relays we trust inbound connections from +# [[trusted]] +# fingerprint = "ee:ff:00:11:..." +# label = "Relay X" + +# Global rooms bridged across all federated peers +# [[global_rooms]] +# name = "general" + +# Debug: log packet headers for a room ("*" for all) +# debug_tap = "*" +"#; diff --git a/crates/wzp-relay/src/main.rs b/crates/wzp-relay/src/main.rs index 9a18ea9..bda1dd6 100644 --- a/crates/wzp-relay/src/main.rs +++ b/crates/wzp-relay/src/main.rs @@ -23,22 +23,30 @@ use wzp_relay::presence::PresenceRegistry; use wzp_relay::room::{self, RoomManager}; use wzp_relay::session_mgr::SessionManager; -fn parse_args() -> RelayConfig { +/// Parsed CLI result — config + identity path. +struct CliResult { + config: RelayConfig, + identity_path: Option, +} + +fn parse_args() -> CliResult { let args: Vec = std::env::args().collect(); - // Check for --config first to use as base + // First pass: extract --config and --identity let mut config_file = None; + let mut identity_path = None; let mut i = 1; while i < args.len() { - if args[i] == "--config" { - i += 1; - config_file = args.get(i).cloned(); + match args[i].as_str() { + "--config" | "-c" => { i += 1; config_file = args.get(i).cloned(); } + "--identity" | "-i" => { i += 1; identity_path = args.get(i).cloned(); } + _ => {} } i += 1; } let mut config = if let Some(ref path) = config_file { - wzp_relay::config::load_config(path) + wzp_relay::config::load_or_create_config(path) .unwrap_or_else(|e| { eprintln!("failed to load config from {path}: {e}"); std::process::exit(1); @@ -51,7 +59,8 @@ fn parse_args() -> RelayConfig { let mut i = 1; while i < args.len() { match args[i].as_str() { - "--config" => { i += 1; } // already handled + "--config" | "-c" => { i += 1; } // already handled + "--identity" | "-i" => { i += 1; } // already handled "--listen" => { i += 1; config.listen_addr = args.get(i).expect("--listen requires an address") @@ -128,7 +137,8 @@ fn parse_args() -> RelayConfig { 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!(" -c, --config Load config from TOML file (creates example if missing)"); + eprintln!(" -i, --identity Identity file path (creates if missing, uses OsRng)"); 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)"); @@ -154,7 +164,7 @@ fn parse_args() -> RelayConfig { } i += 1; } - config + CliResult { config, identity_path } } struct RelayStats { @@ -239,7 +249,7 @@ fn detect_public_ip() -> Option { #[tokio::main] async fn main() -> anyhow::Result<()> { - let config = parse_args(); + let CliResult { config, identity_path } = parse_args(); tracing_subscriber::fmt().init(); rustls::crypto::ring::default_provider() .install_default() @@ -260,36 +270,41 @@ async fn main() -> anyhow::Result<()> { tokio::spawn(wzp_relay::metrics::serve_metrics(port, m, p, rr)); } - // Load or generate relay identity — persisted in ~/.wzp/relay-identity + // Load or generate relay identity let relay_seed = { - let config_dir = dirs::home_dir() - .unwrap_or_else(|| std::path::PathBuf::from(".")) - .join(".wzp"); - let identity_path = config_dir.join("relay-identity"); - if identity_path.exists() { - if let Ok(hex) = std::fs::read_to_string(&identity_path) { + let id_path = match identity_path { + Some(ref p) => std::path::PathBuf::from(p), + None => dirs::home_dir() + .unwrap_or_else(|| std::path::PathBuf::from(".")) + .join(".wzp") + .join("relay-identity"), + }; + if id_path.exists() { + if let Ok(hex) = std::fs::read_to_string(&id_path) { if let Ok(s) = wzp_crypto::Seed::from_hex(hex.trim()) { - info!("loaded relay identity from {}", identity_path.display()); + info!("loaded relay identity from {}", id_path.display()); s } else { - warn!("corrupt relay identity file, generating new"); + warn!("corrupt identity file {}, generating new", id_path.display()); let s = wzp_crypto::Seed::generate(); let hex: String = s.0.iter().map(|b| format!("{b:02x}")).collect(); - let _ = std::fs::write(&identity_path, &hex); + let _ = std::fs::write(&id_path, &hex); s } } else { let s = wzp_crypto::Seed::generate(); let hex: String = s.0.iter().map(|b| format!("{b:02x}")).collect(); - let _ = std::fs::write(&identity_path, &hex); + let _ = std::fs::write(&id_path, &hex); s } } else { let s = wzp_crypto::Seed::generate(); - let _ = std::fs::create_dir_all(&config_dir); + if let Some(parent) = id_path.parent() { + let _ = std::fs::create_dir_all(parent); + } let hex: String = s.0.iter().map(|b| format!("{b:02x}")).collect(); - let _ = std::fs::write(&identity_path, &hex); - info!("generated relay identity at {}", identity_path.display()); + let _ = std::fs::write(&id_path, &hex); + info!("generated relay identity at {}", id_path.display()); s } };