From 406461d460ade88089d24135dff607a4540eccc6 Mon Sep 17 00:00:00 2001 From: Siavash Sameni Date: Wed, 8 Apr 2026 09:38:28 +0400 Subject: [PATCH] feat: personalized config generation with --listen addr + own fingerprint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When --config points to a non-existent file, the relay now generates a personalized example config that includes: - listen_addr matching the --listen flag (not hardcoded 0.0.0.0:4433) - Pre-filled [[peers]] section with this relay's detected IP, port, and TLS fingerprint — ready to copy/paste into other relay configs This makes setting up federation much easier: start each relay, it generates its config with its own peering info commented out, you just uncomment and copy between configs. Co-Authored-By: Claude Opus 4.6 (1M context) --- crates/wzp-relay/src/config.rs | 51 ++++++++++++++++++++++++++-------- crates/wzp-relay/src/main.rs | 40 ++++++++++++++++++++------ 2 files changed, 72 insertions(+), 19 deletions(-) diff --git a/crates/wzp-relay/src/config.rs b/crates/wzp-relay/src/config.rs index b9d7a6a..27c959c 100644 --- a/crates/wzp-relay/src/config.rs +++ b/crates/wzp-relay/src/config.rs @@ -123,8 +123,15 @@ pub fn load_config(path: &str) -> Result { 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 { +/// Info about this relay instance, used to generate personalized example configs. +pub struct RelayInfo { + pub listen_addr: String, + pub tls_fingerprint: String, + pub public_ip: Option, +} + +/// Load config from path, or create a personalized example config if it doesn't exist. +pub fn load_or_create_config(path: &str, info: Option<&RelayInfo>) -> Result { let p = std::path::Path::new(path); if p.exists() { return load_config(path); @@ -133,20 +140,38 @@ pub fn load_or_create_config(path: &str) -> Result { if let Some(parent) = p.parent() { std::fs::create_dir_all(parent)?; } - // Write example config - let example = EXAMPLE_CONFIG; - std::fs::write(p, example)?; + // Generate personalized example config + let example = generate_example_config(info); + std::fs::write(p, &example)?; eprintln!("Created example config at {path} — edit it and restart."); - let config: RelayConfig = toml::from_str(example)?; + 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 +/// Generate an example TOML config, personalized with this relay's info if available. +fn generate_example_config(info: Option<&RelayInfo>) -> String { + let listen = info.map(|i| i.listen_addr.as_str()).unwrap_or("0.0.0.0:4433"); + let peer_example = if let Some(i) = info { + let ip = i.public_ip.as_deref().unwrap_or("this-relay-ip"); + format!( + r#"# Other relays can peer with this relay using: +# [[peers]] +# url = "{ip}:{port}" +# fingerprint = "{fp}" +# label = "This Relay""#, + port = listen.rsplit(':').next().unwrap_or("4433"), + fp = i.tls_fingerprint, + ) + } else { + "# To peer with another relay, add its url + fingerprint:".to_string() + }; + + format!( + r#"# WarzonePhone Relay Configuration # See docs/ADMINISTRATION.md for full reference. # Listen address for client connections -listen_addr = "0.0.0.0:4433" +listen_addr = "{listen}" # Maximum concurrent sessions # max_sessions = 100 @@ -157,9 +182,11 @@ listen_addr = "0.0.0.0:4433" # featherChat auth endpoint (uncomment to enable) # auth_url = "https://chat.example.com/v1/auth/validate" +{peer_example} + # Federation: peer relays we connect to (outbound) # [[peers]] -# url = "relay-b.example.com:4433" +# url = "other-relay.example.com:4433" # fingerprint = "aa:bb:cc:dd:..." # label = "Relay B" @@ -174,4 +201,6 @@ listen_addr = "0.0.0.0:4433" # 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 bda1dd6..d439440 100644 --- a/crates/wzp-relay/src/main.rs +++ b/crates/wzp-relay/src/main.rs @@ -27,6 +27,8 @@ use wzp_relay::session_mgr::SessionManager; struct CliResult { config: RelayConfig, identity_path: Option, + config_file: Option, + config_needs_create: bool, } fn parse_args() -> CliResult { @@ -45,12 +47,20 @@ fn parse_args() -> CliResult { i += 1; } + // Track if we need to create the config after identity is known + let config_needs_create = config_file.as_ref().map(|p| !std::path::Path::new(p).exists()).unwrap_or(false); + let mut config = if let Some(ref path) = config_file { - wzp_relay::config::load_or_create_config(path) - .unwrap_or_else(|e| { - eprintln!("failed to load config from {path}: {e}"); - std::process::exit(1); - }) + if config_needs_create { + // Will be re-created with personalized info after identity is loaded + RelayConfig::default() + } else { + 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() }; @@ -164,7 +174,7 @@ fn parse_args() -> CliResult { } i += 1; } - CliResult { config, identity_path } + CliResult { config, identity_path, config_file, config_needs_create } } struct RelayStats { @@ -249,7 +259,7 @@ fn detect_public_ip() -> Option { #[tokio::main] async fn main() -> anyhow::Result<()> { - let CliResult { config, identity_path } = parse_args(); + let CliResult { mut config, identity_path, config_file, config_needs_create } = parse_args(); tracing_subscriber::fmt().init(); rustls::crypto::ring::default_provider() .install_default() @@ -315,9 +325,23 @@ async fn main() -> anyhow::Result<()> { let tls_fp = wzp_transport::tls_fingerprint(&cert_der); info!(tls_fingerprint = %tls_fp, "TLS certificate (deterministic from relay identity)"); + // Create personalized config file if it was missing + let public_ip = detect_public_ip(); + if config_needs_create { + if let Some(ref path) = config_file { + let info = wzp_relay::config::RelayInfo { + listen_addr: config.listen_addr.to_string(), + tls_fingerprint: tls_fp.clone(), + public_ip: public_ip.clone(), + }; + if let Err(e) = wzp_relay::config::load_or_create_config(path, Some(&info)) { + warn!("failed to create config: {e}"); + } + } + } + // 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]]");