service.rs 11 KB

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