Refactor proxy routing and installer flows

This commit is contained in:
2026-07-08 11:05:52 +03:00
parent e745633d91
commit 88d5b94133
14 changed files with 1899 additions and 487 deletions

View File

@@ -437,39 +437,18 @@ impl Clock for SystemClock {
}
}
pub struct StagedApplyHelper;
impl ProxyApplyHelper for StagedApplyHelper {
fn apply_proxy_config(
&self,
request: HelperApplyRequest<'_>,
) -> Result<HelperApplyResult, CommandError> {
Ok(HelperApplyResult {
success: true,
changed: true,
action: format!("{}.stage-generated-config", request.adapter_id),
message: format!(
"Сгенерированный конфиг подготовлен в {}; интеграция привилегированного помощника еще не подключена",
request.config_path.display()
),
})
}
}
pub struct DetectedProxyApplyHelper<H = SystemProxyfierDetectionHost> {
host: H,
}
impl DetectedProxyApplyHelper<SystemProxyfierDetectionHost> {
pub fn system() -> Self {
Self {
host: SystemProxyfierDetectionHost,
}
SystemProxyfierDetectionHost.into()
}
}
impl<H> DetectedProxyApplyHelper<H> {
pub fn new(host: H) -> Self {
impl<H> From<H> for DetectedProxyApplyHelper<H> {
fn from(host: H) -> Self {
Self { host }
}
}

View File

@@ -3,11 +3,7 @@ use crate::models::{DEFAULT_LOCAL_SINGBOX_INSTALL_ROOT, DEFAULT_LOCAL_SINGBOX_SE
use serde::{Deserialize, Serialize};
use std::path::Path;
pub const SINGBOX_RELEASE_API_URL: &str =
"https://api.github.com/repos/SagerNet/sing-box/releases/latest";
pub const WINSW_RELEASE_API_URL: &str = "https://api.github.com/repos/winsw/winsw/releases/latest";
pub const WINSW_WRAPPER_FILE: &str = "ProxyWardenSingBox.exe";
pub const SINGBOX_BINARY_FILE: &str = "sing-box.exe";
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SingBoxServiceAction {

View File

@@ -75,13 +75,6 @@ impl JsonStorage {
&self.paths
}
pub fn ensure_dirs(&self) -> io::Result<()> {
fs::create_dir_all(&self.paths.config_dir)?;
fs::create_dir_all(&self.paths.state_dir)?;
fs::create_dir_all(&self.paths.generated_dir)?;
Ok(())
}
pub fn read_profiles(&self) -> io::Result<Vec<Profile>> {
self.read_json_or_default(&self.paths.profiles_file)
}
@@ -102,10 +95,6 @@ impl JsonStorage {
self.read_json_or_default(&self.paths.components_file)
}
pub fn write_components(&self, components: &[ComponentStatus]) -> io::Result<()> {
self.write_json(&self.paths.components_file, components)
}
pub fn read_local_singbox_config(&self) -> io::Result<LocalSingBoxConfig> {
self.read_json_or_default(&self.paths.local_singbox_file)
}
@@ -135,11 +124,6 @@ impl JsonStorage {
Ok(cap_activity(entries, self.activity_limit))
}
pub fn write_activity(&self, entries: &[ActivityEntry]) -> io::Result<()> {
let entries = cap_activity(entries.to_vec(), self.activity_limit);
self.write_json(&self.paths.activity_file, &entries)
}
pub fn append_activity(&self, entry: ActivityEntry) -> io::Result<Vec<ActivityEntry>> {
let entries = self.read_activity()?;
let entries = append_activity(entries, entry, self.activity_limit);

View File

@@ -303,9 +303,10 @@ fn apply_generates_derived_config_and_records_activity_with_mock_helper() {
storage
.write_targets(&[external_socks5_target()])
.expect("write targets");
storage
.write_components(&[proxyfier_running(), singbox_missing()])
.expect("write components");
write_json(
&storage.paths().components_file,
&[proxyfier_running(), singbox_missing()],
);
let response = apply_profiles_with_services(
&storage,
@@ -345,9 +346,7 @@ fn apply_blocks_local_singbox_target_when_component_is_missing() {
storage
.write_targets(&[local_singbox_target()])
.expect("write targets");
storage
.write_components(&[singbox_missing()])
.expect("write components");
write_json(&storage.paths().components_file, &[singbox_missing()]);
let error = apply_profiles_with_services(
&storage,
@@ -405,7 +404,7 @@ fn detected_proxy_apply_helper_writes_proxifyre_app_config() {
.with_registry("ProxiFyre", &install_dir)
.with_path(&install_dir)
.with_path(&install_dir.join("ProxiFyre.exe"));
let helper = DetectedProxyApplyHelper::new(host);
let helper = DetectedProxyApplyHelper::from(host);
let result = helper
.apply_proxy_config(HelperApplyRequest {
@@ -438,7 +437,7 @@ fn detected_proxy_apply_helper_ignores_plain_proxifier_install() {
.with_registry("Proxifier", &install_dir)
.with_path(&install_dir)
.with_path(&install_dir.join("Proxifier.exe"));
let helper = DetectedProxyApplyHelper::new(host);
let helper = DetectedProxyApplyHelper::from(host);
let result = helper
.apply_proxy_config(HelperApplyRequest {
@@ -554,6 +553,14 @@ fn cleanup(root: &Path) {
let _ = fs::remove_dir_all(root);
}
fn write_json<T: serde::Serialize + ?Sized>(path: &Path, value: &T) {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create json parent dir");
}
let contents = serde_json::to_vec_pretty(value).expect("serialize json");
fs::write(path, contents).expect("write json");
}
fn discord_profile(target_id: &str) -> Profile {
Profile {
id: "discord".to_string(),

View File

@@ -39,10 +39,8 @@ fn roundtrips_profiles_targets_components_and_activity() {
storage.write_profiles(&profiles).expect("write profiles");
storage.write_targets(&targets).expect("write targets");
storage
.write_components(&components)
.expect("write components");
storage.write_activity(&activity).expect("write activity");
write_json(&storage.paths().components_file, &components);
write_json(&storage.paths().activity_file, &activity);
assert_eq!(storage.read_profiles().expect("read profiles"), profiles);
assert_eq!(storage.read_targets().expect("read targets"), targets);
@@ -118,7 +116,7 @@ fn missing_local_singbox_config_defaults_to_optional_empty_state() {
fn invalid_subscription_cache_falls_back_to_none() {
let root = test_root("invalid-subscription-cache");
let storage = JsonStorage::new(root.clone());
storage.ensure_dirs().expect("create storage dirs");
fs::create_dir_all(&storage.paths().state_dir).expect("create state dir");
fs::write(
&storage.paths().singbox_subscription_cache_file,
"{not valid json",
@@ -139,7 +137,7 @@ fn invalid_subscription_cache_falls_back_to_none() {
fn invalid_json_falls_back_to_empty_collection() {
let root = test_root("invalid-json");
let storage = JsonStorage::new(root.clone());
storage.ensure_dirs().expect("create storage dirs");
fs::create_dir_all(&storage.paths().config_dir).expect("create config dir");
fs::write(&storage.paths().profiles_file, "{not valid json").expect("write invalid json");
assert_eq!(
@@ -227,6 +225,14 @@ fn cleanup(root: &Path) {
let _ = fs::remove_dir_all(root);
}
fn write_json<T: serde::Serialize + ?Sized>(path: &Path, value: &T) {
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("create json parent dir");
}
let contents = serde_json::to_vec_pretty(value).expect("serialize json");
fs::write(path, contents).expect("write json");
}
fn sample_profile(id: &str) -> Profile {
Profile {
id: id.to_string(),