17 Commits
Author SHA1 Message Date
omarandClaude Opus 5 ec781fec19 chore: gofmt files left unformatted
CI / validate (push) Successful in 34s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 1m1s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 47s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 27s
Trailing whitespace and comment/field alignment only; no behaviour change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 17:48:55 +03:00
omarandClaude Opus 5 5785be4964 Subscriptions: uniqueness by subnet, not just by exit IP
unique_ips collapsed proxies per exit address, so one operator holding many
adjacent addresses counted as many distinct nodes. On the live pool the NL
exit layer looked like 87 unique IPs but was 40 distinct /24s, with 33 of
them in a single block — roughly three of four circuits leaving through one
of two operators.

Add a scope alongside the existing metric, so the two axes are independent:

  unique_ips_scope  = ip | subnet     (ip = previous behaviour, default)
  unique_ips_metric = speed | latency (unchanged)
  unique_subnet_v4  = prefix bits, default 24
  unique_subnet_v6  = prefix bits, default 48

The collapse key generalises from the exit address to a masked netip.Prefix.
Prefix lengths are clamped (v4 8-32, v6 16-128) rather than rejected so a
stored subscription can never render an empty payload, and the API persists
the normalized value so the panel shows what is actually served. Exits that
are empty or unparseable still pass through uncollapsed — dropping them would
discard distinct nodes. IPv4-mapped IPv6 is unmapped before masking.

Schema upgrade is idempotent and defaults reproduce the old behaviour, so
existing subscriptions keep serving the same node set until switched over.

The same scope is exposed on the Proxies tab (and its export) so the effect
can be previewed before it is applied to a subscription.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 17:48:55 +03:00
omarandClaude Opus 4.8 979c2a691a Dashboard geo: re-sort by active metric on unique_ips toggle
CI / validate (push) Successful in 14s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 33s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 39s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 21s
The country matrix stayed sorted by proxy-total even in unique_ips mode. The
client now sorts by the active metric (total vs unique exit IPs) and the header
reads "IPs" when unique. Backend returns all countries (≤40) so the re-rank is
correct, not just a reorder of the top-15-by-proxy.

(The dashboard already reflects the last completed session — is_current flips
only on successful completion; the running session is separate.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-08 18:05:15 +03:00
omarandClaude Opus 4.8 07086f19fc chore: gitignore playwright artifacts and screenshots
CI / validate (push) Successful in 14s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 33s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 33s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 21s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-08 17:56:13 +03:00
omarandClaude Opus 4.8 7b638d5e77 Dashboard: Locations by protocol — count + avg ping per protocol per country
CI / validate (push) Successful in 14s
CI / images (deploy/Dockerfile.checker, checker) (push) Has been cancelled
CI / images (deploy/Dockerfile.web, web) (push) Has been cancelled
CI / images (deploy/Dockerfile.api, api) (push) Has been cancelled
Replaces the flat Top-locations bar list with a country × protocol matrix: each
country row shows total plus, for vless/vmess/trojan/ss, the count (in the
protocol's colour) and average ping. The unique_ips toggle switches counts to
distinct exit IPs. Backend adds GeoBreakdown (count/unique/avg-latency per
country+protocol) assembled into a geo[] payload.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-08 17:55:53 +03:00
omarandClaude Opus 4.8 fa2b7d6d92 Dashboard: unique_ips toggle on Top locations (count distinct exit IPs)
CI / validate (push) Successful in 15s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 35s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 33s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 22s
Adds a countries_unique breakdown (count(DISTINCT exit_ip) per country) to the
dashboard payload and a unique_ips toggle on the Top locations card that switches
between raw proxy count and unique-exit-IP count. No metric choice — plain
distinct exit IP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-08 17:40:14 +03:00
omarandClaude Opus 4.8 cbf999d374 Canonicalization: identity-normalize to kill duplicate proxies
CI / validate (push) Successful in 17s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 39s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 35s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 21s
On real data ~half the pool was the same server fractured into "different"
proxies by client-side/default params. The whitelist kept them all
(encryption=none, fp, packetEncoding, type=tcp/raw, headerType=none,
allowInsecure/insecure, security=none, spx, …). Since we re-check the canonical
URL, stripping these is safe — anything that mattered would fail the check.

New canonicalQuery keeps only endpoint+security+transport identity:
- normalize: security=none→∅, type=tcp/raw→∅, headerType=none→∅
- drop client hints: fp, packetEncoding, allowInsecure/insecure, spx, encryption
- scope: path/host only for ws-family, serviceName/mode only grpc/xhttp,
  sni/alpn/pbk/sid only under tls/reality; alpn sorted
- default drops: sni==host under plain TLS, ws Host==sni, path="/"
- trojan defaults to TLS (sni kept without explicit security)
- vmess: drop aid=0, scy=auto, headerType=none, fp, v; same default drops

Verified on the live 2014-proxy export: 2014 → 1024 distinct (49% were dupes),
0 rejects, with a KeepsDistinct test guarding against over-merge (different
pbk/sni/path/uuid/port stay separate).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-08 16:44:39 +03:00
omarandClaude Opus 4.8 40e6938254 Fix speedtest: Cloudflare 429s through proxies → 0 Mbps + false rejects
CI / validate (push) Successful in 14s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 52s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 38s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 22s
Root cause (found by probing 15 live proxies): speed.cloudflare.com/__down
returns HTTP 429 (rate-limited) through shared proxy exit IPs, and its 1-byte
429 body was measured as "0 Mbps" with no error — so the speed gate then
rejected otherwise-fast proxies. Plain-HTTP CDN mirrors (cachefly etc.) download
reliably through the same proxies at 40-80 Mbps.

Fix:
- Default speed-test URL is now http://cachefly.cachefly.net/10mb.test.
- The checker tries the configured URL then hardcoded fallbacks (cachefly, tele2,
  thinkbroadband, cloudflare). A host-side failure (dial error, HTTP 429/non-200,
  empty body) moves to the next URL; a completed download — even a slow one — is
  taken as the proxy's real speed (the proxy is the bottleneck).
- Only a 200 response counts; a 429/non-200 is a host failure, not a 0-Mbps proxy.

Verified end-to-end: a speedtest-ON cycle now yields valid proxies that all carry
a real speed (session had 7 valid, 41-73 Mbps) instead of everything reading 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 16:22:04 +03:00
omarandClaude Opus 4.8 02a16d6fab Fix ugly toggle switches: light/dark knob with proper contrast + flex layout
CI / validate (push) Successful in 14s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 35s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 52s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 22s
The switch knob was bg-ink-950 (near-black) on a dark bg-ink-600 track — a dark
circle on a dark pill, almost invisible, and the absolute positioning rendered
inconsistently. Rewrote it as the standard inline-flex switch: lighter bordered
track, a light knob when off (on grey) and a dark knob when on (on the aqua
track), so state is clearly readable in both positions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 15:37:54 +03:00
omarandClaude Opus 4.8 35ba4e36ad Fix checker deadlock: watchdog on proxy dial + clear stale running sessions
CI / validate (push) Successful in 14s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 36s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 39s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 23s
The reused protocol dialers do blocking socket I/O during the handshake without
honoring the context, so a proxy that accepts TCP but stalls the handshake hung
the worker permanently — the cycle froze mid-way (observed stuck at 3300/4862,
0% CPU). Now the dial runs under a hard watchdog: it is abandoned once the dial
timeout elapses, and on success the connection gets an absolute deadline so a
stalled read/write on the tunnel can't hang the worker either.

Also mark any 'running' session as failed on worker startup — a fresh worker
can't resume a previous process's in-flight cycle (it would otherwise linger as
'running' forever).

Verified: a full ~4860-candidate cycle now advances steadily past the old freeze
point and completes (session #4, 88 valid).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 15:15:58 +03:00
omarandClaude Opus 4.8 f84225da19 ci: static step name (Gitea doesn't interpolate matrix in step names)
CI / validate (push) Successful in 16s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 37s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 38s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 24s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 14:55:01 +03:00
omarandClaude Opus 4.8 8584040cc3 Fix .gitignore swallowing internal/api handlers; commit missing session/ws files
CI / validate (push) Successful in 15s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 1m32s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 40s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 23s
The bare `api` / `checker` ignore patterns matched the internal/api and cmd
directories, so handlers_sessions.go and handlers_ws.go were never committed —
CI's `go vet` failed on undefined handlers. Anchored the patterns to the repo
root (/api, /checker) and added the missing files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 14:50:45 +03:00
omarandClaude Opus 4.8 aee04924da Add sessions page, live websocket, subscription URL search; fix Settings toggles
CI / validate (push) Failing after 7s
CI / images (deploy/Dockerfile.api, api) (push) Has been skipped
CI / images (deploy/Dockerfile.checker, checker) (push) Has been skipped
CI / images (deploy/Dockerfile.web, web) (push) Has been skipped
1. Settings toggles were wrapped in <label>, which forwarded a second click to
   the switch button and cancelled the toggle — unwrapped so they respond.
2. Sessions: new GET /sessions, /sessions/{id} (per-protocol/source/country
   stats), /sessions/{id}/proxies. New Sessions tab: history list + per-session
   breakdown + the proxies that were valid in each session.
3. Subscriptions: url_search text[] — match canonical_url against ANY of several
   substrings (OR ILIKE). Multi-value tag input in the form.
4. Live: /api/v1/ws/status websocket pushes checker status ~1s; a client hook
   keeps the UI live and refreshes data queries on session completion. nginx
   upgrades the connection; statusWriter now implements http.Hijacker.
5. Dashboard + Sessions source breakdown: added valid-% (passed/unique) column.

Verified via Docker + Playwright: WS 101 upgrade and 1s stream, toggle flips,
sessions detail with real data, url_search=[vless://] yields only vless configs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 14:40:10 +03:00
omarandClaude Opus 4.8 5126b0f6a6 docs: README quick-start reflects registry-pull + source types
CI / validate (push) Successful in 15s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 37s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 37s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 22s
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 01:49:56 +03:00
omarandClaude Opus 4.8 0b432f2b11 Polish: render dashboard charts without entry animation
CI / validate (push) Successful in 13s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 35s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 37s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 23s
Data bars/areas are immediately visible instead of animating from zero — better
for an at-a-glance telemetry console.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 01:46:30 +03:00
omarandClaude Opus 4.8 c434b1212f Add selectable source type, fix Gitea CI image push, compose pulls from registry
CI / validate (push) Successful in 16s
CI / images (deploy/Dockerfile.api, api) (push) Successful in 1m39s
CI / images (deploy/Dockerfile.checker, checker) (push) Successful in 38s
CI / images (deploy/Dockerfile.web, web) (push) Successful in 21s
- sources: new `kind` (auto|plain|base64) — fetcher honors it (plain = no
  base64 decode; base64 = force decode; auto = try base64 then plain). Schema
  ALTER for existing DBs, API validation, Settings UI selector (add-form + inline).
- CI: rewrite images job to the proven pattern (buildx + metadata-action +
  build-push-action) using a write:package PAT secret (REGISTRY_TOKEN); the
  automatic GITEA_TOKEN cannot push packages. Matrix over checker/api/web.
- compose: docker-compose.yml now pulls git.qomar.pw/omar/zhguchiy_perchik/*
  images from the Gitea registry; docker-compose.build.yml is the local-build
  override.

Verified on a real run: 3 aggregated lists → 7838 raw → 4842 unique (canonical
dedup) → 194 working proxies across all four protocols, served through the panel
and subscriptions.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 01:35:51 +03:00
omarandClaude Opus 4.8 5f4271b747 Initial implementation: proxy checker panel + subscription system
CI / validate (push) Successful in 41s
CI / images (push) Failing after 5s
Full stack per GOAL.md / TODO.md:
- checker (Go): fetch sources, canonicalize/dedup (whitelist+sort, &amp; fixes),
  validity check (connect+latency+speed gates), geoip by exit IP, session model
  ("only working / last completed session"), global lock, manual run/stop,
  Telegram start/finish notifications. Reuses tessero-checker dialers
  (vless/vmess/trojan/ss), upstream parser, geoip, fetcher.
- api (Go): admin auth (single operator), dashboard stats, proxies
  list/filter/export/bulk, subscriptions CRUD, sources CRUD, settings, checker
  control, and dynamic public /sub endpoints with unique_ips + plain/base64/
  clash/singbox rendering.
- frontend (React+Vite+TS+Tailwind): telemetry-console SPA — Dashboard, Proxies,
  Subscriptions, Settings.
- postgres schema, docker-compose (postgres/checker/api/web-nginx), Dockerfiles,
  Gitea Actions CI.

Verified end-to-end via Docker: pipeline, all sub formats, unique_ips collapse,
and all four panel tabs (Playwright).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TtR4PP2JM9KadaBkhPGAE
2026-07-02 01:02:00 +03:00