# sing-box bridge: how it was set up, and what `gfw` / `gfw-diag` are

Date built: 2026-10-04. Laptop: zaasus. VPS: `lightsail.a` (13.125.209.14, Debian 12, Seoul). Domain: sosweb.org.
No secrets are in this file. They live in `~/.config/sing-box/` (laptop) and `/etc/sing-box/` (VPS).

## 1. The idea in one picture

```
 apps on laptop
      |
   [tun0]  <-- sing-box client (TUN grabs ALL traffic)
      |  VLESS + Reality, TCP 8443, looks like normal TLS
      v
 wlo1 -> hotspot -> internet -> VPS 13.125.209.14:8443
                                   |  sing-box server (Reality)
                                   v
                              the open internet
```

A bridge: your traffic is wrapped so a network observer sees an ordinary TLS connection,
unwraps on the VPS, and exits from the VPS IP. Websites see 13.125.209.14.

## 2. Terms

- **GFW**: the Great Firewall of China, the filtering system the bridge is meant to get past.
  (`gfw` the command is just my name for the on/off switch. It is not a tool that touches the real GFW.)
- **sing-box**: one program that can be a proxy server or client and speaks many protocols.
  Same binary on both sides, different config.
- **VLESS**: a light proxy protocol (carries your traffic, no encryption of its own).
- **Reality**: the stealth layer. The VPS answers a probe by relaying to a real site
  (here, our own sosweb.org), so scanners and DPI see a normal website. Only a client with the
  right key and short ID gets the tunnel. This needs no certificate of its own.
- **Hysteria2**: a fast QUIC (UDP) protocol. Set up on the server but unused, because UDP is
  often throttled and an HTTP proxy cannot carry it.
- **TUN**: a virtual network card (`tun0`). sing-box installs routes so all traffic enters it.
  This catches apps that ignore proxy settings.
- **DPI**: deep packet inspection, how a firewall recognizes proxy traffic by its shape.

## 3. What we built, in order

### VPS (lightsail.a)
1. Recon: Debian 12, 2 vCPU, 2 GB. Caddy already owned tcp 80/443 and udp 443 for sosweb.org
   (real site), so sing-box could not use 443. We left Caddy alone.
2. Installed sing-box from the SagerNet apt repo (v1.14.2).
3. Wrote `/etc/sing-box/config.json` with two inbounds on port 8443:
   - VLESS + Reality, tcp/8443, handshake target = sosweb.org via local Caddy
   - Hysteria2, udp/8443, Salamander obfuscation, reusing Caddy's Let's Encrypt cert
     (copied at each start by `/usr/local/sbin/sb-copy-cert` through a systemd drop-in)
4. Generated keys: UUID, Reality key pair, short ID, passwords. Stored root-only in
   `/etc/sing-box/client-params.env`.
5. Enabled the service. Lightsail's cloud firewall (separate from the OS) needed a rule:
   TCP 8443, Custom, any source (IPv4 and IPv6).

### Laptop
1. sing-box was already in `~/go/bin/sing-box`.
2. `~/.config/sing-box/client.json`: TUN inbound, two Reality outbounds (direct, and chained
   through the hotspot HTTP proxy 10.128.97.214:9910), a `urltest` group named `proxy` that
   picks a working one every minute. DNS goes through the tunnel (DoH 1.1.1.1).
   Routing rules: private ranges, the VPS IP, and 100.64.0.0/10 go direct (no loops).
3. `client-test.json`: same outbounds, but only a local proxy on 127.0.0.1:2080 (no root,
   no route changes). Used for safe testing first.
4. `/etc/systemd/system/sing-box-client.service`: starts sing-box with client.json.
   Not enabled at boot, so the bridge is off unless you turn it on.
5. `~/.local/bin/gfw` and `~/.local/bin/gfw-diag` (next section).

## 4. The two commands

### `gfw`: the on/off switch
| Command | What it does |
|---|---|
| `gfw on` | `sudo systemctl start sing-box-client`, then shows the exit IP |
| `gfw off` | `sudo systemctl stop sing-box-client`, back to the normal network |
| `gfw status` | service state plus the exit IP (curl ignores proxy env vars) |
| `gfw test` | local-only proxy on 127.0.0.1:2080, no root |

Success looks like `exit ip: 13.125.209.14`.

### `gfw-diag`: the one-shot diagnostic
Run with other VPNs (Geph) off. It prints and saves to `~/gfw-diag.txt`:
1. the Wi-Fi state and routes, and whether a Geph rule exists
2. raw TCP probes to the VPS ports 22/443/8443
3. a check of the hotspot HTTP proxy
4. **Test A**: Reality direct, on local port 2081
5. **Test B**: Reality chained through the hotspot proxy, on port 2082

Both A and B printing `exit ip: 13.125.209.14` means the server and keys are fine and the
problem, if any, is routing or the network. It was written to separate "server broken" from
"my network broken", after tests were confused by Geph.

## 5. What we learned (the gotchas)

- **Geph conflict.** Geph installs a policy-routing rule (priority 120) that grabs all traffic
  before sing-box's TUN (priority 9000). Run one or the other. Tests while Geph was on
  were flaky and misleading until it was disconnected.
- **Cloud firewall is separate.** The VPS OS firewall was open, but Lightsail's own firewall
  also needs the port.
- **Proxy env vars lie.** A shell with `https_proxy` set makes curl skip the TUN. `gfw status`
  uses `--noproxy '*'` for that reason. Apps set to the hotspot proxy (Firefox, Claude Code)
  stay outside the bridge because the proxy address is a private IP and goes direct.
- **UDP vs TCP.** An HTTP proxy cannot carry UDP, and UDP is the first thing throttled, so
  Reality (TCP) is the main path and Hysteria2 is parked.
- **8443 vs 443.** Reality sits on 8443 because Caddy holds 443. Some networks only allow 443.
  If 8443 is blocked, the fix is moving Reality to 443 behind Caddy.

## 6. Everyday use and recovery

```bash
gfw on        # bridge on (sudo password)
gfw status    # exit ip should be 13.125.209.14
gfw off       # back to normal
gfw-diag      # when something is wrong (Geph off)
```
On the VPS: `systemctl status sing-box`, `journalctl -u sing-box -n 30`, `sing-box check -c /etc/sing-box/config.json`.
If the bridge hangs your network: `gfw off`, or `sudo systemctl stop sing-box-client` from another terminal.

## 7. Not built yet (optional)
- Trojan / NaiveProxy on tcp/443 (needs a Caddy change on the live site).
- Cloudflare paths (CDN WebSocket, Argo, Workers). The domain is on Google Cloud DNS, not Cloudflare.
- Backup of `~/.config/sing-box/` and `/etc/sing-box/`.
- Close MySQL 3306 on the VPS if Lightsail exposes it.
