mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 08:45:41 +08:00
feat: Modify startup logic and install services by default
This commit is contained in:
@@ -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<Mutex<bool>>,
|
||||
sidecar: Arc<Mutex<Option<CommandChild>>>,
|
||||
}
|
||||
|
||||
impl CoreManager {
|
||||
@@ -22,7 +20,6 @@ impl CoreManager {
|
||||
static CORE_MANAGER: OnceCell<CoreManager> = 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(())
|
||||
}
|
||||
|
||||
|
||||
@@ -26,32 +26,42 @@ 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(_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<JsonResponse> {
|
||||
let url = format!("{SERVICE_URL}/get_clash");
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user