From af0cd4a342a04b1956af2f617ada3e72a7b0749e Mon Sep 17 00:00:00 2001 From: MystiPanda Date: Tue, 16 Jul 2024 11:28:04 +0800 Subject: [PATCH] fix: try to fix install service --- src-tauri/src/cmds.rs | 8 +- src-tauri/src/core/service.rs | 148 +++++++++++------- src-tauri/src/utils/mod.rs | 1 - src-tauri/src/utils/resolve.rs | 1 + src-tauri/src/utils/unix_helper.rs | 14 -- .../setting/mods/password-input.tsx | 54 +++++++ .../setting/mods/service-switcher.tsx | 83 +++++++--- src/locales/en.json | 1 + src/locales/fa.json | 1 + src/locales/ru.json | 1 + src/locales/zh.json | 1 + src/services/cmds.ts | 9 +- 12 files changed, 219 insertions(+), 103 deletions(-) delete mode 100644 src-tauri/src/utils/unix_helper.rs create mode 100644 src/components/setting/mods/password-input.tsx diff --git a/src-tauri/src/cmds.rs b/src-tauri/src/cmds.rs index 2d2eb77..47f8137 100644 --- a/src-tauri/src/cmds.rs +++ b/src-tauri/src/cmds.rs @@ -389,13 +389,13 @@ pub mod service { } #[tauri::command] - pub async fn install_service() -> CmdResult { - wrap_err!(service::install_service().await) + pub async fn install_service(passwd: String) -> CmdResult { + wrap_err!(service::install_service(passwd).await) } #[tauri::command] - pub async fn uninstall_service() -> CmdResult { - wrap_err!(service::uninstall_service().await) + pub async fn uninstall_service(passwd: String) -> CmdResult { + wrap_err!(service::uninstall_service(passwd).await) } } diff --git a/src-tauri/src/core/service.rs b/src-tauri/src/core/service.rs index 7dc7399..f98af63 100644 --- a/src-tauri/src/core/service.rs +++ b/src-tauri/src/core/service.rs @@ -28,11 +28,19 @@ 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() -> Result<()> { +pub async fn install_service(_passwd: String) -> Result<()> { use deelevate::{PrivilegeLevel, Token}; use runas::Command as RunasCommand; use std::os::windows::process::CommandExt; @@ -65,30 +73,45 @@ pub async fn install_service() -> Result<()> { } #[cfg(target_os = "linux")] -pub async fn install_service() -> Result<()> { +pub async fn install_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"); - if !installer_path.exists() { bail!("installer not found"); } - 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()?, + 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 !status.success() { + if output.stderr.len() > 0 { bail!( - "failed to install service with status {}", - status.code().unwrap() + "failed to install service with error: {}", + String::from_utf8_lossy(&output.stderr) ); } @@ -96,7 +119,7 @@ pub async fn install_service() -> Result<()> { } #[cfg(target_os = "macos")] -pub async fn install_service() -> Result<()> { +pub async fn install_service(passwd: String) -> Result<()> { let binary_path = dirs::service_path()?; let installer_path = binary_path.with_file_name("install-service"); @@ -104,22 +127,24 @@ pub async fn install_service() -> Result<()> { bail!("installer not found"); } - let _ = StdCommand::new("chmod") - .arg("+x") - .arg(installer_path.to_string_lossy().replace(" ", "\\ ")) - .output(); + sudo( + &passwd, + format!( + "chmod +x {}", + installer_path.to_string_lossy().replace(" ", "\\ ") + ), + ) + .output()?; + let output = sudo( + &passwd, + format!("{}", installer_path.to_string_lossy().replace(" ", "\\ ")), + ) + .output()?; - let shell = installer_path.to_string_lossy().replace(" ", "\\\\ "); - let command = format!(r#"do shell script "{shell}" with administrator privileges"#); - - let status = StdCommand::new("osascript") - .args(vec!["-e", &command]) - .status()?; - - if !status.success() { + if output.stderr.len() > 0 { bail!( - "failed to install service with status {}", - status.code().unwrap() + "failed to install service with error: {}", + String::from_utf8_lossy(&output.stderr) ); } @@ -128,7 +153,7 @@ pub async fn install_service() -> Result<()> { /// Uninstall the Clash Verge Service /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程 #[cfg(target_os = "windows")] -pub async fn uninstall_service() -> Result<()> { +pub async fn uninstall_service(_passwd: String) -> Result<()> { use deelevate::{PrivilegeLevel, Token}; use runas::Command as RunasCommand; use std::os::windows::process::CommandExt; @@ -161,7 +186,7 @@ pub async fn uninstall_service() -> Result<()> { } #[cfg(target_os = "linux")] -pub async fn uninstall_service() -> Result<()> { +pub async fn uninstall_service(passwd: String) -> Result<()> { use users::get_effective_uid; let binary_path = dirs::service_path()?; @@ -171,20 +196,28 @@ pub async fn uninstall_service() -> Result<()> { bail!("uninstaller not found"); } - 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()?, - }; + 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()?; - if !status.success() { + sudo(&passwd, format!("{}", uninstaller_path.to_string_lossy())).output()? + } + }; + if output.stderr.len() > 0 { bail!( - "failed to install service with status {}", - status.code().unwrap() + "failed to install service with error: {}", + String::from_utf8_lossy(&output.stderr) ); } @@ -192,7 +225,7 @@ pub async fn uninstall_service() -> Result<()> { } #[cfg(target_os = "macos")] -pub async fn uninstall_service() -> Result<()> { +pub async fn uninstall_service(passwd: String) -> Result<()> { let binary_path = dirs::service_path()?; let uninstaller_path = binary_path.with_file_name("uninstall-service"); @@ -200,17 +233,24 @@ pub async fn uninstall_service() -> Result<()> { bail!("uninstaller not found"); } - let shell = uninstaller_path.to_string_lossy().replace(" ", "\\\\ "); - let command = format!(r#"do shell script "{shell}" with administrator privileges"#); + sudo( + &passwd, + format!( + "chmod +x {}", + uninstaller_path.to_string_lossy().replace(" ", "\\ ") + ), + ) + .output()?; + let output = sudo( + &passwd, + format!("{}", uninstaller_path.to_string_lossy().replace(" ", "\\ ")), + ) + .output()?; - let status = StdCommand::new("osascript") - .args(vec!["-e", &command]) - .status()?; - - if !status.success() { + if output.stderr.len() > 0 { bail!( - "failed to install service with status {}", - status.code().unwrap() + "failed to uninstall service with error: {}", + String::from_utf8_lossy(&output.stderr) ); } diff --git a/src-tauri/src/utils/mod.rs b/src-tauri/src/utils/mod.rs index d0037b3..28eacc3 100644 --- a/src-tauri/src/utils/mod.rs +++ b/src-tauri/src/utils/mod.rs @@ -4,4 +4,3 @@ pub mod init; pub mod resolve; pub mod server; pub mod tmpl; -pub mod unix_helper; diff --git a/src-tauri/src/utils/resolve.rs b/src-tauri/src/utils/resolve.rs index 8d22c29..b31b878 100644 --- a/src-tauri/src/utils/resolve.rs +++ b/src-tauri/src/utils/resolve.rs @@ -8,6 +8,7 @@ use serde_yaml::Mapping; use std::net::TcpListener; use tauri::api::notification; use tauri::{App, AppHandle, Manager}; +#[cfg(not(target_os = "linux"))] use window_shadows::set_shadow; pub static VERSION: OnceCell = OnceCell::new(); diff --git a/src-tauri/src/utils/unix_helper.rs b/src-tauri/src/utils/unix_helper.rs deleted file mode 100644 index c573645..0000000 --- a/src-tauri/src/utils/unix_helper.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[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", - } -} diff --git a/src/components/setting/mods/password-input.tsx b/src/components/setting/mods/password-input.tsx new file mode 100644 index 0000000..a42b6a4 --- /dev/null +++ b/src/components/setting/mods/password-input.tsx @@ -0,0 +1,54 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, +} from "@mui/material"; + +interface Props { + onConfirm: (passwd: string) => Promise; +} + +export const PasswordInput = (props: Props) => { + const { onConfirm } = props; + + const { t } = useTranslation(); + const [passwd, setPasswd] = useState(""); + + useEffect(() => { + if (!open) return; + }, [open]); + + return ( + + {t("Please enter your root password")} + + + e.key === "Enter" && onConfirm(passwd)} + onChange={(e) => setPasswd(e.target.value)} + > + + + + + + + ); +}; diff --git a/src/components/setting/mods/service-switcher.tsx b/src/components/setting/mods/service-switcher.tsx index e86aa1e..6c00e07 100644 --- a/src/components/setting/mods/service-switcher.tsx +++ b/src/components/setting/mods/service-switcher.tsx @@ -5,6 +5,8 @@ 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"; @@ -15,7 +17,7 @@ interface Props { 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"; @@ -23,40 +25,30 @@ export const ServiceSwitcher = (props: Props) => { const { t } = useTranslation(); const [serviceLoading, setServiceLoading] = useState(false); const [uninstallServiceLoaing, setUninstallServiceLoading] = useState(false); + const [openInstall, setOpenInstall] = useState(false); + const [openUninstall, setOpenUninstall] = useState(false); - const onInstallOrEnableService = useLockFn(async () => { - setServiceLoading(true); + async function install(passwd: string) { try { - if (isUninstall) { - // install service - await installService(); - await mutate(); - setTimeout(() => { - mutate(); - }, 2000); - Notice.success(t("Service Installed Successfully")); - setServiceLoading(false); - } else { - // enable or disable service - await patchVerge({ enable_service_mode: !isActive }); - onChangeData({ enable_service_mode: !isActive }); - await mutate(); - setTimeout(() => { - mutate(); - }, 2000); - setServiceLoading(false); - } + setOpenInstall(false); + await installService(passwd); + await mutate(); + setTimeout(() => { + mutate(); + }, 2000); + Notice.success(t("Service Installed Successfully")); + setServiceLoading(false); } catch (err: any) { await mutate(); Notice.error(err.message || err.toString()); setServiceLoading(false); } - }); + } - const onUninstallService = useLockFn(async () => { - setUninstallServiceLoading(true); + async function uninstall(passwd: string) { try { - await uninstallService(); + setOpenUninstall(false); + await uninstallService(passwd); await mutate(); setTimeout(() => { mutate(); @@ -68,10 +60,49 @@ export const ServiceSwitcher = (props: Props) => { 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 && } + ("install_service"); +export async function installService(passwd: string) { + console.log(passwd); + return invoke("install_service", { passwd }); } -export async function uninstallService() { - return invoke("uninstall_service"); +export async function uninstallService(passwd: string) { + return invoke("uninstall_service", { passwd }); } export async function invoke_uwp_tool() {