service.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. use super::{notice::Notice, ClashInfo};
  2. use crate::log_if_err;
  3. use crate::utils::{config, dirs};
  4. use anyhow::{bail, Result};
  5. use reqwest::header::HeaderMap;
  6. use serde::{Deserialize, Serialize};
  7. use serde_yaml::Mapping;
  8. use std::{collections::HashMap, time::Duration};
  9. use tauri::api::process::{Command, CommandChild, CommandEvent};
  10. use tokio::time::sleep;
  11. static mut CLASH_CORE: &str = "clash";
  12. #[derive(Debug)]
  13. pub struct Service {
  14. sidecar: Option<CommandChild>,
  15. #[allow(unused)]
  16. service_mode: bool,
  17. }
  18. impl Service {
  19. pub fn new() -> Service {
  20. Service {
  21. sidecar: None,
  22. service_mode: false,
  23. }
  24. }
  25. pub fn set_core(&mut self, clash_core: Option<String>) {
  26. unsafe {
  27. CLASH_CORE = Box::leak(clash_core.unwrap_or("clash".into()).into_boxed_str());
  28. }
  29. }
  30. #[allow(unused)]
  31. pub fn set_mode(&mut self, enable: bool) {
  32. self.service_mode = enable;
  33. }
  34. #[cfg(not(windows))]
  35. pub fn start(&mut self) -> Result<()> {
  36. self.start_clash_by_sidecar()
  37. }
  38. #[cfg(windows)]
  39. pub fn start(&mut self) -> Result<()> {
  40. if !self.service_mode {
  41. return self.start_clash_by_sidecar();
  42. }
  43. tauri::async_runtime::spawn(async move {
  44. match Self::check_service().await {
  45. Ok(status) => {
  46. // 未启动clash
  47. if status.code != 0 {
  48. if let Err(err) = Self::start_clash_by_service().await {
  49. log::error!("{err}");
  50. }
  51. }
  52. }
  53. Err(err) => log::error!("{err}"),
  54. }
  55. });
  56. Ok(())
  57. }
  58. #[cfg(not(windows))]
  59. pub fn stop(&mut self) -> Result<()> {
  60. self.stop_clash_by_sidecar()
  61. }
  62. #[cfg(windows)]
  63. pub fn stop(&mut self) -> Result<()> {
  64. if !self.service_mode {
  65. return self.stop_clash_by_sidecar();
  66. }
  67. tauri::async_runtime::spawn(async move {
  68. if let Err(err) = Self::stop_clash_by_service().await {
  69. log::error!("{err}");
  70. }
  71. });
  72. Ok(())
  73. }
  74. pub fn restart(&mut self) -> Result<()> {
  75. self.stop()?;
  76. self.start()
  77. }
  78. /// start the clash sidecar
  79. fn start_clash_by_sidecar(&mut self) -> Result<()> {
  80. if self.sidecar.is_some() {
  81. bail!("could not run clash sidecar twice");
  82. }
  83. let app_dir = dirs::app_home_dir();
  84. let app_dir = app_dir.as_os_str().to_str().unwrap();
  85. let clash_core = unsafe { CLASH_CORE };
  86. let cmd = Command::new_sidecar(clash_core)?;
  87. let (mut rx, cmd_child) = cmd.args(["-d", app_dir]).spawn()?;
  88. self.sidecar = Some(cmd_child);
  89. // clash log
  90. tauri::async_runtime::spawn(async move {
  91. while let Some(event) = rx.recv().await {
  92. match event {
  93. CommandEvent::Stdout(line) => log::info!("[clash]: {}", line),
  94. CommandEvent::Stderr(err) => log::error!("[clash]: {}", err),
  95. _ => {}
  96. }
  97. }
  98. });
  99. Ok(())
  100. }
  101. /// stop the clash sidecar
  102. fn stop_clash_by_sidecar(&mut self) -> Result<()> {
  103. if let Some(sidecar) = self.sidecar.take() {
  104. sidecar.kill()?;
  105. }
  106. Ok(())
  107. }
  108. /// update clash config
  109. /// using PUT methods
  110. pub fn set_config(&self, info: ClashInfo, config: Mapping, notice: Notice) -> Result<()> {
  111. if !self.service_mode && self.sidecar.is_none() {
  112. bail!("did not start sidecar");
  113. }
  114. let temp_path = dirs::profiles_temp_path();
  115. config::save_yaml(temp_path.clone(), &config, Some("# Clash Verge Temp File"))?;
  116. if info.server.is_none() {
  117. bail!("failed to parse the server");
  118. }
  119. let server = info.server.unwrap();
  120. let server = format!("http://{server}/configs");
  121. let mut headers = HeaderMap::new();
  122. headers.insert("Content-Type", "application/json".parse().unwrap());
  123. if let Some(secret) = info.secret.as_ref() {
  124. let secret = format!("Bearer {}", secret.clone()).parse().unwrap();
  125. headers.insert("Authorization", secret);
  126. }
  127. tauri::async_runtime::spawn(async move {
  128. let mut data = HashMap::new();
  129. data.insert("path", temp_path.as_os_str().to_str().unwrap());
  130. // retry 5 times
  131. for _ in 0..5 {
  132. match reqwest::ClientBuilder::new().no_proxy().build() {
  133. Ok(client) => {
  134. let builder = client.put(&server).headers(headers.clone()).json(&data);
  135. match builder.send().await {
  136. Ok(resp) => {
  137. if resp.status() != 204 {
  138. log::error!("failed to activate clash with status \"{}\"", resp.status());
  139. }
  140. notice.refresh_clash();
  141. // do not retry
  142. break;
  143. }
  144. Err(err) => log::error!("failed to activate for `{err}`"),
  145. }
  146. }
  147. Err(err) => log::error!("failed to activate for `{err}`"),
  148. }
  149. sleep(Duration::from_millis(500)).await;
  150. }
  151. });
  152. Ok(())
  153. }
  154. }
  155. impl Drop for Service {
  156. fn drop(&mut self) {
  157. log_if_err!(self.stop());
  158. }
  159. }
  160. /// ### Service Mode
  161. ///
  162. #[cfg(windows)]
  163. pub mod win_service {
  164. use super::*;
  165. use anyhow::Context;
  166. use deelevate::{PrivilegeLevel, Token};
  167. use runas::Command as RunasCommand;
  168. use std::os::windows::process::CommandExt;
  169. use std::{env::current_exe, process::Command as StdCommand};
  170. const SERVICE_NAME: &str = "clash_verge_service";
  171. const SERVICE_URL: &str = "http://127.0.0.1:33211";
  172. #[derive(Debug, Deserialize, Serialize, Clone)]
  173. pub struct ResponseBody {
  174. pub bin_path: String,
  175. pub config_dir: String,
  176. pub log_file: String,
  177. }
  178. #[derive(Debug, Deserialize, Serialize, Clone)]
  179. pub struct JsonResponse {
  180. pub code: u64,
  181. pub msg: String,
  182. pub data: Option<ResponseBody>,
  183. }
  184. impl Service {
  185. /// Install the Clash Verge Service
  186. /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
  187. pub async fn install_service() -> Result<()> {
  188. let binary_path = dirs::service_path();
  189. let install_path = binary_path.with_file_name("install-service.exe");
  190. if !install_path.exists() {
  191. bail!("installer exe not found");
  192. }
  193. let token = Token::with_current_process()?;
  194. let level = token.privilege_level()?;
  195. let status = match level {
  196. PrivilegeLevel::NotPrivileged => RunasCommand::new(install_path).status()?,
  197. _ => StdCommand::new(install_path)
  198. .creation_flags(0x08000000)
  199. .status()?,
  200. };
  201. if !status.success() {
  202. bail!(
  203. "failed to install service with status {}",
  204. status.code().unwrap()
  205. );
  206. }
  207. Ok(())
  208. }
  209. /// Uninstall the Clash Verge Service
  210. /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
  211. pub async fn uninstall_service() -> Result<()> {
  212. let binary_path = dirs::service_path();
  213. let uninstall_path = binary_path.with_file_name("uninstall-service.exe");
  214. if !uninstall_path.exists() {
  215. bail!("uninstaller exe not found");
  216. }
  217. let token = Token::with_current_process()?;
  218. let level = token.privilege_level()?;
  219. let status = match level {
  220. PrivilegeLevel::NotPrivileged => RunasCommand::new(uninstall_path).status()?,
  221. _ => StdCommand::new(uninstall_path)
  222. .creation_flags(0x08000000)
  223. .status()?,
  224. };
  225. if !status.success() {
  226. bail!(
  227. "failed to uninstall service with status {}",
  228. status.code().unwrap()
  229. );
  230. }
  231. Ok(())
  232. }
  233. /// [deprecated]
  234. /// start service
  235. /// 该函数应该在协程或者线程中执行,避免UAC弹窗阻塞主线程
  236. pub async fn start_service() -> Result<()> {
  237. let token = Token::with_current_process()?;
  238. let level = token.privilege_level()?;
  239. let args = ["start", SERVICE_NAME];
  240. let status = match level {
  241. PrivilegeLevel::NotPrivileged => RunasCommand::new("sc").args(&args).status()?,
  242. _ => StdCommand::new("sc").args(&args).status()?,
  243. };
  244. match status.success() {
  245. true => Ok(()),
  246. false => bail!(
  247. "failed to start service with status {}",
  248. status.code().unwrap()
  249. ),
  250. }
  251. }
  252. /// stop service
  253. pub async fn stop_service() -> Result<()> {
  254. let url = format!("{SERVICE_URL}/stop_service");
  255. let res = reqwest::ClientBuilder::new()
  256. .no_proxy()
  257. .build()?
  258. .post(url)
  259. .send()
  260. .await?
  261. .json::<JsonResponse>()
  262. .await
  263. .context("failed to connect to the Clash Verge Service")?;
  264. if res.code != 0 {
  265. bail!(res.msg);
  266. }
  267. Ok(())
  268. }
  269. /// check the windows service status
  270. pub async fn check_service() -> Result<JsonResponse> {
  271. let url = format!("{SERVICE_URL}/get_clash");
  272. let response = reqwest::ClientBuilder::new()
  273. .no_proxy()
  274. .build()?
  275. .get(url)
  276. .send()
  277. .await?
  278. .json::<JsonResponse>()
  279. .await
  280. .context("failed to connect to the Clash Verge Service")?;
  281. Ok(response)
  282. }
  283. /// start the clash by service
  284. pub(super) async fn start_clash_by_service() -> Result<()> {
  285. let status = Self::check_service().await?;
  286. if status.code == 0 {
  287. Self::stop_clash_by_service().await?;
  288. sleep(Duration::from_secs(1)).await;
  289. }
  290. let clash_core = unsafe { CLASH_CORE };
  291. let clash_bin = format!("{clash_core}.exe");
  292. let bin_path = current_exe().unwrap().with_file_name(clash_bin);
  293. let bin_path = bin_path.as_os_str().to_str().unwrap();
  294. let config_dir = dirs::app_home_dir();
  295. let config_dir = config_dir.as_os_str().to_str().unwrap();
  296. let log_path = dirs::service_log_file();
  297. let log_path = log_path.as_os_str().to_str().unwrap();
  298. let mut map = HashMap::new();
  299. map.insert("bin_path", bin_path);
  300. map.insert("config_dir", config_dir);
  301. map.insert("log_file", log_path);
  302. let url = format!("{SERVICE_URL}/start_clash");
  303. let res = reqwest::ClientBuilder::new()
  304. .no_proxy()
  305. .build()?
  306. .post(url)
  307. .json(&map)
  308. .send()
  309. .await?
  310. .json::<JsonResponse>()
  311. .await
  312. .context("failed to connect to the Clash Verge Service")?;
  313. if res.code != 0 {
  314. bail!(res.msg);
  315. }
  316. Ok(())
  317. }
  318. /// stop the clash by service
  319. pub(super) async fn stop_clash_by_service() -> Result<()> {
  320. let url = format!("{SERVICE_URL}/stop_clash");
  321. let res = reqwest::ClientBuilder::new()
  322. .no_proxy()
  323. .build()?
  324. .post(url)
  325. .send()
  326. .await?
  327. .json::<JsonResponse>()
  328. .await
  329. .context("failed to connect to the Clash Verge Service")?;
  330. if res.code != 0 {
  331. bail!(res.msg);
  332. }
  333. Ok(())
  334. }
  335. }
  336. }