service.rs 14 KB

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