service.rs 16 KB

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