51 Commits

Author SHA1 Message Date
eli 9ca581c1e9 fix: ECH 默认 DNS 8.8.8.8 -> 9.9.9.9(Quad9,实测不被屏蔽;8.8.8.8 偶发断)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 15:25:23 +08:00
eli 9385f02421 fix: ECH 默认查询改用 udp://8.8.8.8(明文 UDP53,墙内实测最稳)
8.8.8.8:443 DoH 墙内不稳(疑似被 passwall 本机代理劫进代理通道);
源码只有 udp:///https:///h2c:// 三个分支,tcp:// 不存在。
full 策略下 UDP 投毒只会断连不会静默降级,风险可接受。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 13:13:23 +08:00
eli e362c3d3fd fix: ECH default DoH -> 8.8.8.8 IP 字面量(实测连通;免 dns.google 域名二次解析)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 12:30:54 +08:00
eli e8787278b7 fix: ECH default DoH endpoint cloudflare-dns.com -> dns.google
Xray ECH DoH 是 RFC8484 wireformat(POST application/dns-message 走 h2),
/resolve 是 JSON 调试接口不认。cloudflare-dns.com/1.1.1.1 墙内实测被封,
dns.google:443 可达。备选 alidns / IP 字面量形式见 README 与 ECH_CONFIG。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 12:24:43 +08:00
eli 36e9b06976 feat: CDN links carry ech= — dynamic DoH query form by default
静态 ECH 快照随 CF 密钥轮换很快失效(实测),改为默认内嵌
"CDN域名+https://cloudflare-dns.com/dns-query":客户端 Xray ≥25.7.26
拨号时用内置 DoH 自查并按 TTL 刷新,轮换无感。ECH_CONFIG=off 关闭,
=其他值(静态 base64 / 其他 DoH 端点)原样嵌入。仅 CDN 链接携带
(直连 TLS 在 Caddy 终止,无 ECH)。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 12:12:30 +08:00
eli f4d11671c1 feat: XHTTP extra padding on by default (XHTTP_PADDING=0 to disable)
升级注意:旧 setup.env 存的 XHTTP_PADDING 为空,upgrade 后会自动开启并
重新生成 header/key——XHTTP 链接变更,忽略 extra= 的旧客户端会 400。
想保持关闭需在 setup.env 里显式写 XHTTP_PADDING=0。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 12:12:16 +08:00
eli 4e4d0f489b feat: optional XHTTP extra padding via XHTTP_PADDING=1 (link carries extra=)
Share-link standard §4.3.19 supports extra= (URL-encoded XHttpObject JSON),
and passwall exposes it as XHTTP Extra. Verified end-to-end with real xray
v26.3.27 + caddy: matching client+server extra → 204 through the full TLS
h2 chain. Default off because a client that ignores the extra param gets
400 from a padding-enabled server.

Also correct the previous claim that links can't carry extra.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-25 00:09:31 +08:00
eli 15d91b2b7a fix: drop XHTTP extra/xPadding block — breaks every link-imported client (400)
End-to-end matrix with real xray v26.3.27 + caddy:
- server with extra padding + plain client: 400 (direct AND via caddy)
- server without extra + plain client: 204 (direct AND via caddy, TLS h2)

Padding placement/key names must match on both ends, but share links cannot
carry these params and mainstream clients can't configure them — so a
server-side-only extra block rejects every real client. Removed from the
template along with XPADDING_HEADER/XPADDING_KEY.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 23:55:38 +08:00
eli 1a040cb54e fix: WS route stripped /scilad prefix (handle_path) — xray 404'd every handshake
Root-caused with live probes: WS upgrade to /scilad returned 404 on both
direct and CDN paths. handle_path strips the prefix before proxying, but
xray wsSettings.path=/scilad expects it intact. Replaced with a site-level
@websockets matcher (path + Upgrade headers) + path-preserving reverse_proxy
in the main block; deleted the redundant handle_path /scilad* in the CDN
block (it also shadowed reverse_proxy @wspaths into dead code).

Verified with real caddy + mock upstream: WS handshake gets 101 and the
upstream receives /scilad unstripped.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 23:27:59 +08:00
eli bc1c751e2a feat: fp=chrome on all TLS client links; ws-cdn link test
Go's native TLS ClientHello fingerprint is a cheap high-confidence proxy
signal (JA3/JA4); make the uTLS fingerprint explicit on ws/xhttp links
(direct + CDN) instead of relying on client-side defaults. Also assert
the ws-cdn link and fp=chrome coverage in tests.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 22:47:37 +08:00
eli 13432e1091 fix: drop h3 from Caddy TLS ALPN; add ws-cdn client link
Caddy only listens on loopback 8003 behind Xray REALITY fallback (pure TCP),
so advertising h3 in TLS ALPN is both useless and harmful: a client offering
h3 gets h3 negotiated over TCP and the connection hangs (verified with
openssl s_client -alpn h3,h2 -> 'ALPN protocol: h3'). Drop h3 from both
site blocks. Also print the WS CDN link (CDN site block already routes
/scilad) and assert 'alpn h3' never reappears.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 21:21:44 +08:00
eli 3c58aec356 fix: disable Caddy auto_https redirects shadowing hy2 ACME challenge route
Caddy injects runtime auto-redirect routes (host-matched, ahead of the
catch-all http:// site, not excluding ACME challenge paths) for domains
it manages certs for — invisible in static 'caddy adapt' output, so the
previous live test could not reproduce it. hy2's tokens got 308'd to 443
(LE: 'Invalid response from https://DOMAIN/...: 404' via masquerade).

Verified bidirectionally with real caddy v2.11.4: without the option the
challenge path gets 308 (exact production repro), with it 200 from hy2.
Live test now extracts the global block + http block + a tls-internal
managed site block to reproduce production conditions permanently.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 20:27:05 +08:00
eli d8269b12c1 feat: self-check probes ACME challenge route; live caddy route test
Verified with real caddy v2.11.4: fixed Caddyfile proxies
/.well-known/acme-challenge/* to hy2 (200 from mock) and 301s everything
else; the old bare-redir variant 301s the challenge (exact production
bug reproduction). tests/run_tests.sh now runs this live route test when
a caddy binary is on PATH. self_check additionally probes the challenge
path post-deploy and warns if it gets redirected.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 19:54:30 +08:00
eli 2d10c12e9f fix: default hy2 masquerade to engineersblog.net (match xray camouflage)
www.people.com answers every non-browser request with a Cloudflare JS
challenge (cf-mitigated: challenge) — the masquerade instantly fails
active probing, and the open reverse proxy gets the VPS IP flagged by
CF for scraping. engineersblog.net serves real 200 content without a
challenge; using the same target as the Xray fallback makes 443 and
8443 present the same site, which strengthens the disguise.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 19:37:53 +08:00
eli da2387dbbc fix: port-80 ACME challenge route was shadowed by bare redir
Caddyfile directive order puts redir before handle; the matcherless
'redir https://...' in the http:// block caught EVERY port-80 request
including /.well-known/acme-challenge/*, so LE's hy2 validation got a
301 to 443 (visible as 'Fetching https://...' + tls internal error) and
hysteria-server FATALed on startup. The redir now carries a
not-path matcher for the challenge prefix, making evaluation
order-independent (same pattern the main site block already used via
@notpaths).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 19:31:44 +08:00
eli ba99627efb docs: fix HTTP/3 toggle location (Speed > Settings) and dig HTTPS syntax
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 19:04:50 +08:00
eli c78cac6d8b docs: ECH is default-on for Free zones; clarify HTTPS redirect, TLS 1.2, ACM
Verified against Cloudflare docs: ECH is enabled by default on Free zones
(no toggle shown); Always Use HTTPS does not loop with Caddy's one-way
http->https redirect; Minimum TLS 1.0->1.2 is a free hardening; HTTP/3
toggle lives under Network; ACM is unrelated paid addon.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 18:56:22 +08:00
eli 72cf63b08b docs: drop Enterprise-only Origin Rule from CDN guide — Preserve works
Host/SNI/DNS-record overrides in Origin Rules are Enterprise-only (greyed
out on free plans). The architecture never needed them: CF origins with
SNI/Host=CDN_DOMAIN, Xray REALITY falls through to Caddy, and Caddy's CDN
site block matches on that SNI directly. Guide now says leave everything
at Preserve.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 18:46:32 +08:00
eli b8fba66302 docs: Cloudflare origin setup guide for current 2026 dashboard
DNS grey/orange split for REALITY vs CDN domains, Full (strict) mode,
Origin Rule (Host/SNI/DNS override to DOMAIN), ECH/Always-Use-HTTPS/HTTP3,
WebSockets toggle, common pitfalls, and minimal firewall port list.
Also dedup ECH paragraph in optional-enhancements (points to the guide).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 18:36:13 +08:00
eli d006d6455e fix: preflight silently killed the script under set -e when DRY_RUN unset
The function's last command was '[ -n "$DRY_RUN" ] && info ...', which returns
1 when DRY_RUN is empty; as the function's exit status that triggered set -e
on the very first pipeline stage — zero output, instant exit. Rewritten as an
if block, comment added, awk scan confirms no other function ends with an
&&/|| list, and a regression test executes the real preflight body with
mocked id/apt-get outside DRY_RUN.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 15:51:00 +08:00
eli 1738eb3614 docs: clarify tty detection comment
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 15:32:45 +08:00
eli eb9a0c90ca feat: ACTION=upgrade — non-interactive upgrade of all components
Install is already idempotent-upgrade (apt install caddy / xray install script /
get.hy2.sh all fetch latest); upgrade mode additionally requires an existing
setup.env (dies instead of accidentally installing) and never prompts.
Legacy setup.env without HY2_CERT_MODE infers dns when CF_API_TOKEN present,
so upgrades keep wildcard certs instead of silently switching to http mode.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 15:27:32 +08:00
eli 31facd9c7a feat: optional VLESS post-quantum encryption (XRAY_VLESS_ENC), README index + ECH/PQ docs
- XRAY_VLESS_ENC=1 generates ML-KEM-768 keypair via xray vlessenc, applies
  to all three VLESS inbounds, share links carry client encryption param,
  keys persisted in setup.env
- README: project intro + script index at top, optional-enhancements section
  (VLESS PQ, ECH for CDN_DOMAIN, hy2 obfs/mimic pointers)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 15:20:15 +08:00
eli b540cfc558 docs: add 2026-08 protocol landscape review to design doc
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 13:36:19 +08:00
eli e41a5b5ab3 feat: hysteria2 HTTP-01 standalone mode binds port 80 when Caddy skipped
SKIP_CADDY=1 + HY2_CERT_MODE=http now checks port 80/TCP is free (ss) and
lets hy2 answer HTTP-01 directly (altPort 80, all interfaces) instead of
erroring out; still dies if 80 is occupied.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 13:28:27 +08:00
eli 70a19d126c feat: default hysteria2 cert to ACME HTTP-01 via Caddy port-80 proxy
hy2 certmagic HTTP-01 listens only on 127.0.0.1:9180 (altPort); Caddy's
port-80 block reverse-proxies /.well-known/acme-challenge/ to it. Non-wildcard
DOMAIN cert, auto-renewed and hot-applied, no DNS API token needed, no new
public ports. TLS-ALPN is unusable (CA only connects to TCP 443 = Xray) and
reusing Caddy's cert files needs permission/copy hooks, so HTTP-01 is the
minimal stable default. HY2_CERT_MODE=dns keeps Cloudflare DNS-01 wildcard
as fallback (CF_API_TOKEN required only in dns mode).

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 12:57:49 +08:00
eli 35e3a5e06c fix: DNS takeover mode by resolv.conf disposition, in-place restore
Two bugs in the bootstrap DNS64 handling:

- Restore used whole-file cp, replacing the inode and silently
  killing systemd-resolved's inotify watch (resolv.conf contents
  correct but resolvectl shows no global DNS until restart).
  Restore now writes back in place (cat > file).
- On resolved-managed systems (resolv.conf symlinked into
  /run/systemd/resolve/*, the Ubuntu default), writing DNS64
  through the symlink clobbered resolved's stub file without
  taking effect, and restore hardcoded the wrong symlink target.
  These systems now get a resolved.conf.d drop-in (global DNS64 +
  Domains=~.) instead; resolv.conf and provider DNS stay untouched.

Also handles dangling-symlink/missing-file edge cases and updates
CLAUDE.md invariant #1 accordingly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 11:04:17 +08:00
eli 50c0de2d60 docs: usage docs for caddy+xray+hysteria script, archive original templates
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:37:04 +08:00
eli 85b6305fd4 chore: untrack hand-written templates and codegraph index
Templates live in docs/templates-original/ (Task 8); .codegraph is a local index.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:34:35 +08:00
eli 922a397e70 feat: self-check, client summary, uninstall
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:34:24 +08:00
eli 7d9524ac87 feat: nftables port hopping, validation, service enablement
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:32:45 +08:00
eli 97a3d9dcc2 feat: hysteria2 install + config render
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:31:13 +08:00
eli 45552433a9 feat: xray install, keypair generation, config render
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:30:05 +08:00
eli d616d751dd feat: caddy install + Caddyfile render
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:27:49 +08:00
eli e0c3341219 feat: parameter collection, env persistence, test scaffold
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:24:46 +08:00
eli 5b99adb57f feat: add setup_caddy_xray_hysteria.sh skeleton
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:21:54 +08:00
eli a6d42ae602 docs: condense implementation plan (keep tasks + key info)
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-24 00:15:49 +08:00
eli afe4aa3756 Add optional CDN_DOMAIN second site block to design
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 23:47:29 +08:00
eli bbf5b50a68 Add design doc for caddy+xray+hysteria2 setup script
Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 23:44:25 +08:00
eli e4bb6ee07c Diagnose team-name typos: explain HTTP 404 on enrollment JWT fetch
A 404 from <team>.cloudflareaccess.com/warp means the team domain does
not exist (misspelled team name) — print that explicitly and suggest
copying the team name from Zero Trust > Settings instead of typing it.
README FAQ updated accordingly.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 22:39:32 +08:00
eli 3b4beb9d41 Add README with 2026 Zero Trust enrollment paths, improve JWT error diagnostics
- README.md: step-by-step dashboard guide using current navigation
  (Access controls > Service credentials > Service Tokens;
  Team & Resources > Devices > Management > Device enrollment
  permissions), incl. the two classic pitfalls: policy Action must be
  Service Auth (not Allow), and Free plan still requires a payment
  method on file. Also documents team name vs domain, verification,
  FAQ (403/429, device seats, plain-WireGuard limitations), and the
  supply-chain notes.
- Script: when the enrollment JWT fetch fails, print HTTP status and
  the Location redirect target plus a 3-point checklist (Service Auth
  vs Allow, policy not attached to enrollment permissions, bad/expired
  credentials) — a 302 to /cdn-cgi/access/login/ is now immediately
  recognizable as a wrong policy action.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 22:18:44 +08:00
eli e6f8b5be20 Add interactive mode, wireguard-go fallback, supply-chain hardening
Interactive enrollment:
- Prompt for team name + service token via /dev/tty (works with
  wget|bash pipes); empty or incomplete input falls back to free mode
- Env vars take precedence; non-interactive shells skip prompts
  (tested via pty and setsid, including the ENXIO openability check)
- Device-credential reuse (PATCH) now requires the saved team to match
  the current mode, so free<->team switches re-register cleanly

wireguard-go fallback:
- Probe kernel WireGuard support (ip link add type wireguard, then
  modprobe); if missing, install wireguard-go from Ubuntu universe and
  let wg-quick's built-in userspace fallback drive the tunnel
- Error early with a hint when /dev/net/tun is unavailable (LXC)

Supply-chain hardening:
- Connectivity check prefers Cloudflare's own cdn-cgi/trace (first
  party); third-party ip.sb is only a fallback
- Bootstrap DNS64 resolvers overridable via DNS64_SERVERS
- Header comments document the audit surface: apt-only installs, no
  third-party repos/binaries/remote code, keys stay local (mode 600)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 22:01:08 +08:00
eli 0b59471c52 Rewrite WARP setup: fix registration, add Zero Trust team mode
Bug fixes:
- Add mandatory CF-Client-Version header (v0a4471/a-6.35-4471 with
  fallback to v0a1922/a-6.3-1922); the API now rejects requests without it
- Parse flat JSON response (no .result wrapper) so successful
  registrations are no longer misreported as failures
- Pin TLS 1.2 + HTTP/1.1 on registration calls to avoid error 1020
- Print raw server response on failure instead of failing silently

New features:
- Zero Trust team enrollment without the official client: service token
  (or WARP_JWT) -> JWT at <team>.cloudflareaccess.com/warp -> register
  with CF-Access-Jwt-Assertion header
- Idempotent re-runs: PATCH existing device key via saved credentials
  in /etc/wireguard/warp-account.json instead of burning device seats
- ACTION=delete to deregister the device and uninstall wg0
- ALLOWED_IPS / MTU env overrides; warp= trace check in final output

Also add CLAUDE.md with repo guidance.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-23 21:47:38 +08:00
eli 32d465d4fa Update setup_warp_zerotrust.sh 2026-08-23 18:42:59 +08:00
eli 630397e7c4 Update setup_warp_zerotrust.sh 2026-08-23 17:19:21 +08:00
eli 7ec70cd66b Update setup_warp_zerotrust.sh 2026-08-23 12:00:02 +08:00
eli 3177d0b890 Update setup_warp_zerotrust.sh 2026-08-23 11:57:11 +08:00
eli 54a8df0764 Update setup_warp_zerotrust.sh 2026-08-23 11:55:48 +08:00
eli 5bd5e7043e Update setup_warp_zerotrust.sh 2026-08-23 11:53:53 +08:00
eli fe44a6c350 Update setup_warp_zerotrust.sh 2026-08-23 11:46:40 +08:00