feat: personalized config generation with --listen addr + own fingerprint
Some checks failed
Mirror to GitHub / mirror (push) Failing after 39s
Build Release Binaries / build-amd64 (push) Failing after 3m16s

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) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-04-08 09:38:28 +04:00
parent 7064f484af
commit 406461d460
2 changed files with 72 additions and 19 deletions

View File

@@ -123,8 +123,15 @@ pub fn load_config(path: &str) -> Result<RelayConfig, anyhow::Error> {
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<RelayConfig, anyhow::Error> {
/// 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<String>,
}
/// 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<RelayConfig, anyhow::Error> {
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<RelayConfig, anyhow::Error> {
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 = "*"
"#;
"#
)
}

View File

@@ -27,6 +27,8 @@ use wzp_relay::session_mgr::SessionManager;
struct CliResult {
config: RelayConfig,
identity_path: Option<String>,
config_file: Option<String>,
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<String> {
#[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]]");