WireGuard overlay for NAT traversal (Phase 2, Slice 1) #9

Merged
balint merged 3 commits from feat/wireguard-overlay into main 2026-06-10 06:26:47 +01:00
Owner

Phase 2, Slice 1: connect the two Pis through NAT using a WireGuard overlay relayed through a public hub, with no app code changespeer_host just becomes an overlay IP.

What this adds

  • Design (PLAN.md, DECISIONS.md): WireGuard data plane + Django control plane, hub-and-spoke topology, on-device keygen, pre-shared per-device token enrollment.
  • Reference (wireguard/): config templates + a by-hand runbook; .gitignore tracks only the .example templates (rendered configs hold private keys).
  • Automation (scripts/wg_setup.sh, scripts/wg_node.sh, Makefile): bring nodes up one at a time over SSH — make wg-hub, make wg-pi NAME=pi-a, make wg-pi NAME=pi-b, make wg-status. Keys are generated on each node; only public keys are fetched, so private keys never traverse the network.

Hardening surfaced during bring-up

  • Hub ListenPort is persisted in state, so a later wg-pi (which re-renders the hub) does not revert it to a default that clashes with another overlay on the shared host.
  • Hub adds an explicit wg0<->wg0 FORWARD accept — the shared host defaults that policy to DROP (Docker/NetBird), so spoke↔spoke relay was silently dropped.
  • Hub peers are updated in place with wg syncconf instead of a restart, so adding/repointing a node does not drop live spokes or a call in progress.
  • ip_forward is left enabled on tunnel-down (other overlays on the shared host depend on it).

Verification status

  • Verified: hub up, both spokes handshaking, spoke↔spoke relay ping works, two phones pointed at the overlay.
  • Not yet tested: both Pis on different networks (real NAT on both ends). The riskiest assumption — relayed-audio latency across the internet — is still open. To be tested next.

🤖 Generated with Claude Code

Phase 2, Slice 1: connect the two Pis through NAT using a WireGuard overlay relayed through a public hub, with **no app code changes** — `peer_host` just becomes an overlay IP. ## What this adds - **Design** (`PLAN.md`, `DECISIONS.md`): WireGuard data plane + Django control plane, hub-and-spoke topology, on-device keygen, pre-shared per-device token enrollment. - **Reference** (`wireguard/`): config templates + a by-hand runbook; `.gitignore` tracks only the `.example` templates (rendered configs hold private keys). - **Automation** (`scripts/wg_setup.sh`, `scripts/wg_node.sh`, `Makefile`): bring nodes up one at a time over SSH — `make wg-hub`, `make wg-pi NAME=pi-a`, `make wg-pi NAME=pi-b`, `make wg-status`. Keys are generated on each node; only public keys are fetched, so private keys never traverse the network. ## Hardening surfaced during bring-up - Hub `ListenPort` is persisted in state, so a later `wg-pi` (which re-renders the hub) does not revert it to a default that clashes with another overlay on the shared host. - Hub adds an explicit `wg0<->wg0` FORWARD accept — the shared host defaults that policy to `DROP` (Docker/NetBird), so spoke↔spoke relay was silently dropped. - Hub peers are updated in place with `wg syncconf` instead of a restart, so adding/repointing a node does not drop live spokes or a call in progress. - `ip_forward` is left enabled on tunnel-down (other overlays on the shared host depend on it). ## Verification status - Verified: hub up, both spokes handshaking, spoke↔spoke relay ping works, two phones pointed at the overlay. - **Not yet tested:** both Pis on *different* networks (real NAT on both ends). The riskiest assumption — relayed-audio latency across the internet — is still open. To be tested next. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
WireGuard carries the existing TCP signaling and UDP audio through NAT
unchanged, so the hardware-verified app stays untouched — peer_host just
becomes an overlay IP. A Django media/signaling relay would instead mean
reimplementing transport we already have working. Django is kept for the
control plane (registry/orchestration), the IDEAS.md direction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Captures the hub-and-spoke setup as config templates plus a by-hand
runbook, so the overlay can be stood up (and debugged) without the
automation. The gitignore tracks only the .example templates — rendered
configs hold private keys and must never be committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brings nodes up one at a time (hub, then each Pi) so the hub and a single
Pi can be verified before adding the second and testing audio — the
vertical-slice habit. Keys are generated on each node and only public
keys are fetched, so private keys never traverse the network. The hub is
updated in place with `wg syncconf` rather than restarted, so adding or
repointing a node doesn't drop live spokes (or a call in progress), and
the hub adds an explicit wg0<->wg0 FORWARD accept because the shared host
defaults that policy to DROP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
balint merged commit 2e8a80b3df into main 2026-06-10 06:26:47 +01:00
balint deleted branch feat/wireguard-overlay 2026-06-10 06:28:22 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
balint/phone!9
No description provided.