use crate::{ controller::Controller, storage::{atomic_private_write, private_directory}, subscription::HttpSource, xray::systemctl, }; use anyhow::{Result, ensure}; use std::{fs, path::Path, process::Stdio}; use tokio::process::Command; const SERVICE: &str = include_str!("../assets/roentgen.service"); const GUARD: &str = include_str!("../assets/roentgen-guard.service"); const XRAY: &str = include_str!("../assets/roentgen-xray.service"); const BUS: &str = include_str!("../assets/org.roentgen.Controller1.conf"); const POLKIT: &str = include_str!("../assets/org.roentgen.policy"); fn root() -> Result<()> { ensure!( unsafe { libc::geteuid() } == 0, "Run this explicit installation/recovery command as root" ); Ok(()) } pub async fn install() -> Result<()> { root()?; let _ = crate::daemon::runtime()?; crate::xray::preflight().await?; private_directory(Path::new("/etc/roentgen"))?; private_directory(Path::new("/var/lib/roentgen"))?; if crate::xray::identity().is_err() { let status = Command::new("/usr/sbin/useradd") .args([ "--system", "--user-group", "--no-create-home", "--shell", "/sbin/nologin", "roentgen-xray", ]) .stdin(Stdio::null()) .status() .await?; ensure!( status.success(), "Could not create the dedicated Xray account" ); } let _ = systemctl(&["stop", "roentgen.service"]).await; let _lock = crate::locking::controller_lock()?; let mut controller = Controller::open_paths( Path::new("/etc/roentgen"), Path::new("/var/lib/roentgen"), crate::daemon::runtime()?, Box::new(HttpSource), )?; // Do not overwrite the saved guard or enabled intent on reinstallation. #[cfg(feature = "linux-runtime")] if !Path::new("/etc/roentgen/guard.toml").exists() { crate::linux::write_guard(controller.status()?.requested_enabled)?; } let current = std::env::current_exe()?; let target = Path::new("/usr/local/bin/roentgen"); if current != target { atomic_private_write(target, &fs::read(current)?)?; use std::os::unix::fs::PermissionsExt; fs::set_permissions(target, fs::Permissions::from_mode(0o755))?; } for (path, content) in assets() { if let Some(parent) = Path::new(path).parent() { fs::create_dir_all(parent)?; } fs::write(path, content)?; } systemctl(&["daemon-reload"]).await?; // Ask dbus to reload policy without restarting the system bus. let connection = zbus::Connection::system().await?; zbus::fdo::DBusProxy::new(&connection) .await? .reload_config() .await?; systemctl(&["enable", "roentgen-guard.service", "roentgen.service"]).await?; systemctl(&["start", "roentgen-guard.service"]).await?; controller.restore().await?; drop(controller); drop(_lock); systemctl(&["restart", "roentgen.service"]).await?; Ok(()) } fn assets() -> [(&'static str, &'static str); 8] { [ ("/etc/systemd/system/roentgen.service", SERVICE), ("/etc/systemd/system/roentgen-guard.service", GUARD), ("/etc/systemd/system/roentgen-xray.service", XRAY), ("/etc/dbus-1/system.d/org.roentgen.Controller1.conf", BUS), ("/usr/share/polkit-1/actions/org.roentgen.policy", POLKIT), ( "/etc/systemd/system/NetworkManager.service.d/50-roentgen-guard.conf", "[Unit]\nRequires=roentgen-guard.service\nAfter=roentgen-guard.service\n", ), ( "/etc/systemd/system/systemd-networkd.service.d/50-roentgen-guard.conf", "[Unit]\nRequires=roentgen-guard.service\nAfter=roentgen-guard.service\n", ), ( "/etc/systemd/system/network.service.d/50-roentgen-guard.conf", "[Unit]\nRequires=roentgen-guard.service\nAfter=roentgen-guard.service\n", ), ] } pub async fn recover() -> Result<()> { root()?; // Serialize recovery against ordinary controller transactions by stopping it first. let _ = systemctl(&["stop", "roentgen.service"]).await; let _lock = crate::locking::controller_lock()?; crate::storage::persist_recovery_disabled(Path::new("/var/lib/roentgen"))?; let mut runtime = crate::daemon::runtime()?; runtime.disable().await } pub async fn uninstall(purge: bool) -> Result<()> { root()?; recover().await?; systemctl(&[ "disable", "--now", "roentgen.service", "roentgen-guard.service", ]) .await?; #[cfg(feature = "linux-runtime")] crate::linux::firewall::remove()?; for (path, _) in assets() { if Path::new(path).exists() { fs::remove_file(path)?; } } systemctl(&["daemon-reload"]).await?; if Path::new("/run/roentgen").exists() { fs::remove_dir_all("/run/roentgen")?; } if purge { fs::remove_dir_all("/etc/roentgen")?; fs::remove_dir_all("/var/lib/roentgen")?; } if Path::new("/usr/local/bin/roentgen").exists() { fs::remove_file("/usr/local/bin/roentgen")?; } Ok(()) }