From 3f3fad0db7b0b7595005cd98de5557fd6f87be2e Mon Sep 17 00:00:00 2001 From: huzibaca Date: Thu, 10 Oct 2024 00:34:36 +0800 Subject: [PATCH] feat: Modify startup logic and install services by default --- src-tauri/src/cmds.rs | 10 - src-tauri/src/config/verge.rs | 5 - src-tauri/src/core/core.rs | 73 +----- src-tauri/src/core/service.rs | 212 +++++++----------- src-tauri/src/core/tray.rs | 63 ++---- src-tauri/src/feat.rs | 5 - src-tauri/src/lib.rs | 2 - src-tauri/src/utils/help.rs | 15 ++ src-tauri/src/utils/resolve.rs | 7 + .../setting/mods/service-switcher.tsx | 134 ----------- src/components/setting/setting-system.tsx | 27 +-- src/services/cmds.ts | 9 - 12 files changed, 126 insertions(+), 436 deletions(-) delete mode 100644 src/components/setting/mods/service-switcher.tsx diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 9152151a..a2c7cb8a 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -383,16 +383,6 @@ pub mod service { pub async fn check_service() -> CmdResult { wrap_err!(service::check_service().await) } - - #[tauri::command] - pub async fn install_service(passwd: String) -> CmdResult { - wrap_err!(service::install_service(passwd).await) - } - - #[tauri::command] - pub async fn uninstall_service(passwd: String) -> CmdResult { - wrap_err!(service::uninstall_service(passwd).await) - } } #[cfg(not(windows))] diff --git a/src-tauri/src/config/verge.rs b/src-tauri/src/config/verge.rs index 2aaa98b1..3f988831 100644 --- a/src-tauri/src/config/verge.rs +++ b/src-tauri/src/config/verge.rs @@ -56,10 +56,6 @@ pub struct IVerge { /// clash tun mode pub enable_tun_mode: Option, - /// windows service mode - #[serde(skip_serializing_if = "Option::is_none")] - pub enable_service_mode: Option, - /// can the app auto startup pub enable_auto_launch: Option, @@ -279,7 +275,6 @@ impl IVerge { patch!(tun_tray_icon); patch!(enable_tun_mode); - patch!(enable_service_mode); patch!(enable_auto_launch); patch!(enable_silent_start); patch!(enable_random_port); diff --git a/src-tauri/src/core/core.rs b/src-tauri/src/core/core.rs index 863f744f..3acc03e2 100644 --- a/src-tauri/src/core/core.rs +++ b/src-tauri/src/core/core.rs @@ -6,7 +6,6 @@ use anyhow::{bail, Result}; use once_cell::sync::OnceCell; use serde_yaml::Mapping; use std::{sync::Arc, time::Duration}; -use tauri_plugin_shell::process::{CommandChild, CommandEvent}; use tauri_plugin_shell::ShellExt; use tokio::sync::Mutex; use tokio::time::sleep; @@ -14,7 +13,6 @@ use tokio::time::sleep; #[derive(Debug)] pub struct CoreManager { running: Arc>, - sidecar: Arc>>, } impl CoreManager { @@ -22,7 +20,6 @@ impl CoreManager { static CORE_MANAGER: OnceCell = OnceCell::new(); CORE_MANAGER.get_or_init(|| CoreManager { running: Arc::new(Mutex::new(false)), - sidecar: Arc::new(Mutex::new(None)), }) } @@ -75,6 +72,7 @@ impl CoreManager { log::debug!("core is not running"); return Ok(()); } + println!("stop core"); // 关闭tun模式 let mut disable = Mapping::new(); @@ -84,22 +82,18 @@ impl CoreManager { log::debug!(target: "app", "disable tun mode"); log_err!(clash_api::patch_configs(&disable).await); - if let Some(sidecar) = self.sidecar.lock().await.take() { - let _ = sidecar.kill(); - } else { - // 服务模式 - if service::check_service().await.is_ok() { - log::debug!(target: "app", "stop the core by service"); - log_err!(service::stop_core_by_service().await); - } + // 服务模式 + if service::check_service().await.is_ok() { + log::debug!(target: "app", "stop the core by service"); + service::stop_core_by_service().await?; } *running = false; - Ok(()) } /// 启动核心 pub async fn start_core(&self) -> Result<()> { + println!("start core"); let mut running = self.running.lock().await; if *running { log::debug!("core is running"); @@ -107,62 +101,13 @@ impl CoreManager { } let config_path = Config::generate_file(ConfigType::Run)?; - let clash_core = { Config::verge().latest().clash_core.clone() }; - let clash_core = clash_core.unwrap_or("verge-mihomo".into()); - // 服务模式 - let service_enable = { Config::verge().latest().enable_service_mode }; - let service_enable = service_enable.unwrap_or(false); + // 服务模式 if service::check_service().await.is_ok() { log::debug!(target: "app", "try to run core in service mode"); - if service_enable { - service::run_core_by_service(&config_path).await?; - let mut sidecar = self.sidecar.lock().await; - if sidecar.is_some() { - sidecar.take(); - } - *running = true; - return Ok(()); - } + service::run_core_by_service(&config_path).await?; + *running = true; } - - let app_dir = dirs::app_home_dir()?; - let app_dir = dirs::path_to_str(&app_dir)?; - let config_path = dirs::path_to_str(&config_path)?; - let args = vec!["-d", app_dir, "-f", config_path]; - let app_handle = handle::Handle::global().app_handle().unwrap(); - let cmd = app_handle.shell().sidecar(clash_core)?; - let (mut rx, cmd_child) = cmd.args(args).spawn()?; - let mut sidecar = self.sidecar.lock().await; - - *sidecar = Some(cmd_child); - - tauri::async_runtime::spawn(async move { - while let Some(event) = rx.recv().await { - match event { - CommandEvent::Stdout(line) => { - let line = String::from_utf8(line).unwrap_or_default(); - log::info!(target: "app", "[mihomo]: {line}"); - Logger::global().set_log(line); - } - CommandEvent::Stderr(err) => { - let err = String::from_utf8(err).unwrap_or_default(); - log::error!(target: "app", "[mihomo]: {err}"); - Logger::global().set_log(err); - } - CommandEvent::Error(err) => { - log::error!(target: "app", "[mihomo]: {err}"); - Logger::global().set_log(err); - } - CommandEvent::Terminated(_) => { - log::info!(target: "app", "mihomo core terminated"); - break; - } - _ => {} - } - } - }); - *running = true; Ok(()) } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index e27c3684..222d0ca4 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -26,32 +26,42 @@ pub struct JsonResponse { pub data: Option, } -#[cfg(not(target_os = "windows"))] -pub fn sudo(passwd: &String, cmd: String) -> StdCommand { - let shell = format!("echo \"{}\" | sudo -S {}", passwd, cmd); - let mut command = StdCommand::new("bash"); - command.arg("-c").arg(shell); - command -} - /// Install the Clash Verge Service /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 /// #[cfg(target_os = "windows")] -pub async fn install_service(_passwd: String) -> Result<()> { +pub async fn reinstall_service(_passwd: String) -> 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"); + let uninstall_path = binary_path.with_file_name("uninstall-service.exe"); if !install_path.exists() { bail!("installer exe not found"); } + if !uninstall_path.exists() { + bail!("uninstaller exe not found"); + } + let token = Token::with_current_process()?; let level = token.privilege_level()?; + let status = match level { + PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?, + _ => StdCommand::new(uninstall_path) + .creation_flags(0x08000000) + .status()?, + }; + + if !status.success() { + bail!( + "failed to uninstall service with status {}", + status.code().unwrap() + ); + } let status = match level { PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?, @@ -71,45 +81,52 @@ pub async fn install_service(_passwd: String) -> Result<()> { } #[cfg(target_os = "linux")] -pub async fn install_service(passwd: String) -> Result<()> { +pub async fn reinstall_service(passwd: String) -> Result<()> { use users::get_effective_uid; let binary_path = dirs::service_path()?; let installer_path = binary_path.with_file_name("install-service"); + let uninstaller_path = binary_path.with_file_name("uninstall-service"); + if !installer_path.exists() { bail!("installer not found"); } - let output = match get_effective_uid() { - 0 => { - StdCommand::new("chmod") - .arg("+x") - .arg(installer_path.clone()) - .output()?; - StdCommand::new("chmod") - .arg("+x") - .arg(binary_path) - .output()?; - StdCommand::new(installer_path.clone()).output()? - } - _ => { - sudo( - &passwd, - format!("chmod +x {}", installer_path.to_string_lossy()), - ) - .output()?; - sudo( - &passwd, - format!("chmod +x {}", binary_path.to_string_lossy()), - ) - .output()?; - sudo(&passwd, format!("{}", installer_path.to_string_lossy())).output()? - } + if !uninstaller_path.exists() { + bail!("uninstaller not found"); + } + + let elevator = crate::utils::help::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()?, }; - if !output.status.success() { + + if !status.success() { bail!( - "failed to install service with error: {}", - String::from_utf8_lossy(&output.stderr) + "failed to install service with status {}", + status.code().unwrap() + ); + } + + let elevator = crate::utils::help::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()?, + }; + + if !status.success() { + bail!( + "failed to install service with status {}", + status.code().unwrap() ); } @@ -117,38 +134,35 @@ pub async fn install_service(passwd: String) -> Result<()> { } #[cfg(target_os = "macos")] -pub async fn install_service(passwd: String) -> Result<()> { +pub async fn reinstall_service() -> Result<()> { let binary_path = dirs::service_path()?; let installer_path = binary_path.with_file_name("install-service"); + let uninstall_path = binary_path.with_file_name("uninstall-service"); if !installer_path.exists() { bail!("installer not found"); } - sudo( - &passwd, - format!( - "chmod +x {}", - installer_path.to_string_lossy().replace(" ", "\\ ") - ), - ) - .output()?; - let output = sudo( - &passwd, - installer_path - .to_string_lossy() - .replace(" ", "\\ ") - .to_string(), - ) - .output()?; - - if !output.status.success() { - bail!( - "failed to install service with error: {}", - String::from_utf8_lossy(&output.stderr) - ); + if !uninstall_path.exists() { + bail!("uninstaller not found"); } + let install_shell: String = installer_path.to_string_lossy().replace(" ", "\\\\ "); + let uninstall_shell: String = uninstall_path.to_string_lossy().replace(" ", "\\\\ "); + let command = format!( + r#"do shell script "{uninstall_shell} && {install_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 @@ -186,82 +200,6 @@ pub async fn uninstall_service(_passwd: String) -> Result<()> { Ok(()) } -#[cfg(target_os = "linux")] -pub async fn uninstall_service(passwd: String) -> Result<()> { - use users::get_effective_uid; - - 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 output = match get_effective_uid() { - 0 => { - StdCommand::new("chmod") - .arg("+x") - .arg(uninstaller_path.clone()) - .output()?; - StdCommand::new(uninstaller_path.clone()).output()? - } - _ => { - sudo( - &passwd, - format!("chmod +x {}", uninstaller_path.to_string_lossy()), - ) - .output()?; - - sudo(&passwd, format!("{}", uninstaller_path.to_string_lossy())).output()? - } - }; - - if !output.status.success() { - bail!( - "failed to install service with error: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - Ok(()) -} - -#[cfg(target_os = "macos")] -pub async fn uninstall_service(passwd: String) -> 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"); - } - - sudo( - &passwd, - format!( - "chmod +x {}", - uninstaller_path.to_string_lossy().replace(" ", "\\ ") - ), - ) - .output()?; - let output = sudo( - &passwd, - uninstaller_path - .to_string_lossy() - .replace(" ", "\\ ") - .to_string(), - ) - .output()?; - - if !output.status.success() { - bail!( - "failed to uninstall service with error: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - - Ok(()) -} - /// check the windows service status pub async fn check_service() -> Result { let url = format!("{SERVICE_URL}/get_clash"); diff --git a/src-tauri/src/core/tray.rs b/src-tauri/src/core/tray.rs index 7e8ac626..e9c75adc 100644 --- a/src-tauri/src/core/tray.rs +++ b/src-tauri/src/core/tray.rs @@ -364,52 +364,25 @@ fn create_tray_menu( .unwrap(); let separator = &PredefinedMenuItem::separator(app_handle).unwrap(); - let enable = { - Config::verge() - .latest() - .enable_service_mode - .unwrap_or(false) - }; - - let menu = if enable { - tauri::menu::MenuBuilder::new(app_handle) - .items(&[ - open_window, - separator, - rule_mode, - global_mode, - direct_mode, - separator, - system_proxy, - tun_mode, - copy_env, - open_dir, - more, - separator, - quit, - ]) - .build() - .unwrap() - } else { - tauri::menu::MenuBuilder::new(app_handle) - .items(&[ - open_window, - separator, - rule_mode, - global_mode, - direct_mode, - separator, - system_proxy, - copy_env, - open_dir, - more, - separator, - quit, - ]) - .build() - .unwrap() - }; + let menu = tauri::menu::MenuBuilder::new(app_handle) + .items(&[ + open_window, + separator, + rule_mode, + global_mode, + direct_mode, + separator, + system_proxy, + tun_mode, + copy_env, + open_dir, + more, + separator, + quit, + ]) + .build() + .unwrap(); Ok(menu) } diff --git a/src-tauri/src/feat.rs b/src-tauri/src/feat.rs index 261f2ea4..5975f690 100644 --- a/src-tauri/src/feat.rs +++ b/src-tauri/src/feat.rs @@ -177,11 +177,6 @@ pub async fn patch_verge(patch: IVerge) -> Result<()> { let mut should_update_sysproxy = false; let mut should_update_systray_part = false; - let service_mode = patch.enable_service_mode; - if service_mode.is_some() { - should_restart_core = true; - } - if tun_mode.is_some() { should_update_clash_config = true; } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index a04561f1..5eb860d3 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -118,8 +118,6 @@ pub fn run() { cmds::save_profile_file, // service mode cmds::service::check_service, - cmds::service::install_service, - cmds::service::uninstall_service, // clash api cmds::clash_api_get_proxy_delay ]); diff --git a/src-tauri/src/utils/help.rs b/src-tauri/src/utils/help.rs index 3583540a..ee47fa8b 100644 --- a/src-tauri/src/utils/help.rs +++ b/src-tauri/src/utils/help.rs @@ -105,6 +105,21 @@ pub fn open_file(app: tauri::AppHandle, path: PathBuf) -> Result<()> { Ok(()) } +#[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() { + "sudo" + } else { + "pkexec" + } + } + Err(_) => "sudo", + } +} + #[macro_export] macro_rules! error { ($result: expr) => { diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index af220eee..074aea4a 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -42,6 +42,13 @@ pub async fn resolve_setup(app: &mut App) { handle::Handle::global().init(app.app_handle()); VERSION.get_or_init(|| version.clone()); + + if service::check_service().await.is_err() { + log_err!(service::reinstall_service().await); + //延迟启动,避免闪屏 + std::thread::sleep(std::time::Duration::from_millis(1000)); + } + log_err!(init::init_config()); log_err!(init::init_resources()); log_err!(init::init_scheme()); diff --git a/src/components/setting/mods/service-switcher.tsx b/src/components/setting/mods/service-switcher.tsx deleted file mode 100644 index 9dd2d5a6..00000000 --- a/src/components/setting/mods/service-switcher.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import { KeyedMutator } from "swr"; -import { useState } from "react"; -import { useLockFn } from "ahooks"; -import { useTranslation } from "react-i18next"; -import { installService, uninstallService } from "@/services/cmds"; -import { Notice } from "@/components/base"; -import { LoadingButton } from "@mui/lab"; -import { PasswordInput } from "./password-input"; -import getSystem from "@/utils/get-system"; - -interface Props { - status: "active" | "installed" | "unknown" | "uninstall"; - mutate: KeyedMutator<"active" | "installed" | "unknown" | "uninstall">; - patchVerge: (value: Partial) => Promise; - onChangeData: (patch: Partial) => void; -} - -export const ServiceSwitcher = (props: Props) => { - const { status, mutate, patchVerge, onChangeData } = props; - const isWindows = getSystem() === "windows"; - const isActive = status === "active"; - const isInstalled = status === "installed"; - const isUninstall = status === "uninstall" || status === "unknown"; - - const { t } = useTranslation(); - const [serviceLoading, setServiceLoading] = useState(false); - const [uninstallServiceLoaing, setUninstallServiceLoading] = useState(false); - const [openInstall, setOpenInstall] = useState(false); - const [openUninstall, setOpenUninstall] = useState(false); - - async function install(passwd: string) { - try { - setOpenInstall(false); - await installService(passwd); - await mutate(); - setTimeout(() => { - mutate(); - }, 2000); - Notice.success(t("Service Installed Successfully")); - setServiceLoading(false); - } catch (err: any) { - await mutate(); - setTimeout(() => { - mutate(); - }, 2000); - Notice.error(err.message || err.toString()); - setServiceLoading(false); - } - } - - async function uninstall(passwd: string) { - try { - setOpenUninstall(false); - await uninstallService(passwd); - await mutate(); - setTimeout(() => { - mutate(); - }, 2000); - Notice.success(t("Service Uninstalled Successfully")); - setUninstallServiceLoading(false); - } catch (err: any) { - await mutate(); - setTimeout(() => { - mutate(); - }, 2000); - Notice.error(err.message || err.toString()); - setUninstallServiceLoading(false); - } - } - - const onInstallOrEnableService = useLockFn(async () => { - setServiceLoading(true); - if (isUninstall) { - // install service - if (isWindows) { - await install(""); - } else { - setOpenInstall(true); - } - } else { - try { - // enable or disable service - await patchVerge({ enable_service_mode: !isActive }); - onChangeData({ enable_service_mode: !isActive }); - await mutate(); - setTimeout(() => { - mutate(); - }, 2000); - setServiceLoading(false); - } catch (err: any) { - await mutate(); - Notice.error(err.message || err.toString()); - setServiceLoading(false); - } - } - }); - - const onUninstallService = useLockFn(async () => { - setUninstallServiceLoading(true); - if (isWindows) { - await uninstall(""); - } else { - setOpenUninstall(true); - } - }); - - return ( - <> - {openInstall && } - {openUninstall && } - - - {isActive ? t("Disable") : isInstalled ? t("Enable") : t("Install")} - - {isInstalled && ( - - {t("Uninstall")} - - )} - - ); -}; diff --git a/src/components/setting/setting-system.tsx b/src/components/setting/setting-system.tsx index bb827b7b..7f5799cb 100644 --- a/src/components/setting/setting-system.tsx +++ b/src/components/setting/setting-system.tsx @@ -7,7 +7,6 @@ import { useVerge } from "@/hooks/use-verge"; import { DialogRef, Notice, Switch } from "@/components/base"; import { SettingList, SettingItem } from "./mods/setting-comp"; import { GuardState } from "./mods/guard-state"; -import { ServiceSwitcher } from "./mods/service-switcher"; import { SysproxyViewer } from "./mods/sysproxy-viewer"; import { TunViewer } from "./mods/tun-viewer"; import { TooltipIcon } from "@/components/base/base-tooltip-icon"; @@ -67,37 +66,15 @@ const SettingSystem = ({ onError }: Props) => { onCatch={onError} onFormat={onSwitchFormat} onChange={(e) => { - if (serviceStatus !== "active") { - onChangeData({ enable_tun_mode: false }); - } else { - onChangeData({ enable_tun_mode: e }); - } + onChangeData({ enable_tun_mode: e }); }} onGuard={(e) => { - if (serviceStatus !== "active" && e) { - Notice.error(t("Please Enable Service Mode")); - return Promise.resolve(); - } else { - return patchVerge({ enable_tun_mode: e }); - } + return patchVerge({ enable_tun_mode: e }); }} > - - } - > - - - ("install_service", { passwd }); -} - -export async function uninstallService(passwd: string) { - return invoke("uninstall_service", { passwd }); -} - export async function invoke_uwp_tool() { return invoke("invoke_uwp_tool").catch((err) => Notice.error(err?.message || err.toString(), 1500)