fix: use FHS-compliant /var/lib/numa as Linux data dir default #43

Merged
razvandimescu merged 2 commits from fix/linux-fhs-paths into main 2026-04-08 23:00:28 +08:00
2 changed files with 83 additions and 8 deletions
Showing only changes of commit e8dd95a2bd - Show all commits

View File

@@ -2,11 +2,12 @@
bind_addr = "0.0.0.0:53" bind_addr = "0.0.0.0:53"
api_port = 5380 api_port = 5380
# api_bind_addr = "127.0.0.1" # default; set to "0.0.0.0" for LAN dashboard access # 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 # data_dir = "/var/lib/numa" # where numa stores TLS CA and cert material
# (default: /usr/local/var/numa on unix, # Defaults: /var/lib/numa on linux (FHS),
# %PROGRAMDATA%\numa on windows). Override for # /usr/local/var/numa on macos (homebrew prefix),
# containerized deploys or tests that can't # %PROGRAMDATA%\numa on windows. Override for
# write to the system path. # containerized deploys or tests that can't
# write to the system path.
# [upstream] # [upstream]
# mode = "forward" # "forward" (default) — relay to upstream # mode = "forward" # "forward" (default) — relay to upstream

View File

@@ -26,7 +26,10 @@ pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
/// Shared config directory for persistent data (services.json, etc). /// 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 /// Windows: %APPDATA%\numa
pub fn config_dir() -> std::path::PathBuf { pub fn config_dir() -> std::path::PathBuf {
#[cfg(windows)] #[cfg(windows)]
@@ -63,13 +66,15 @@ fn config_dir_unix() -> std::path::PathBuf {
} }
// Running as root daemon (launchd/systemd) — use system-wide path // 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 /// Default system-wide data directory for TLS certs. Overridable via
/// `[server] data_dir = "..."` in numa.toml — this function only provides /// `[server] data_dir = "..."` in numa.toml — this function only provides
/// the fallback when the config doesn't set it. /// 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 /// Windows: %PROGRAMDATA%\numa
pub fn data_dir() -> std::path::PathBuf { pub fn data_dir() -> std::path::PathBuf {
#[cfg(windows)] #[cfg(windows)]
@@ -81,6 +86,75 @@ pub fn data_dir() -> std::path::PathBuf {
} }
#[cfg(not(windows))] #[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") 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");
}
}