fix(linux): copy binary to /usr/local/bin when source path isn't world-traversable
DynamicUser=yes' transient account can only traverse world-x directories. The CI binary at /home/runner/work/numa/numa/target/release/numa fails exec with EACCES because /home/runner is mode 0700; same applies to a build under /home/<user>/, ~/.cargo/bin, or any private $HOME tree. install_service_binary_linux now walks the binary's path. If every ancestor grants world-execute (Linuxbrew /home/linuxbrew is 0755, /usr/local/bin is fine, install.sh layout works), keep the source path so brew/distro upgrades propagate in place. Otherwise copy to /usr/local/bin/numa and reference that in the unit. Locally verified both branches in an Ubuntu 24.04 systemd container: - CI-like /home/runner (0700) → copies + service binds 5380 - Brew-like /home/linuxbrew (0755) → keeps source path + service binds 5380
This commit is contained in:
@@ -1664,10 +1664,65 @@ fn uninstall_linux() -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fallback install location when current_exe() sits on a path the
|
||||||
|
/// dynamic user cannot traverse (e.g. `/home/<user>/` mode 0700).
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn linux_service_exe_path() -> std::path::PathBuf {
|
||||||
|
std::path::PathBuf::from("/usr/local/bin/numa")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True iff every ancestor of `p` (excluding `/`) grants world-execute —
|
||||||
|
/// i.e. the `DynamicUser=yes` service account can traverse the path and
|
||||||
|
/// exec the binary without being in any group. Linuxbrew's
|
||||||
|
/// `/home/linuxbrew` is 0755 (traversable, keep brew's path, upgrades
|
||||||
|
/// via `brew` propagate). A build tree under `/home/<user>/` (0700) or
|
||||||
|
/// `~/.cargo/bin/` is not (copy to /usr/local/bin so systemd can reach it).
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn path_world_traversable_linux(p: &std::path::Path) -> bool {
|
||||||
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
let mut current = p;
|
||||||
|
while let Some(parent) = current.parent() {
|
||||||
|
if parent.as_os_str().is_empty() || parent == std::path::Path::new("/") {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
match std::fs::metadata(parent) {
|
||||||
|
Ok(m) if m.permissions().mode() & 0o001 != 0 => {}
|
||||||
|
_ => return false,
|
||||||
|
}
|
||||||
|
current = parent;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn install_service_binary_linux() -> Result<std::path::PathBuf, String> {
|
||||||
|
let src = std::env::current_exe().map_err(|e| format!("current_exe(): {}", e))?;
|
||||||
|
if path_world_traversable_linux(&src) {
|
||||||
|
return Ok(src);
|
||||||
|
}
|
||||||
|
let dst = linux_service_exe_path();
|
||||||
|
if src == dst {
|
||||||
|
return Ok(dst);
|
||||||
|
}
|
||||||
|
if let Some(parent) = dst.parent() {
|
||||||
|
std::fs::create_dir_all(parent)
|
||||||
|
.map_err(|e| format!("failed to create {}: {}", parent.display(), e))?;
|
||||||
|
}
|
||||||
|
std::fs::copy(&src, &dst).map_err(|e| {
|
||||||
|
format!(
|
||||||
|
"failed to copy {} -> {}: {}",
|
||||||
|
src.display(),
|
||||||
|
dst.display(),
|
||||||
|
e
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Ok(dst)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn install_service_linux() -> Result<(), String> {
|
fn install_service_linux() -> Result<(), String> {
|
||||||
let unit = include_str!("../numa.service");
|
let exe = install_service_binary_linux()?;
|
||||||
let unit = replace_exe_path(unit)?;
|
let unit = include_str!("../numa.service").replace("{{exe_path}}", &exe.to_string_lossy());
|
||||||
std::fs::write(SYSTEMD_UNIT, unit)
|
std::fs::write(SYSTEMD_UNIT, unit)
|
||||||
.map_err(|e| format!("failed to write {}: {}", SYSTEMD_UNIT, e))?;
|
.map_err(|e| format!("failed to write {}: {}", SYSTEMD_UNIT, e))?;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user