win_service.rs 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #![cfg(target_os = "windows")]
  2. use crate::config::Config;
  3. use crate::utils::dirs;
  4. use anyhow::{bail, Context, Result};
  5. use deelevate::{PrivilegeLevel, Token};
  6. use runas::Command as RunasCommand;
  7. use serde::{Deserialize, Serialize};
  8. use std::collections::HashMap;
  9. use std::os::windows::process::CommandExt;
  10. use std::path::PathBuf;
  11. use std::time::Duration;
  12. use std::{env::current_exe, process::Command as StdCommand};
  13. use tokio::time::sleep;
  14. const SERVICE_URL: &str = "http://127.0.0.1:33211";
  15. #[derive(Debug, Deserialize, Serialize, Clone)]
  16. pub struct ResponseBody {
  17. pub core_type: Option<String>,
  18. pub bin_path: String,
  19. pub config_dir: String,
  20. pub log_file: String,
  21. }
  22. #[derive(Debug, Deserialize, Serialize, Clone)]
  23. pub struct JsonResponse {
  24. pub code: u64,
  25. pub msg: String,
  26. pub data: Option<ResponseBody>,
  27. }
  28. /// Install the Clash Verge Service
  29. /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
  30. pub async fn install_service() -> Result<()> {
  31. let binary_path = dirs::service_path()?;
  32. let install_path = binary_path.with_file_name("install-service.exe");
  33. if !install_path.exists() {
  34. bail!("installer exe not found");
  35. }
  36. let token = Token::with_current_process()?;
  37. let level = token.privilege_level()?;
  38. let status = match level {
  39. PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).show(false).status()?,
  40. _ => StdCommand::new(install_path)
  41. .creation_flags(0x08000000)
  42. .status()?,
  43. };
  44. if !status.success() {
  45. bail!(
  46. "failed to install service with status {}",
  47. status.code().unwrap()
  48. );
  49. }
  50. Ok(())
  51. }
  52. /// Uninstall the Clash Verge Service
  53. /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
  54. pub async fn uninstall_service() -> Result<()> {
  55. let binary_path = dirs::service_path()?;
  56. let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
  57. if !uninstall_path.exists() {
  58. bail!("uninstaller exe not found");
  59. }
  60. let token = Token::with_current_process()?;
  61. let level = token.privilege_level()?;
  62. let status = match level {
  63. PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).show(false).status()?,
  64. _ => StdCommand::new(uninstall_path)
  65. .creation_flags(0x08000000)
  66. .status()?,
  67. };
  68. if !status.success() {
  69. bail!(
  70. "failed to uninstall service with status {}",
  71. status.code().unwrap()
  72. );
  73. }
  74. Ok(())
  75. }
  76. /// check the windows service status
  77. pub async fn check_service() -> Result<JsonResponse> {
  78. let url = format!("{SERVICE_URL}/get_clash");
  79. let response = reqwest::ClientBuilder::new()
  80. .no_proxy()
  81. .build()?
  82. .get(url)
  83. .send()
  84. .await
  85. .context("failed to connect to the Clash Verge Service")?
  86. .json::<JsonResponse>()
  87. .await
  88. .context("failed to parse the Clash Verge Service response")?;
  89. Ok(response)
  90. }
  91. /// start the clash by service
  92. pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
  93. let status = check_service().await?;
  94. if status.code == 0 {
  95. stop_core_by_service().await?;
  96. sleep(Duration::from_secs(1)).await;
  97. }
  98. let clash_core = { Config::verge().latest().clash_core.clone() };
  99. let clash_core = clash_core.unwrap_or("clash".into());
  100. let clash_bin = format!("{clash_core}.exe");
  101. let bin_path = current_exe()?.with_file_name(clash_bin);
  102. let bin_path = dirs::path_to_str(&bin_path)?;
  103. let config_dir = dirs::app_home_dir()?;
  104. let config_dir = dirs::path_to_str(&config_dir)?;
  105. let log_path = dirs::service_log_file()?;
  106. let log_path = dirs::path_to_str(&log_path)?;
  107. let config_file = dirs::path_to_str(config_file)?;
  108. let mut map = HashMap::new();
  109. map.insert("core_type", clash_core.as_str());
  110. map.insert("bin_path", bin_path);
  111. map.insert("config_dir", config_dir);
  112. map.insert("config_file", config_file);
  113. map.insert("log_file", log_path);
  114. let url = format!("{SERVICE_URL}/start_clash");
  115. let res = reqwest::ClientBuilder::new()
  116. .no_proxy()
  117. .build()?
  118. .post(url)
  119. .json(&map)
  120. .send()
  121. .await?
  122. .json::<JsonResponse>()
  123. .await
  124. .context("failed to connect to the Clash Verge Service")?;
  125. if res.code != 0 {
  126. bail!(res.msg);
  127. }
  128. Ok(())
  129. }
  130. /// stop the clash by service
  131. pub(super) async fn stop_core_by_service() -> Result<()> {
  132. let url = format!("{SERVICE_URL}/stop_clash");
  133. let res = reqwest::ClientBuilder::new()
  134. .no_proxy()
  135. .build()?
  136. .post(url)
  137. .send()
  138. .await?
  139. .json::<JsonResponse>()
  140. .await
  141. .context("failed to connect to the Clash Verge Service")?;
  142. if res.code != 0 {
  143. bail!(res.msg);
  144. }
  145. Ok(())
  146. }