Files
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

8.1 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this repo is

Two self-contained bash scripts:

  1. setup_warp_zerotrust.sh — gives an IPv6-only Debian/Ubuntu VPS an IPv4 egress via Cloudflare WARP as a plain WireGuard interface (wg0) — no official client. Two modes, selected by environment variables:
  • Consumer mode (default): anonymous free-WARP registration.
  • Team mode (CF_TEAM + CF_ACCESS_CLIENT_ID/CF_ACCESS_CLIENT_SECRET, or WARP_JWT): enrolls as a Cloudflare One (Zero Trust) organization device via service-token → JWT exchange at https://<team>.cloudflareaccess.com/warp, then registers with header CF-Access-Jwt-Assertion.
  1. setup_caddy_xray_hysteria.sh — deploys Caddy + Xray (VLESS+REALITY/WS/XHTTP) + Hysteria2 + nftables port-hopping on a Debian/Ubuntu VPS. Xray:443 takes REALITY traffic and falls back to Caddy on 127.0.0.1:8003 (PROXY protocol); Caddy serves the camouflage reverse-proxy and routes /scilad (WS:54442) and a random path (XHTTP:8080). Hysteria2:8443/UDP gets its cert via HY2_CERT_MODE (default http): built-in certmagic ACME HTTP-01 listening only on 127.0.0.1:9180 (altPort), with Caddy's port-80 block reverse-proxying /.well-known/acme-challenge/ to it — no public port opened, no DNS token needed, non-wildcard DOMAIN cert, auto-renewed and hot-applied (TLS-ALPN is unusable: the CA only connects to TCP 443, which Xray owns). Fallback dns: Cloudflare DNS-01 for a *.BASE_DOMAIN wildcard (needs CF_API_TOKEN). nftables redirects UDP 20000-30000 → 8443. Config templates are embedded as quoted heredocs with __VAR__ placeholders (rendered via bash parameter expansion, never sed); credentials auto-generate and persist to /etc/caddy-xray-hy2/setup.env (mode 600) which is sourced on re-run. DRY_RUN=1 renders everything to ./dry-run-output/ without system changes — this is how tests/run_tests.sh validates the script on a dev machine. Actions: ACTION=install (default) / upgrade (same idempotent pipeline — apt/script installs already pull latest — but requires an existing setup.env and never prompts) / uninstall. Legacy setup.env files without HY2_CERT_MODE infer dns when CF_API_TOKEN is present, so upgrades don't silently switch wildcard certs to single-domain. Unlike the WARP script, this one intentionally uses the official cloudsmith apt repo (Caddy) and the official Xray/Hysteria2 install scripts; original hand-written templates are archived in docs/templates-original/.

There is no build system or package manifest; tests are tests/run_tests.sh (DRY_RUN + grep/json.tool assertions) for the caddy/xray/hysteria script only.

Commands

  • Syntax check: bash -n setup_warp_zerotrust.sh setup_caddy_xray_hysteria.sh
  • Lint: shellcheck setup_warp_zerotrust.sh setup_caddy_xray_hysteria.sh
  • Test (caddy/xray/hysteria script): bash tests/run_tests.sh
  • Run (only on a disposable target VPS, never on a dev machine): sudo bash setup_warp_zerotrust.sh
    • With no env vars it interactively prompts for team name + service token (reads from /dev/tty, so wget|bash piping still works); empty team name or incomplete credentials silently fall back to consumer mode. Env vars fully set → no prompts. No controlling tty → prompts skipped, env-only.
    • Team mode: sudo CF_TEAM=<team> CF_ACCESS_CLIENT_ID=<id> CF_ACCESS_CLIENT_SECRET=<secret> bash setup_warp_zerotrust.sh
    • Uninstall + free the device seat: sudo ACTION=delete bash setup_warp_zerotrust.sh
    • Optional overrides: ALLOWED_IPS (default 0.0.0.0/0), MTU (default 1280)
  • It requires root, apt-installs packages, overwrites /etc/resolv.conf, writes /etc/wireguard/wg0.conf + /etc/wireguard/warp-account.json, and enables the wg-quick@wg0 systemd unit.
  • Verification is built into the script's final output: IPv4 exit IP, unchanged IPv6 exit, and warp= from cloudflare.com/cdn-cgi/trace.

Architecture and invariants

The script is a one-shot bootstrap sequence; its correctness depends on a few deliberate constraints:

  1. Temporary DNS64 for bootstrap only, with two takeover modes chosen by resolv.conf's actual disposition. If /etc/resolv.conf resolves (via readlink -f) into /run/systemd/resolve/* and systemd-resolved is active, the script never touches resolv.conf — it injects global DNS64 via an /etc/systemd/resolved.conf.d/zz-warp-dns64.conf drop-in (DNS= + Domains=~.) and restarts resolved; restore just deletes the drop-in (writing through the Ubuntu-default stub symlink would clobber a resolved-managed file in /run that resolved never reads, silently breaking the bootstrap). Otherwise it backs up the file's content and overwrites resolv.conf; restore writes the backup back in place (cat > file, never whole-file cp) so the inode and any symlink survive and systemd-resolved's inotify watch keeps working (a cp-style inode replacement leaves resolved blind to the file while its contents look correct). A trap cleanup EXIT INT TERM guarantees restoration even on failure — any new early-exit path must preserve this guarantee, or the VPS is left with broken/wrong DNS.
  2. IPv6 transport, IPv4 payload. Registration uses curl -6 (the API has native AAAA) and the WireGuard endpoint is Cloudflare's IPv6 anycast ([2606:4700:d0::a29f:c001]:2408), but AllowedIPs = 0.0.0.0/0 by default — IPv6 traffic must stay on the native connection. Dashboard split-tunnel settings do NOT apply to a plain WireGuard client; ALLOWED_IPS is the only routing control.
  3. The reg API returns a FLAT JSON object (.id, .token, .config.interface.addresses.{v4,v6}, .config.peers[0]) — there is no .result wrapper (that's the v4 dashboard API convention, not this one). A CF-Client-Version header is mandatory or registration is rejected; known-good pairings (as of 2026-08): v0a4471+a-6.35-4471 (Aether) and v0a1922+a-6.3-1922 (wgcf v2.2.32). The script tries both. Registration curls pin --tlsv1.2 --tls-max 1.2 --http1.1 because Cloudflare rejects non-app TLS fingerprints with 403 error 1020. HTTP 403 otherwise means the VPS IP is flagged; 429 means rate-limited.
  4. Idempotency via /etc/wireguard/warp-account.json. Re-runs PATCH the existing device's key (PATCH /reg/<id> with its Bearer token) instead of registering anew — Zero Trust free plans cap at 50 devices, so repeated fresh registrations would burn seats. The PATCH path is only taken when the saved team matches the current CF_TEAM (switching free ↔ team always re-registers). Fall back to fresh registration on 401/404/410.
  5. MTU 1280 survives the double overhead of NAT64/DNS64 plus the WireGuard tunnel; don't raise it without testing on the target network.
  6. Kernel-module probe with wireguard-go fallback. The script probes ip link add wgprobe type wireguard (after modprobe wireguard); on failure it installs wireguard-go (noble universe, ~1MB) and relies on wg-quick's built-in userspace fallback (add_if() falls back to wireguard-go when the kernel module is absent — covers LXC containers and custom old kernels, works for boot autostart too). Requires /dev/net/tun; if absent (some LXC), the script errors out with a hint.
  7. Supply-chain constraint (user requirement). Install only via the distro's signed apt repos; never add third-party apt sources, download binaries, or pipe remote code. Outbound calls go to Cloudflare-owned domains only (registration API, team Access endpoint, cdn-cgi/trace for the connectivity check — ip.sb kept only as fallback). The bootstrap DNS64 resolvers are overridable via DNS64_SERVERS. Keys/credentials stay local under /etc/wireguard/ (mode 600); only the WireGuard public key leaves the machine.

Conventions

  • Script comments are written in Chinese; match that when editing.
  • README.md (Chinese) documents the 2026 dashboard paths for Zero Trust service-token enrollment — keep its UI paths in sync with the script header comment when Cloudflare reorganizes navigation.
  • The repo has no CI; commits are small single-file updates (historically made via the GitHub web UI).