Forráskód Böngészése

fix: try to fix install service

MystiPanda 10 hónapja
szülő
commit
af0cd4a342

+ 4 - 4
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)
     }
 }
 

+ 96 - 56
src-tauri/src/core/service.rs

@@ -28,11 +28,19 @@ pub struct JsonResponse {
     pub data: Option<ResponseBody>,
 }
 
+#[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();
-
-    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() {
+    sudo(
+        &passwd,
+        format!(
+            "chmod +x {}",
+            installer_path.to_string_lossy().replace(" ", "\\ ")
+        ),
+    )
+    .output()?;
+    let output = sudo(
+        &passwd,
+        format!("{}", installer_path.to_string_lossy().replace(" ", "\\ ")),
+    )
+    .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)
         );
     }
 
@@ -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()?;
+
+            sudo(&passwd, format!("{}", uninstaller_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)
         );
     }
 
@@ -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"#);
-
-    let status = StdCommand::new("osascript")
-        .args(vec!["-e", &command])
-        .status()?;
-
-    if !status.success() {
+    sudo(
+        &passwd,
+        format!(
+            "chmod +x {}",
+            uninstaller_path.to_string_lossy().replace(" ", "\\ ")
+        ),
+    )
+    .output()?;
+    let output = sudo(
+        &passwd,
+        format!("{}", uninstaller_path.to_string_lossy().replace(" ", "\\ ")),
+    )
+    .output()?;
+
+    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)
         );
     }
 

+ 0 - 1
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;

+ 1 - 0
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<String> = OnceCell::new();

+ 0 - 14
src-tauri/src/utils/unix_helper.rs

@@ -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",
-    }
-}

+ 54 - 0
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<void>;
+}
+
+export const PasswordInput = (props: Props) => {
+  const { onConfirm } = props;
+
+  const { t } = useTranslation();
+  const [passwd, setPasswd] = useState("");
+
+  useEffect(() => {
+    if (!open) return;
+  }, [open]);
+
+  return (
+    <Dialog open={true} maxWidth="xs" fullWidth>
+      <DialogTitle>{t("Please enter your root password")}</DialogTitle>
+
+      <DialogContent>
+        <TextField
+          sx={{ mt: 1 }}
+          autoFocus
+          label={t("Password")}
+          fullWidth
+          size="small"
+          type="password"
+          value={passwd}
+          onKeyDown={(e) => e.key === "Enter" && onConfirm(passwd)}
+          onChange={(e) => setPasswd(e.target.value)}
+        ></TextField>
+      </DialogContent>
+
+      <DialogActions>
+        <Button
+          onClick={async () => await onConfirm(passwd)}
+          variant="contained"
+        >
+          {t("Confirm")}
+        </Button>
+      </DialogActions>
+    </Dialog>
+  );
+};

+ 57 - 26
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 && <PasswordInput onConfirm={install} />}
+      {openUninstall && <PasswordInput onConfirm={uninstall} />}
+
       <LoadingButton
         size="small"
         variant={isUninstall ? "outlined" : "contained"}

+ 1 - 0
src/locales/en.json

@@ -267,6 +267,7 @@
   "Release Version": "Release Version",
   "Alpha Version": "Alpha Version",
   "Please Enable Service Mode": "Please Install and Enable Service Mode First",
+  "Please enter your root password": "Please enter your root password",
   "Grant": "Grant",
   "Open UWP tool": "Open UWP tool",
   "Open UWP tool Info": "Since Windows 8, UWP apps (such as Microsoft Store) are restricted from directly accessing local host network services, and this tool can be used to bypass this restriction",

+ 1 - 0
src/locales/fa.json

@@ -262,6 +262,7 @@
   "Release Version": "نسخه نهایی",
   "Alpha Version": "نسخه آلفا",
   "Please Install and Enable Service Mode First": "لطفاً ابتدا حالت سرویس را نصب و فعال کنید",
+  "Please enter your root password": "لطفاً رمز ریشه خود را وارد کنید",
   "Grant": "اعطا",
   "Open UWP tool": "باز کردن ابزار UWP",
   "Open UWP tool Info": "از ویندوز 8 به بعد، برنامه‌های UWP (مانند Microsoft Store) از دسترسی مستقیم به خدمات شبکه محلی محدود شده‌اند و این ابزار می‌تواند برای دور زدن این محدودیت استفاده شود",

+ 1 - 0
src/locales/ru.json

@@ -265,6 +265,7 @@
   "Release Version": "Официальная версия",
   "Alpha Version": "Альфа-версия",
   "Please Enable Service Mode": "Пожалуйста, сначала установите и включите режим обслуживания",
+  "Please enter your root password": "Пожалуйста, введите ваш пароль root",
   "Grant": "Предоставить",
   "Open UWP tool": "Открыть UWP инструмент",
   "Open UWP tool Info": "С Windows 8 приложения UWP (такие как Microsoft Store) ограничены в прямом доступе к сетевым службам локального хоста, и этот инструмент позволяет обойти это ограничение",

+ 1 - 0
src/locales/zh.json

@@ -267,6 +267,7 @@
   "Release Version": "正式版",
   "Alpha Version": "预览版",
   "Please Enable Service Mode": "请先安装并启用服务模式",
+  "Please enter your root password": "请输入您的 root 密码",
   "Grant": "授权",
   "Open UWP tool": "UWP 工具",
   "Open UWP tool Info": "Windows 8开始限制 UWP 应用(如微软商店)直接访问本地主机的网络服务,使用此工具可绕过该限制",

+ 5 - 4
src/services/cmds.ts

@@ -201,12 +201,13 @@ export async function checkService() {
   }
 }
 
-export async function installService() {
-  return invoke<void>("install_service");
+export async function installService(passwd: string) {
+  console.log(passwd);
+  return invoke<void>("install_service", { passwd });
 }
 
-export async function uninstallService() {
-  return invoke<void>("uninstall_service");
+export async function uninstallService(passwd: string) {
+  return invoke<void>("uninstall_service", { passwd });
 }
 
 export async function invoke_uwp_tool() {