# WireGuard gate (SP1) — manual runbook

The host scripts (`docker/wg/clusev-wg.sh`, `clusev-wg-gate.service`, the `install.sh` additions)
cannot be exercised by the PHPUnit suite — the test runner is the app container, with no host
WireGuard/firewall/kernel access. This runbook is their real verification.

Run on a **throwaway Debian-13 VM** with the Clusev prod stack up. **Keep an SSH session open the
whole time** — it is the escape hatch. Tick each step against its expected result.

## Pre
- [ ] `clusev wg help` prints usage; `clusev wg status` shows wg0 not active, gate off.
- [ ] `wireguard-tools` + `qrencode` present (installed by `install.sh`): `command -v wg qrencode`.

## setup
- [ ] `sudo clusev wg setup`, enter a subnet that overlaps the VM's LAN (e.g. the VM's own /24) →
      **rejected**, re-prompts.
- [ ] Re-run, accept the default `10.99.0.0/24` → **accepted**; server keys + first peer created;
      client config + QR printed; `systemctl is-active wg-quick@wg0` → `active`.
- [ ] `/etc/wireguard/wg0.conf` and `/etc/clusev/wg.env` exist, mode `0600`.
- [ ] `sudo clusev wg setup` again → it **warns** that wg0.conf exists and does NOT silently clobber
      it (aborts unless you type `reconfigure`).

## tunnel (gate still OFF)
- [ ] Import the printed client (QR) into the WireGuard app; connect.
- [ ] Over the tunnel: `http://<server-tunnel-ip>` reaches the panel.
- [ ] Public `http://<public-ip>` still reachable (gate is off).

## gate up
- [ ] `sudo clusev wg up` → prints the escape reminder; refuses (non-zero) if wg0 is down.
- [ ] Public `http://<public-ip>` 80/443 now **refused / times out**; the tunnel still serves the panel.
- [ ] **SSH still works** (port 22 untouched).
- [ ] `clusev wg status` shows the peer + gate on (marker `/etc/clusev/wg-gate.enabled` present, chain
      `CLUSEV-WG-GATE` present).
- [ ] Add a manual rule: `sudo iptables -I DOCKER-USER -s 203.0.113.0/24 -j RETURN`. Run
      `clusev wg down` then `clusev wg up` → the manual rule **survives** (only `CLUSEV-WG-GATE` and
      its jump are touched). Remove it afterwards.

## persistence
- [ ] Reboot → wg0 + the gate come back (public still refused, tunnel still serves).
- [ ] Restart Docker: `sudo systemctl restart docker` → the gate is re-applied (Docker flushes
      `DOCKER-USER`, the `clusev-wg-gate.service` re-runs via `WantedBy=docker.service`). Public still refused.
- [ ] Force wg0 to fail on boot (`sudo systemctl disable wg-quick@wg0` + reboot) → the gate is **NOT
      applied** (`ConditionPathExists=/sys/class/net/wg0` fails the unit), the panel is publicly
      reachable (NOT bricked); SSH + `clusev wg down` recover. Re-enable wg0 afterwards.

## peers + down
- [ ] `clusev wg add-peer client-2` → new config + QR; the split-vs-full-tunnel note is shown.
- [ ] `clusev wg remove-peer client-2` → peer gone from `wg show wg0` AND from `/etc/wireguard/wg0.conf`.
- [ ] A peer name with a space or odd character round-trips (add then remove) without corrupting wg0.conf.
- [ ] `clusev wg down` → public open again; marker + `CLUSEV-WG-GATE` chain gone (`clusev wg status`).

## Known SP1 limitations (by design — not bugs)
- Peer-IP allocation + the subnet collision check assume the default **/24** (`next_free_ip` scans
  `.2–.254`; the collision check matches the first three octets). Non-/24 subnets work for the tunnel
  but allocation is /24-shaped. Wider prefixes are SP2 territory.
- The collision check is a heuristic (first-three-octets match against host routes/addresses), not a
  formal CIDR-overlap proof — it catches the common LAN clash, which is the point.
- Peers are stored only in `wg0.conf` (no DB). SP2 adds the dashboard status page + peer models.
