service.rs 12 KB

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