diff --git a/src/config/defaults.rs b/src/config/defaults.rs index d171945..beedd10 100644 --- a/src/config/defaults.rs +++ b/src/config/defaults.rs @@ -100,7 +100,7 @@ pub(crate) fn default_fake_cert_len() -> usize { } pub(crate) fn default_tls_front_dir() -> String { - "tlsfront".to_string() + "/etc/telemt/tlsfront".to_string() } pub(crate) fn default_replay_check_len() -> usize { @@ -558,7 +558,7 @@ pub(crate) fn default_beobachten_flush_secs() -> u64 { } pub(crate) fn default_beobachten_file() -> String { - "cache/beobachten.txt".to_string() + "/etc/telemt/beobachten.txt".to_string() } pub(crate) fn default_tls_new_session_tickets() -> u8 { diff --git a/src/maestro/helpers.rs b/src/maestro/helpers.rs index d9d8e8b..d545b21 100644 --- a/src/maestro/helpers.rs +++ b/src/maestro/helpers.rs @@ -18,19 +18,38 @@ use crate::transport::middle_proxy::{ pub(crate) fn resolve_runtime_config_path( config_path_cli: &str, startup_cwd: &std::path::Path, + config_path_explicit: bool, ) -> PathBuf { - let raw = PathBuf::from(config_path_cli); - let absolute = if raw.is_absolute() { - raw - } else { - startup_cwd.join(raw) - }; - absolute.canonicalize().unwrap_or(absolute) + if config_path_explicit { + let raw = PathBuf::from(config_path_cli); + let absolute = if raw.is_absolute() { + raw + } else { + startup_cwd.join(raw) + }; + return absolute.canonicalize().unwrap_or(absolute); + } + + let etc_telemt = std::path::Path::new("/etc/telemt"); + let candidates = [ + startup_cwd.join("config.toml"), + startup_cwd.join("telemt.toml"), + etc_telemt.join("telemt.toml"), + etc_telemt.join("config.toml"), + ]; + for candidate in candidates { + if candidate.is_file() { + return candidate.canonicalize().unwrap_or(candidate); + } + } + + startup_cwd.join("config.toml") } /// Parsed CLI arguments. pub(crate) struct CliArgs { pub config_path: String, + pub config_path_explicit: bool, pub data_path: Option, pub silent: bool, pub log_level: Option, @@ -39,6 +58,7 @@ pub(crate) struct CliArgs { pub(crate) fn parse_cli() -> CliArgs { let mut config_path = "config.toml".to_string(); + let mut config_path_explicit = false; let mut data_path: Option = None; let mut silent = false; let mut log_level: Option = None; @@ -74,6 +94,20 @@ pub(crate) fn parse_cli() -> CliArgs { s.trim_start_matches("--data-path=").to_string(), )); } + "--working-dir" => { + i += 1; + if i < args.len() { + data_path = Some(PathBuf::from(args[i].clone())); + } else { + eprintln!("Missing value for --working-dir"); + std::process::exit(0); + } + } + s if s.starts_with("--working-dir=") => { + data_path = Some(PathBuf::from( + s.trim_start_matches("--working-dir=").to_string(), + )); + } "--silent" | "-s" => { silent = true; } @@ -111,13 +145,11 @@ pub(crate) fn parse_cli() -> CliArgs { i += 1; } } - s if s.starts_with("--working-dir") => { - if !s.contains('=') { - i += 1; - } - } s if !s.starts_with('-') => { - config_path = s.to_string(); + if !matches!(s, "run" | "start" | "stop" | "reload" | "status") { + config_path = s.to_string(); + config_path_explicit = true; + } } other => { eprintln!("Unknown option: {}", other); @@ -128,6 +160,7 @@ pub(crate) fn parse_cli() -> CliArgs { CliArgs { config_path, + config_path_explicit, data_path, silent, log_level, @@ -152,6 +185,7 @@ fn print_help() { eprintln!( " --data-path Set data directory (absolute path; overrides config value)" ); + eprintln!(" --working-dir Alias for --data-path"); eprintln!(" --silent, -s Suppress info logs"); eprintln!(" --log-level debug|verbose|normal|silent"); eprintln!(" --help, -h Show this help"); @@ -210,7 +244,7 @@ mod tests { let target = startup_cwd.join("config.toml"); std::fs::write(&target, " ").unwrap(); - let resolved = resolve_runtime_config_path("config.toml", &startup_cwd); + let resolved = resolve_runtime_config_path("config.toml", &startup_cwd, true); assert_eq!(resolved, target.canonicalize().unwrap()); let _ = std::fs::remove_file(&target); @@ -226,11 +260,44 @@ mod tests { let startup_cwd = std::env::temp_dir().join(format!("telemt_cfg_path_missing_{nonce}")); std::fs::create_dir_all(&startup_cwd).unwrap(); - let resolved = resolve_runtime_config_path("missing.toml", &startup_cwd); + let resolved = resolve_runtime_config_path("missing.toml", &startup_cwd, true); assert_eq!(resolved, startup_cwd.join("missing.toml")); let _ = std::fs::remove_dir(&startup_cwd); } + + #[test] + fn resolve_runtime_config_path_uses_startup_candidates_when_not_explicit() { + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let startup_cwd = std::env::temp_dir().join(format!("telemt_cfg_startup_candidates_{nonce}")); + std::fs::create_dir_all(&startup_cwd).unwrap(); + let telemt = startup_cwd.join("telemt.toml"); + std::fs::write(&telemt, " ").unwrap(); + + let resolved = resolve_runtime_config_path("config.toml", &startup_cwd, false); + assert_eq!(resolved, telemt.canonicalize().unwrap()); + + let _ = std::fs::remove_file(&telemt); + let _ = std::fs::remove_dir(&startup_cwd); + } + + #[test] + fn resolve_runtime_config_path_defaults_to_startup_config_when_none_found() { + let nonce = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_nanos(); + let startup_cwd = std::env::temp_dir().join(format!("telemt_cfg_startup_default_{nonce}")); + std::fs::create_dir_all(&startup_cwd).unwrap(); + + let resolved = resolve_runtime_config_path("config.toml", &startup_cwd, false); + assert_eq!(resolved, startup_cwd.join("config.toml")); + + let _ = std::fs::remove_dir(&startup_cwd); + } } pub(crate) fn print_proxy_links(host: &str, port: u16, config: &ProxyConfig) { diff --git a/src/maestro/mod.rs b/src/maestro/mod.rs index 164071e..e610ae8 100644 --- a/src/maestro/mod.rs +++ b/src/maestro/mod.rs @@ -112,6 +112,7 @@ async fn run_inner( .await; let cli_args = parse_cli(); let config_path_cli = cli_args.config_path; + let config_path_explicit = cli_args.config_path_explicit; let data_path = cli_args.data_path; let cli_silent = cli_args.silent; let cli_log_level = cli_args.log_level; @@ -123,7 +124,8 @@ async fn run_inner( std::process::exit(1); } }; - let config_path = resolve_runtime_config_path(&config_path_cli, &startup_cwd); + let mut config_path = + resolve_runtime_config_path(&config_path_cli, &startup_cwd, config_path_explicit); let mut config = match ProxyConfig::load(&config_path) { Ok(c) => c, @@ -133,11 +135,100 @@ async fn run_inner( std::process::exit(1); } else { let default = ProxyConfig::default(); - std::fs::write(&config_path, toml::to_string_pretty(&default).unwrap()).unwrap(); - eprintln!( - "[telemt] Created default config at {}", - config_path.display() - ); + + let serialized = match toml::to_string_pretty(&default) + .or_else(|_| toml::to_string(&default)) + { + Ok(value) => Some(value), + Err(serialize_error) => { + eprintln!( + "[telemt] Warning: failed to serialize default config: {}", + serialize_error + ); + None + } + }; + + if config_path_explicit { + if let Some(serialized) = serialized.as_ref() { + if let Err(write_error) = std::fs::write(&config_path, serialized) { + eprintln!( + "[telemt] Error: failed to create explicit config at {}: {}", + config_path.display(), + write_error + ); + std::process::exit(1); + } + eprintln!( + "[telemt] Created default config at {}", + config_path.display() + ); + } else { + eprintln!( + "[telemt] Warning: running with in-memory default config without writing to disk" + ); + } + } else { + let system_dir = std::path::Path::new("/etc/telemt"); + let system_config_path = system_dir.join("telemt.toml"); + let startup_config_path = startup_cwd.join("config.toml"); + let mut persisted = false; + + if let Some(serialized) = serialized.as_ref() { + match std::fs::create_dir_all(system_dir) { + Ok(()) => match std::fs::write(&system_config_path, serialized) { + Ok(()) => { + config_path = system_config_path; + eprintln!( + "[telemt] Created default config at {}", + config_path.display() + ); + persisted = true; + } + Err(write_error) => { + eprintln!( + "[telemt] Warning: failed to write default config at {}: {}", + system_config_path.display(), + write_error + ); + } + }, + Err(create_error) => { + eprintln!( + "[telemt] Warning: failed to create {}: {}", + system_dir.display(), + create_error + ); + } + } + + if !persisted { + match std::fs::write(&startup_config_path, serialized) { + Ok(()) => { + config_path = startup_config_path; + eprintln!( + "[telemt] Created default config at {}", + config_path.display() + ); + persisted = true; + } + Err(write_error) => { + eprintln!( + "[telemt] Warning: failed to write default config at {}: {}", + startup_config_path.display(), + write_error + ); + } + } + } + } + + if !persisted { + eprintln!( + "[telemt] Warning: running with in-memory default config without writing to disk" + ); + } + } default } }