Refine ProxyWarden routing and config flows
This commit is contained in:
+671
-66
File diff suppressed because it is too large
Load Diff
@@ -9,6 +9,10 @@ use std::{
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
pub const PROXYWARDEN_COMPONENTS_DIR_NAME: &str = "components";
|
||||
pub const PROXIFYRE_COMPONENT_DIR_NAME: &str = "ProxiFyre";
|
||||
pub const SINGBOX_COMPONENT_DIR_NAME: &str = "sing-box";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum ProxyfierEngine {
|
||||
ProxiFyre,
|
||||
@@ -23,6 +27,13 @@ pub struct DetectedProxyfier {
|
||||
pub config_path: Option<PathBuf>,
|
||||
pub running: bool,
|
||||
pub service_name: Option<String>,
|
||||
pub service_status: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct DetectedService {
|
||||
pub name: String,
|
||||
pub status: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -50,7 +61,12 @@ pub trait ProxyfierDetectionHost {
|
||||
|
||||
fn process_running(&self, process_name: &str) -> bool;
|
||||
|
||||
fn service_running(&self, service_name: &str) -> bool;
|
||||
fn service_status(&self, service_name: &str) -> Option<String>;
|
||||
|
||||
fn service_running(&self, service_name: &str) -> bool {
|
||||
self.service_status(service_name)
|
||||
.is_some_and(|status| service_status_is_running(&status))
|
||||
}
|
||||
|
||||
fn registry_install_entries(&self) -> Vec<RegistryInstallEntry>;
|
||||
}
|
||||
@@ -77,13 +93,13 @@ impl ProxyfierDetectionHost for SystemProxyfierDetectionHost {
|
||||
powershell_bool(&script)
|
||||
}
|
||||
|
||||
fn service_running(&self, service_name: &str) -> bool {
|
||||
fn service_status(&self, service_name: &str) -> Option<String> {
|
||||
let script = format!(
|
||||
"$s = Get-Service -Name '{}' -ErrorAction SilentlyContinue; if ($s -and $s.Status -eq 'Running') {{ 'true' }} else {{ 'false' }}",
|
||||
"$s = Get-Service -Name '{}' -ErrorAction SilentlyContinue; if ($s) {{ $s.Status.ToString() }}",
|
||||
escape_powershell_single(service_name)
|
||||
);
|
||||
|
||||
powershell_bool(&script)
|
||||
powershell_text(&script).map(|status| status.to_ascii_lowercase())
|
||||
}
|
||||
|
||||
fn registry_install_entries(&self) -> Vec<RegistryInstallEntry> {
|
||||
@@ -95,16 +111,53 @@ pub fn detect_proxyfier_install() -> Option<DetectedProxyfier> {
|
||||
detect_proxyfier_install_with_host(&SystemProxyfierDetectionHost)
|
||||
}
|
||||
|
||||
pub fn app_install_dir_from_current_exe() -> Option<PathBuf> {
|
||||
env::current_exe()
|
||||
.ok()
|
||||
.and_then(|path| path.parent().map(Path::to_path_buf))
|
||||
}
|
||||
|
||||
pub fn component_root_from_app_dir(app_dir: &Path) -> PathBuf {
|
||||
app_dir.join(PROXYWARDEN_COMPONENTS_DIR_NAME)
|
||||
}
|
||||
|
||||
pub fn proxifyre_install_dir_from_app_dir(app_dir: &Path) -> PathBuf {
|
||||
component_root_from_app_dir(app_dir).join(PROXIFYRE_COMPONENT_DIR_NAME)
|
||||
}
|
||||
|
||||
pub fn singbox_install_dir_from_app_dir(app_dir: &Path) -> PathBuf {
|
||||
component_root_from_app_dir(app_dir).join(SINGBOX_COMPONENT_DIR_NAME)
|
||||
}
|
||||
|
||||
pub fn default_proxifyre_install_dir() -> PathBuf {
|
||||
app_install_dir_from_current_exe()
|
||||
.map(|app_dir| proxifyre_install_dir_from_app_dir(&app_dir))
|
||||
.unwrap_or_else(|| {
|
||||
PathBuf::from(r"C:\Program Files\ProxyWarden")
|
||||
.join(PROXYWARDEN_COMPONENTS_DIR_NAME)
|
||||
.join(PROXIFYRE_COMPONENT_DIR_NAME)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn default_singbox_install_dir() -> PathBuf {
|
||||
app_install_dir_from_current_exe()
|
||||
.map(|app_dir| singbox_install_dir_from_app_dir(&app_dir))
|
||||
.unwrap_or_else(|| PathBuf::from(DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT))
|
||||
}
|
||||
|
||||
pub fn detect_proxyfier_install_with_host(
|
||||
host: &impl ProxyfierDetectionHost,
|
||||
) -> Option<DetectedProxyfier> {
|
||||
let proxifyre_running = host.process_running("ProxiFyre.exe")
|
||||
|| host.service_running("ProxiFyreService")
|
||||
|| host.service_running("ProxiFyre");
|
||||
let detected_service = detect_proxifyre_service(host);
|
||||
let proxifyre_running = detected_service
|
||||
.as_ref()
|
||||
.is_some_and(|service| service_status_is_running(&service.status));
|
||||
|
||||
proxyfier_candidates(host)
|
||||
.into_iter()
|
||||
.filter_map(|candidate| candidate.into_detected(host, proxifyre_running))
|
||||
.filter_map(|candidate| {
|
||||
candidate.into_detected(host, proxifyre_running, detected_service.as_ref())
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
||||
@@ -161,6 +214,15 @@ fn detected_proxyfier_component(proxyfier: &DetectedProxyfier) -> ComponentStatu
|
||||
}
|
||||
}
|
||||
};
|
||||
let service_name = proxyfier
|
||||
.service_name
|
||||
.clone()
|
||||
.or_else(|| service_name(&proxyfier.engine).map(str::to_string));
|
||||
let service_status = proxyfier.service_status.clone();
|
||||
let mut problems = Vec::new();
|
||||
if service_status.is_none() {
|
||||
problems.push("Служба ProxiFyre не установлена".to_string());
|
||||
}
|
||||
|
||||
ComponentStatus {
|
||||
id: ComponentId::Proxyfier,
|
||||
@@ -169,10 +231,16 @@ fn detected_proxyfier_component(proxyfier: &DetectedProxyfier) -> ComponentStatu
|
||||
installed: true,
|
||||
running: proxyfier.running,
|
||||
version: Some(match proxyfier.engine {
|
||||
ProxyfierEngine::ProxiFyre => "ProxiFyre найден".to_string(),
|
||||
ProxyfierEngine::ProxiFyre => match service_status.as_deref() {
|
||||
Some(status) if service_status_is_running(status) => "служба запущена".to_string(),
|
||||
Some(_) => "служба остановлена".to_string(),
|
||||
None => "служба не установлена".to_string(),
|
||||
},
|
||||
}),
|
||||
path: Some(proxyfier.install_dir.display().to_string()),
|
||||
problems: Vec::new(),
|
||||
service_name,
|
||||
service_status,
|
||||
problems,
|
||||
actions,
|
||||
}
|
||||
}
|
||||
@@ -186,6 +254,8 @@ fn missing_proxyfier_component() -> ComponentStatus {
|
||||
running: false,
|
||||
version: None,
|
||||
path: None,
|
||||
service_name: service_name(&ProxyfierEngine::ProxiFyre).map(str::to_string),
|
||||
service_status: None,
|
||||
problems: vec!["ProxiFyre нужен для маршрутизации выбранных приложений".to_string()],
|
||||
actions: vec!["Установить ProxiFyre".to_string()],
|
||||
}
|
||||
@@ -224,6 +294,15 @@ fn detected_singbox_component(singbox: &DetectedSingBox) -> ComponentStatus {
|
||||
running: singbox.running,
|
||||
version: Some("sing-box найден".to_string()),
|
||||
path: Some(singbox.executable_path.display().to_string()),
|
||||
service_name: Some(singbox.service_name.clone()),
|
||||
service_status: Some(
|
||||
if singbox.running {
|
||||
"running"
|
||||
} else {
|
||||
"stopped"
|
||||
}
|
||||
.to_string(),
|
||||
),
|
||||
problems,
|
||||
actions,
|
||||
}
|
||||
@@ -238,6 +317,8 @@ fn missing_singbox_component() -> ComponentStatus {
|
||||
running: false,
|
||||
version: None,
|
||||
path: None,
|
||||
service_name: Some(DEFAULT_LOCAL_SINGBOX_SERVICE_NAME.to_string()),
|
||||
service_status: None,
|
||||
problems: Vec::new(),
|
||||
actions: vec!["Установить Local sing-box".to_string()],
|
||||
}
|
||||
@@ -255,21 +336,19 @@ impl ProxyfierCandidate {
|
||||
self,
|
||||
host: &impl ProxyfierDetectionHost,
|
||||
proxifyre_running: bool,
|
||||
detected_service: Option<&DetectedService>,
|
||||
) -> Option<DetectedProxyfier> {
|
||||
let executable_path = self.install_dir.join(executable_name(&self.engine));
|
||||
let config_path = config_path(&self.engine, &self.install_dir);
|
||||
let exists = host.path_exists(&self.install_dir)
|
||||
|| host.path_exists(&executable_path)
|
||||
|| config_path
|
||||
.as_ref()
|
||||
.is_some_and(|path| host.path_exists(path));
|
||||
|
||||
if !exists {
|
||||
if !host.path_exists(&executable_path) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(DetectedProxyfier {
|
||||
service_name: service_name(&self.engine).map(str::to_string),
|
||||
service_name: detected_service
|
||||
.map(|service| service.name.clone())
|
||||
.or_else(|| service_name(&self.engine).map(str::to_string)),
|
||||
service_status: detected_service.map(|service| service.status.clone()),
|
||||
engine: self.engine,
|
||||
name: self.name,
|
||||
install_dir: self.install_dir,
|
||||
@@ -290,6 +369,14 @@ fn proxyfier_candidates(host: &impl ProxyfierDetectionHost) -> Vec<ProxyfierCand
|
||||
"ProxiFyre",
|
||||
"PROXYWARDEN_PROXIFYRE_ROOT",
|
||||
);
|
||||
push_candidate(
|
||||
&mut candidates,
|
||||
ProxyfierCandidate {
|
||||
engine: ProxyfierEngine::ProxiFyre,
|
||||
name: "ProxiFyre".to_string(),
|
||||
install_dir: default_proxifyre_install_dir(),
|
||||
},
|
||||
);
|
||||
for entry in host.registry_install_entries() {
|
||||
if let Some(engine) = engine_from_name(&entry.display_name) {
|
||||
let install_dir = entry
|
||||
@@ -350,11 +437,17 @@ fn push_candidate(candidates: &mut Vec<ProxyfierCandidate>, candidate: Proxyfier
|
||||
}
|
||||
|
||||
fn common_install_dirs(host: &impl ProxyfierDetectionHost, folder_name: &str) -> Vec<PathBuf> {
|
||||
let mut dirs = vec![PathBuf::from(format!(r"C:\Tools\{folder_name}"))];
|
||||
let mut dirs = Vec::new();
|
||||
|
||||
for env_name in ["ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"] {
|
||||
if let Some(root) = host.env_var(env_name) {
|
||||
dirs.push(PathBuf::from(root).join(folder_name));
|
||||
let proxywarden_root = PathBuf::from(root).join("ProxyWarden");
|
||||
dirs.push(
|
||||
proxywarden_root
|
||||
.join(PROXYWARDEN_COMPONENTS_DIR_NAME)
|
||||
.join(folder_name),
|
||||
);
|
||||
dirs.push(proxywarden_root.join(folder_name));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -367,21 +460,26 @@ fn singbox_candidates(host: &impl ProxyfierDetectionHost) -> Vec<PathBuf> {
|
||||
if let Some(path) = host.env_var("PROXYWARDEN_SINGBOX_ROOT") {
|
||||
push_path_candidate(&mut candidates, PathBuf::from(path));
|
||||
}
|
||||
push_path_candidate(&mut candidates, default_singbox_install_dir());
|
||||
push_path_candidate(
|
||||
&mut candidates,
|
||||
PathBuf::from(DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT),
|
||||
);
|
||||
push_path_candidate(
|
||||
&mut candidates,
|
||||
PathBuf::from(r"C:\Tools\ProxyWarden\sing-box"),
|
||||
);
|
||||
for env_name in ["ProgramFiles", "ProgramFiles(x86)", "LOCALAPPDATA"] {
|
||||
if let Some(root) = host.env_var(env_name) {
|
||||
push_path_candidate(
|
||||
&mut candidates,
|
||||
PathBuf::from(&root).join("ProxyWarden").join("sing-box"),
|
||||
PathBuf::from(&root)
|
||||
.join("ProxyWarden")
|
||||
.join(PROXYWARDEN_COMPONENTS_DIR_NAME)
|
||||
.join(SINGBOX_COMPONENT_DIR_NAME),
|
||||
);
|
||||
push_path_candidate(
|
||||
&mut candidates,
|
||||
PathBuf::from(&root)
|
||||
.join("ProxyWarden")
|
||||
.join(SINGBOX_COMPONENT_DIR_NAME),
|
||||
);
|
||||
push_path_candidate(&mut candidates, PathBuf::from(root).join("sing-box"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,6 +539,27 @@ fn service_name(engine: &ProxyfierEngine) -> Option<&'static str> {
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_proxifyre_service(host: &impl ProxyfierDetectionHost) -> Option<DetectedService> {
|
||||
for name in ["ProxiFyreService", "ProxiFyre"] {
|
||||
if let Some(status) = host.service_status(name) {
|
||||
return Some(DetectedService {
|
||||
name: name.to_string(),
|
||||
status: normalize_service_status(&status),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn normalize_service_status(status: &str) -> String {
|
||||
status.trim().to_ascii_lowercase()
|
||||
}
|
||||
|
||||
fn service_status_is_running(status: &str) -> bool {
|
||||
status.trim().eq_ignore_ascii_case("running")
|
||||
}
|
||||
|
||||
fn engine_from_name(name: &str) -> Option<ProxyfierEngine> {
|
||||
let normalized = name.to_ascii_lowercase();
|
||||
if normalized.contains("proxifyre") {
|
||||
@@ -459,6 +578,17 @@ fn same_path(left: &Path, right: &Path) -> bool {
|
||||
.eq_ignore_ascii_case(&right.to_string_lossy())
|
||||
}
|
||||
|
||||
fn powershell_text(script: &str) -> Option<String> {
|
||||
command_no_window("powershell")
|
||||
.args(["-NoProfile", "-NonInteractive", "-Command", script])
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|output| output.status.success())
|
||||
.and_then(|output| String::from_utf8(output.stdout).ok())
|
||||
.map(|stdout| stdout.trim().to_string())
|
||||
.filter(|stdout| !stdout.is_empty())
|
||||
}
|
||||
|
||||
fn powershell_bool(script: &str) -> bool {
|
||||
command_no_window("powershell")
|
||||
.args(["-NoProfile", "-NonInteractive", "-Command", script])
|
||||
|
||||
@@ -33,6 +33,7 @@ pub fn run() {
|
||||
commands::save_target,
|
||||
commands::get_components,
|
||||
commands::get_proxifyre_setup_status,
|
||||
commands::get_proxifyre_setup_progress,
|
||||
commands::get_singbox_status,
|
||||
commands::get_singbox_setup_status,
|
||||
commands::resolve_profile_preview,
|
||||
|
||||
@@ -6,7 +6,8 @@ use url::Url;
|
||||
pub const DEFAULT_LOCAL_SINGBOX_LISTEN_HOST: &str = "127.0.0.1";
|
||||
pub const DEFAULT_LOCAL_SINGBOX_LISTEN_PORT: u16 = 1080;
|
||||
pub const DEFAULT_LOCAL_SINGBOX_SERVICE_NAME: &str = "ProxyWardenSingBox";
|
||||
pub const DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT: &str = r"C:\Program Files\ProxyWarden\sing-box";
|
||||
pub const DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT: &str =
|
||||
r"C:\Program Files\ProxyWarden\components\sing-box";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
||||
@@ -131,6 +132,10 @@ pub struct ComponentStatus {
|
||||
pub version: Option<String>,
|
||||
pub path: Option<String>,
|
||||
#[serde(default)]
|
||||
pub service_name: Option<String>,
|
||||
#[serde(default)]
|
||||
pub service_status: Option<String>,
|
||||
#[serde(default)]
|
||||
pub problems: Vec<String>,
|
||||
#[serde(default)]
|
||||
pub actions: Vec<String>,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::component_detection::DetectedSingBox;
|
||||
use crate::models::{DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT, DEFAULT_LOCAL_SINGBOX_SERVICE_NAME};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
pub const WINSW_WRAPPER_FILE: &str = "ProxyWardenSingBox.exe";
|
||||
|
||||
@@ -56,9 +56,19 @@ pub struct ServiceCommandOutput {
|
||||
}
|
||||
|
||||
pub fn build_singbox_setup_status(detected: Option<&DetectedSingBox>) -> SingBoxSetupStatus {
|
||||
build_singbox_setup_status_with_install_root(
|
||||
detected,
|
||||
&PathBuf::from(DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn build_singbox_setup_status_with_install_root(
|
||||
detected: Option<&DetectedSingBox>,
|
||||
default_install_root: &Path,
|
||||
) -> SingBoxSetupStatus {
|
||||
let install_root = detected
|
||||
.map(|singbox| singbox.install_dir.display().to_string())
|
||||
.unwrap_or_else(|| DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT.to_string());
|
||||
.unwrap_or_else(|| default_install_root.display().to_string());
|
||||
let binary_item = match detected {
|
||||
Some(singbox) if singbox.binary_exists => SingBoxSetupItem {
|
||||
id: "sing-box-binary".to_string(),
|
||||
@@ -152,9 +162,10 @@ pub fn ensure_safe_singbox_install_dir(path: &Path) -> Result<(), String> {
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase();
|
||||
|
||||
if file_name == "sing-box"
|
||||
&& (normalized.contains("\\proxywarden\\") || normalized.contains("\\proxywarden\\"))
|
||||
{
|
||||
let is_proxywarden_component = normalized.contains("\\proxywarden\\components\\");
|
||||
let is_legacy_proxywarden_child = normalized.ends_with("\\proxywarden\\sing-box");
|
||||
|
||||
if file_name == "sing-box" && (is_proxywarden_component || is_legacy_proxywarden_child) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user