From b13c46806c28fc93c2a35cc876a9a58b8f656f16 Mon Sep 17 00:00:00 2001 From: Jeena Date: Fri, 16 Jan 2026 22:21:50 +0000 Subject: [PATCH] Implement comprehensive config validation with graceful error handling - Replace panic-prone config loading with detailed error messages - Validate config file existence, TOML syntax, required fields - Check filesystem access for root directory and certificate files - Provide actionable error messages explaining how to fix each issue - Exit gracefully with clear guidance instead of cryptic panics - Maintain backward compatibility for valid configurations --- src/main.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7088e84..d482e26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,27 +48,105 @@ async fn main() { // Load config let config_path = args.config.as_deref().unwrap_or("/etc/pollux/config.toml"); - let config = config::load_config(config_path).unwrap_or(config::Config { - root: None, - cert: None, - key: None, - bind_host: None, - hostname: None, - port: None, - log_level: None, - max_concurrent_requests: None, - }); + // Check if config file exists + if !std::path::Path::new(&config_path).exists() { + eprintln!("Error: Config file '{}' not found", config_path); + eprintln!("Create the config file with required fields:"); + eprintln!(" root = \"/path/to/gemini/content\""); + eprintln!(" cert = \"/path/to/certificate.pem\""); + eprintln!(" key = \"/path/to/private-key.pem\""); + eprintln!(" bind_host = \"0.0.0.0\""); + eprintln!(" hostname = \"your.domain.com\""); + std::process::exit(1); + } - // Initialize logging + // Load and parse config + let config = match config::load_config(&config_path) { + Ok(config) => config, + Err(e) => { + eprintln!("Error: Failed to parse config file '{}': {}", config_path, e); + eprintln!("Check the TOML syntax and ensure all values are properly quoted."); + std::process::exit(1); + } + }; + + // Validate required fields + if config.root.is_none() { + eprintln!("Error: 'root' field is required in config file"); + eprintln!("Add: root = \"/path/to/gemini/content\""); + std::process::exit(1); + } + + if config.cert.is_none() { + eprintln!("Error: 'cert' field is required in config file"); + eprintln!("Add: cert = \"/path/to/certificate.pem\""); + std::process::exit(1); + } + + if config.key.is_none() { + eprintln!("Error: 'key' field is required in config file"); + eprintln!("Add: key = \"/path/to/private-key.pem\""); + std::process::exit(1); + } + + if config.hostname.is_none() { + eprintln!("Error: 'hostname' field is required in config file"); + eprintln!("Add: hostname = \"your.domain.com\""); + std::process::exit(1); + } + + // Validate filesystem + let root_path = std::path::Path::new(config.root.as_ref().unwrap()); + if !root_path.exists() { + eprintln!("Error: Root directory '{}' does not exist", config.root.as_ref().unwrap()); + eprintln!("Create the directory and add your Gemini files (.gmi, .txt, images)"); + std::process::exit(1); + } + if !root_path.is_dir() { + eprintln!("Error: Root path '{}' is not a directory", config.root.as_ref().unwrap()); + eprintln!("The 'root' field must point to a directory containing your content"); + std::process::exit(1); + } + if let Err(e) = std::fs::read_dir(root_path) { + eprintln!("Error: Cannot read root directory '{}': {}", config.root.as_ref().unwrap(), e); + eprintln!("Ensure the directory exists and the server user has read permission"); + std::process::exit(1); + } + + let cert_path = std::path::Path::new(config.cert.as_ref().unwrap()); + if !cert_path.exists() { + eprintln!("Error: Certificate file '{}' does not exist", config.cert.as_ref().unwrap()); + eprintln!("Generate or obtain TLS certificates for your domain"); + std::process::exit(1); + } + if let Err(e) = std::fs::File::open(cert_path) { + eprintln!("Error: Cannot read certificate file '{}': {}", config.cert.as_ref().unwrap(), e); + eprintln!("Ensure the file exists and the server user has read permission"); + std::process::exit(1); + } + + let key_path = std::path::Path::new(config.key.as_ref().unwrap()); + if !key_path.exists() { + eprintln!("Error: Private key file '{}' does not exist", config.key.as_ref().unwrap()); + eprintln!("Generate or obtain TLS private key for your domain"); + std::process::exit(1); + } + if let Err(e) = std::fs::File::open(key_path) { + eprintln!("Error: Cannot read private key file '{}': {}", config.key.as_ref().unwrap(), e); + eprintln!("Ensure the file exists and the server user has read permission"); + std::process::exit(1); + } + + // Initialize logging after config validation let log_level = config.log_level.as_deref().unwrap_or("info"); init_logging(log_level); - // Load configuration from file only - let root = config.root.expect("root is required"); - let cert_path = config.cert.expect("cert is required"); - let key_path = config.key.expect("key is required"); + // Extract validated config values + let root = config.root.unwrap(); + let cert_path = config.cert.unwrap(); + let key_path = config.key.unwrap(); let bind_host = config.bind_host.unwrap_or_else(|| "0.0.0.0".to_string()); - let hostname = config.hostname.expect("hostname is required"); + let hostname = config.hostname.unwrap(); let port = config.port.unwrap_or(1965); // Validate max concurrent requests