feat: -c/--config and -i/--identity flags for multi-instance relay
Enables running multiple relays on the same machine: wzp-relay -c ~/.wzp1/config.toml -i ~/.wzp1/relay-identity --listen :4433 wzp-relay -c ~/.wzp2/config.toml -i ~/.wzp2/relay-identity --listen :4434 wzp-relay -c ~/.wzp3/config.toml -i ~/.wzp3/relay-identity --listen :4435 Config auto-creation: if the config file doesn't exist, writes an example config with all fields documented and commented. The relay starts with defaults but the file is ready to edit. Identity auto-generation: if the identity file doesn't exist, generates a new random seed (OsRng via wzp_crypto::Seed::generate) and saves it. Subsequent starts load the same identity. Short flags: -c for --config, -i for --identity. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -122,3 +122,56 @@ pub fn load_config(path: &str) -> Result<RelayConfig, anyhow::Error> {
|
|||||||
let config: RelayConfig = toml::from_str(&content)?;
|
let config: RelayConfig = toml::from_str(&content)?;
|
||||||
Ok(config)
|
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> {
|
||||||
|
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 = "*"
|
||||||
|
"#;
|
||||||
|
|||||||
@@ -23,22 +23,30 @@ use wzp_relay::presence::PresenceRegistry;
|
|||||||
use wzp_relay::room::{self, RoomManager};
|
use wzp_relay::room::{self, RoomManager};
|
||||||
use wzp_relay::session_mgr::SessionManager;
|
use wzp_relay::session_mgr::SessionManager;
|
||||||
|
|
||||||
fn parse_args() -> RelayConfig {
|
/// Parsed CLI result — config + identity path.
|
||||||
|
struct CliResult {
|
||||||
|
config: RelayConfig,
|
||||||
|
identity_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_args() -> CliResult {
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = 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 config_file = None;
|
||||||
|
let mut identity_path = None;
|
||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
while i < args.len() {
|
while i < args.len() {
|
||||||
if args[i] == "--config" {
|
match args[i].as_str() {
|
||||||
i += 1;
|
"--config" | "-c" => { i += 1; config_file = args.get(i).cloned(); }
|
||||||
config_file = args.get(i).cloned();
|
"--identity" | "-i" => { i += 1; identity_path = args.get(i).cloned(); }
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut config = if let Some(ref path) = config_file {
|
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| {
|
.unwrap_or_else(|e| {
|
||||||
eprintln!("failed to load config from {path}: {e}");
|
eprintln!("failed to load config from {path}: {e}");
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
@@ -51,7 +59,8 @@ fn parse_args() -> RelayConfig {
|
|||||||
let mut i = 1;
|
let mut i = 1;
|
||||||
while i < args.len() {
|
while i < args.len() {
|
||||||
match args[i].as_str() {
|
match args[i].as_str() {
|
||||||
"--config" => { i += 1; } // already handled
|
"--config" | "-c" => { i += 1; } // already handled
|
||||||
|
"--identity" | "-i" => { i += 1; } // already handled
|
||||||
"--listen" => {
|
"--listen" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
config.listen_addr = args.get(i).expect("--listen requires an address")
|
config.listen_addr = args.get(i).expect("--listen requires an address")
|
||||||
@@ -128,7 +137,8 @@ fn parse_args() -> RelayConfig {
|
|||||||
eprintln!("Usage: wzp-relay [--config <path>] [--listen <addr>] [--remote <addr>] [--auth-url <url>] [--metrics-port <port>] [--probe <addr>]... [--probe-mesh] [--mesh-status]");
|
eprintln!("Usage: wzp-relay [--config <path>] [--listen <addr>] [--remote <addr>] [--auth-url <url>] [--metrics-port <port>] [--probe <addr>]... [--probe-mesh] [--mesh-status]");
|
||||||
eprintln!();
|
eprintln!();
|
||||||
eprintln!("Options:");
|
eprintln!("Options:");
|
||||||
eprintln!(" --config <path> Load configuration from TOML file (peers, listen, etc.)");
|
eprintln!(" -c, --config <path> Load config from TOML file (creates example if missing)");
|
||||||
|
eprintln!(" -i, --identity <path> Identity file path (creates if missing, uses OsRng)");
|
||||||
eprintln!(" --listen <addr> Listen address (default: 0.0.0.0:4433)");
|
eprintln!(" --listen <addr> Listen address (default: 0.0.0.0:4433)");
|
||||||
eprintln!(" --remote <addr> Remote relay for forwarding (disables room mode)");
|
eprintln!(" --remote <addr> Remote relay for forwarding (disables room mode)");
|
||||||
eprintln!(" --auth-url <url> featherChat auth endpoint (e.g., https://chat.example.com/v1/auth/validate)");
|
eprintln!(" --auth-url <url> featherChat auth endpoint (e.g., https://chat.example.com/v1/auth/validate)");
|
||||||
@@ -154,7 +164,7 @@ fn parse_args() -> RelayConfig {
|
|||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
config
|
CliResult { config, identity_path }
|
||||||
}
|
}
|
||||||
|
|
||||||
struct RelayStats {
|
struct RelayStats {
|
||||||
@@ -239,7 +249,7 @@ fn detect_public_ip() -> Option<String> {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let config = parse_args();
|
let CliResult { config, identity_path } = parse_args();
|
||||||
tracing_subscriber::fmt().init();
|
tracing_subscriber::fmt().init();
|
||||||
rustls::crypto::ring::default_provider()
|
rustls::crypto::ring::default_provider()
|
||||||
.install_default()
|
.install_default()
|
||||||
@@ -260,36 +270,41 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
tokio::spawn(wzp_relay::metrics::serve_metrics(port, m, p, rr));
|
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 relay_seed = {
|
||||||
let config_dir = dirs::home_dir()
|
let id_path = match identity_path {
|
||||||
.unwrap_or_else(|| std::path::PathBuf::from("."))
|
Some(ref p) => std::path::PathBuf::from(p),
|
||||||
.join(".wzp");
|
None => dirs::home_dir()
|
||||||
let identity_path = config_dir.join("relay-identity");
|
.unwrap_or_else(|| std::path::PathBuf::from("."))
|
||||||
if identity_path.exists() {
|
.join(".wzp")
|
||||||
if let Ok(hex) = std::fs::read_to_string(&identity_path) {
|
.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()) {
|
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
|
s
|
||||||
} else {
|
} else {
|
||||||
warn!("corrupt relay identity file, generating new");
|
warn!("corrupt identity file {}, generating new", id_path.display());
|
||||||
let s = wzp_crypto::Seed::generate();
|
let s = wzp_crypto::Seed::generate();
|
||||||
let hex: String = s.0.iter().map(|b| format!("{b:02x}")).collect();
|
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
|
s
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let s = wzp_crypto::Seed::generate();
|
let s = wzp_crypto::Seed::generate();
|
||||||
let hex: String = s.0.iter().map(|b| format!("{b:02x}")).collect();
|
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
|
s
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let s = wzp_crypto::Seed::generate();
|
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 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);
|
||||||
info!("generated relay identity at {}", identity_path.display());
|
info!("generated relay identity at {}", id_path.display());
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user