feat: personalized config generation with --listen addr + own fingerprint
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:
@@ -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 = "*"
|
||||
"#;
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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]]");
|
||||
|
||||
Reference in New Issue
Block a user