From 4f6159d9616bf485af38bacaf08ed98a5afe0aa5 Mon Sep 17 00:00:00 2001 From: Razvan Dimescu Date: Sat, 18 Apr 2026 08:20:07 +0300 Subject: [PATCH] refactor(linux): switch to DynamicUser=yes, drop install-time user creation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit AUR installs never call `numa install` — PKGBUILD drops the unit straight into /usr/lib/systemd/system and the user runs `systemctl enable numa`. With User=numa the Rust installer's useradd code never fires there, breaking Arch out of the box. DynamicUser=yes sidesteps packaging entirely — systemd allocates a transient UID per start and remaps StateDirectory ownership (including legacy root-owned trees) automatically. Works on any modern systemd. Drops the ensure_numa_user_linux/chown helpers plus NUMA_USER; the unit file alone now captures the privilege-drop story. --- numa.service | 8 +++---- src/system_dns.rs | 60 ----------------------------------------------- 2 files changed, 4 insertions(+), 64 deletions(-) diff --git a/numa.service b/numa.service index 44e90c5..5380b83 100644 --- a/numa.service +++ b/numa.service @@ -9,14 +9,14 @@ ExecStart={{exe_path}} Restart=always RestartSec=2 -User=numa -Group=numa +# Transient system user per start; no PKGBUILD/sysusers setup required. +# systemd remaps the StateDirectory ownership to the dynamic UID on each +# launch, including legacy root-owned trees from pre-drop installs. +DynamicUser=yes AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE -# StateDirectory maps to crate::data_dir() default on Linux (/var/lib/numa). -# systemd auto-creates + chowns on every start, fixing legacy root-owned trees. StateDirectory=numa StateDirectoryMode=0750 ConfigurationDirectory=numa diff --git a/src/system_dns.rs b/src/system_dns.rs index 7b4de42..b70b9d9 100644 --- a/src/system_dns.rs +++ b/src/system_dns.rs @@ -1664,68 +1664,8 @@ fn uninstall_linux() -> Result<(), String> { Ok(()) } -#[cfg(target_os = "linux")] -const NUMA_USER: &str = "numa"; - -#[cfg(target_os = "linux")] -fn ensure_numa_user_linux() -> Result<(), String> { - let _ = std::process::Command::new("groupadd") - .args(["-f", "-r", NUMA_USER]) - .status(); - - let data_dir = crate::data_dir(); - let status = std::process::Command::new("useradd") - .args([ - "-r", - "-g", - NUMA_USER, - "-d", - &data_dir.to_string_lossy(), - "-s", - "/usr/sbin/nologin", - "-c", - "Numa DNS service", - NUMA_USER, - ]) - .status() - .map_err(|e| format!("failed to run useradd: {}", e))?; - - // useradd exit 9 = "username already in use"; idempotent reinstall. - match status.code() { - Some(0) | Some(9) => Ok(()), - Some(code) => Err(format!("useradd {} failed (exit {})", NUMA_USER, code)), - None => Err(format!("useradd {} killed by signal", NUMA_USER)), - } -} - -#[cfg(target_os = "linux")] -fn chown_data_dir_to_numa_linux() -> Result<(), String> { - let dir = crate::data_dir(); - std::fs::create_dir_all(&dir) - .map_err(|e| format!("failed to create {}: {}", dir.display(), e))?; - let owner = format!("{0}:{0}", NUMA_USER); - let status = std::process::Command::new("chown") - .args(["-R", &owner, &dir.to_string_lossy()]) - .status() - .map_err(|e| format!("failed to run chown: {}", e))?; - if !status.success() { - return Err(format!( - "chown {} failed (exit {})", - dir.display(), - status.code().unwrap_or(-1) - )); - } - Ok(()) -} - #[cfg(target_os = "linux")] fn install_service_linux() -> Result<(), String> { - // Create the numa account and hand it ownership of data_dir before the - // first start — TLS-cert generation and state writes happen on the - // unit's first launch and need to land on a numa-owned tree. - ensure_numa_user_linux()?; - chown_data_dir_to_numa_linux()?; - let unit = include_str!("../numa.service"); let unit = replace_exe_path(unit)?; std::fs::write(SYSTEMD_UNIT, unit)