No description
  • Go 96.8%
  • Dockerfile 3.2%
Find a file
Rune Jensen 4c9afb0e96
All checks were successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/docker Pipeline was successful
ci/woodpecker/tag/build Pipeline was successful
ci/woodpecker/tag/docker Pipeline was successful
add Dockerfile and docker push workflow
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>
2026-07-07 06:42:31 +00:00
.woodpecker add Dockerfile and docker push workflow 2026-07-07 06:42:31 +00:00
.gitignore pcsproxy: cookie-free reverse proxy for procyclingstats.com 2026-07-06 17:53:43 +00:00
Dockerfile add Dockerfile and docker push workflow 2026-07-07 06:42:31 +00:00
go.mod pcsproxy: cookie-free reverse proxy for procyclingstats.com 2026-07-06 17:53:43 +00:00
go.sum pcsproxy: cookie-free reverse proxy for procyclingstats.com 2026-07-06 17:53:43 +00:00
main.go support remote archives: -store-token bearer auth 2026-07-06 21:06:33 +00:00
proxy.go support remote archives: -store-token bearer auth 2026-07-06 21:06:33 +00:00
README.md add Dockerfile and docker push workflow 2026-07-07 06:42:31 +00:00
rewrite.go pcsproxy: cookie-free reverse proxy for procyclingstats.com 2026-07-06 17:53:43 +00:00
rewrite_test.go pcsproxy: cookie-free reverse proxy for procyclingstats.com 2026-07-06 17:53:43 +00:00
store.go support remote archives: -store-token bearer auth 2026-07-06 21:06:33 +00:00
store_test.go support remote archives: -store-token bearer auth 2026-07-06 21:06:33 +00:00

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). Every Set-Cookie header (PCS sets PHPSESSID) is stripped before responses reach the browser, and browser Cookie headers 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 a confirm() 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 ?cmpscreen consent screen), html-load.com and error-report.com are rewritten to the proxy's /blocked sink, which answers 204 No Content. No ads, no consent banner.
  • Navigation stays on the proxy: https://(www.)procyclingstats.com URLs 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 gets 403.

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/Accept headers, 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 ./...