From e95808e6be05acf3614f0440b6178e813f56cbaf Mon Sep 17 00:00:00 2001 From: MystiPanda Date: Sun, 31 Mar 2024 21:44:34 +0800 Subject: [PATCH] feat: Try support service mode for MacOS --- .gitignore | 2 +- scripts/check.mjs | 80 +++++++------------- src-tauri/src/cmds.rs | 19 ----- src-tauri/src/core/core.rs | 49 ++++++------ src-tauri/src/core/service.rs | 70 ++++++++++++++++-- src-tauri/src/utils/dirs.rs | 5 +- src-tauri/src/utils/init.rs | 11 ++- src-tauri/src/utils/unix_helper.rs | 3 +- src/components/setting/setting-system.tsx | 90 +++++++++-------------- src/locales/en.json | 3 +- src/locales/zh.json | 3 +- 11 files changed, 158 insertions(+), 177 deletions(-) diff --git a/.gitignore b/.gitignore index 0c1f9551..d3537d98 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,4 @@ dist-ssr update.json scripts/_env.sh .vscode -.tool-version \ No newline at end of file +.tool-versions \ No newline at end of file diff --git a/scripts/check.mjs b/scripts/check.mjs index 2fdb0e9e..ad9c4aac 100644 --- a/scripts/check.mjs +++ b/scripts/check.mjs @@ -353,8 +353,8 @@ const resolvePlugin = async () => { } }; -// Linux service chmod -const resolveLinuxServicePermission = async () => { +// service chmod +const resolveServicePermission = async () => { const serviceExecutables = [ "clash-verge-service", "install-service", @@ -376,37 +376,30 @@ const resolveLinuxServicePermission = async () => { const SERVICE_URL = `https://github.com/clash-verge-rev/clash-verge-service/releases/download/${SIDECAR_HOST}`; -const resolveService = () => +const resolveService = () => { + let ext = platform === "win32" ? ".exe" : ""; resolveResource({ - file: "clash-verge-service.exe", - downloadURL: `${SERVICE_URL}/clash-verge-service.exe`, - }); -const resolveLinuxService = () => { - resolveResource({ - file: "clash-verge-service", - downloadURL: `${SERVICE_URL}/clash-verge-service`, + file: "clash-verge-service" + ext, + downloadURL: `${SERVICE_URL}/clash-verge-service${ext}`, }); }; -const resolveInstall = () => + +const resolveInstall = () => { + let ext = platform === "win32" ? ".exe" : ""; resolveResource({ - file: "install-service.exe", - downloadURL: `${SERVICE_URL}/install-service.exe`, + file: "install-service" + ext, + downloadURL: `${SERVICE_URL}/install-service.exe${ext}`, }); -const resolveLinuxInstall = () => +}; + +const resolveUninstall = () => { + let ext = platform === "win32" ? ".exe" : ""; resolveResource({ - file: "install-service", - downloadURL: `${SERVICE_URL}/install-service`, - }); -const resolveUninstall = () => - resolveResource({ - file: "uninstall-service.exe", - downloadURL: `${SERVICE_URL}/uninstall-service.exe`, - }); -const resolveLinuxUninstall = () => - resolveResource({ - file: "uninstall-service", - downloadURL: `${SERVICE_URL}/uninstall-service`, + file: "uninstall-service" + ext, + downloadURL: `${SERVICE_URL}/uninstall-service${ext}`, }); +}; + const resolveSetDnsScript = () => resolveResource({ file: "set_dns.sh", @@ -453,27 +446,9 @@ const tasks = [ retry: 5, }, { name: "plugin", func: resolvePlugin, retry: 5, winOnly: true }, - { name: "service", func: resolveService, retry: 5, winOnly: true }, - { - name: "linux_service", - func: resolveLinuxService, - retry: 5, - linuxOnly: true, - }, - { name: "install", func: resolveInstall, retry: 5, winOnly: true }, - { - name: "linux_install", - func: resolveLinuxInstall, - retry: 5, - linuxOnly: true, - }, - { name: "uninstall", func: resolveUninstall, retry: 5, winOnly: true }, - { - name: "linux_uninstall", - func: resolveLinuxUninstall, - retry: 5, - linuxOnly: true, - }, + { name: "service", func: resolveService, retry: 5 }, + { name: "install", func: resolveInstall, retry: 5 }, + { name: "uninstall", func: resolveUninstall, retry: 5 }, { name: "set_dns_script", func: resolveSetDnsScript, retry: 5 }, { name: "unset_dns_script", func: resolveUnSetDnsScript, retry: 5 }, { name: "mmdb", func: resolveMmdb, retry: 5 }, @@ -486,18 +461,19 @@ const tasks = [ winOnly: true, }, { - name: "linux_service_chmod", - func: resolveLinuxServicePermission, + name: "service_chmod", + func: resolveServicePermission, retry: 1, - linuxOnly: true, + unixOnly: true, }, ]; async function runTask() { const task = tasks.shift(); if (!task) return; - if (task.winOnly && process.platform !== "win32") return runTask(); - if (task.linuxOnly && process.platform !== "linux") return runTask(); + if (task.winOnly && platform !== "win32") return runTask(); + if (task.linuxOnly && platform !== "linux") return runTask(); + if (task.unixOnly && platform === "win32") return runTask(); for (let i = 0; i < task.retry; i++) { try { diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index b61aa9f8..eb6c0010 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -331,7 +331,6 @@ pub fn exit_app(app_handle: tauri::AppHandle) { std::process::exit(0); } -#[cfg(any(windows, target_os = "linux"))] pub mod service { use super::*; use crate::core::service; @@ -352,24 +351,6 @@ pub mod service { } } -#[cfg(not(any(windows, target_os = "linux")))] -pub mod service { - use super::*; - - #[tauri::command] - pub async fn check_service() -> CmdResult { - Ok(()) - } - #[tauri::command] - pub async fn install_service() -> CmdResult { - Ok(()) - } - #[tauri::command] - pub async fn uninstall_service() -> CmdResult { - Ok(()) - } -} - #[cfg(not(windows))] pub mod uwp { use super::*; diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index b58d0afc..47732206 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -1,3 +1,4 @@ +use super::service; use super::{clash_api, logger::Logger}; use crate::log_err; use crate::{config::*, utils::dirs}; @@ -93,10 +94,9 @@ impl CoreManager { None => false, }; - #[cfg(any(target_os = "windows", target_os = "linux"))] if *self.use_service_mode.lock() { log::debug!(target: "app", "stop the core by service"); - log_err!(super::service::stop_core_by_service().await); + log_err!(service::stop_core_by_service().await); should_kill = true; } @@ -105,32 +105,27 @@ impl CoreManager { sleep(Duration::from_millis(500)).await; } - #[cfg(any(target_os = "windows", target_os = "linux"))] - { - use super::service; + // 服务模式 + let enable = { Config::verge().latest().enable_service_mode }; + let enable = enable.unwrap_or(false); - // 服务模式 - let enable = { Config::verge().latest().enable_service_mode }; - let enable = enable.unwrap_or(false); + *self.use_service_mode.lock() = enable; - *self.use_service_mode.lock() = enable; + if enable { + // 服务模式启动失败就直接运行sidecar + log::debug!(target: "app", "try to run core in service mode"); - if enable { - // 服务模式启动失败就直接运行sidecar - log::debug!(target: "app", "try to run core in service mode"); - - match (|| async { - service::check_service().await?; - service::run_core_by_service(&config_path).await - })() - .await - { - Ok(_) => return Ok(()), - Err(err) => { - // 修改这个值,免得stop出错 - *self.use_service_mode.lock() = false; - log::error!(target: "app", "{err}"); - } + match (|| async { + service::check_service().await?; + service::run_core_by_service(&config_path).await + })() + .await + { + Ok(_) => return Ok(()), + Err(err) => { + // 修改这个值,免得stop出错 + *self.use_service_mode.lock() = false; + log::error!(target: "app", "{err}"); } } } @@ -205,7 +200,6 @@ impl CoreManager { /// 重启内核 pub fn recover_core(&'static self) -> Result<()> { // 服务模式不管 - #[cfg(any(target_os = "windows", target_os = "linux"))] if *self.use_service_mode.lock() { return Ok(()); } @@ -238,11 +232,10 @@ impl CoreManager { /// 停止核心运行 pub fn stop_core(&self) -> Result<()> { - #[cfg(any(target_os = "windows", target_os = "linux"))] if *self.use_service_mode.lock() { log::debug!(target: "app", "stop the core by service"); tauri::async_runtime::block_on(async move { - log_err!(super::service::stop_core_by_service().await); + log_err!(service::stop_core_by_service().await); }); return Ok(()); } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 3027a956..af529ea1 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -1,5 +1,3 @@ -#![cfg(any(target_os = "windows", target_os = "linux"))] - use crate::config::Config; use crate::utils::dirs; use anyhow::{bail, Context, Result}; @@ -31,14 +29,13 @@ pub struct JsonResponse { /// Install the Clash Verge Service /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 -/// +/// #[cfg(target_os = "windows")] pub async fn install_service() -> Result<()> { use deelevate::{PrivilegeLevel, Token}; use runas::Command as RunasCommand; use std::os::windows::process::CommandExt; - let binary_path = dirs::service_path()?; let install_path = binary_path.with_file_name("install-service.exe"); @@ -80,7 +77,11 @@ pub async fn install_service() -> Result<()> { let elevator = crate::utils::unix_helper::linux_elevator(); let status = match get_effective_uid() { 0 => StdCommand::new(installer_path).status()?, - _ => StdCommand::new(elevator).arg("sh").arg("-c").arg(installer_path).status()?, + _ => StdCommand::new(elevator) + .arg("sh") + .arg("-c") + .arg(installer_path) + .status()?, }; if !status.success() { @@ -93,6 +94,30 @@ pub async fn install_service() -> Result<()> { Ok(()) } +#[cfg(target_os = "macos")] +pub async fn install_service() -> Result<()> { + let binary_path = dirs::service_path()?; + let installer_path = binary_path.with_file_name("install-service"); + + if !installer_path.exists() { + bail!("installer not found"); + } + let shell = installer_path.to_string_lossy(); + let command = format!(r#"do shell script "{shell}" with administrator privileges"#); + + let status = StdCommand::new("osascript") + .args(vec!["-e", &command]) + .status()?; + + if !status.success() { + bail!( + "failed to install service with status {}", + status.code().unwrap() + ); + } + + Ok(()) +} /// Uninstall the Clash Verge Service /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 #[cfg(target_os = "windows")] @@ -101,7 +126,6 @@ pub async fn uninstall_service() -> Result<()> { use runas::Command as RunasCommand; use std::os::windows::process::CommandExt; - let binary_path = dirs::service_path()?; let uninstall_path = binary_path.with_file_name("uninstall-service.exe"); @@ -143,7 +167,11 @@ pub async fn uninstall_service() -> Result<()> { let elevator = crate::utils::unix_helper::linux_elevator(); let status = match get_effective_uid() { 0 => StdCommand::new(uninstaller_path).status()?, - _ => StdCommand::new(elevator).arg("sh").arg("-c").arg(uninstaller_path).status()?, + _ => StdCommand::new(elevator) + .arg("sh") + .arg("-c") + .arg(uninstaller_path) + .status()?, }; if !status.success() { @@ -156,6 +184,32 @@ pub async fn uninstall_service() -> Result<()> { Ok(()) } +#[cfg(target_os = "macos")] +pub async fn uninstall_service() -> Result<()> { + let binary_path = dirs::service_path()?; + let uninstaller_path = binary_path.with_file_name("uninstall-service"); + + if !uninstaller_path.exists() { + bail!("uninstaller not found"); + } + + let shell = uninstaller_path.to_string_lossy(); + let command = format!(r#"do shell script "{shell}" with administrator privileges"#); + + let status = StdCommand::new("osascript") + .args(vec!["-e", &command]) + .status()?; + + if !status.success() { + bail!( + "failed to install service with status {}", + status.code().unwrap() + ); + } + + Ok(()) +} + /// check the windows service status pub async fn check_service() -> Result { let url = format!("{SERVICE_URL}/get_clash"); @@ -185,7 +239,7 @@ pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> { let clash_core = { Config::verge().latest().clash_core.clone() }; let clash_core = clash_core.unwrap_or("clash".into()); - let bin_ext = if cfg!(windows) {".exe"} else {""}; + let bin_ext = if cfg!(windows) { ".exe" } else { "" }; let clash_bin = format!("{clash_core}{bin_ext}"); let bin_path = current_exe()?.with_file_name(clash_bin); let bin_path = dirs::path_to_str(&bin_path)?; diff --git a/src-tauri/src/utils/dirs.rs b/src-tauri/src/utils/dirs.rs index 8a38bd17..675a78eb 100644 --- a/src-tauri/src/utils/dirs.rs +++ b/src-tauri/src/utils/dirs.rs @@ -92,17 +92,16 @@ pub fn clash_pid_path() -> Result { Ok(app_home_dir()?.join("clash.pid")) } -#[cfg(target_os = "linux")] +#[cfg(not(target_os = "windows"))] pub fn service_path() -> Result { Ok(app_resources_dir()?.join("clash-verge-service")) -} +} #[cfg(windows)] pub fn service_path() -> Result { Ok(app_resources_dir()?.join("clash-verge-service.exe")) } -#[cfg(any(windows, target_os = "linux"))] pub fn service_log_file() -> Result { use chrono::Local; diff --git a/src-tauri/src/utils/init.rs b/src-tauri/src/utils/init.rs index 5331de5e..8ffabc87 100644 --- a/src-tauri/src/utils/init.rs +++ b/src-tauri/src/utils/init.rs @@ -135,13 +135,12 @@ pub fn delete_log() -> Result<()> { for file in fs::read_dir(&log_dir)?.flatten() { let _ = process_file(file); } - #[cfg(target_os = "windows")] - { - let service_log_dir = log_dir.join("service"); - for file in fs::read_dir(&service_log_dir)?.flatten() { - let _ = process_file(file); - } + + let service_log_dir = log_dir.join("service"); + for file in fs::read_dir(&service_log_dir)?.flatten() { + let _ = process_file(file); } + Ok(()) } diff --git a/src-tauri/src/utils/unix_helper.rs b/src-tauri/src/utils/unix_helper.rs index d16fc67e..c5736459 100644 --- a/src-tauri/src/utils/unix_helper.rs +++ b/src-tauri/src/utils/unix_helper.rs @@ -1,7 +1,6 @@ -use std::process::Command; - #[cfg(target_os = "linux")] pub fn linux_elevator() -> &'static str { + use std::process::Command; match Command::new("which").arg("pkexec").output() { Ok(output) => { if output.stdout.is_empty() { diff --git a/src/components/setting/setting-system.tsx b/src/components/setting/setting-system.tsx index fda18986..44b67f8f 100644 --- a/src/components/setting/setting-system.tsx +++ b/src/components/setting/setting-system.tsx @@ -17,24 +17,17 @@ interface Props { onError?: (err: Error) => void; } -const isServiceModeAvailable = - getSystem() === "windows" || getSystem() === "linux"; - const SettingSystem = ({ onError }: Props) => { const { t } = useTranslation(); const { verge, mutateVerge, patchVerge } = useVerge(); // service mode - const { data: serviceStatus } = useSWR( - isServiceModeAvailable ? "checkService" : null, - checkService, - { - revalidateIfStale: false, - shouldRetryOnError: false, - focusThrottleInterval: 36e5, // 1 hour - } - ); + const { data: serviceStatus } = useSWR("checkService", checkService, { + revalidateIfStale: false, + shouldRetryOnError: false, + focusThrottleInterval: 36e5, // 1 hour + }); const serviceRef = useRef(null); const sysproxyRef = useRef(null); @@ -57,22 +50,13 @@ const SettingSystem = ({ onError }: Props) => { - {isServiceModeAvailable && ( - - )} + - + { - {isServiceModeAvailable && ( - serviceRef.current?.open()} - > - - - } - > - onChangeData({ enable_service_mode: e })} - onGuard={(e) => patchVerge({ enable_service_mode: e })} + serviceRef.current?.open()} > - - - - )} + + } + > + onChangeData({ enable_service_mode: e })} + onGuard={(e) => patchVerge({ enable_service_mode: e })} + > + + +