From 93b7bb8fe8353f8cb48cbf452647ce97760ae32c Mon Sep 17 00:00:00 2001 From: wonfen Date: Tue, 22 Sep 2026 08:41:55 +0800 Subject: [PATCH] fix(linux): stage approved cores when installing service Pass available cores and their SHA-256 digests explicitly during service installation and repair, instead of relying on automatic discovery that can skip untrusted source locations. Preserve the arguments across root, pkexec, and sudo execution paths. Fixes #7995 --- Changelog.md | 4 +++ src-tauri/src/core/service.rs | 60 +++++++++++++++++++++-------------- 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/Changelog.md b/Changelog.md index 3058f54f4..215dbc5f1 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,10 @@ - 修复 DNS 覆写:空内容报错、初始值覆盖已有设置、已填写字段残留原配置、空字段在高级编辑器中缺失 - 修复首次以管理员身份启动后,系统服务因数据目录所有权不符而拒绝启动内核的问题 +**🐧 Linux** + +- Linux 修复安装或修复服务后仍无法启动内核、开启 TUN 模式的问题 +
diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 9245c1ce2..19e8f9cfc 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -583,11 +583,11 @@ fn install_service() -> Result<()> { .iter() .map(|core| service_core_path(core, ".exe")) .collect::>>()?; - install_windows_service_with_cores(&cores, run_windows_service_installer) + install_service_with_cores(&cores, run_windows_service_installer) } -#[cfg(target_os = "windows")] -fn install_windows_service_with_cores( +#[cfg(any(target_os = "windows", target_os = "linux", test))] +fn install_service_with_cores( cores: &[PathBuf], mut run_installer: impl FnMut(&[std::ffi::OsString]) -> Result<()>, ) -> Result<()> { @@ -712,7 +712,15 @@ fn uninstall_service() -> Result<()> { #[cfg(target_os = "linux")] fn install_service() -> Result<()> { logging!(info, Type::Service, "install service"); + let cores = crate::config::IVerge::VALID_CLASH_CORES + .iter() + .map(|core| service_core_path(core, "")) + .collect::>>()?; + install_service_with_cores(&cores, run_linux_service_installer) +} +#[cfg(target_os = "linux")] +fn run_linux_service_installer(arguments: &[std::ffi::OsString]) -> Result<()> { let install_path = packaged_service_tool_path("clash-verge-service-install", || { Ok(tauri::utils::platform::current_exe()?.with_file_name("clash-verge-service-install")) })?; @@ -723,12 +731,13 @@ fn install_service() -> Result<()> { let elevator = crate::utils::help::linux_elevator(); let output = if linux_running_as_root() { - StdCommand::new(&install_path).output()? + StdCommand::new(&install_path).args(arguments).output()? } else { - let result = StdCommand::new(&elevator) - .arg("--disable-internal-agent") - .arg(&install_path) - .output()?; + let mut elevated = StdCommand::new(&elevator); + if elevator.contains("pkexec") { + elevated.arg("--disable-internal-agent"); + } + let result = elevated.arg(&install_path).args(arguments).output()?; // 如果 pkexec 执行失败,回退到 sudo if !result.status.success() && elevator.contains("pkexec") { @@ -738,7 +747,7 @@ fn install_service() -> Result<()> { "pkexec failed with code {}, falling back to sudo", result.status.code().unwrap_or(-1) ); - StdCommand::new("sudo").arg(&install_path).output()? + StdCommand::new("sudo").arg(&install_path).args(arguments).output()? } else { result } @@ -2243,20 +2252,19 @@ mod tests { } } - #[cfg(target_os = "windows")] #[test] - fn windows_service_install_combines_service_and_attested_cores() -> anyhow::Result<()> { + fn service_install_combines_service_and_attested_cores() -> anyhow::Result<()> { use std::ffi::OsString; - let root = TestDirectory::new("scoop install")?; + let root = TestDirectory::new("service install")?; let cores: Vec<_> = crate::config::IVerge::VALID_CLASH_CORES .iter() - .map(|name| root.path().join(format!("{name}.exe"))) + .map(|name| root.path().join(format!("{name}{}", std::env::consts::EXE_SUFFIX))) .collect(); for core in &cores { std::fs::write(core, b"abc")?; } let mut calls = Vec::new(); - super::install_windows_service_with_cores(&cores, |arguments| { + super::install_service_with_cores(&cores, |arguments| { calls.push(arguments.to_vec()); Ok(()) })?; @@ -2275,16 +2283,19 @@ mod tests { Ok(()) } - #[cfg(target_os = "windows")] #[test] - fn windows_service_install_skips_unavailable_cores() -> anyhow::Result<()> { + fn service_install_skips_unavailable_cores() -> anyhow::Result<()> { let root = TestDirectory::new("missing-core")?; - let core = root.path().join("verge-mihomo.exe"); - let missing = root.path().join("verge-mihomo-alpha.exe"); + let core = root + .path() + .join(format!("verge-mihomo{}", std::env::consts::EXE_SUFFIX)); + let missing = root + .path() + .join(format!("verge-mihomo-alpha{}", std::env::consts::EXE_SUFFIX)); std::fs::write(&core, b"abc")?; let cores = [core.clone(), missing.clone(), root.path().to_path_buf()]; let mut calls = Vec::new(); - super::install_windows_service_with_cores(&cores, |arguments| { + super::install_service_with_cores(&cores, |arguments| { calls.push(arguments.to_vec()); Ok(()) })?; @@ -2293,7 +2304,7 @@ mod tests { assert_eq!(calls[0][0], "--install-service"); assert_eq!(calls[0][2], core.as_os_str()); - let error = super::install_windows_service_with_cores(&[missing, root.path().to_path_buf()], |_| { + let error = super::install_service_with_cores(&[missing, root.path().to_path_buf()], |_| { panic!("no available core must fail before invoking the installer") }) .expect_err("installation requires at least one available core"); @@ -2301,14 +2312,15 @@ mod tests { Ok(()) } - #[cfg(target_os = "windows")] #[test] - fn windows_service_install_propagates_installer_failure() -> anyhow::Result<()> { + fn service_install_propagates_installer_failure() -> anyhow::Result<()> { let root = TestDirectory::new("install-failure")?; - let core = root.path().join("verge-mihomo.exe"); + let core = root + .path() + .join(format!("verge-mihomo{}", std::env::consts::EXE_SUFFIX)); std::fs::write(&core, b"abc")?; let mut calls = 0; - let result = super::install_windows_service_with_cores(std::slice::from_ref(&core), |_| { + let result = super::install_service_with_cores(std::slice::from_ref(&core), |_| { calls += 1; bail!("installer failed with exit code 1") });