feat: fixed exit proxy (last hop only), optional pool-free single-hop mode, gitignore pycache

Made-with: Cursor
This commit is contained in:
Dr Jones
2026-04-14 19:28:50 -07:00
parent 45798464e4
commit 644229719f
17 changed files with 140 additions and 17 deletions

View File

@@ -10,6 +10,17 @@ from .paths import app_data_dir
# do not affect 127.0.0.1; avoid storing LAN IPs or hostnames for the listener.
LISTEN_HOST = "127.0.0.1"
def normalize_proxy_url(raw: str) -> str:
"""Strip, add http:// if missing. Empty input → empty string."""
t = (raw or "").strip()
if not t:
return ""
if "://" not in t:
t = "http://" + t
return t.rstrip("/")
OBFUSCATION_MODES = ["auto", "http_only", "socks5_only", "random_mix"]
OBFUSCATION_LABELS = {
"auto": "Auto (best available)",
@@ -30,6 +41,8 @@ class Settings:
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
# Fixed last hop only (ignored when use_pinned_chain is True — full manual chain wins)
manual_exit_proxy: str = ""
# ── timing ────────────────────────────────────────────────────────────
health_check_seconds: int = 180 # how often to re-verify exit IP
@@ -88,6 +101,8 @@ def load_settings() -> Settings:
s.obfuscation_mode = "auto"
if not isinstance(s.pinned_chain, list):
s.pinned_chain = []
if not hasattr(s, "manual_exit_proxy") or not isinstance(s.manual_exit_proxy, str):
s.manual_exit_proxy = ""
s, changed = normalize_listen_host(s)
if changed:
try: