eb9a0c90ca
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>
48 lines
8.1 KiB
Markdown
48 lines
8.1 KiB
Markdown
# 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`.
|
|
|
|
2. `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).
|