v2: tabs, chain builder, obfuscation modes, hop slider, Defender exclusion, firewall kept
Made-with: Cursor
This commit is contained in:
103
proxy_chain_manager/config.py
Normal file
103
proxy_chain_manager/config.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import asdict, dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from .paths import app_data_dir
|
||||
|
||||
# Always bind the local forwarder to loopback. VM clones and NIC/IP changes (DHCP)
|
||||
# do not affect 127.0.0.1; avoid storing LAN IPs or hostnames for the listener.
|
||||
LISTEN_HOST = "127.0.0.1"
|
||||
|
||||
OBFUSCATION_MODES = ["auto", "http_only", "socks5_only", "random_mix"]
|
||||
OBFUSCATION_LABELS = {
|
||||
"auto": "Auto (best available)",
|
||||
"http_only": "HTTP only",
|
||||
"socks5_only": "SOCKS5 only",
|
||||
"random_mix": "Random Mix (max noise)",
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class Settings:
|
||||
# ── network ──────────────────────────────────────────────────────────
|
||||
local_host: str = LISTEN_HOST
|
||||
local_port: int = 18888
|
||||
|
||||
# ── chain ─────────────────────────────────────────────────────────────
|
||||
chain_length: int = 3 # number of hops (2-8)
|
||||
obfuscation_mode: str = "auto" # see OBFUSCATION_MODES
|
||||
use_pinned_chain: bool = False # use manually ordered chain
|
||||
pinned_chain: list[str] = field(default_factory=list) # user-ordered hop list
|
||||
|
||||
# ── timing ────────────────────────────────────────────────────────────
|
||||
health_check_seconds: int = 180 # how often to re-verify exit IP
|
||||
full_refresh_seconds: int = 1800 # how often to re-fetch/re-validate pool
|
||||
|
||||
# ── validation ────────────────────────────────────────────────────────
|
||||
validation_concurrency: int = 64
|
||||
max_candidates: int = 400
|
||||
validation_timeout_seconds: float = 12.0
|
||||
prefer_elite: bool = False
|
||||
|
||||
# ── security ──────────────────────────────────────────────────────────
|
||||
kill_switch_enabled: bool = True # engage Windows Firewall kill-switch when running
|
||||
proxy_bypass: str = "localhost;127.*;10.*;192.168.*;<local>"
|
||||
|
||||
# ── sources ───────────────────────────────────────────────────────────
|
||||
sources: list[str] = field(
|
||||
default_factory=lambda: [
|
||||
"https://cdn.jsdelivr.net/gh/proxifly/free-proxy-list@main/proxies/protocols/http/data.json",
|
||||
"https://cdn.jsdelivr.net/gh/proxifly/free-proxy-list@main/proxies/protocols/socks5/data.json",
|
||||
"https://cdn.jsdelivr.net/gh/proxifly/free-proxy-list@main/proxies/protocols/https/data.json",
|
||||
]
|
||||
)
|
||||
ip_check_url: str = "https://api.ipify.org?format=json"
|
||||
|
||||
def listen_addr(self) -> str:
|
||||
return f"{self.local_host}:{self.local_port}"
|
||||
|
||||
def settings_path(self) -> Path:
|
||||
return app_data_dir() / "settings.json"
|
||||
|
||||
|
||||
def normalize_listen_host(s: Settings) -> tuple[Settings, bool]:
|
||||
"""Force loopback bind so LAN/DHCP IP changes (e.g. cloned VMs) never break the listener."""
|
||||
if s.local_host == LISTEN_HOST:
|
||||
return s, False
|
||||
s.local_host = LISTEN_HOST
|
||||
return s, True
|
||||
|
||||
|
||||
def load_settings() -> Settings:
|
||||
p = app_data_dir() / "settings.json"
|
||||
if not p.is_file():
|
||||
return Settings()
|
||||
try:
|
||||
raw = json.loads(p.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return Settings()
|
||||
s = Settings()
|
||||
for k, v in raw.items():
|
||||
if hasattr(s, k):
|
||||
setattr(s, k, v)
|
||||
if not isinstance(s.sources, list) or not all(isinstance(x, str) for x in s.sources):
|
||||
s.sources = Settings().sources
|
||||
if s.obfuscation_mode not in OBFUSCATION_MODES:
|
||||
s.obfuscation_mode = "auto"
|
||||
if not isinstance(s.pinned_chain, list):
|
||||
s.pinned_chain = []
|
||||
s, changed = normalize_listen_host(s)
|
||||
if changed:
|
||||
try:
|
||||
p.write_text(json.dumps(asdict(s), indent=2), encoding="utf-8")
|
||||
except OSError:
|
||||
pass
|
||||
return s
|
||||
|
||||
|
||||
def save_settings(s: Settings) -> None:
|
||||
s, _ = normalize_listen_host(s)
|
||||
p = app_data_dir() / "settings.json"
|
||||
p.write_text(json.dumps(asdict(s), indent=2), encoding="utf-8")
|
||||
Reference in New Issue
Block a user