mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-01-29 00:35:38 +08:00
refactor: clash-verge-service management (#4674)
* refactor: clash-verge-service management * fix: correct service state checks in ProxyControlSwitches component refactor: improve logging in service state update functions * fix: add missing async handler for Windows and adjust logging import for macOS * fix: streamline logging imports and add missing async handler for Windows * refactor: remove unused useServiceStateSync hook and update imports in _layout * refactor: remove unused useServiceStateSync import and clean up code in ProxyControlSwitches and _layout * refactor: simplify service status checks and reduce wait time in useServiceInstaller hook * refactor: remove unnecessary logging statements in service checks and IPC connection * refactor: extract SwitchRow component for better code organization and readability * refactor: enhance service state management and update related mutations in layout * refactor: streamline core stopping logic and improve IPC connection logging * refactor: consolidate service uninstallation logic and improve error handling * fix: simplify conditional statements in CoreManager and service functions * feat: add backoff dependency and implement retry strategy for IPC requests * refactor: remove redundant Windows conditional and improve error handling in IPC tests * test: improve error handling in IPC tests for message signing and verification * fix: adjust IPC backoff retry parameters * refactor: Remove service state tracking and related logic from service management * feat: Enhance service status handling with logging and running mode updates * fix: Improve service status handling with enhanced error logging * fix: Ensure proper handling of service operations with error propagation * refactor: Simplify service operation execution and enhance service status handling * fix: Improve error message formatting in service operation execution and simplify service status retrieval * refactor: Replace Cache with CacheProxy in multiple modules and update CacheEntry to be generic * fix: Remove unnecessary success message from config validation * refactor: Comment out logging statements in service version check and IPC request handling
This commit is contained in:
@@ -1,9 +1,13 @@
|
|||||||
## v2.4.3
|
## v2.4.3
|
||||||
|
|
||||||
|
### 🚀 性能优化
|
||||||
|
|
||||||
|
- 重构并简化服务模式启动检测流程,消除重复检测
|
||||||
|
|
||||||
### 🐞 修复问题
|
### 🐞 修复问题
|
||||||
|
|
||||||
|
- 优化服务模式重装逻辑,避免不必要的重复检查
|
||||||
- 修复轻量模式退出无响应的问题
|
- 修复轻量模式退出无响应的问题
|
||||||
- macOS intel Mihomo 兼容性
|
|
||||||
- macOS Tun/系统代理 模式下图标大小不统一
|
- macOS Tun/系统代理 模式下图标大小不统一
|
||||||
- 托盘节点切换不再显示隐藏组
|
- 托盘节点切换不再显示隐藏组
|
||||||
|
|
||||||
|
|||||||
15
src-tauri/Cargo.lock
generated
15
src-tauri/Cargo.lock
generated
@@ -556,6 +556,20 @@ dependencies = [
|
|||||||
"tower-service",
|
"tower-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backoff"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"getrandom 0.2.16",
|
||||||
|
"instant",
|
||||||
|
"pin-project-lite",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.75"
|
version = "0.3.75"
|
||||||
@@ -1086,6 +1100,7 @@ version = "2.4.3"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-gcm",
|
"aes-gcm",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"backoff",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"boa_engine",
|
"boa_engine",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ isahc = { version = "1.7.2", default-features = false, features = [
|
|||||||
"text-decoding",
|
"text-decoding",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
] }
|
] }
|
||||||
|
backoff = { version = "0.4.0", features = ["tokio"] }
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
|||||||
31
src-tauri/src/cache/mod.rs
vendored
31
src-tauri/src/cache/mod.rs
vendored
@@ -1,20 +1,23 @@
|
|||||||
use crate::singleton;
|
use crate::singleton;
|
||||||
|
use anyhow::Result;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tokio::sync::OnceCell;
|
use tokio::sync::OnceCell;
|
||||||
|
|
||||||
pub struct CacheEntry {
|
pub const SHORT_TERM_TTL: Duration = Duration::from_millis(4_250);
|
||||||
pub value: Arc<Value>,
|
|
||||||
|
pub struct CacheEntry<T> {
|
||||||
|
pub value: Arc<T>,
|
||||||
pub expires_at: Instant,
|
pub expires_at: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Cache {
|
pub struct Cache<T> {
|
||||||
pub map: DashMap<String, Arc<OnceCell<Box<CacheEntry>>>>,
|
pub map: DashMap<String, Arc<OnceCell<Box<CacheEntry<T>>>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl<T> Cache<T> {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Cache {
|
Cache {
|
||||||
map: DashMap::new(),
|
map: DashMap::new(),
|
||||||
@@ -25,10 +28,11 @@ impl Cache {
|
|||||||
format!("{prefix}:{id}")
|
format!("{prefix}:{id}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_or_fetch<F, Fut>(&self, key: String, ttl: Duration, fetch_fn: F) -> Arc<Value>
|
pub async fn get_or_fetch<F, Fut>(&self, key: String, ttl: Duration, fetch_fn: F) -> Arc<T>
|
||||||
where
|
where
|
||||||
F: Fn() -> Fut + Send + 'static,
|
F: Fn() -> Fut + Send + Sync + 'static,
|
||||||
Fut: std::future::Future<Output = Value> + Send + 'static,
|
Fut: std::future::Future<Output = T> + Send + 'static,
|
||||||
|
T: Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
loop {
|
loop {
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
@@ -79,6 +83,10 @@ impl Cache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// pub fn clean_key(&self, key: &str) {
|
||||||
|
// self.map.remove(key);
|
||||||
|
// }
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
pub fn clean_default_keys(&self) {
|
pub fn clean_default_keys(&self) {
|
||||||
// logging!(info, Type::Cache, "Cleaning proxies keys");
|
// logging!(info, Type::Cache, "Cleaning proxies keys");
|
||||||
@@ -96,5 +104,8 @@ impl Cache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use singleton macro
|
pub type CacheService = Cache<Result<String>>;
|
||||||
singleton!(Cache, INSTANCE);
|
pub type CacheProxy = Cache<Value>;
|
||||||
|
|
||||||
|
singleton!(Cache<Value>, PROXY_INSTANCE);
|
||||||
|
singleton!(Cache<Result<String>>, SERVICE_INSTANCE);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::{
|
use crate::{
|
||||||
cache::Cache,
|
cache::CacheProxy,
|
||||||
config::Config,
|
config::Config,
|
||||||
core::{CoreManager, handle},
|
core::{CoreManager, handle},
|
||||||
};
|
};
|
||||||
@@ -317,8 +317,8 @@ pub async fn get_clash_version() -> CmdResult<serde_json::Value> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_clash_config() -> CmdResult<serde_json::Value> {
|
pub async fn get_clash_config() -> CmdResult<serde_json::Value> {
|
||||||
let manager = IpcManager::global();
|
let manager = IpcManager::global();
|
||||||
let cache = Cache::global();
|
let cache = CacheProxy::global();
|
||||||
let key = Cache::make_key("clash_config", "default");
|
let key = CacheProxy::make_key("clash_config", "default");
|
||||||
let value = cache
|
let value = cache
|
||||||
.get_or_fetch(key, CONFIG_REFRESH_INTERVAL, || async {
|
.get_or_fetch(key, CONFIG_REFRESH_INTERVAL, || async {
|
||||||
manager.get_config().await.unwrap_or_else(|e| {
|
manager.get_config().await.unwrap_or_else(|e| {
|
||||||
@@ -333,8 +333,8 @@ pub async fn get_clash_config() -> CmdResult<serde_json::Value> {
|
|||||||
/// 强制刷新Clash配置缓存
|
/// 强制刷新Clash配置缓存
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn force_refresh_clash_config() -> CmdResult<serde_json::Value> {
|
pub async fn force_refresh_clash_config() -> CmdResult<serde_json::Value> {
|
||||||
let cache = Cache::global();
|
let cache = CacheProxy::global();
|
||||||
let key = Cache::make_key("clash_config", "default");
|
let key = CacheProxy::make_key("clash_config", "default");
|
||||||
cache.map.remove(&key);
|
cache.map.remove(&key);
|
||||||
get_clash_config().await
|
get_clash_config().await
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use tauri::Emitter;
|
|||||||
|
|
||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::{
|
use crate::{
|
||||||
cache::Cache,
|
cache::CacheProxy,
|
||||||
core::{handle::Handle, tray::Tray},
|
core::{handle::Handle, tray::Tray},
|
||||||
ipc::IpcManager,
|
ipc::IpcManager,
|
||||||
logging,
|
logging,
|
||||||
@@ -15,8 +15,8 @@ const PROVIDERS_REFRESH_INTERVAL: Duration = Duration::from_secs(60);
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
||||||
let cache = Cache::global();
|
let cache = CacheProxy::global();
|
||||||
let key = Cache::make_key("proxies", "default");
|
let key = CacheProxy::make_key("proxies", "default");
|
||||||
let value = cache
|
let value = cache
|
||||||
.get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async {
|
.get_or_fetch(key, PROXIES_REFRESH_INTERVAL, || async {
|
||||||
let manager = IpcManager::global();
|
let manager = IpcManager::global();
|
||||||
@@ -32,16 +32,16 @@ pub async fn get_proxies() -> CmdResult<serde_json::Value> {
|
|||||||
/// 强制刷新代理缓存用于profile切换
|
/// 强制刷新代理缓存用于profile切换
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn force_refresh_proxies() -> CmdResult<serde_json::Value> {
|
pub async fn force_refresh_proxies() -> CmdResult<serde_json::Value> {
|
||||||
let cache = Cache::global();
|
let cache = CacheProxy::global();
|
||||||
let key = Cache::make_key("proxies", "default");
|
let key = CacheProxy::make_key("proxies", "default");
|
||||||
cache.map.remove(&key);
|
cache.map.remove(&key);
|
||||||
get_proxies().await
|
get_proxies().await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
pub async fn get_providers_proxies() -> CmdResult<serde_json::Value> {
|
||||||
let cache = Cache::global();
|
let cache = CacheProxy::global();
|
||||||
let key = Cache::make_key("providers", "default");
|
let key = CacheProxy::make_key("providers", "default");
|
||||||
let value = cache
|
let value = cache
|
||||||
.get_or_fetch(key, PROVIDERS_REFRESH_INTERVAL, || async {
|
.get_or_fetch(key, PROVIDERS_REFRESH_INTERVAL, || async {
|
||||||
let manager = IpcManager::global();
|
let manager = IpcManager::global();
|
||||||
@@ -85,8 +85,8 @@ pub async fn update_proxy_and_sync(group: String, proxy: String) -> CmdResult<()
|
|||||||
proxy
|
proxy
|
||||||
);
|
);
|
||||||
|
|
||||||
let cache = Cache::global();
|
let cache = CacheProxy::global();
|
||||||
let key = Cache::make_key("proxies", "default");
|
let key = CacheProxy::make_key("proxies", "default");
|
||||||
cache.map.remove(&key);
|
cache.map.remove(&key);
|
||||||
|
|
||||||
if let Err(e) = Tray::global().update_menu().await {
|
if let Err(e) = Tray::global().update_menu().await {
|
||||||
|
|||||||
@@ -1,45 +1,47 @@
|
|||||||
use super::CmdResult;
|
use super::CmdResult;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{CoreManager, service},
|
core::{
|
||||||
|
CoreManager,
|
||||||
|
service::{self, SERVICE_MANAGER, ServiceStatus},
|
||||||
|
},
|
||||||
utils::i18n::t,
|
utils::i18n::t,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
async fn execute_service_operation_sync<F, Fut, E>(service_op: F, op_type: &str) -> CmdResult
|
async fn execute_service_operation_sync(status: ServiceStatus, op_type: &str) -> CmdResult {
|
||||||
where
|
if let Err(e) = SERVICE_MANAGER
|
||||||
F: FnOnce() -> Fut,
|
.lock()
|
||||||
Fut: std::future::Future<Output = Result<(), E>>,
|
.await
|
||||||
E: ToString + std::fmt::Debug,
|
.handle_service_status(&status)
|
||||||
{
|
.await
|
||||||
if let Err(e) = service_op().await {
|
{
|
||||||
let emsg = format!("{} {} failed: {}", op_type, "Service", e.to_string());
|
let emsg = format!("{} Service failed: {}", op_type, e);
|
||||||
return Err(t(emsg.as_str()).await);
|
return Err(t(emsg.as_str()).await);
|
||||||
}
|
}
|
||||||
if CoreManager::global().restart_core().await.is_err() {
|
if CoreManager::global().restart_core().await.is_err() {
|
||||||
let emsg = format!("{} {} failed", "Restart", "Core");
|
let emsg = "Restart Core failed";
|
||||||
return Err(t(emsg.as_str()).await);
|
return Err(t(emsg).await);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn install_service() -> CmdResult {
|
pub async fn install_service() -> CmdResult {
|
||||||
execute_service_operation_sync(service::install_service, "Install").await
|
execute_service_operation_sync(ServiceStatus::InstallRequired, "Install").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn uninstall_service() -> CmdResult {
|
pub async fn uninstall_service() -> CmdResult {
|
||||||
execute_service_operation_sync(service::uninstall_service, "Uninstall").await
|
execute_service_operation_sync(ServiceStatus::UninstallRequired, "Uninstall").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn reinstall_service() -> CmdResult {
|
pub async fn reinstall_service() -> CmdResult {
|
||||||
execute_service_operation_sync(service::reinstall_service, "Reinstall").await
|
execute_service_operation_sync(ServiceStatus::ReinstallRequired, "Reinstall").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn repair_service() -> CmdResult {
|
pub async fn repair_service() -> CmdResult {
|
||||||
execute_service_operation_sync(service::force_reinstall_service, "Repair").await
|
execute_service_operation_sync(ServiceStatus::ForceReinstallRequired, "Repair").await
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ impl Config {
|
|||||||
Some(("config_validate::boot_error", error_msg))
|
Some(("config_validate::boot_error", error_msg))
|
||||||
} else {
|
} else {
|
||||||
logging!(info, Type::Config, true, "配置验证成功");
|
logging!(info, Type::Config, true, "配置验证成功");
|
||||||
Some(("config_validate::success", String::new()))
|
// 前端没有必要知道验证成功的消息,也没有事件驱动
|
||||||
|
// Some(("config_validate::success", String::new()))
|
||||||
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
|||||||
@@ -203,9 +203,6 @@ pub struct IVerge {
|
|||||||
|
|
||||||
/// 启用外部控制器
|
/// 启用外部控制器
|
||||||
pub enable_external_controller: Option<bool>,
|
pub enable_external_controller: Option<bool>,
|
||||||
|
|
||||||
/// 服务状态跟踪
|
|
||||||
pub service_state: Option<crate::core::service::ServiceState>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
|
||||||
@@ -407,7 +404,6 @@ impl IVerge {
|
|||||||
auto_light_weight_minutes: Some(10),
|
auto_light_weight_minutes: Some(10),
|
||||||
enable_dns_settings: Some(false),
|
enable_dns_settings: Some(false),
|
||||||
home_cards: None,
|
home_cards: None,
|
||||||
service_state: None,
|
|
||||||
enable_external_controller: Some(false),
|
enable_external_controller: Some(false),
|
||||||
..Self::default()
|
..Self::default()
|
||||||
}
|
}
|
||||||
@@ -495,7 +491,6 @@ impl IVerge {
|
|||||||
patch!(auto_light_weight_minutes);
|
patch!(auto_light_weight_minutes);
|
||||||
patch!(enable_dns_settings);
|
patch!(enable_dns_settings);
|
||||||
patch!(home_cards);
|
patch!(home_cards);
|
||||||
patch!(service_state);
|
|
||||||
patch!(enable_external_controller);
|
patch!(enable_external_controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,7 +587,6 @@ pub struct IVergeResponse {
|
|||||||
pub home_cards: Option<serde_json::Value>,
|
pub home_cards: Option<serde_json::Value>,
|
||||||
pub enable_hover_jump_navigator: Option<bool>,
|
pub enable_hover_jump_navigator: Option<bool>,
|
||||||
pub enable_external_controller: Option<bool>,
|
pub enable_external_controller: Option<bool>,
|
||||||
pub service_state: Option<crate::core::service::ServiceState>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IVerge> for IVergeResponse {
|
impl From<IVerge> for IVergeResponse {
|
||||||
@@ -664,7 +658,6 @@ impl From<IVerge> for IVergeResponse {
|
|||||||
home_cards: verge.home_cards,
|
home_cards: verge.home_cards,
|
||||||
enable_hover_jump_navigator: verge.enable_hover_jump_navigator,
|
enable_hover_jump_navigator: verge.enable_hover_jump_navigator,
|
||||||
enable_external_controller: verge.enable_external_controller,
|
enable_external_controller: verge.enable_external_controller,
|
||||||
service_state: verge.service_state,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
use crate::AsyncHandler;
|
||||||
use crate::{
|
use crate::{
|
||||||
config::*,
|
config::*,
|
||||||
core::{
|
core::{
|
||||||
handle,
|
handle,
|
||||||
service::{self},
|
service::{self, SERVICE_MANAGER, ServiceStatus},
|
||||||
},
|
},
|
||||||
ipc::IpcManager,
|
ipc::IpcManager,
|
||||||
logging, logging_error,
|
logging, logging_error, singleton_lazy,
|
||||||
process::AsyncHandler,
|
|
||||||
singleton_lazy,
|
|
||||||
utils::{
|
utils::{
|
||||||
dirs,
|
dirs,
|
||||||
help::{self},
|
help::{self},
|
||||||
@@ -384,8 +384,6 @@ impl CoreManager {
|
|||||||
return Ok((true, String::new()));
|
return Ok((true, String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(info, Type::Config, true, "开始更新配置");
|
|
||||||
|
|
||||||
// 1. 先生成新的配置内容
|
// 1. 先生成新的配置内容
|
||||||
logging!(info, Type::Config, true, "生成新的配置内容");
|
logging!(info, Type::Config, true, "生成新的配置内容");
|
||||||
Config::generate().await?;
|
Config::generate().await?;
|
||||||
@@ -393,9 +391,8 @@ impl CoreManager {
|
|||||||
// 2. 验证配置
|
// 2. 验证配置
|
||||||
match self.validate_config().await {
|
match self.validate_config().await {
|
||||||
Ok((true, _)) => {
|
Ok((true, _)) => {
|
||||||
logging!(info, Type::Config, true, "配置验证通过");
|
|
||||||
// 4. 验证通过后,生成正式的运行时配置
|
// 4. 验证通过后,生成正式的运行时配置
|
||||||
logging!(info, Type::Config, true, "生成运行时配置");
|
logging!(info, Type::Config, true, "配置验证通过, 生成运行时配置");
|
||||||
let run_path = Config::generate_file(ConfigType::Run).await?;
|
let run_path = Config::generate_file(ConfigType::Run).await?;
|
||||||
logging_error!(Type::Config, true, self.put_configs_force(run_path).await);
|
logging_error!(Type::Config, true, self.put_configs_force(run_path).await);
|
||||||
Ok((true, "something".into()))
|
Ok((true, "something".into()))
|
||||||
@@ -734,7 +731,8 @@ impl CoreManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn start_core_by_sidecar(&self) -> Result<()> {
|
async fn start_core_by_sidecar(&self) -> Result<()> {
|
||||||
logging!(trace, Type::Core, true, "Running core by sidecar");
|
logging!(info, Type::Core, true, "Running core by sidecar");
|
||||||
|
|
||||||
let config_file = &Config::generate_file(ConfigType::Run).await?;
|
let config_file = &Config::generate_file(ConfigType::Run).await?;
|
||||||
let app_handle = handle::Handle::global()
|
let app_handle = handle::Handle::global()
|
||||||
.app_handle()
|
.app_handle()
|
||||||
@@ -763,7 +761,6 @@ impl CoreManager {
|
|||||||
])
|
])
|
||||||
.spawn()?;
|
.spawn()?;
|
||||||
|
|
||||||
AsyncHandler::spawn(move || async move {
|
|
||||||
while let Some(event) = rx.recv().await {
|
while let Some(event) = rx.recv().await {
|
||||||
if let tauri_plugin_shell::process::CommandEvent::Stdout(line) = event
|
if let tauri_plugin_shell::process::CommandEvent::Stdout(line) = event
|
||||||
&& let Err(e) = writeln!(log_file, "{}", String::from_utf8_lossy(&line))
|
&& let Err(e) = writeln!(log_file, "{}", String::from_utf8_lossy(&line))
|
||||||
@@ -777,7 +774,6 @@ impl CoreManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let pid = child.pid();
|
let pid = child.pid();
|
||||||
logging!(
|
logging!(
|
||||||
@@ -792,7 +788,7 @@ impl CoreManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn stop_core_by_sidecar(&self) -> Result<()> {
|
fn stop_core_by_sidecar(&self) -> Result<()> {
|
||||||
logging!(trace, Type::Core, true, "Stopping core by sidecar");
|
logging!(info, Type::Core, true, "Stopping core by sidecar");
|
||||||
|
|
||||||
if let Some(child) = self.child_sidecar.lock().take() {
|
if let Some(child) = self.child_sidecar.lock().take() {
|
||||||
let pid = child.pid();
|
let pid = child.pid();
|
||||||
@@ -812,14 +808,14 @@ impl CoreManager {
|
|||||||
|
|
||||||
impl CoreManager {
|
impl CoreManager {
|
||||||
async fn start_core_by_service(&self) -> Result<()> {
|
async fn start_core_by_service(&self) -> Result<()> {
|
||||||
logging!(trace, Type::Core, true, "Running core by service");
|
logging!(info, Type::Core, true, "Running core by service");
|
||||||
let config_file = &Config::generate_file(ConfigType::Run).await?;
|
let config_file = &Config::generate_file(ConfigType::Run).await?;
|
||||||
service::run_core_by_service(config_file).await?;
|
service::run_core_by_service(config_file).await?;
|
||||||
self.set_running_mode(RunningMode::Service);
|
self.set_running_mode(RunningMode::Service);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn stop_core_by_service(&self) -> Result<()> {
|
async fn stop_core_by_service(&self) -> Result<()> {
|
||||||
logging!(trace, Type::Core, true, "Stopping core by service");
|
logging!(info, Type::Core, true, "Stopping core by service");
|
||||||
service::stop_core_by_service().await?;
|
service::stop_core_by_service().await?;
|
||||||
self.set_running_mode(RunningMode::NotRunning);
|
self.set_running_mode(RunningMode::NotRunning);
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -835,58 +831,9 @@ impl Default for CoreManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use simplified singleton_lazy macro
|
|
||||||
singleton_lazy!(CoreManager, CORE_MANAGER, CoreManager::default);
|
|
||||||
|
|
||||||
impl CoreManager {
|
impl CoreManager {
|
||||||
// 当服务安装失败时的回退逻辑
|
|
||||||
async fn attempt_service_init(&self) -> Result<()> {
|
|
||||||
if service::check_service_needs_reinstall().await {
|
|
||||||
logging!(info, Type::Core, true, "服务版本不匹配或状态异常,执行重装");
|
|
||||||
if let Err(e) = service::reinstall_service().await {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"服务重装失败 during attempt_service_init: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
// 如果重装成功,还需要尝试启动服务
|
|
||||||
logging!(info, Type::Core, true, "服务重装成功,尝试启动服务");
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = self.start_core_by_service().await {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"通过服务启动核心失败 during attempt_service_init: {}",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
// 确保 prefer_sidecar 在 start_core_by_service 失败时也被设置
|
|
||||||
let mut state = service::ServiceState::get().await;
|
|
||||||
if !state.prefer_sidecar {
|
|
||||||
state.prefer_sidecar = true;
|
|
||||||
state.last_error = Some(format!("通过服务启动核心失败: {e}"));
|
|
||||||
if let Err(save_err) = state.save().await {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"保存ServiceState失败 (in attempt_service_init/start_core_by_service): {}",
|
|
||||||
save_err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn init(&self) -> Result<()> {
|
pub async fn init(&self) -> Result<()> {
|
||||||
logging!(trace, Type::Core, "Initializing core");
|
logging!(info, Type::Core, "Initializing core");
|
||||||
|
|
||||||
// 应用启动时先清理任何遗留的 mihomo 进程
|
// 应用启动时先清理任何遗留的 mihomo 进程
|
||||||
if let Err(e) = self.cleanup_orphaned_mihomo_processes().await {
|
if let Err(e) = self.cleanup_orphaned_mihomo_processes().await {
|
||||||
@@ -899,155 +846,11 @@ impl CoreManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut core_started_successfully = false;
|
// 使用简化的启动流程
|
||||||
|
logging!(info, Type::Core, true, "开始核心初始化");
|
||||||
|
self.start_core().await?;
|
||||||
|
|
||||||
if service::is_service_available().await.is_ok() {
|
logging!(info, Type::Core, true, "核心初始化完成");
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"服务当前可用或看似可用,尝试通过服务模式启动/重装"
|
|
||||||
);
|
|
||||||
match self.attempt_service_init().await {
|
|
||||||
Ok(_) => {
|
|
||||||
logging!(info, Type::Core, true, "服务模式成功启动核心");
|
|
||||||
core_started_successfully = true;
|
|
||||||
}
|
|
||||||
Err(_err) => {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"服务模式启动或重装失败。将尝试Sidecar模式回退。"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"服务初始不可用 (is_service_available 调用失败)"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !core_started_successfully {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"核心未通过服务模式启动,执行Sidecar回退或首次安装逻辑"
|
|
||||||
);
|
|
||||||
|
|
||||||
let service_state = service::ServiceState::get().await;
|
|
||||||
|
|
||||||
if service_state.prefer_sidecar {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"用户偏好Sidecar模式或先前服务启动失败,使用Sidecar模式启动"
|
|
||||||
);
|
|
||||||
self.start_core_by_sidecar().await?;
|
|
||||||
// 如果 sidecar 启动成功,我们可以认为核心初始化流程到此结束
|
|
||||||
// 后续的 Tray::global().subscribe_traffic().await 仍然会执行
|
|
||||||
} else {
|
|
||||||
let has_service_install_record = service_state.last_install_time > 0;
|
|
||||||
if !has_service_install_record {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"无服务安装记录 (首次运行或状态重置),尝试安装服务"
|
|
||||||
);
|
|
||||||
match service::install_service().await {
|
|
||||||
Ok(_) => {
|
|
||||||
logging!(info, Type::Core, true, "服务安装成功(首次尝试)");
|
|
||||||
let mut new_state = service::ServiceState::default();
|
|
||||||
new_state.record_install();
|
|
||||||
new_state.prefer_sidecar = false;
|
|
||||||
new_state.save().await?;
|
|
||||||
|
|
||||||
if service::is_service_available().await.is_ok() {
|
|
||||||
logging!(info, Type::Core, true, "新安装的服务可用,尝试启动");
|
|
||||||
if self.start_core_by_service().await.is_ok() {
|
|
||||||
logging!(info, Type::Core, true, "新安装的服务启动成功");
|
|
||||||
} else {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"新安装的服务启动失败,回退到Sidecar模式"
|
|
||||||
);
|
|
||||||
let mut final_state = service::ServiceState::get().await;
|
|
||||||
final_state.prefer_sidecar = true;
|
|
||||||
final_state.last_error =
|
|
||||||
Some("Newly installed service failed to start".to_string());
|
|
||||||
final_state.save().await?;
|
|
||||||
self.start_core_by_sidecar().await?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"服务安装成功但未能连接/立即可用,回退到Sidecar模式"
|
|
||||||
);
|
|
||||||
let mut final_state = service::ServiceState::get().await;
|
|
||||||
final_state.prefer_sidecar = true;
|
|
||||||
final_state.last_error = Some(
|
|
||||||
"Newly installed service not immediately available/connectable"
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
final_state.save().await?;
|
|
||||||
self.start_core_by_sidecar().await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
logging!(warn, Type::Core, true, "服务首次安装失败: {}", err);
|
|
||||||
let new_state = service::ServiceState {
|
|
||||||
last_error: Some(err.to_string()),
|
|
||||||
prefer_sidecar: true,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
new_state.save().await?;
|
|
||||||
self.start_core_by_sidecar().await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 有安装记录,服务未成功启动,且初始不偏好sidecar
|
|
||||||
// 这意味着服务之前可能可用,但 attempt_service_init 失败了(并应已设置 prefer_sidecar),
|
|
||||||
// 或者服务初始不可用,无偏好,有记录。应强制使用 sidecar。
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"有服务安装记录但服务不可用/未启动,强制切换到Sidecar模式"
|
|
||||||
);
|
|
||||||
let mut final_state = service::ServiceState::get().await;
|
|
||||||
if !final_state.prefer_sidecar {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"prefer_sidecar 为 false,因服务启动失败或不可用而强制设置为 true"
|
|
||||||
);
|
|
||||||
final_state.prefer_sidecar = true;
|
|
||||||
final_state.last_error =
|
|
||||||
Some(final_state.last_error.unwrap_or_else(|| {
|
|
||||||
"Service startup failed or unavailable before sidecar fallback"
|
|
||||||
.to_string()
|
|
||||||
}));
|
|
||||||
final_state.save().await?;
|
|
||||||
}
|
|
||||||
self.start_core_by_sidecar().await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logging!(trace, Type::Core, "Initied core logic completed");
|
|
||||||
// #[cfg(target_os = "macos")]
|
|
||||||
// logging_error!(Type::Core, true, Tray::global().subscribe_traffic().await);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1061,31 +864,32 @@ impl CoreManager {
|
|||||||
(*guard).clone()
|
(*guard).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn prestart_core(&self) -> Result<()> {
|
||||||
|
SERVICE_MANAGER.lock().await.refresh().await?;
|
||||||
|
match SERVICE_MANAGER.lock().await.current() {
|
||||||
|
ServiceStatus::Ready => {
|
||||||
|
self.set_running_mode(RunningMode::Service);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.set_running_mode(RunningMode::Sidecar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// 启动核心
|
/// 启动核心
|
||||||
pub async fn start_core(&self) -> Result<()> {
|
pub async fn start_core(&self) -> Result<()> {
|
||||||
if service::is_service_available().await.is_ok() {
|
self.prestart_core().await?;
|
||||||
if service::check_service_needs_reinstall().await {
|
|
||||||
service::reinstall_service().await?;
|
match self.get_running_mode() {
|
||||||
|
RunningMode::Service => {
|
||||||
|
logging_error!(Type::Core, true, self.start_core_by_service().await);
|
||||||
|
}
|
||||||
|
RunningMode::NotRunning | RunningMode::Sidecar => {
|
||||||
|
logging_error!(Type::Core, true, self.start_core_by_sidecar().await);
|
||||||
}
|
}
|
||||||
logging!(info, Type::Core, true, "服务可用,使用服务模式启动");
|
|
||||||
self.start_core_by_service().await?;
|
|
||||||
return Ok(());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 服务不可用,检查用户偏好
|
|
||||||
let service_state = service::ServiceState::get().await;
|
|
||||||
if service_state.prefer_sidecar {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Core,
|
|
||||||
true,
|
|
||||||
"服务不可用,根据用户偏好使用Sidecar模式"
|
|
||||||
);
|
|
||||||
self.start_core_by_sidecar().await?;
|
|
||||||
} else {
|
|
||||||
logging!(info, Type::Core, true, "服务不可用,使用Sidecar模式");
|
|
||||||
self.start_core_by_sidecar().await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,6 +904,7 @@ impl CoreManager {
|
|||||||
|
|
||||||
/// 重启内核
|
/// 重启内核
|
||||||
pub async fn restart_core(&self) -> Result<()> {
|
pub async fn restart_core(&self) -> Result<()> {
|
||||||
|
logging!(info, Type::Core, true, "Restarting core");
|
||||||
self.stop_core().await?;
|
self.stop_core().await?;
|
||||||
self.start_core().await?;
|
self.start_core().await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -1141,3 +946,6 @@ impl CoreManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use simplified singleton_lazy macro
|
||||||
|
singleton_lazy!(CoreManager, CORE_MANAGER, CoreManager::default);
|
||||||
|
|||||||
@@ -1,95 +1,27 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
|
cache::{CacheService, SHORT_TERM_TTL},
|
||||||
config::Config,
|
config::Config,
|
||||||
core::service_ipc::{IpcCommand, send_ipc_request},
|
core::service_ipc::{IpcCommand, send_ipc_request},
|
||||||
logging,
|
logging, logging_error,
|
||||||
utils::{dirs, logging::Type},
|
utils::{dirs, logging::Type},
|
||||||
};
|
};
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::{env::current_exe, path::PathBuf, process::Command as StdCommand};
|
||||||
env::current_exe,
|
use tokio::sync::Mutex;
|
||||||
path::PathBuf,
|
|
||||||
process::Command as StdCommand,
|
|
||||||
time::{SystemTime, UNIX_EPOCH},
|
|
||||||
};
|
|
||||||
|
|
||||||
const REQUIRED_SERVICE_VERSION: &str = "1.1.2"; // 定义所需的服务版本号
|
const REQUIRED_SERVICE_VERSION: &str = "1.1.2"; // 定义所需的服务版本号
|
||||||
|
|
||||||
// 限制重装时间和次数的常量
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
const REINSTALL_COOLDOWN_SECS: u64 = 300; // 5分钟冷却期
|
pub enum ServiceStatus {
|
||||||
const MAX_REINSTALLS_PER_DAY: u32 = 3; // 每24小时最多重装3次
|
Ready,
|
||||||
const ONE_DAY_SECS: u64 = 86400; // 24小时的秒数
|
NeedsReinstall,
|
||||||
|
InstallRequired,
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, Default)]
|
UninstallRequired,
|
||||||
pub struct ServiceState {
|
ReinstallRequired,
|
||||||
pub last_install_time: u64, // 上次安装时间戳 (Unix 时间戳,秒)
|
ForceReinstallRequired,
|
||||||
pub install_count: u32, // 24小时内安装次数
|
Unavailable(String),
|
||||||
pub last_check_time: u64, // 上次检查时间
|
|
||||||
pub last_error: Option<String>, // 上次错误信息
|
|
||||||
pub prefer_sidecar: bool, // 用户是否偏好sidecar模式,如拒绝安装服务或安装失败
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServiceState {
|
|
||||||
// 获取当前的服务状态
|
|
||||||
pub async fn get() -> Self {
|
|
||||||
if let Some(state) = Config::verge().await.latest_ref().service_state.clone() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存服务状态
|
|
||||||
pub async fn save(&self) -> Result<()> {
|
|
||||||
let config = Config::verge().await;
|
|
||||||
let mut latest = config.latest_ref().clone();
|
|
||||||
latest.service_state = Some(self.clone());
|
|
||||||
*config.draft_mut() = latest;
|
|
||||||
config.apply();
|
|
||||||
|
|
||||||
// 先获取数据,再异步保存,避免跨await持有锁
|
|
||||||
let verge_data = config.latest_ref().clone();
|
|
||||||
drop(config); // 显式释放锁
|
|
||||||
|
|
||||||
verge_data.save_file().await
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新安装信息
|
|
||||||
pub fn record_install(&mut self) {
|
|
||||||
let now = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_secs();
|
|
||||||
|
|
||||||
// 检查是否需要重置计数器(24小时已过)
|
|
||||||
if now - self.last_install_time > ONE_DAY_SECS {
|
|
||||||
self.install_count = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.last_install_time = now;
|
|
||||||
self.install_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否可以重新安装
|
|
||||||
pub fn can_reinstall(&self) -> bool {
|
|
||||||
let now = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.as_secs();
|
|
||||||
|
|
||||||
// 如果在冷却期内,不允许重装
|
|
||||||
if now - self.last_install_time < REINSTALL_COOLDOWN_SECS {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果24小时内安装次数过多,也不允许
|
|
||||||
if now - self.last_install_time < ONE_DAY_SECS
|
|
||||||
&& self.install_count >= MAX_REINSTALLS_PER_DAY
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保留核心数据结构,但将HTTP特定的结构体合并为通用结构体
|
// 保留核心数据结构,但将HTTP特定的结构体合并为通用结构体
|
||||||
@@ -101,12 +33,6 @@ pub struct ResponseBody {
|
|||||||
pub log_file: String,
|
pub log_file: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
|
||||||
pub struct VersionResponse {
|
|
||||||
pub service: String,
|
|
||||||
pub version: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保留通用的响应结构体,用于IPC通信后的数据解析
|
// 保留通用的响应结构体,用于IPC通信后的数据解析
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone)]
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
pub struct JsonResponse {
|
pub struct JsonResponse {
|
||||||
@@ -115,8 +41,12 @@ pub struct JsonResponse {
|
|||||||
pub data: Option<ResponseBody>,
|
pub data: Option<ResponseBody>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServiceManager(ServiceStatus);
|
||||||
|
|
||||||
|
#[allow(clippy::unused_async)]
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub async fn uninstall_service() -> Result<()> {
|
async fn uninstall_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "uninstall service");
|
logging!(info, Type::Service, true, "uninstall service");
|
||||||
|
|
||||||
use deelevate::{PrivilegeLevel, Token};
|
use deelevate::{PrivilegeLevel, Token};
|
||||||
@@ -149,8 +79,9 @@ pub async fn uninstall_service() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::unused_async)]
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub async fn install_service() -> Result<()> {
|
async fn install_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "install service");
|
logging!(info, Type::Service, true, "install service");
|
||||||
|
|
||||||
use deelevate::{PrivilegeLevel, Token};
|
use deelevate::{PrivilegeLevel, Token};
|
||||||
@@ -184,23 +115,9 @@ pub async fn install_service() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
pub async fn reinstall_service() -> Result<()> {
|
async fn reinstall_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "reinstall service");
|
logging!(info, Type::Service, true, "reinstall service");
|
||||||
|
|
||||||
// 获取当前服务状态
|
|
||||||
let mut service_state = ServiceState::get().await;
|
|
||||||
|
|
||||||
// 检查是否允许重装
|
|
||||||
if !service_state.can_reinstall() {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"service reinstall rejected: cooldown period or max attempts reached"
|
|
||||||
);
|
|
||||||
bail!("Service reinstallation is rate limited. Please try again later.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先卸载服务
|
// 先卸载服务
|
||||||
if let Err(err) = uninstall_service().await {
|
if let Err(err) = uninstall_service().await {
|
||||||
logging!(
|
logging!(
|
||||||
@@ -214,26 +131,16 @@ pub async fn reinstall_service() -> Result<()> {
|
|||||||
|
|
||||||
// 再安装服务
|
// 再安装服务
|
||||||
match install_service().await {
|
match install_service().await {
|
||||||
Ok(_) => {
|
Ok(_) => Ok(()),
|
||||||
// 记录安装信息并保存
|
|
||||||
service_state.record_install();
|
|
||||||
service_state.last_error = None;
|
|
||||||
service_state.save().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error = format!("failed to install service: {err}");
|
bail!(format!("failed to install service: {err}"))
|
||||||
service_state.last_error = Some(error.clone());
|
|
||||||
service_state.prefer_sidecar = true;
|
|
||||||
service_state.save().await?;
|
|
||||||
bail!(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::unused_async)]
|
#[allow(clippy::unused_async)]
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub async fn uninstall_service() -> Result<()> {
|
async fn uninstall_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "uninstall service");
|
logging!(info, Type::Service, true, "uninstall service");
|
||||||
use users::get_effective_uid;
|
use users::get_effective_uid;
|
||||||
|
|
||||||
@@ -273,7 +180,8 @@ pub async fn uninstall_service() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub async fn install_service() -> Result<()> {
|
#[allow(clippy::unused_async)]
|
||||||
|
async fn install_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "install service");
|
logging!(info, Type::Service, true, "install service");
|
||||||
use users::get_effective_uid;
|
use users::get_effective_uid;
|
||||||
|
|
||||||
@@ -313,23 +221,9 @@ pub async fn install_service() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub async fn reinstall_service() -> Result<()> {
|
async fn reinstall_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "reinstall service");
|
logging!(info, Type::Service, true, "reinstall service");
|
||||||
|
|
||||||
// 获取当前服务状态
|
|
||||||
let mut service_state = ServiceState::get().await;
|
|
||||||
|
|
||||||
// 检查是否允许重装
|
|
||||||
if !service_state.can_reinstall() {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"service reinstall rejected: cooldown period or max attempts reached"
|
|
||||||
);
|
|
||||||
bail!("Service reinstallation is rate limited. Please try again later.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先卸载服务
|
// 先卸载服务
|
||||||
if let Err(err) = uninstall_service().await {
|
if let Err(err) = uninstall_service().await {
|
||||||
logging!(
|
logging!(
|
||||||
@@ -343,25 +237,15 @@ pub async fn reinstall_service() -> Result<()> {
|
|||||||
|
|
||||||
// 再安装服务
|
// 再安装服务
|
||||||
match install_service().await {
|
match install_service().await {
|
||||||
Ok(_) => {
|
Ok(_) => Ok(()),
|
||||||
// 记录安装信息并保存
|
|
||||||
service_state.record_install();
|
|
||||||
service_state.last_error = None;
|
|
||||||
service_state.save().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error = format!("failed to install service: {err}");
|
bail!(format!("failed to install service: {err}"))
|
||||||
service_state.last_error = Some(error.clone());
|
|
||||||
service_state.prefer_sidecar = true;
|
|
||||||
service_state.save().await?;
|
|
||||||
bail!(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub async fn uninstall_service() -> Result<()> {
|
async fn uninstall_service() -> Result<()> {
|
||||||
use crate::utils::i18n::t;
|
use crate::utils::i18n::t;
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "uninstall service");
|
logging!(info, Type::Service, true, "uninstall service");
|
||||||
@@ -397,7 +281,7 @@ pub async fn uninstall_service() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub async fn install_service() -> Result<()> {
|
async fn install_service() -> Result<()> {
|
||||||
use crate::utils::i18n::t;
|
use crate::utils::i18n::t;
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "install service");
|
logging!(info, Type::Service, true, "install service");
|
||||||
@@ -433,23 +317,9 @@ pub async fn install_service() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub async fn reinstall_service() -> Result<()> {
|
async fn reinstall_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "reinstall service");
|
logging!(info, Type::Service, true, "reinstall service");
|
||||||
|
|
||||||
// 获取当前服务状态
|
|
||||||
let mut service_state = ServiceState::get().await;
|
|
||||||
|
|
||||||
// 检查是否允许重装
|
|
||||||
if !service_state.can_reinstall() {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"service reinstall rejected: cooldown period or max attempts reached"
|
|
||||||
);
|
|
||||||
bail!("Service reinstallation is rate limited. Please try again later.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 先卸载服务
|
// 先卸载服务
|
||||||
if let Err(err) = uninstall_service().await {
|
if let Err(err) = uninstall_service().await {
|
||||||
logging!(
|
logging!(
|
||||||
@@ -463,440 +333,114 @@ pub async fn reinstall_service() -> Result<()> {
|
|||||||
|
|
||||||
// 再安装服务
|
// 再安装服务
|
||||||
match install_service().await {
|
match install_service().await {
|
||||||
Ok(_) => {
|
Ok(_) => Ok(()),
|
||||||
// 记录安装信息并保存
|
|
||||||
service_state.record_install();
|
|
||||||
service_state.last_error = None;
|
|
||||||
service_state.save().await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let error = format!("failed to install service: {err}");
|
bail!(format!("failed to install service: {err}"))
|
||||||
service_state.last_error = Some(error.clone());
|
|
||||||
service_state.prefer_sidecar = true;
|
|
||||||
service_state.save().await?;
|
|
||||||
bail!(error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查服务状态 - 使用IPC通信
|
/// 强制重装服务(UI修复按钮)
|
||||||
pub async fn check_ipc_service_status() -> Result<JsonResponse> {
|
pub async fn force_reinstall_service() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "开始检查服务状态 (IPC)");
|
logging!(info, Type::Service, true, "用户请求强制重装服务");
|
||||||
|
reinstall_service().await.map_err(|err| {
|
||||||
// 使用IPC通信
|
logging!(error, Type::Service, true, "强制重装服务失败: {}", err);
|
||||||
let payload = serde_json::json!({});
|
err
|
||||||
// logging!(debug, Type::Service, true, "发送GetClash请求");
|
})
|
||||||
|
|
||||||
match send_ipc_request(IpcCommand::GetClash, payload).await {
|
|
||||||
Ok(response) => {
|
|
||||||
/* logging!(
|
|
||||||
debug,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"收到GetClash响应: success={}, error={:?}",
|
|
||||||
response.success,
|
|
||||||
response.error
|
|
||||||
); */
|
|
||||||
|
|
||||||
if !response.success {
|
|
||||||
let err_msg = response.error.unwrap_or_else(|| "未知服务错误".to_string());
|
|
||||||
logging!(error, Type::Service, true, "服务响应错误: {}", err_msg);
|
|
||||||
bail!(err_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
match response.data {
|
|
||||||
Some(data) => {
|
|
||||||
// 检查嵌套结构
|
|
||||||
if let (Some(code), Some(msg)) = (data.get("code"), data.get("msg")) {
|
|
||||||
let code_value = code.as_u64().unwrap_or(0);
|
|
||||||
let msg_value = msg.as_str().unwrap_or("ok").to_string();
|
|
||||||
|
|
||||||
// 提取嵌套的data字段并解析为ResponseBody
|
|
||||||
let response_body = if let Some(nested_data) = data.get("data") {
|
|
||||||
match serde_json::from_value::<ResponseBody>(nested_data.clone()) {
|
|
||||||
Ok(body) => Some(body),
|
|
||||||
Err(e) => {
|
|
||||||
logging!(
|
|
||||||
warn,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"解析嵌套的ResponseBody失败: {}; 尝试其他方式",
|
|
||||||
e
|
|
||||||
);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let json_response = JsonResponse {
|
|
||||||
code: code_value,
|
|
||||||
msg: msg_value,
|
|
||||||
data: response_body,
|
|
||||||
};
|
|
||||||
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"服务检测成功: code={}, msg={}, data存在={}",
|
|
||||||
json_response.code,
|
|
||||||
json_response.msg,
|
|
||||||
json_response.data.is_some()
|
|
||||||
);
|
|
||||||
Ok(json_response)
|
|
||||||
} else {
|
|
||||||
// 尝试直接解析
|
|
||||||
match serde_json::from_value::<JsonResponse>(data.clone()) {
|
|
||||||
Ok(json_response) => {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"服务检测成功: code={}, msg={}",
|
|
||||||
json_response.code,
|
|
||||||
json_response.msg
|
|
||||||
);
|
|
||||||
Ok(json_response)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"解析服务响应失败: {}; 原始数据: {:?}",
|
|
||||||
e,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
bail!("无法解析服务响应数据: {}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
logging!(error, Type::Service, true, "服务响应中没有数据");
|
|
||||||
bail!("服务响应中没有数据")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查服务版本 - 使用IPC通信
|
/// 检查服务版本 - 使用IPC通信
|
||||||
pub async fn check_service_version() -> Result<String> {
|
async fn check_service_version() -> Result<String> {
|
||||||
|
let cache = CacheService::global();
|
||||||
|
let key = CacheService::make_key("service", "version");
|
||||||
|
let version_arc = cache
|
||||||
|
.get_or_fetch(key, SHORT_TERM_TTL, || async {
|
||||||
logging!(info, Type::Service, true, "开始检查服务版本 (IPC)");
|
logging!(info, Type::Service, true, "开始检查服务版本 (IPC)");
|
||||||
|
|
||||||
let payload = serde_json::json!({});
|
let payload = serde_json::json!({});
|
||||||
// logging!(debug, Type::Service, true, "发送GetVersion请求");
|
let response = send_ipc_request(IpcCommand::GetVersion, payload).await?;
|
||||||
|
|
||||||
match send_ipc_request(IpcCommand::GetVersion, payload).await {
|
let data = response
|
||||||
Ok(response) => {
|
.data
|
||||||
/* logging!(
|
.ok_or_else(|| anyhow::anyhow!("服务版本响应中没有数据"))?;
|
||||||
debug,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"收到GetVersion响应: success={}, error={:?}",
|
|
||||||
response.success,
|
|
||||||
response.error
|
|
||||||
); */
|
|
||||||
|
|
||||||
if !response.success {
|
if let Some(nested_data) = data.get("data")
|
||||||
let err_msg = response
|
&& let Some(version) = nested_data.get("version").and_then(|v| v.as_str())
|
||||||
.error
|
|
||||||
.unwrap_or_else(|| "获取服务版本失败".to_string());
|
|
||||||
logging!(error, Type::Service, true, "获取版本错误: {}", err_msg);
|
|
||||||
bail!(err_msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
match response.data {
|
|
||||||
Some(data) => {
|
|
||||||
if let Some(nested_data) = data.get("data") {
|
|
||||||
if let Some(version) = nested_data.get("version")
|
|
||||||
&& let Some(version_str) = version.as_str()
|
|
||||||
{
|
{
|
||||||
logging!(info, Type::Service, true, "获取到服务版本: {}", version_str);
|
// logging!(info, Type::Service, true, "获取到服务版本: {}", version);
|
||||||
return Ok(version_str.to_string());
|
return Ok(version.to_string());
|
||||||
}
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"嵌套数据中没有version字段: {:?}",
|
|
||||||
nested_data
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// 兼容旧格式
|
|
||||||
match serde_json::from_value::<VersionResponse>(data.clone()) {
|
|
||||||
Ok(version_response) => {
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"获取到服务版本: {}",
|
|
||||||
version_response.version
|
|
||||||
);
|
|
||||||
return Ok(version_response.version);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"解析版本响应失败: {}; 原始数据: {:?}",
|
|
||||||
e,
|
|
||||||
data
|
|
||||||
);
|
|
||||||
bail!("无法解析服务版本数据: {}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bail!("响应中未找到有效的版本信息")
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
logging!(error, Type::Service, true, "版本响应中没有数据");
|
|
||||||
bail!("服务版本响应中没有数据")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Service, true, "IPC通信失败: {}", e);
|
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok("unknown".to_string())
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match version_arc.as_ref() {
|
||||||
|
Ok(v) => Ok(v.clone()),
|
||||||
|
Err(e) => Err(anyhow::Error::msg(e.to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查服务是否需要重装
|
/// 检查服务是否需要重装
|
||||||
pub async fn check_service_needs_reinstall() -> bool {
|
pub async fn check_service_needs_reinstall() -> bool {
|
||||||
logging!(info, Type::Service, true, "开始检查服务是否需要重装");
|
|
||||||
|
|
||||||
let service_state = ServiceState::get().await;
|
|
||||||
|
|
||||||
if !service_state.can_reinstall() {
|
|
||||||
log::info!(target: "app", "服务重装检查: 处于冷却期或已达最大尝试次数");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查版本和可用性
|
|
||||||
match check_service_version().await {
|
match check_service_version().await {
|
||||||
Ok(version) => {
|
Ok(version) => version != REQUIRED_SERVICE_VERSION,
|
||||||
log::info!(target: "app", "服务版本检测:当前={version}, 要求={REQUIRED_SERVICE_VERSION}");
|
Err(_) => false,
|
||||||
/* logging!(
|
|
||||||
info,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"服务版本检测:当前={}, 要求={}",
|
|
||||||
version,
|
|
||||||
REQUIRED_SERVICE_VERSION
|
|
||||||
); */
|
|
||||||
|
|
||||||
let needs_reinstall = version != REQUIRED_SERVICE_VERSION;
|
|
||||||
if needs_reinstall {
|
|
||||||
log::warn!(target: "app", "发现服务版本不匹配,需要重装! 当前={version}, 要求={REQUIRED_SERVICE_VERSION}");
|
|
||||||
logging!(warn, Type::Service, true, "服务版本不匹配,需要重装");
|
|
||||||
|
|
||||||
// log::debug!(target: "app", "当前版本字节: {:?}", version.as_bytes());
|
|
||||||
// log::debug!(target: "app", "要求版本字节: {:?}", REQUIRED_SERVICE_VERSION.as_bytes());
|
|
||||||
} else {
|
|
||||||
log::info!(target: "app", "服务版本匹配,无需重装");
|
|
||||||
// logging!(info, Type::Service, true, "服务版本匹配,无需重装");
|
|
||||||
}
|
|
||||||
|
|
||||||
needs_reinstall
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
logging!(error, Type::Service, true, "检查服务版本失败: {}", err);
|
|
||||||
|
|
||||||
// 检查服务是否可用
|
|
||||||
match is_service_available().await {
|
|
||||||
Ok(()) => {
|
|
||||||
log::info!(target: "app", "服务正在运行但版本检查失败: {err}");
|
|
||||||
/* logging!(
|
|
||||||
info,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"服务正在运行但版本检查失败: {}",
|
|
||||||
err
|
|
||||||
); */
|
|
||||||
false
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
log::info!(target: "app", "服务不可用或未运行,需要重装");
|
|
||||||
// logging!(info, Type::Service, true, "服务不可用或未运行,需要重装");
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 尝试使用服务启动core
|
/// 尝试使用服务启动core
|
||||||
pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> {
|
pub(super) async fn start_with_existing_service(config_file: &PathBuf) -> Result<()> {
|
||||||
log::info!(target:"app", "尝试使用现有服务启动核心 (IPC)");
|
logging!(info, Type::Service, true, "尝试使用现有服务启动核心");
|
||||||
// logging!(info, Type::Service, true, "尝试使用现有服务启动核心");
|
|
||||||
|
|
||||||
let clash_core = Config::verge().await.latest_ref().get_valid_clash_core();
|
let verge_config = Config::verge().await;
|
||||||
|
let clash_core = verge_config.latest_ref().get_valid_clash_core();
|
||||||
|
drop(verge_config);
|
||||||
|
|
||||||
let bin_ext = if cfg!(windows) { ".exe" } else { "" };
|
let bin_ext = if cfg!(windows) { ".exe" } else { "" };
|
||||||
let clash_bin = format!("{clash_core}{bin_ext}");
|
let bin_path = current_exe()?.with_file_name(format!("{clash_core}{bin_ext}"));
|
||||||
let bin_path = current_exe()?.with_file_name(clash_bin);
|
|
||||||
let bin_path = dirs::path_to_str(&bin_path)?;
|
|
||||||
|
|
||||||
let config_dir = dirs::app_home_dir()?;
|
|
||||||
let config_dir = dirs::path_to_str(&config_dir)?;
|
|
||||||
|
|
||||||
let log_path = dirs::service_log_file()?;
|
|
||||||
let log_path = dirs::path_to_str(&log_path)?;
|
|
||||||
|
|
||||||
let config_file = dirs::path_to_str(config_file)?;
|
|
||||||
|
|
||||||
// 构建启动参数
|
|
||||||
let payload = serde_json::json!({
|
let payload = serde_json::json!({
|
||||||
"core_type": clash_core,
|
"core_type": clash_core,
|
||||||
"bin_path": bin_path,
|
"bin_path": dirs::path_to_str(&bin_path)?,
|
||||||
"config_dir": config_dir,
|
"config_dir": dirs::path_to_str(&dirs::app_home_dir()?)?,
|
||||||
"config_file": config_file,
|
"config_file": dirs::path_to_str(config_file)?,
|
||||||
"log_file": log_path,
|
"log_file": dirs::path_to_str(&dirs::service_log_file()?)?,
|
||||||
});
|
});
|
||||||
|
|
||||||
// log::info!(target:"app", "启动服务参数: {:?}", payload);
|
let response = send_ipc_request(IpcCommand::StartClash, payload)
|
||||||
// logging!(info, Type::Service, true, "发送StartClash请求");
|
.await
|
||||||
|
.context("无法连接到Clash Verge Service")?;
|
||||||
// 使用IPC通信
|
|
||||||
match send_ipc_request(IpcCommand::StartClash, payload).await {
|
|
||||||
Ok(response) => {
|
|
||||||
/* logging!(
|
|
||||||
info,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"收到StartClash响应: success={}, error={:?}",
|
|
||||||
response.success,
|
|
||||||
response.error
|
|
||||||
); */
|
|
||||||
|
|
||||||
if !response.success {
|
if !response.success {
|
||||||
let err_msg = response.error.unwrap_or_else(|| "启动核心失败".to_string());
|
let err_msg = response.error.unwrap_or_else(|| "启动核心失败".to_string());
|
||||||
logging!(error, Type::Service, true, "启动核心失败: {}", err_msg);
|
|
||||||
bail!(err_msg);
|
bail!(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加对嵌套JSON结构的处理
|
|
||||||
if let Some(data) = &response.data
|
if let Some(data) = &response.data
|
||||||
&& let Some(code) = data.get("code")
|
&& let Some(code) = data.get("code").and_then(|c| c.as_u64())
|
||||||
|
&& code != 0
|
||||||
{
|
{
|
||||||
let code_value = code.as_u64().unwrap_or(1);
|
|
||||||
let msg = data
|
let msg = data
|
||||||
.get("msg")
|
.get("msg")
|
||||||
.and_then(|m| m.as_str())
|
.and_then(|m| m.as_str())
|
||||||
.unwrap_or("未知错误");
|
.unwrap_or("未知错误");
|
||||||
|
|
||||||
if code_value != 0 {
|
|
||||||
logging!(
|
|
||||||
error,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"启动核心返回错误: code={}, msg={}",
|
|
||||||
code_value,
|
|
||||||
msg
|
|
||||||
);
|
|
||||||
bail!("启动核心失败: {}", msg);
|
bail!("启动核心失败: {}", msg);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "服务成功启动核心");
|
logging!(info, Type::Service, true, "服务成功启动核心");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Service, true, "启动核心IPC通信失败: {}", e);
|
|
||||||
bail!("无法连接到Clash Verge Service: {}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 以服务启动core
|
// 以服务启动core
|
||||||
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
|
pub(super) async fn run_core_by_service(config_file: &PathBuf) -> Result<()> {
|
||||||
log::info!(target: "app", "正在尝试通过服务启动核心");
|
logging!(info, Type::Service, true, "正在尝试通过服务启动核心");
|
||||||
|
|
||||||
// 先检查服务版本,不受冷却期限制
|
|
||||||
let version_check = match check_service_version().await {
|
|
||||||
Ok(version) => {
|
|
||||||
log::info!(target: "app", "检测到服务版本: {version}, 要求版本: {REQUIRED_SERVICE_VERSION}");
|
|
||||||
|
|
||||||
if version.as_bytes() != REQUIRED_SERVICE_VERSION.as_bytes() {
|
|
||||||
log::warn!(target: "app", "服务版本不匹配,需要重装");
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
log::info!(target: "app", "服务版本匹配");
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!(target: "app", "无法获取服务版本: {err}");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if version_check && is_service_available().await.is_ok() {
|
|
||||||
log::info!(target: "app", "服务已在运行且版本匹配,尝试使用");
|
|
||||||
return start_with_existing_service(config_file).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if !version_check {
|
|
||||||
log::info!(target: "app", "服务版本不匹配,尝试重装");
|
|
||||||
|
|
||||||
let service_state = ServiceState::get().await;
|
|
||||||
if !service_state.can_reinstall() {
|
|
||||||
log::warn!(target: "app", "由于限制无法重装服务");
|
|
||||||
if let Ok(()) = start_with_existing_service(config_file).await {
|
|
||||||
log::info!(target: "app", "尽管版本不匹配,但成功启动了服务");
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
bail!("服务版本不匹配且无法重装,启动失败");
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!(target: "app", "开始重装服务");
|
|
||||||
if let Err(err) = reinstall_service().await {
|
|
||||||
log::warn!(target: "app", "服务重装失败: {err}");
|
|
||||||
bail!("Failed to reinstall service: {}", err);
|
|
||||||
}
|
|
||||||
|
|
||||||
log::info!(target: "app", "服务重装成功,尝试启动");
|
|
||||||
return start_with_existing_service(config_file).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查服务状态
|
|
||||||
match check_ipc_service_status().await {
|
|
||||||
Ok(_) => {
|
|
||||||
log::info!(target: "app", "服务可用但未运行核心,尝试启动");
|
|
||||||
if let Ok(()) = start_with_existing_service(config_file).await {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::warn!(target: "app", "服务检查失败: {err}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 服务不可用或启动失败,检查是否需要重装
|
|
||||||
if check_service_needs_reinstall().await {
|
if check_service_needs_reinstall().await {
|
||||||
log::info!(target: "app", "服务需要重装");
|
reinstall_service().await?;
|
||||||
|
|
||||||
if let Err(err) = reinstall_service().await {
|
|
||||||
log::warn!(target: "app", "服务重装失败: {err}");
|
|
||||||
bail!("Failed to reinstall service: {}", err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log::info!(target: "app", "服务重装完成,尝试启动核心");
|
logging!(info, Type::Service, true, "服务已运行且版本匹配,直接使用");
|
||||||
start_with_existing_service(config_file).await
|
start_with_existing_service(config_file).await
|
||||||
} else {
|
|
||||||
log::warn!(target: "app", "服务不可用且无法重装");
|
|
||||||
bail!("Service is not available and cannot be reinstalled at this time")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 通过服务停止core
|
/// 通过服务停止core
|
||||||
@@ -909,7 +453,9 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
|||||||
.context("无法连接到Clash Verge Service")?;
|
.context("无法连接到Clash Verge Service")?;
|
||||||
|
|
||||||
if !response.success {
|
if !response.success {
|
||||||
bail!(response.error.unwrap_or_else(|| "停止核心失败".to_string()));
|
let err_msg = response.error.unwrap_or_else(|| "停止核心失败".to_string());
|
||||||
|
logging!(error, Type::Service, true, "停止核心失败: {}", err_msg);
|
||||||
|
bail!(err_msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(data) = &response.data
|
if let Some(data) = &response.data
|
||||||
@@ -934,54 +480,105 @@ pub(super) async fn stop_core_by_service() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logging!(info, Type::Service, true, "服务成功停止核心");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 检查服务是否正在运行
|
/// 检查服务是否正在运行
|
||||||
pub async fn is_service_available() -> Result<()> {
|
pub async fn is_service_available() -> Result<()> {
|
||||||
logging!(info, Type::Service, true, "开始检查服务是否正在运行");
|
check_service_version().await?;
|
||||||
|
|
||||||
match check_ipc_service_status().await {
|
|
||||||
Ok(resp) => {
|
|
||||||
if resp.code == 0 && resp.msg == "ok" && resp.data.is_some() {
|
|
||||||
logging!(info, Type::Service, true, "服务正在运行");
|
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
}
|
||||||
logging!(
|
|
||||||
warn,
|
impl ServiceManager {
|
||||||
|
pub fn default() -> Self {
|
||||||
|
Self(ServiceStatus::Unavailable("Need Checks".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current(&self) -> ServiceStatus {
|
||||||
|
self.0.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn refresh(&mut self) -> Result<()> {
|
||||||
|
let status = self.check_service_comprehensive().await;
|
||||||
|
logging_error!(
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"服务未正常运行: code={}, msg={}",
|
self.handle_service_status(&status).await
|
||||||
resp.code,
|
|
||||||
resp.msg
|
|
||||||
);
|
);
|
||||||
|
self.0 = status;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 综合服务状态检查(一次性完成所有检查)
|
||||||
|
pub async fn check_service_comprehensive(&self) -> ServiceStatus {
|
||||||
|
match is_service_available().await {
|
||||||
|
Ok(_) => {
|
||||||
|
logging!(info, Type::Service, true, "服务当前可用,检查是否需要重装");
|
||||||
|
if check_service_needs_reinstall().await {
|
||||||
|
logging!(info, Type::Service, true, "服务需要重装且允许重装");
|
||||||
|
ServiceStatus::NeedsReinstall
|
||||||
|
} else {
|
||||||
|
ServiceStatus::Ready
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
logging!(error, Type::Service, true, "检查服务运行状态失败: {}", err);
|
logging!(warn, Type::Service, true, "服务不可用,检查安装状态");
|
||||||
Err(err)
|
ServiceStatus::Unavailable(err.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据服务状态执行相应操作
|
||||||
|
pub async fn handle_service_status(&mut self, status: &ServiceStatus) -> Result<()> {
|
||||||
|
match status {
|
||||||
|
ServiceStatus::Ready => {
|
||||||
|
logging!(info, Type::Service, true, "服务就绪,直接启动");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ServiceStatus::NeedsReinstall | ServiceStatus::ReinstallRequired => {
|
||||||
|
logging!(info, Type::Service, true, "服务需要重装,执行重装流程");
|
||||||
|
reinstall_service().await?;
|
||||||
|
self.0 = ServiceStatus::Ready;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ServiceStatus::ForceReinstallRequired => {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"服务需要强制重装,执行强制重装流程"
|
||||||
|
);
|
||||||
|
force_reinstall_service().await?;
|
||||||
|
self.0 = ServiceStatus::Ready;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ServiceStatus::InstallRequired => {
|
||||||
|
logging!(info, Type::Service, true, "需要安装服务,执行安装流程");
|
||||||
|
install_service().await?;
|
||||||
|
self.0 = ServiceStatus::Ready;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ServiceStatus::UninstallRequired => {
|
||||||
|
logging!(info, Type::Service, true, "服务需要卸载,执行卸载流程");
|
||||||
|
uninstall_service().await?;
|
||||||
|
self.0 = ServiceStatus::Unavailable("Service Uninstalled".into());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
ServiceStatus::Unavailable(reason) => {
|
||||||
|
logging!(
|
||||||
|
info,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"服务不可用: {},将使用Sidecar模式",
|
||||||
|
reason
|
||||||
|
);
|
||||||
|
self.0 = ServiceStatus::Unavailable(reason.clone());
|
||||||
|
Err(anyhow::anyhow!("服务不可用: {}", reason))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 强制重装服务(UI修复按钮)
|
pub static SERVICE_MANAGER: Lazy<Mutex<ServiceManager>> =
|
||||||
pub async fn force_reinstall_service() -> Result<()> {
|
Lazy::new(|| Mutex::new(ServiceManager::default()));
|
||||||
log::info!(target: "app", "用户请求强制重装服务");
|
|
||||||
|
|
||||||
let service_state = ServiceState::default();
|
|
||||||
service_state.save().await?;
|
|
||||||
|
|
||||||
log::info!(target: "app", "已重置服务状态,开始执行重装");
|
|
||||||
|
|
||||||
match reinstall_service().await {
|
|
||||||
Ok(()) => {
|
|
||||||
log::info!(target: "app", "服务重装成功");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
log::error!(target: "app", "强制重装服务失败: {err}");
|
|
||||||
bail!("强制重装服务失败: {}", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
#[cfg(target_os = "windows")]
|
|
||||||
use crate::process::AsyncHandler;
|
|
||||||
use crate::{logging, utils::logging::Type};
|
use crate::{logging, utils::logging::Type};
|
||||||
use anyhow::{Context, Result, bail};
|
use anyhow::{Context, Result, bail};
|
||||||
|
use backoff::{Error as BackoffError, ExponentialBackoff};
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
#[cfg(unix)]
|
||||||
|
use tokio::net::UnixStream;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use tokio::net::windows::named_pipe::ClientOptions;
|
||||||
|
|
||||||
const IPC_SOCKET_NAME: &str = if cfg!(windows) {
|
const IPC_SOCKET_NAME: &str = if cfg!(windows) {
|
||||||
r"\\.\pipe\clash-verge-service"
|
r"\\.\pipe\clash-verge-service"
|
||||||
@@ -112,170 +116,134 @@ pub fn verify_response_signature(response: &IpcResponse) -> Result<bool> {
|
|||||||
Ok(expected_signature == response.signature)
|
Ok(expected_signature == response.signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPC连接管理-win
|
fn create_backoff_strategy() -> ExponentialBackoff {
|
||||||
#[cfg(target_os = "windows")]
|
ExponentialBackoff {
|
||||||
|
initial_interval: Duration::from_millis(50),
|
||||||
|
max_interval: Duration::from_secs(1),
|
||||||
|
max_elapsed_time: Some(Duration::from_secs(3)),
|
||||||
|
multiplier: 1.5,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn send_ipc_request(
|
pub async fn send_ipc_request(
|
||||||
command: IpcCommand,
|
command: IpcCommand,
|
||||||
payload: serde_json::Value,
|
payload: serde_json::Value,
|
||||||
) -> Result<IpcResponse> {
|
) -> Result<IpcResponse> {
|
||||||
use std::{
|
|
||||||
ffi::CString,
|
|
||||||
fs::File,
|
|
||||||
io::{Read, Write},
|
|
||||||
os::windows::io::{FromRawHandle, RawHandle},
|
|
||||||
ptr,
|
|
||||||
};
|
|
||||||
use winapi::um::{
|
|
||||||
fileapi::{CreateFileA, OPEN_EXISTING},
|
|
||||||
handleapi::INVALID_HANDLE_VALUE,
|
|
||||||
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
|
|
||||||
};
|
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "正在连接服务 (Windows)...");
|
|
||||||
|
|
||||||
let command_type = format!("{command:?}");
|
let command_type = format!("{command:?}");
|
||||||
|
|
||||||
let request = match create_signed_request(command, payload) {
|
let operation = || async {
|
||||||
Ok(req) => req,
|
match send_ipc_request_internal(command.clone(), payload.clone()).await {
|
||||||
|
Ok(response) => Ok(response),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "创建签名请求失败: {}", e);
|
logging!(
|
||||||
return Err(e);
|
warn,
|
||||||
|
Type::Service,
|
||||||
|
true,
|
||||||
|
"IPC请求失败,准备重试: 命令={}, 错误={}",
|
||||||
|
command_type,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
Err(BackoffError::transient(e))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let request_json = serde_json::to_string(&request)?;
|
match backoff::future::retry(create_backoff_strategy(), operation).await {
|
||||||
|
Ok(response) => {
|
||||||
let result = AsyncHandler::spawn_blocking(move || -> Result<IpcResponse> {
|
// logging!(
|
||||||
let c_pipe_name = match CString::new(IPC_SOCKET_NAME) {
|
// info,
|
||||||
Ok(name) => name,
|
// Type::Service,
|
||||||
Err(e) => {
|
// true,
|
||||||
logging!(error, Type::Service, true, "创建CString失败: {}", e);
|
// "IPC请求成功: 命令={}, 成功={}",
|
||||||
return Err(anyhow::anyhow!("创建CString失败: {}", e));
|
// command_type,
|
||||||
|
// response.success
|
||||||
|
// );
|
||||||
|
Ok(response)
|
||||||
}
|
}
|
||||||
};
|
Err(e) => {
|
||||||
|
|
||||||
let handle = unsafe {
|
|
||||||
CreateFileA(
|
|
||||||
c_pipe_name.as_ptr(),
|
|
||||||
GENERIC_READ | GENERIC_WRITE,
|
|
||||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
||||||
ptr::null_mut(),
|
|
||||||
OPEN_EXISTING,
|
|
||||||
0,
|
|
||||||
ptr::null_mut(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
if handle == INVALID_HANDLE_VALUE {
|
|
||||||
let error = std::io::Error::last_os_error();
|
|
||||||
logging!(
|
logging!(
|
||||||
error,
|
error,
|
||||||
Type::Service,
|
Type::Service,
|
||||||
true,
|
true,
|
||||||
"连接到服务命名管道失败: {}",
|
"IPC请求最终失败,重试已耗尽: 命令={}, 错误={}",
|
||||||
error
|
command_type,
|
||||||
|
e
|
||||||
);
|
);
|
||||||
return Err(anyhow::anyhow!("无法连接到服务命名管道: {}", error));
|
Err(anyhow::anyhow!("IPC请求重试失败: {}", e))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut pipe = unsafe { File::from_raw_handle(handle as RawHandle) };
|
// 内部IPC请求实现(不带重试)
|
||||||
logging!(info, Type::Service, true, "服务连接成功 (Windows)");
|
async fn send_ipc_request_internal(
|
||||||
|
command: IpcCommand,
|
||||||
|
payload: serde_json::Value,
|
||||||
|
) -> Result<IpcResponse> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
send_ipc_request_windows(command, payload).await
|
||||||
|
}
|
||||||
|
#[cfg(target_family = "unix")]
|
||||||
|
{
|
||||||
|
send_ipc_request_unix(command, payload).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IPC连接管理-win
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
async fn send_ipc_request_windows(
|
||||||
|
command: IpcCommand,
|
||||||
|
payload: serde_json::Value,
|
||||||
|
) -> Result<IpcResponse> {
|
||||||
|
let request = create_signed_request(command, payload)?;
|
||||||
|
let request_json = serde_json::to_string(&request)?;
|
||||||
let request_bytes = request_json.as_bytes();
|
let request_bytes = request_json.as_bytes();
|
||||||
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
||||||
|
|
||||||
if let Err(e) = pipe.write_all(&len_bytes) {
|
let mut pipe = match ClientOptions::new().open(IPC_SOCKET_NAME) {
|
||||||
logging!(error, Type::Service, true, "写入请求长度失败: {}", e);
|
Ok(p) => p,
|
||||||
return Err(anyhow::anyhow!("写入请求长度失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = pipe.write_all(request_bytes) {
|
|
||||||
logging!(error, Type::Service, true, "写入请求内容失败: {}", e);
|
|
||||||
return Err(anyhow::anyhow!("写入请求内容失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = pipe.flush() {
|
|
||||||
logging!(error, Type::Service, true, "刷新管道失败: {}", e);
|
|
||||||
return Err(anyhow::anyhow!("刷新管道失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut response_len_bytes = [0u8; 4];
|
|
||||||
if let Err(e) = pipe.read_exact(&mut response_len_bytes) {
|
|
||||||
logging!(error, Type::Service, true, "读取响应长度失败: {}", e);
|
|
||||||
return Err(anyhow::anyhow!("读取响应长度失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
|
||||||
|
|
||||||
let mut response_bytes = vec![0u8; response_len];
|
|
||||||
if let Err(e) = pipe.read_exact(&mut response_bytes) {
|
|
||||||
logging!(error, Type::Service, true, "读取响应内容失败: {}", e);
|
|
||||||
return Err(anyhow::anyhow!("读取响应内容失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "服务响应解析失败: {}", e);
|
logging!(error, Type::Service, true, "连接到服务命名管道失败: {}", e);
|
||||||
return Err(anyhow::anyhow!("解析响应失败: {}", e));
|
return Err(anyhow::anyhow!("无法连接到服务命名管道: {}", e));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match verify_response_signature(&response) {
|
logging!(info, Type::Service, true, "服务连接成功 (Windows)");
|
||||||
Ok(valid) => {
|
|
||||||
if !valid {
|
pipe.write_all(&len_bytes).await?;
|
||||||
|
pipe.write_all(request_bytes).await?;
|
||||||
|
pipe.flush().await?;
|
||||||
|
|
||||||
|
let mut response_len_bytes = [0u8; 4];
|
||||||
|
pipe.read_exact(&mut response_len_bytes).await?;
|
||||||
|
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
||||||
|
|
||||||
|
let mut response_bytes = vec![0u8; response_len];
|
||||||
|
pipe.read_exact(&mut response_bytes).await?;
|
||||||
|
|
||||||
|
let response: IpcResponse = serde_json::from_slice(&response_bytes)
|
||||||
|
.map_err(|e| anyhow::anyhow!("解析响应失败: {}", e))?;
|
||||||
|
|
||||||
|
if !verify_response_signature(&response)? {
|
||||||
logging!(error, Type::Service, true, "服务响应签名验证失败");
|
logging!(error, Type::Service, true, "服务响应签名验证失败");
|
||||||
bail!("服务响应签名验证失败");
|
bail!("服务响应签名验证失败");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Service, true, "验证响应签名时出错: {}", e);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logging!(
|
|
||||||
info,
|
|
||||||
Type::Service,
|
|
||||||
true,
|
|
||||||
"IPC请求完成: 命令={}, 成功={}",
|
|
||||||
command_type,
|
|
||||||
response.success
|
|
||||||
);
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
})
|
|
||||||
.await??;
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IPC连接管理-unix
|
// IPC连接管理-unix
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub async fn send_ipc_request(
|
async fn send_ipc_request_unix(
|
||||||
command: IpcCommand,
|
command: IpcCommand,
|
||||||
payload: serde_json::Value,
|
payload: serde_json::Value,
|
||||||
) -> Result<IpcResponse> {
|
) -> Result<IpcResponse> {
|
||||||
use std::os::unix::net::UnixStream;
|
let request = create_signed_request(command, payload)?;
|
||||||
|
|
||||||
logging!(info, Type::Service, true, "正在连接服务 (Unix)...");
|
|
||||||
|
|
||||||
let command_type = format!("{command:?}");
|
|
||||||
|
|
||||||
let request = match create_signed_request(command, payload) {
|
|
||||||
Ok(req) => req,
|
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Service, true, "创建签名请求失败: {}", e);
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let request_json = serde_json::to_string(&request)?;
|
let request_json = serde_json::to_string(&request)?;
|
||||||
|
|
||||||
let mut stream = match UnixStream::connect(IPC_SOCKET_NAME) {
|
let mut stream = match UnixStream::connect(IPC_SOCKET_NAME).await {
|
||||||
Ok(s) => {
|
Ok(s) => s,
|
||||||
logging!(info, Type::Service, true, "服务连接成功 (Unix)");
|
|
||||||
s
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
logging!(error, Type::Service, true, "连接到Unix套接字失败: {}", e);
|
logging!(error, Type::Service, true, "连接到Unix套接字失败: {}", e);
|
||||||
return Err(anyhow::anyhow!("无法连接到服务Unix套接字: {}", e));
|
return Err(anyhow::anyhow!("无法连接到服务Unix套接字: {}", e));
|
||||||
@@ -285,58 +253,97 @@ pub async fn send_ipc_request(
|
|||||||
let request_bytes = request_json.as_bytes();
|
let request_bytes = request_json.as_bytes();
|
||||||
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
let len_bytes = (request_bytes.len() as u32).to_be_bytes();
|
||||||
|
|
||||||
if let Err(e) = std::io::Write::write_all(&mut stream, &len_bytes) {
|
stream.write_all(&len_bytes).await?;
|
||||||
logging!(error, Type::Service, true, "写入请求长度失败: {}", e);
|
stream.write_all(request_bytes).await?;
|
||||||
return Err(anyhow::anyhow!("写入请求长度失败: {}", e));
|
stream.flush().await?;
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = std::io::Write::write_all(&mut stream, request_bytes) {
|
|
||||||
logging!(error, Type::Service, true, "写入请求内容失败: {}", e);
|
|
||||||
return Err(anyhow::anyhow!("写入请求内容失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 读取响应长度
|
||||||
let mut response_len_bytes = [0u8; 4];
|
let mut response_len_bytes = [0u8; 4];
|
||||||
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_len_bytes) {
|
stream.read_exact(&mut response_len_bytes).await?;
|
||||||
logging!(error, Type::Service, true, "读取响应长度失败: {}", e);
|
|
||||||
return Err(anyhow::anyhow!("读取响应长度失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
let response_len = u32::from_be_bytes(response_len_bytes) as usize;
|
||||||
|
|
||||||
let mut response_bytes = vec![0u8; response_len];
|
let mut response_bytes = vec![0u8; response_len];
|
||||||
if let Err(e) = std::io::Read::read_exact(&mut stream, &mut response_bytes) {
|
stream.read_exact(&mut response_bytes).await?;
|
||||||
logging!(error, Type::Service, true, "读取响应内容失败: {}", e);
|
|
||||||
return Err(anyhow::anyhow!("读取响应内容失败: {}", e));
|
|
||||||
}
|
|
||||||
|
|
||||||
let response: IpcResponse = match serde_json::from_slice::<IpcResponse>(&response_bytes) {
|
let response: IpcResponse = serde_json::from_slice(&response_bytes)
|
||||||
Ok(r) => r,
|
.map_err(|e| anyhow::anyhow!("解析响应失败: {}", e))?;
|
||||||
Err(e) => {
|
|
||||||
logging!(error, Type::Service, true, "服务响应解析失败: {}", e,);
|
|
||||||
return Err(anyhow::anyhow!("解析响应失败: {}", e));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match verify_response_signature(&response) {
|
if !verify_response_signature(&response)? {
|
||||||
Ok(valid) => {
|
|
||||||
if !valid {
|
|
||||||
logging!(error, Type::Service, true, "服务响应签名验证失败");
|
logging!(error, Type::Service, true, "服务响应签名验证失败");
|
||||||
bail!("服务响应签名验证失败");
|
bail!("服务响应签名验证失败");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(e) => {
|
Ok(response)
|
||||||
logging!(error, Type::Service, true, "验证响应签名时出错: {}", e);
|
}
|
||||||
return Err(e);
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_signed_request() {
|
||||||
|
let command = IpcCommand::GetVersion;
|
||||||
|
let payload = serde_json::json!({"test": "data"});
|
||||||
|
|
||||||
|
let result = create_signed_request(command, payload);
|
||||||
|
assert!(result.is_ok());
|
||||||
|
|
||||||
|
if let Ok(request) = result {
|
||||||
|
assert!(!request.id.is_empty());
|
||||||
|
assert!(!request.signature.is_empty());
|
||||||
|
assert_eq!(request.command, IpcCommand::GetVersion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logging!(
|
#[test]
|
||||||
info,
|
fn test_sign_and_verify_message() {
|
||||||
Type::Service,
|
let test_message = "test message for signing";
|
||||||
true,
|
|
||||||
"IPC请求完成: 命令={}, 成功={}",
|
let signature_result = sign_message(test_message);
|
||||||
command_type,
|
assert!(signature_result.is_ok());
|
||||||
response.success
|
|
||||||
);
|
if let Ok(signature) = signature_result {
|
||||||
Ok(response)
|
assert!(!signature.is_empty());
|
||||||
|
|
||||||
|
// 测试相同消息产生相同签名
|
||||||
|
if let Ok(signature2) = sign_message(test_message) {
|
||||||
|
assert_eq!(signature, signature2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_verify_response_signature() {
|
||||||
|
let response = IpcResponse {
|
||||||
|
id: "test-id".to_string(),
|
||||||
|
success: true,
|
||||||
|
data: Some(serde_json::json!({"result": "success"})),
|
||||||
|
error: None,
|
||||||
|
signature: String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建正确的签名
|
||||||
|
let verification_response = IpcResponse {
|
||||||
|
id: response.id.clone(),
|
||||||
|
success: response.success,
|
||||||
|
data: response.data.clone(),
|
||||||
|
error: response.error.clone(),
|
||||||
|
signature: String::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(message) = serde_json::to_string(&verification_response)
|
||||||
|
&& let Ok(correct_signature) = sign_message(&message)
|
||||||
|
{
|
||||||
|
let signed_response = IpcResponse {
|
||||||
|
signature: correct_signature,
|
||||||
|
..response
|
||||||
|
};
|
||||||
|
|
||||||
|
let verification_result = verify_response_signature(&signed_response);
|
||||||
|
assert!(verification_result.is_ok());
|
||||||
|
if let Ok(is_valid) = verification_result {
|
||||||
|
assert!(is_valid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ pub struct IpcManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IpcManager {
|
impl IpcManager {
|
||||||
fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
logging!(info, Type::Ipc, true, "Creating new IpcManager instance");
|
||||||
let ipc_path_buf = ipc_path().unwrap_or_else(|e| {
|
let ipc_path_buf = ipc_path().unwrap_or_else(|e| {
|
||||||
logging!(error, Type::Ipc, true, "Failed to get IPC path: {}", e);
|
logging!(error, Type::Ipc, true, "Failed to get IPC path: {}", e);
|
||||||
std::path::PathBuf::from("/tmp/clash-verge-ipc") // fallback path
|
std::path::PathBuf::from("/tmp/clash-verge-ipc") // fallback path
|
||||||
@@ -51,9 +52,6 @@ impl IpcManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use singleton macro with logging
|
|
||||||
singleton_with_logging!(IpcManager, INSTANCE, "IpcManager");
|
|
||||||
|
|
||||||
impl IpcManager {
|
impl IpcManager {
|
||||||
pub async fn request(
|
pub async fn request(
|
||||||
&self,
|
&self,
|
||||||
@@ -367,3 +365,6 @@ impl IpcManager {
|
|||||||
|
|
||||||
// 日志相关功能已迁移到 logs.rs 模块,使用流式处理
|
// 日志相关功能已迁移到 logs.rs 模块,使用流式处理
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use singleton macro with logging
|
||||||
|
singleton_with_logging!(IpcManager, INSTANCE, "IpcManager");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
cache::Cache,
|
cache::CacheProxy,
|
||||||
config::Config,
|
config::Config,
|
||||||
core::{handle, timer::Timer, tray::Tray},
|
core::{handle, timer::Timer, tray::Tray},
|
||||||
log_err, logging,
|
log_err, logging,
|
||||||
@@ -183,7 +183,7 @@ pub async fn entry_lightweight_mode() -> bool {
|
|||||||
// 回到 In
|
// 回到 In
|
||||||
set_state(LightweightState::In);
|
set_state(LightweightState::In);
|
||||||
|
|
||||||
Cache::global().clean_default_keys();
|
CacheProxy::global().clean_default_keys();
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use tauri::AppHandle;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
config::Config,
|
||||||
core::{CoreManager, Timer, handle, hotkey::Hotkey, sysopt, tray::Tray},
|
core::{
|
||||||
|
CoreManager, Timer, handle, hotkey::Hotkey, service::SERVICE_MANAGER, sysopt, tray::Tray,
|
||||||
|
},
|
||||||
logging, logging_error,
|
logging, logging_error,
|
||||||
module::lightweight::auto_lightweight_mode_init,
|
module::lightweight::auto_lightweight_mode_init,
|
||||||
process::AsyncHandler,
|
process::AsyncHandler,
|
||||||
@@ -38,6 +40,8 @@ pub fn resolve_setup_async() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
AsyncHandler::spawn(|| async {
|
AsyncHandler::spawn(|| async {
|
||||||
|
init_service_manager().await;
|
||||||
|
|
||||||
futures::join!(
|
futures::join!(
|
||||||
init_work_config(),
|
init_work_config(),
|
||||||
init_resources(),
|
init_resources(),
|
||||||
@@ -185,6 +189,15 @@ pub(super) async fn init_verge_config() {
|
|||||||
logging_error!(Type::Setup, true, Config::init_config().await);
|
logging_error!(Type::Setup, true, Config::init_config().await);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) async fn init_service_manager() {
|
||||||
|
logging!(info, Type::Setup, true, "Initializing service manager...");
|
||||||
|
logging_error!(
|
||||||
|
Type::Setup,
|
||||||
|
true,
|
||||||
|
SERVICE_MANAGER.lock().await.refresh().await
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) async fn init_core_manager() {
|
pub(super) async fn init_core_manager() {
|
||||||
logging!(info, Type::Setup, true, "Initializing core manager...");
|
logging!(info, Type::Setup, true, "Initializing core manager...");
|
||||||
logging_error!(Type::Setup, true, CoreManager::global().init().await);
|
logging_error!(Type::Setup, true, CoreManager::global().init().await);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useRef } from "react";
|
import React, { useRef, useCallback } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import {
|
import {
|
||||||
SettingsRounded,
|
SettingsRounded,
|
||||||
@@ -19,8 +19,8 @@ import { useSystemProxyState } from "@/hooks/use-system-proxy-state";
|
|||||||
import { useSystemState } from "@/hooks/use-system-state";
|
import { useSystemState } from "@/hooks/use-system-state";
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import { useServiceInstaller } from "@/hooks/useServiceInstaller";
|
import { useServiceInstaller } from "@/hooks/useServiceInstaller";
|
||||||
import { uninstallService, restartCore, stopCore } from "@/services/cmds";
|
|
||||||
import { useLockFn } from "ahooks";
|
import { useLockFn } from "ahooks";
|
||||||
|
import { useServiceUninstaller } from "@/hooks/useServiceUninstaller";
|
||||||
|
|
||||||
interface ProxySwitchProps {
|
interface ProxySwitchProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
@@ -28,10 +28,83 @@ interface ProxySwitchProps {
|
|||||||
noRightPadding?: boolean;
|
noRightPadding?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SwitchRowProps {
|
||||||
|
label: string;
|
||||||
|
active: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
infoTitle: string;
|
||||||
|
onInfoClick?: () => void;
|
||||||
|
extraIcons?: React.ReactNode;
|
||||||
|
onToggle: (value: boolean) => Promise<void>;
|
||||||
|
onError?: (err: Error) => void;
|
||||||
|
highlight?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 可复用的代理控制开关组件
|
* 抽取的子组件:统一的开关 UI
|
||||||
* 包含 Tun Mode 和 System Proxy 的开关功能
|
|
||||||
*/
|
*/
|
||||||
|
const SwitchRow = ({
|
||||||
|
label,
|
||||||
|
active,
|
||||||
|
disabled,
|
||||||
|
infoTitle,
|
||||||
|
onInfoClick,
|
||||||
|
extraIcons,
|
||||||
|
onToggle,
|
||||||
|
onError,
|
||||||
|
highlight,
|
||||||
|
}: SwitchRowProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
p: 1,
|
||||||
|
pr: 2,
|
||||||
|
borderRadius: 1.5,
|
||||||
|
bgcolor: highlight
|
||||||
|
? alpha(theme.palette.success.main, 0.07)
|
||||||
|
: "transparent",
|
||||||
|
opacity: disabled ? 0.6 : 1,
|
||||||
|
transition: "background-color 0.3s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
{active ? (
|
||||||
|
<PlayCircleOutlineRounded sx={{ color: "success.main", mr: 1 }} />
|
||||||
|
) : (
|
||||||
|
<PauseCircleOutlineRounded sx={{ color: "text.disabled", mr: 1 }} />
|
||||||
|
)}
|
||||||
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
sx={{ fontWeight: 500, fontSize: "15px" }}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
<TooltipIcon
|
||||||
|
title={infoTitle}
|
||||||
|
icon={SettingsRounded}
|
||||||
|
onClick={onInfoClick}
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
/>
|
||||||
|
{extraIcons}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<GuardState
|
||||||
|
value={active}
|
||||||
|
valueProps="checked"
|
||||||
|
onCatch={onError}
|
||||||
|
onFormat={(_, v) => v}
|
||||||
|
onGuard={onToggle}
|
||||||
|
>
|
||||||
|
<Switch edge="end" disabled={disabled} />
|
||||||
|
</GuardState>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const ProxyControlSwitches = ({
|
const ProxyControlSwitches = ({
|
||||||
label,
|
label,
|
||||||
onError,
|
onError,
|
||||||
@@ -39,159 +112,94 @@ const ProxyControlSwitches = ({
|
|||||||
}: ProxySwitchProps) => {
|
}: ProxySwitchProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { verge, mutateVerge, patchVerge } = useVerge();
|
const { verge, mutateVerge, patchVerge } = useVerge();
|
||||||
const theme = useTheme();
|
|
||||||
const { installServiceAndRestartCore } = useServiceInstaller();
|
const { installServiceAndRestartCore } = useServiceInstaller();
|
||||||
|
const { uninstallServiceAndRestartCore } = useServiceUninstaller();
|
||||||
const { actualState: systemProxyActualState, toggleSystemProxy } =
|
const { actualState: systemProxyActualState, toggleSystemProxy } =
|
||||||
useSystemProxyState();
|
useSystemProxyState();
|
||||||
|
const {
|
||||||
const { isAdminMode, isServiceMode, mutateRunningMode } = useSystemState();
|
isServiceMode,
|
||||||
|
isTunModeAvailable,
|
||||||
const isTunAvailable = isServiceMode || isAdminMode;
|
mutateRunningMode,
|
||||||
|
mutateServiceOk,
|
||||||
|
} = useSystemState();
|
||||||
|
|
||||||
const sysproxyRef = useRef<DialogRef>(null);
|
const sysproxyRef = useRef<DialogRef>(null);
|
||||||
const tunRef = useRef<DialogRef>(null);
|
const tunRef = useRef<DialogRef>(null);
|
||||||
|
|
||||||
const { enable_tun_mode, enable_system_proxy } = verge ?? {};
|
const { enable_tun_mode, enable_system_proxy } = verge ?? {};
|
||||||
|
|
||||||
// 确定当前显示哪个开关
|
const showErrorNotice = useCallback(
|
||||||
const isSystemProxyMode = label === t("System Proxy") || !label;
|
(msg: string) => showNotice("error", t(msg)),
|
||||||
const isTunMode = label === t("Tun Mode");
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
const onSwitchFormat = (
|
const handleTunToggle = async (value: boolean) => {
|
||||||
_e: React.ChangeEvent<HTMLInputElement>,
|
if (!isTunModeAvailable) {
|
||||||
value: boolean,
|
const msg = "TUN requires Service Mode or Admin Mode";
|
||||||
) => value;
|
showErrorNotice(msg);
|
||||||
const onChangeData = (patch: Partial<IVergeConfig>) => {
|
throw new Error(t(msg));
|
||||||
mutateVerge({ ...verge, ...patch }, false);
|
}
|
||||||
|
mutateVerge({ ...verge, enable_tun_mode: value }, false);
|
||||||
|
await patchVerge({ enable_tun_mode: value });
|
||||||
};
|
};
|
||||||
|
|
||||||
// 安装系统服务
|
const onInstallService = useLockFn(async () => {
|
||||||
const onInstallService = installServiceAndRestartCore;
|
|
||||||
|
|
||||||
// 卸载系统服务
|
|
||||||
const onUninstallService = useLockFn(async () => {
|
|
||||||
try {
|
try {
|
||||||
showNotice("info", t("Stopping Core..."));
|
await installServiceAndRestartCore();
|
||||||
await stopCore();
|
|
||||||
showNotice("info", t("Uninstalling Service..."));
|
|
||||||
await uninstallService();
|
|
||||||
showNotice("success", t("Service Uninstalled Successfully"));
|
|
||||||
showNotice("info", t("Restarting Core..."));
|
|
||||||
await restartCore();
|
|
||||||
await mutateRunningMode();
|
await mutateRunningMode();
|
||||||
} catch (err: unknown) {
|
await mutateServiceOk();
|
||||||
showNotice("error", (err as Error).message || err?.toString());
|
} catch (err) {
|
||||||
try {
|
showNotice("error", (err as Error).message || String(err));
|
||||||
showNotice("info", t("Try running core as Sidecar..."));
|
|
||||||
await restartCore();
|
|
||||||
await mutateRunningMode();
|
|
||||||
} catch (e: unknown) {
|
|
||||||
showNotice("error", (e as Error)?.message || e?.toString());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const onUninstallService = useLockFn(async () => {
|
||||||
|
try {
|
||||||
|
await uninstallServiceAndRestartCore();
|
||||||
|
await mutateRunningMode();
|
||||||
|
await mutateServiceOk();
|
||||||
|
} catch (err) {
|
||||||
|
showNotice("error", (err as Error).message || String(err));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const isSystemProxyMode = label === t("System Proxy") || !label;
|
||||||
|
const isTunMode = label === t("Tun Mode");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ width: "100%" }}>
|
<Box sx={{ width: "100%", pr: noRightPadding ? 1 : 2 }}>
|
||||||
{/* 仅显示当前选中的开关 */}
|
|
||||||
{isSystemProxyMode && (
|
{isSystemProxyMode && (
|
||||||
<Box
|
<SwitchRow
|
||||||
sx={{
|
label={t("System Proxy")}
|
||||||
display: "flex",
|
active={systemProxyActualState}
|
||||||
alignItems: "center",
|
infoTitle={t("System Proxy Info")}
|
||||||
justifyContent: "space-between",
|
onInfoClick={() => sysproxyRef.current?.open()}
|
||||||
p: 1,
|
onToggle={(value) => toggleSystemProxy(value)}
|
||||||
pr: noRightPadding ? 1 : 2,
|
onError={onError}
|
||||||
borderRadius: 1.5,
|
highlight={enable_system_proxy}
|
||||||
bgcolor: enable_system_proxy
|
|
||||||
? alpha(theme.palette.success.main, 0.07)
|
|
||||||
: "transparent",
|
|
||||||
transition: "background-color 0.3s",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
|
||||||
{systemProxyActualState ? (
|
|
||||||
<PlayCircleOutlineRounded sx={{ color: "success.main", mr: 1 }} />
|
|
||||||
) : (
|
|
||||||
<PauseCircleOutlineRounded
|
|
||||||
sx={{ color: "text.disabled", mr: 1 }}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Typography
|
|
||||||
variant="subtitle1"
|
|
||||||
sx={{ fontWeight: 500, fontSize: "15px" }}
|
|
||||||
>
|
|
||||||
{t("System Proxy")}
|
|
||||||
</Typography>
|
|
||||||
<TooltipIcon
|
|
||||||
title={t("System Proxy Info")}
|
|
||||||
icon={SettingsRounded}
|
|
||||||
onClick={() => sysproxyRef.current?.open()}
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<GuardState
|
|
||||||
value={systemProxyActualState}
|
|
||||||
valueProps="checked"
|
|
||||||
onCatch={onError}
|
|
||||||
onFormat={onSwitchFormat}
|
|
||||||
onGuard={(e) => toggleSystemProxy(e)}
|
|
||||||
>
|
|
||||||
<Switch edge="end" />
|
|
||||||
</GuardState>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isTunMode && (
|
{isTunMode && (
|
||||||
<Box
|
<SwitchRow
|
||||||
sx={{
|
label={t("Tun Mode")}
|
||||||
display: "flex",
|
active={!!enable_tun_mode}
|
||||||
alignItems: "center",
|
infoTitle={t("Tun Mode Info")}
|
||||||
justifyContent: "space-between",
|
onInfoClick={() => tunRef.current?.open()}
|
||||||
p: 1,
|
onToggle={handleTunToggle}
|
||||||
pr: noRightPadding ? 1 : 2,
|
onError={onError}
|
||||||
borderRadius: 1.5,
|
disabled={!isServiceMode}
|
||||||
bgcolor: enable_tun_mode
|
highlight={!!enable_tun_mode}
|
||||||
? alpha(theme.palette.success.main, 0.07)
|
extraIcons={
|
||||||
: "transparent",
|
<>
|
||||||
opacity: !isTunAvailable ? 0.6 : 1,
|
{!isServiceMode && (
|
||||||
transition: "background-color 0.3s",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
|
||||||
{enable_tun_mode ? (
|
|
||||||
<PlayCircleOutlineRounded sx={{ color: "success.main", mr: 1 }} />
|
|
||||||
) : (
|
|
||||||
<PauseCircleOutlineRounded
|
|
||||||
sx={{ color: "text.disabled", mr: 1 }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Typography
|
|
||||||
variant="subtitle1"
|
|
||||||
sx={{ fontWeight: 500, fontSize: "15px" }}
|
|
||||||
>
|
|
||||||
{t("Tun Mode")}
|
|
||||||
</Typography>
|
|
||||||
<TooltipIcon
|
|
||||||
title={t("Tun Mode Info")}
|
|
||||||
icon={SettingsRounded}
|
|
||||||
onClick={() => tunRef.current?.open()}
|
|
||||||
sx={{ ml: 1 }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!isTunAvailable && (
|
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
title={t("TUN requires Service Mode or Admin Mode")}
|
title={t("TUN requires Service Mode or Admin Mode")}
|
||||||
icon={WarningRounded}
|
icon={WarningRounded}
|
||||||
sx={{ color: "warning.main", ml: 1 }}
|
sx={{ color: "warning.main", ml: 1 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{!isServiceMode ? (
|
||||||
{!isTunAvailable && (
|
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
title={t("Install Service")}
|
title={t("Install Service")}
|
||||||
icon={BuildRounded}
|
icon={BuildRounded}
|
||||||
@@ -199,9 +207,7 @@ const ProxyControlSwitches = ({
|
|||||||
onClick={onInstallService}
|
onClick={onInstallService}
|
||||||
sx={{ ml: 1 }}
|
sx={{ ml: 1 }}
|
||||||
/>
|
/>
|
||||||
)}
|
) : (
|
||||||
|
|
||||||
{isServiceMode && (
|
|
||||||
<TooltipIcon
|
<TooltipIcon
|
||||||
title={t("Uninstall Service")}
|
title={t("Uninstall Service")}
|
||||||
icon={DeleteForeverRounded}
|
icon={DeleteForeverRounded}
|
||||||
@@ -210,44 +216,11 @@ const ProxyControlSwitches = ({
|
|||||||
sx={{ ml: 1 }}
|
sx={{ ml: 1 }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</>
|
||||||
|
|
||||||
<GuardState
|
|
||||||
value={enable_tun_mode ?? false}
|
|
||||||
valueProps="checked"
|
|
||||||
onCatch={onError}
|
|
||||||
onFormat={onSwitchFormat}
|
|
||||||
onChange={(e) => {
|
|
||||||
if (!isTunAvailable) {
|
|
||||||
showNotice(
|
|
||||||
"error",
|
|
||||||
t("TUN requires Service Mode or Admin Mode"),
|
|
||||||
);
|
|
||||||
return Promise.reject(
|
|
||||||
new Error(t("TUN requires Service Mode or Admin Mode")),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
onChangeData({ enable_tun_mode: e });
|
/>
|
||||||
}}
|
|
||||||
onGuard={(e) => {
|
|
||||||
if (!isTunAvailable) {
|
|
||||||
showNotice(
|
|
||||||
"error",
|
|
||||||
t("TUN requires Service Mode or Admin Mode"),
|
|
||||||
);
|
|
||||||
return Promise.reject(
|
|
||||||
new Error(t("TUN requires Service Mode or Admin Mode")),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return patchVerge({ enable_tun_mode: e });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Switch edge="end" disabled={!isTunAvailable} />
|
|
||||||
</GuardState>
|
|
||||||
</Box>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 引用对话框组件 */}
|
|
||||||
<SysproxyViewer ref={sysproxyRef} />
|
<SysproxyViewer ref={sysproxyRef} />
|
||||||
<TunViewer ref={tunRef} />
|
<TunViewer ref={tunRef} />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ export function useSystemState() {
|
|||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
const isSidecarMode = runningMode === "Sidecar";
|
||||||
|
const isServiceMode = runningMode === "Service";
|
||||||
|
|
||||||
// 获取管理员状态
|
// 获取管理员状态
|
||||||
const { data: isAdminMode = false } = useSWR("isAdmin", isAdmin, {
|
const { data: isAdminMode = false } = useSWR("isAdmin", isAdmin, {
|
||||||
@@ -22,24 +24,32 @@ export function useSystemState() {
|
|||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 获取系统服务状态
|
const { data: isServiceOk = false, mutate: mutateServiceOk } = useSWR(
|
||||||
const isServiceMode = runningMode === "Service";
|
|
||||||
const { data: isServiceOk = false } = useSWR(
|
|
||||||
"isServiceAvailable",
|
"isServiceAvailable",
|
||||||
isServiceAvailable,
|
isServiceAvailable,
|
||||||
{
|
{
|
||||||
suspense: false,
|
suspense: false,
|
||||||
revalidateOnFocus: false,
|
revalidateOnFocus: false,
|
||||||
isPaused: () => !isServiceMode, // 仅在 Service 模式下请求
|
onSuccess: (data) => {
|
||||||
|
console.log("[useSystemState] 服务状态更新:", data);
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("[useSystemState] 服务状态检查失败:", error);
|
||||||
|
},
|
||||||
|
isPaused: () => !isServiceMode, // 仅在非 Service 模式下暂停请求
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const isTunModeAvailable = isAdminMode || isServiceOk;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
runningMode,
|
runningMode,
|
||||||
isAdminMode,
|
isAdminMode,
|
||||||
isSidecarMode: runningMode === "Sidecar",
|
isSidecarMode,
|
||||||
isServiceMode: runningMode === "Service",
|
isServiceMode,
|
||||||
isServiceOk,
|
isServiceOk,
|
||||||
|
isTunModeAvailable: isTunModeAvailable,
|
||||||
mutateRunningMode,
|
mutateRunningMode,
|
||||||
|
mutateServiceOk,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,121 +1,35 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { installService, restartCore } from "@/services/cmds";
|
||||||
import { useLockFn } from "ahooks";
|
|
||||||
import { showNotice } from "@/services/noticeService";
|
import { showNotice } from "@/services/noticeService";
|
||||||
import {
|
import { t } from "i18next";
|
||||||
installService,
|
import { useCallback } from "react";
|
||||||
isServiceAvailable,
|
|
||||||
restartCore,
|
|
||||||
} from "@/services/cmds";
|
|
||||||
import { useSystemState } from "@/hooks/use-system-state";
|
|
||||||
import { mutate } from "swr";
|
|
||||||
|
|
||||||
export function useServiceInstaller() {
|
const executeWithErrorHandling = async (
|
||||||
const { t } = useTranslation();
|
operation: () => Promise<void>,
|
||||||
const { mutateRunningMode } = useSystemState();
|
loadingMessage: string,
|
||||||
|
successMessage?: string,
|
||||||
const installServiceAndRestartCore = useLockFn(async () => {
|
) => {
|
||||||
try {
|
try {
|
||||||
showNotice("info", t("Installing Service..."));
|
showNotice("info", t(loadingMessage));
|
||||||
await installService();
|
await operation();
|
||||||
showNotice("success", t("Service Installed Successfully"));
|
if (successMessage) {
|
||||||
|
showNotice("success", t(successMessage));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const msg = (err as Error)?.message || String(err);
|
||||||
|
showNotice("error", msg);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
showNotice("info", t("Waiting for service to be ready..."));
|
export const useServiceInstaller = () => {
|
||||||
let serviceReady = false;
|
const installServiceAndRestartCore = useCallback(async () => {
|
||||||
for (let i = 0; i < 5; i++) {
|
await executeWithErrorHandling(
|
||||||
try {
|
() => installService(),
|
||||||
// 等待1秒再检查
|
"Installing Service...",
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
"Service Installed Successfully",
|
||||||
const isAvailable = await isServiceAvailable();
|
|
||||||
if (isAvailable) {
|
|
||||||
serviceReady = true;
|
|
||||||
mutate("isServiceAvailable", true, false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// 最后一次尝试不显示重试信息
|
|
||||||
if (i < 4) {
|
|
||||||
showNotice(
|
|
||||||
"info",
|
|
||||||
t("Service not ready, retrying attempt {count}/{total}...", {
|
|
||||||
count: i + 1,
|
|
||||||
total: 5,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(t("Error checking service status:"), error);
|
|
||||||
if (i < 4) {
|
|
||||||
showNotice(
|
|
||||||
"error",
|
|
||||||
t(
|
|
||||||
"Failed to check service status, retrying attempt {count}/{total}...",
|
|
||||||
{
|
|
||||||
count: i + 1,
|
|
||||||
total: 5,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!serviceReady) {
|
|
||||||
showNotice(
|
|
||||||
"info",
|
|
||||||
t(
|
|
||||||
"Service did not become ready after attempts. Proceeding with core restart.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
showNotice("info", t("Restarting Core..."));
|
|
||||||
await restartCore();
|
|
||||||
|
|
||||||
// 核心重启后,再次确认并更新相关状态
|
|
||||||
await mutateRunningMode();
|
|
||||||
const finalServiceStatus = await isServiceAvailable();
|
|
||||||
mutate("isServiceAvailable", finalServiceStatus, false);
|
|
||||||
|
|
||||||
if (serviceReady && finalServiceStatus) {
|
|
||||||
showNotice("success", t("Service is ready and core restarted"));
|
|
||||||
} else if (finalServiceStatus) {
|
|
||||||
showNotice("success", t("Core restarted. Service is now available."));
|
|
||||||
} else if (serviceReady) {
|
|
||||||
showNotice(
|
|
||||||
"info",
|
|
||||||
t(
|
|
||||||
"Service was ready, but core restart might have issues or service became unavailable. Please check.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
showNotice(
|
|
||||||
"error",
|
|
||||||
t(
|
|
||||||
"Service installation or core restart encountered issues. Service might not be available. Please check system logs.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return finalServiceStatus;
|
|
||||||
} catch (err: any) {
|
|
||||||
showNotice("error", err.message || err.toString());
|
|
||||||
// 尝试性回退或最终操作
|
|
||||||
try {
|
|
||||||
showNotice("info", t("Attempting to restart core as a fallback..."));
|
|
||||||
await restartCore();
|
|
||||||
await mutateRunningMode();
|
|
||||||
await isServiceAvailable().then((status) =>
|
|
||||||
mutate("isServiceAvailable", status, false),
|
|
||||||
);
|
|
||||||
} catch (recoveryError: any) {
|
|
||||||
showNotice(
|
|
||||||
"error",
|
|
||||||
t("Fallback core restart also failed: {message}", {
|
|
||||||
message: recoveryError.message,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
await executeWithErrorHandling(() => restartCore(), "Restarting Core...");
|
||||||
|
}, []);
|
||||||
return { installServiceAndRestartCore };
|
return { installServiceAndRestartCore };
|
||||||
}
|
};
|
||||||
|
|||||||
41
src/hooks/useServiceUninstaller.ts
Normal file
41
src/hooks/useServiceUninstaller.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { restartCore, stopCore, uninstallService } from "@/services/cmds";
|
||||||
|
import { showNotice } from "@/services/noticeService";
|
||||||
|
import { t } from "i18next";
|
||||||
|
import { useSystemState } from "./use-system-state";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
|
||||||
|
const executeWithErrorHandling = async (
|
||||||
|
operation: () => Promise<void>,
|
||||||
|
loadingMessage: string,
|
||||||
|
successMessage?: string,
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
showNotice("info", t(loadingMessage));
|
||||||
|
await operation();
|
||||||
|
if (successMessage) {
|
||||||
|
showNotice("success", t(successMessage));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
const msg = (err as Error)?.message || String(err);
|
||||||
|
showNotice("error", msg);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useServiceUninstaller = () => {
|
||||||
|
const { mutateRunningMode, mutateServiceOk } = useSystemState();
|
||||||
|
|
||||||
|
const uninstallServiceAndRestartCore = useCallback(async () => {
|
||||||
|
await executeWithErrorHandling(() => stopCore(), "Stopping Core...");
|
||||||
|
|
||||||
|
await executeWithErrorHandling(
|
||||||
|
() => uninstallService(),
|
||||||
|
"Uninstalling Service...",
|
||||||
|
"Service Uninstalled Successfully",
|
||||||
|
);
|
||||||
|
|
||||||
|
await executeWithErrorHandling(() => restartCore(), "Restarting Core...");
|
||||||
|
}, [mutateRunningMode, mutateServiceOk]);
|
||||||
|
|
||||||
|
return { uninstallServiceAndRestartCore };
|
||||||
|
};
|
||||||
@@ -208,6 +208,9 @@ const Layout = () => {
|
|||||||
mutate("getVergeConfig");
|
mutate("getVergeConfig");
|
||||||
mutate("getSystemProxy");
|
mutate("getSystemProxy");
|
||||||
mutate("getAutotemProxy");
|
mutate("getAutotemProxy");
|
||||||
|
// 运行模式变更时也需要刷新相关状态
|
||||||
|
mutate("getRunningMode");
|
||||||
|
mutate("isServiceAvailable");
|
||||||
}),
|
}),
|
||||||
|
|
||||||
addListener("verge://notice-message", ({ payload }) =>
|
addListener("verge://notice-message", ({ payload }) =>
|
||||||
|
|||||||
Reference in New Issue
Block a user