Add api_host/api_port to Config dataclass, parse api_listen key in
load_config(), add --api [HOST:]PORT CLI flag. Start/stop API server
in serve() alongside the SOCKS5 listener.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Lightweight asyncio HTTP handler for runtime inspection and management.
Endpoints: /status, /metrics, /pool, /pool/alive, /config (GET) and
/reload, /pool/test, /pool/refresh (POST). Raw HTTP/1.1 parsing, JSON
responses, no new dependencies.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove source.py from architecture (deleted)
- Add metrics.py to module list
- Update warm start: trusts cached state, instant startup
- Update signal handling: registered before startup
- Add refactoring tasks to TASKS.md
- Remove stale troubleshooting entry
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move SIGTERM/SIGINT handler registration to the top of serve()
so signals are never ignored during slow startup phases (cold
start health tests, source fetching). Previously, signals sent
before pool.start() returned had no handler, causing podman to
escalate to SIGKILL after the stop timeout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On warm start, trust persisted alive state and serve immediately.
Health tests run in background. Cold start behavior unchanged.
Previously warm start blocked on testing all cached-alive proxies
before binding the listen port, causing multi-minute delays when
the chain or proxies were slow to respond.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Delete source.py, ProxySourceConfig, and Config.proxy_source.
ProxyPool fully supersedes ProxySource. The YAML backward-compat
conversion in load_config is preserved so old configs still work.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use the modern asyncio.create_task() in pool.py and server.py.
Replace redundant list comprehension with list() in evict_keys copy.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The server is pure asyncio (single-threaded). The threading.Lock
was never contended. Use a float accumulator in _human_bytes to
avoid the int-to-float type: ignore.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract _http_check() to deduplicate identical HTTP GET + status
parsing between _test_proxy and _test_chain.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move VALID_PROTOS to config.py as single source of truth.
Add parse_api_proxies() to eliminate duplicate API response
parsing in pool.py and source.py.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update USAGE, CHEATSHEET, README, PROJECT, and TASKS for the three
performance improvements: max_connections semaphore, async HTTP
source fetching, and first-hop TCP connection pool.
Add FirstHopPool that maintains a deque of pre-established TCP
connections to chain[0]. Connections idle beyond pool_max_idle are
evicted; a background task refills to pool_size. build_chain() tries
the pool first, falls back to open_connection. Enabled with
pool_size > 0 in config. Only pools the TCP handshake -- SOCKS/HTTP
tunnels are consumed, not returned.
Replace blocking urllib with a minimal async HTTP/1.1 client (http.py)
using asyncio streams. Pool source fetches now run in parallel via
asyncio.gather. Dead proxy reporting uses async POST. Handles
Content-Length, chunked transfer-encoding, and connection-close bodies.
No new dependencies.
Add max_connections config (default 256) with -m/--max-connections CLI
flag. Server wraps on_client in asyncio.Semaphore to prevent fd
exhaustion under load. Value reloads on SIGHUP; active connections
drain normally. Also adds pool_size/pool_max_idle config fields and
first_hop_pool wiring in server.py (used by next commits), and fixes
asyncio.TimeoutError -> TimeoutError lint warnings.
Mount ~/.cache/s5p as /data for pool state and profile output.
Enable cProfile by default, dumping to /data/s5p.prof.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
basicConfig creates a StreamHandler with its own level filter.
Changing only the logger levels left the handler filtering at the
original level, so debug messages were silently dropped after reload.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add hot reload section to USAGE with reloadable settings table.
Add dead proxy reporting section with report_url config and payload
format. Update example.yaml, ROADMAP, TASKS, TODO, CHEATSHEET.
When report_url is configured, POST evicted proxy list as JSON after
each health test cycle. Fire-and-forget: failures are logged at debug
level. Payload format: {"dead": [{"proto": "socks5", "proxy": "host:port"}]}.
On SIGHUP, re-read the YAML config file and update mutable runtime
settings: timeout, retries, log_level, and pool config (sources,
intervals, thresholds). Pool triggers an immediate source re-fetch.
Listen address and chain require restart.
Add warm start and chain pre-flight sections to USAGE. Mark both
features complete in ROADMAP and TASKS. Remove implemented items
from TODO. Update README, PROJECT, and CHEATSHEET.
Test the static chain (without pool proxy) before running pool health
tests. If the chain itself is unreachable, skip proxy testing and log a
clear warning. Prevents false mass-failure when the issue is upstream
(e.g., Tor is down), not the exit proxies.
On warm start (state has alive proxies), only quick-test the
previously-alive subset before serving. Full health test runs in
background. Cold start behavior unchanged (test all before serving).
Reduces startup blocking from minutes to seconds on warm restarts.
Add failure backoff and stale expiry sections to USAGE. Document pool=
field in metrics output. Update ROADMAP, TASKS, TODO with completed
items and remaining suggestions. Add metrics example to CHEATSHEET.
Evict proxies not returned by sources for >3 refresh cycles and not
currently alive. Cleans up proxies removed upstream faster than waiting
for max_fails consecutive health test failures.
Track last_fail timestamp on ProxyEntry. When a connection attempt fails
in server.py, report_failure() records the time. The selection weight
multiplies by min(fail_age/60, 1.0), ramping back from floor over 60s.
Prevents wasting retries on proxies that just failed.
Add selection weight section to USAGE.md with decay formula and
reference table. Mark feature complete in ROADMAP and TASKS.
Update README and PROJECT descriptions.
Replace uniform random.choice with random.choices weighted by last_ok
recency. Proxies tested successfully more recently get higher selection
probability (weight = 1/(1 + age/300)), decaying over ~5 minutes.
Update all docs for managed proxy pool: README, USAGE, CHEATSHEET,
PROJECT, TASKS, and example config. Document multi-source config,
proxy file format, health testing, persistence, and legacy compat.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ProxyPool replaces ProxySource with:
- Multiple sources: HTTP APIs and text files (one proxy URL per line)
- Deduplication by proto://host:port
- Health testing: full chain test with configurable concurrency
- Mass-failure guard: skip eviction when >90% fail
- Background loops for periodic refresh and health checks
- JSON state persistence with atomic writes (warm starts)
- Backward compat: ProxySource still works for legacy configs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add PoolSourceConfig and ProxyPoolConfig for multi-source proxy pool
with health testing. Config supports both HTTP API and file sources.
Backward compat: legacy proxy_source YAML key auto-converts to
proxy_pool. CLI -S flag creates ProxyPoolConfig with single source.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Moves _negotiate_hop() and build_chain() from server.py to proto.py
to break circular import between server and the upcoming pool module.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Retry failed proxy connections with a fresh random proxy on each
attempt (configurable via retries setting, proxy_source only).
Track connection metrics and log summary every 60s and on shutdown.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fetches proxies from an HTTP API, caches them in memory, and appends
a random proxy to the chain on each connection. Supports proto/country
filters and configurable refresh interval.
Config: proxy_source.url, proto, country, limit, refresh
CLI: -S/--proxy-source URL
Asyncio-based SOCKS5 server that tunnels connections through
configurable chains of SOCKS5, SOCKS4/4a, and HTTP CONNECT proxies.
Tor integration via standard SOCKS5 hop.