- Go 96.8%
- Dockerfile 3.2%
Static distroless image built and pushed to code.jaduer.dk/rune/pcsproxy by .woodpecker/docker.yaml, so the pcsarchive compose stack can run the proxy as a container alongside the archive and web UI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .woodpecker | ||
| .gitignore | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| main.go | ||
| proxy.go | ||
| README.md | ||
| rewrite.go | ||
| rewrite_test.go | ||
| store.go | ||
| store_test.go | ||
pcsproxy
A small reverse proxy for procyclingstats.com that keeps your browser completely cookie-free:
- All upstream cookies live in a proxy-side jar (
net/http/cookiejar). EverySet-Cookieheader (PCS setsPHPSESSID) is stripped before responses reach the browser, and browserCookieheaders are never forwarded upstream. - The AdShield anti-adblock loader never runs — the first-party
v3_adshield.js(which pulls html-load.com and nags with aconfirm()modal when blocked) is stripped from HTML and served as an empty stub. - Ad/tracker URLs are neutered: references to googletagmanager.com,
google-analytics.com, googlesyndication.com, doubleclick.net,
refinery89.com (ads + the
?cmpscreenconsent screen), html-load.com and error-report.com are rewritten to the proxy's/blockedsink, which answers204 No Content. No ads, no consent banner. - Navigation stays on the proxy:
https://(www.)procyclingstats.comURLs in HTML, JS, JSON and CSS (including\/-escaped JSON forms) are rewritten to the proxy origin. - Allow-listed third parties (ajax.googleapis.com, which serves the
jQuery/jQuery UI the site's own scripts depend on) are fetched through
/ext/<host>/..., cookie-stripped like everything else. Any other host gets403.
Run
go build -o pcsproxy .
./pcsproxy # listens on 127.0.0.1:8081
./pcsproxy -listen 127.0.0.1:8888
Then browse http://localhost:8081.
Container
A Dockerfile builds a static, distroless, non-root image; CI
(.woodpecker/docker.yaml) pushes it to code.jaduer.dk/rune/pcsproxy. The
pcsarchive repo ships a
docker-compose.yaml that runs this proxy together with the archive and the
web UI off one .env — the easiest way to run the whole stack.
docker run -d -p 8081:8081 code.jaduer.dk/rune/pcsproxy \
-listen 0.0.0.0:8081 -store http://archive:8082 # -store optional
Archiving browsed pages
With -store, every PCS page you browse is also shipped to a local
pcsarchive instance — a personal
offline copy that grows as a side effect of browsing:
./pcsproxy -store http://127.0.0.1:8082
The original upstream HTML is archived (before URL rewriting); assets,
/ext/ traffic, redirects and error pages are skipped. Delivery is
fire-and-forget through a bounded queue: if the archive is down or slow,
pages are dropped (logged, at most once a minute) and browsing is never
slowed down.
For a remote archive (pcsarchive running elsewhere with -token), pass
the shared token — via the environment for preference, so it doesn't show up
in ps output:
PCSPROXY_STORE_TOKEN=<token> ./pcsproxy -store https://archive.example.com
./pcsproxy -store https://archive.example.com -store-token <token> # equivalent
Use https for remote archives (e.g. behind a TLS reverse proxy) — the token and page contents travel in the clear otherwise.
Adding allow-listed hosts
Exact hosts go in extAllowed, whole CDN domains with variable subdomains in
extAllowedSuffixes (both in proxy.go).
Notes
- The jar is in-memory: cookies vanish on restart. That includes any login session — log in again after restarting the proxy.
- CSP/HSTS headers are stripped so rewritten pages work over plain http from localhost. Don't expose the listener beyond localhost.
- PCS sits behind Cloudflare bot protection. The proxy forwards your
browser's real
User-Agent/Acceptheaders, which normally passes. If Cloudflare ever serves an interactive challenge, it is unlikely to solve from a localhost origin — wait it out or try again with a fresh proxy restart.
Tests
go test ./...