1552dd15da
The previous commit documented the clock-zone requirement as "the zone must match the egress IP's country". Re-measuring against the deployment case shows that is wrong. The original inference came from reading the host's /etc/timezone (Asia/Bangkok) and assuming the egress IP was Thai. It is not: this host egresses from an Indonesian IP. Asia/Bangkok cleared the challenge not because it matched a country but because it is simply not UTC, and the two share +07, which hid the distinction. Measured 2026-08-08, identical container, one Indonesian egress IP: TZ=UTC never cleared (60s, twice) TZ=Asia/Jakarta cleared in 4s TZ=America/New_York cleared in 4s America/New_York matches neither the country nor the offset nor the hemisphere, and clears just as fast. So a UTC clock is itself the bot signal - Cloudflare scores it as the datacenter default - and any real zone satisfies the check. This makes the knob considerably less fragile than documented: BROWSER_TZ needs a plausible zone, not a geolocated one, and a deployment that moves region does not have to keep it in sync. Comments in chrome/entrypoint.sh and docker-compose.yml, the hard constraint in AGENTS.md, and the PR description are corrected accordingly. BROWSER_TZ is also documented in .env.example for the first time, which is the file an operator actually copies - the setting decides whether kagane works at all, and a UTC server (the common case) is exactly the one that fails with it unset. Re-verified against the shipped image with TZ=Asia/Jakarta: TestSmokeKaganeImage PASS (5.00s, 56710 bytes of image/webp), TestSmokeKaganeGet PASS (1.24s, status 200).
62 lines
3.1 KiB
Bash
Executable File
62 lines
3.1 KiB
Bash
Executable File
#!/bin/sh
|
|
set -eu
|
|
|
|
# A UTC clock is itself the bot signal — Cloudflare treats it as the datacenter
|
|
# default — and kagane's challenge then never clears. Measured 2026-08-08 with
|
|
# an identical container on one Indonesian egress IP: UTC never cleared in 60s
|
|
# (twice), while Asia/Jakarta and America/New_York both cleared in 4s. Any real
|
|
# zone will do; the zone does not have to match the IP's country, it just must
|
|
# not be UTC. It does have to be right the way Chrome reads it.
|
|
#
|
|
# TZ must carry the zone *name*. Chrome resolves the zone through ICU, which
|
|
# takes the name from /etc/localtime's symlink target and ignores the file's
|
|
# contents; bind-mounting the host's /etc/localtime therefore lands on the
|
|
# image's own symlink to Etc/UTC and leaves glibc reporting +07 while Chrome
|
|
# still reports UTC. /etc/timezone, mounted by docker-compose.yml, is the name.
|
|
[ -n "${TZ:-}" ] || TZ=$(cat /etc/timezone 2>/dev/null || echo UTC)
|
|
export TZ
|
|
|
|
# Chrome's own UA advertises "HeadlessChrome" under --headless=new, and that one
|
|
# token is the difference between kagane.to's challenge clearing in ~4s and
|
|
# never clearing at all (measured 2026-08-08, same host, same Chrome, only the
|
|
# UA changed). Overriding it does not touch the Sec-CH-UA client hints, which
|
|
# report the real version, so the version is read back out of the binary rather
|
|
# than hardcoded: a hardcoded one would drift out of step with the hints on the
|
|
# next Chrome update and become a fresh tell.
|
|
major=$(google-chrome-stable --version | sed -E 's/[^0-9]*([0-9]+)\..*/\1/')
|
|
ua="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${major}.0.0.0 Safari/537.36"
|
|
|
|
# Chrome binds its DevTools port to loopback and silently ignores
|
|
# --remote-debugging-address (verified 2026-08-08: Chrome 151 with
|
|
# --remote-debugging-address=0.0.0.0 still listened on 127.0.0.1 only), so the
|
|
# caller — another container — cannot reach it directly. socat fronting the
|
|
# loopback port is how chromedp/headless-shell solved the same problem and is
|
|
# why this image is a drop-in for it.
|
|
#
|
|
# Nothing publishes 9222; reachability is the `browser` network in
|
|
# docker-compose.yml, and an exposed CDP endpoint is remote code execution.
|
|
socat TCP-LISTEN:9222,fork,reuseaddr TCP:127.0.0.1:9223 &
|
|
|
|
# Chrome stays in the foreground so that its death takes the container down and
|
|
# compose's restart policy applies; a backgrounded browser behind a live socat
|
|
# would leave the sidecar looking healthy while answering nothing.
|
|
#
|
|
# No --enable-automation: it sets navigator.webdriver, the first thing a bot
|
|
# check reads.
|
|
#
|
|
# --no-sandbox because Chrome's zygote wants user namespaces, which Docker's
|
|
# default profile does not hand out; the alternative is --cap-add=SYS_ADMIN,
|
|
# which gives the container strictly more than it takes away. Containment here
|
|
# is the unprivileged user, the isolated network, and the fact that this
|
|
# browser only ever navigates to kagane.to and novelfull.com.
|
|
exec google-chrome-stable \
|
|
--headless=new \
|
|
--no-sandbox \
|
|
--remote-debugging-port=9223 \
|
|
--user-agent="$ua" \
|
|
--user-data-dir=/home/chrome/profile \
|
|
--no-first-run \
|
|
--no-default-browser-check \
|
|
--disable-gpu \
|
|
about:blank
|