From d608ffeeda1a479c9dc0e5f6ad269ba07a510d7f Mon Sep 17 00:00:00 2001 From: Andrew Guschin Date: Fri, 30 Oct 2026 03:10:41 +0400 Subject: Implement Linux proxy controller, subscription state, and CLI/TUI --- src/installation.rs | 152 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 src/installation.rs (limited to 'src/installation.rs') diff --git a/src/installation.rs b/src/installation.rs new file mode 100644 index 0000000..bc86db6 --- /dev/null +++ b/src/installation.rs @@ -0,0 +1,152 @@ +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(()) +} -- cgit v1.2.3