diff --git a/numa.toml b/numa.toml index 35d92de..77ba231 100644 --- a/numa.toml +++ b/numa.toml @@ -2,11 +2,12 @@ bind_addr = "0.0.0.0:53" api_port = 5380 # api_bind_addr = "127.0.0.1" # default; set to "0.0.0.0" for LAN dashboard access -# data_dir = "/usr/local/var/numa" # where numa stores TLS CA and cert material - # (default: /usr/local/var/numa on unix, - # %PROGRAMDATA%\numa on windows). Override for - # containerized deploys or tests that can't - # write to the system path. +# data_dir = "/var/lib/numa" # where numa stores TLS CA and cert material + # Defaults: /var/lib/numa on linux (FHS), + # /usr/local/var/numa on macos (homebrew prefix), + # %PROGRAMDATA%\numa on windows. Override for + # containerized deploys or tests that can't + # write to the system path. # [upstream] # mode = "forward" # "forward" (default) — relay to upstream diff --git a/src/lib.rs b/src/lib.rs index 347e72f..08c9df4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,10 @@ pub type Error = Box; pub type Result = std::result::Result; /// Shared config directory for persistent data (services.json, etc). -/// Unix: ~/.config/numa/ (or /usr/local/var/numa/ when running as root daemon) +/// Unix users: ~/.config/numa/ +/// Linux root daemon: /var/lib/numa (FHS) — falls back to /usr/local/var/numa +/// if a pre-v0.10.1 install already lives there. +/// macOS root daemon: /usr/local/var/numa (Homebrew prefix) /// Windows: %APPDATA%\numa pub fn config_dir() -> std::path::PathBuf { #[cfg(windows)] @@ -63,13 +66,15 @@ fn config_dir_unix() -> std::path::PathBuf { } // Running as root daemon (launchd/systemd) — use system-wide path - std::path::PathBuf::from("/usr/local/var/numa") + daemon_data_dir() } /// Default system-wide data directory for TLS certs. Overridable via /// `[server] data_dir = "..."` in numa.toml — this function only provides /// the fallback when the config doesn't set it. -/// Unix: /usr/local/var/numa +/// Linux: /var/lib/numa (FHS) — falls back to /usr/local/var/numa if a +/// pre-v0.10.1 install already has data there. +/// macOS: /usr/local/var/numa (Homebrew prefix) /// Windows: %PROGRAMDATA%\numa pub fn data_dir() -> std::path::PathBuf { #[cfg(windows)] @@ -81,6 +86,75 @@ pub fn data_dir() -> std::path::PathBuf { } #[cfg(not(windows))] { + daemon_data_dir() + } +} + +/// Resolve the system-wide data directory for the running platform. +/// Honors backwards compatibility with pre-v0.10.1 installs that still +/// have their CA cert + services.json under `/usr/local/var/numa`. +#[cfg(not(windows))] +fn daemon_data_dir() -> std::path::PathBuf { + #[cfg(target_os = "linux")] + { + std::path::PathBuf::from(resolve_linux_data_dir( + std::path::Path::new(LEGACY_LINUX_DATA_DIR).exists(), + std::path::Path::new(FHS_LINUX_DATA_DIR).exists(), + )) + } + #[cfg(target_os = "macos")] + { + // macOS uses the Homebrew prefix convention; no FHS migration needed. std::path::PathBuf::from("/usr/local/var/numa") } } + +#[cfg(any(target_os = "linux", test))] +const FHS_LINUX_DATA_DIR: &str = "/var/lib/numa"; +#[cfg(any(target_os = "linux", test))] +const LEGACY_LINUX_DATA_DIR: &str = "/usr/local/var/numa"; + +/// Pure path-decision logic for Linux. Returns the FHS-compliant default +/// for fresh installs, or the legacy pre-v0.10.1 path if data already +/// lives there (so users don't lose their CA cert on upgrade). Extracted +/// as a pure function so the migration logic is unit-testable without +/// touching the real filesystem. +#[cfg(any(target_os = "linux", test))] +fn resolve_linux_data_dir(legacy_exists: bool, fhs_exists: bool) -> &'static str { + if legacy_exists && !fhs_exists { + LEGACY_LINUX_DATA_DIR + } else { + FHS_LINUX_DATA_DIR + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn linux_data_dir_fresh_install_uses_fhs() { + // No data anywhere → fresh install gets the FHS path. + assert_eq!(resolve_linux_data_dir(false, false), "/var/lib/numa"); + } + + #[test] + fn linux_data_dir_upgrading_install_keeps_legacy() { + // Pre-v0.10.1 install: legacy path has data, FHS path doesn't yet. + // Migration must keep using legacy so the user doesn't lose their CA. + assert_eq!(resolve_linux_data_dir(true, false), "/usr/local/var/numa"); + } + + #[test] + fn linux_data_dir_after_migration_uses_fhs() { + // Both paths exist (e.g., user manually copied data to FHS path). + // Prefer FHS since the legacy path is no longer the canonical home. + assert_eq!(resolve_linux_data_dir(true, true), "/var/lib/numa"); + } + + #[test] + fn linux_data_dir_only_fhs_uses_fhs() { + // Only FHS path has data — straightforward fresh-FHS case. + assert_eq!(resolve_linux_data_dir(false, true), "/var/lib/numa"); + } +}