mcp: session-notes — WireGuard Postgres Access — Initial Deploy & Peer How-To

This commit is contained in:
Lovebug MCP 2026-05-25 15:30:59 +00:00
parent 854732bab1
commit 68862a4f54

View File

@ -0,0 +1,131 @@
---
created: '2026-05-25'
path: Sources/Homelab
project: wireguard-postgres-access
tags:
- wireguard
- postgres
- networking
- knot
- dns
- homelab
type: session-notes
---
## Outcome
Stood up WireGuard hub-and-spoke overlay on herbys-dev (`wg-lan` interface, `10.99.0.0/24`). First peer (ThinkPad as POC, standing in for tower) handshakes and pings hub successfully. Knot DNS serves `db.wg.herbylab.dev` and `herbys-dev.wg.herbylab.dev``10.99.0.1`. Postgres rebind handled by Lovebug out of band.
## What got built
- **Hub** on herbys-dev: `wg-lan` interface at `10.99.0.1/24`, listening UDP/51820, enabled via `wg-quick@wg-lan` systemd unit
- **First peer** (ThinkPad): `10.99.0.2`, dials hub via `10.0.21.207:51820`, persistent keepalive 25s
- **OpenWrt firewall**: Lab→Private UDP/51820 allowed (scoped to source IP)
- **Knot zone update**: `herbylab.dev` zone gained a `$ORIGIN wg.herbylab.dev.` section with two A records, serial bumped
- **Per-host key generation pattern**: private keys generated and locked to each host; only public keys cross machines
## Learnings
- **`$(cat secret_file)` splice pattern** for moving on-disk secrets into config files without crossing the terminal. Keeps secrets out of scrollback and shell history.
- **`read -s` (bash) / `read -s -P` (fish)** for prompting secrets without echo when the secret is coming from a human, not a file.
- **WireGuard `AllowedIPs` is both a routing rule and an ACL** — on the hub it's per-peer (`/32` per peer); on the client it's "what to route through the tunnel" (`/24` for the whole overlay subnet).
- **`PersistentKeepalive`** is needed when the peer is behind NAT or stateful firewalls (inter-VLAN through OpenWrt counts). Without it, the return path can age out.
- **DNS zone files — three ways to handle subdomains:**
1. Flat records in parent zone (`db.wg IN A ...`) — simplest, fine for small zones
2. `$ORIGIN` shift mid-file — visual grouping, same zone
3. Separate zone file with NS delegation — real separation, production pattern
Chose Way 2 here. Way 3 is overkill until there's a real reason to split.
- **Serial number must increment on every zone change** — and the file edit doesn't take effect until `knotc zone-reload`. `knotc zone-status` shows the *loaded* serial, not the file's serial.
- **DNS caching layers** to consider when fixes don't propagate: Pi-hole, local stub resolver (systemd-resolved if present), application caches. TTL bounds the worst case.
## Decisions
- **Way 2 (origin shift)** chosen over separate zone for `wg.herbylab.dev`. Two records doesn't justify a new zone file.
- **POC on laptop, not tower** — proved the shape works before touching the actual target rig. Tower peer to be added later as a follow-up.
- **Knot record naming**: `db.wg.herbylab.dev` (service-oriented) and `herbys-dev.wg.herbylab.dev` (host-oriented), both pointing at `10.99.0.1`. Use whichever fits the connection string.
---
## How to add a new peer to wg-lan
### 1. Generate keypair on the new peer
```
sudo mkdir -p /etc/wireguard
cd /etc/wireguard
sudo sh -c 'umask 077; wg genkey | tee <peer>_private.key | wg pubkey > <peer>_public.key'
sudo cat /etc/wireguard/<peer>_public.key
```
Capture the public key output. Replace `<peer>` with a short identifier (e.g., `tower`, `nas`).
### 2. Register the peer's pubkey on the hub (herbys-dev)
Append to `/etc/wireguard/wg-lan.conf`:
```
[Peer]
# <peer-name>
PublicKey = <peer-public-key>
AllowedIPs = 10.99.0.<N>/32
```
Where `<N>` is the next free address (`.2` taken, start at `.3` for new peers).
Reload the hub without dropping existing tunnels:
```
sudo wg syncconf wg-lan <(sudo wg-quick strip wg-lan)
```
(`wg syncconf` applies the new config without bouncing the interface — existing peers stay connected.)
### 3. Write the peer's config
On the new peer:
```
sudo sh -c 'cat > /etc/wireguard/wg-lan.conf << EOF
[Interface]
PrivateKey = $(cat /etc/wireguard/<peer>_private.key)
Address = 10.99.0.<N>/24
[Peer]
# herbys-dev hub
PublicKey = nbLpIXOmbaIXeJySVLZ+8sCQkR9Qz2uQzAfcIkTACmE=
Endpoint = 10.0.21.207:51820
AllowedIPs = 10.99.0.0/24
PersistentKeepalive = 25
EOF'
sudo chmod 600 /etc/wireguard/wg-lan.conf
```
### 4. OpenWrt firewall (if peer is on a new VLAN)
If the new peer is on a VLAN that doesn't already have a Lab→Private:UDP/51820 rule, add one in LuCI. Scope source to the peer's IP if possible.
### 5. Bring tunnel up and verify
```
sudo systemctl enable --now wg-quick@wg-lan
sudo wg show
ping -c 3 10.99.0.1
```
`wg show` should list the hub peer with a recent handshake and non-zero transfer after the ping.
---
## Open follow-ups
- Add actual tower (not laptop) as a peer
- Confirm Postgres boot ordering — `wg-lan` must be up before Docker brings Postgres up (add `After=wg-quick@wg-lan.service` to Docker unit, or similar)
- Document key rotation procedure (what to do if a peer private key leaks)
- Sort out laptop's DNS resolver — `systemd-resolved` not present, need to know what's actually resolving for deliberate cache flushes
- Ansible role: extend in-progress role to template hub config from peer registry once the manual pattern is stable
## References
- Project plan: `wireguard-postgres-access`
- Knot zone: `/var/lib/knot/herbylab.dev.zone`
- Hub config: `/etc/wireguard/wg-lan.conf` on herbys-dev (10.0.21.207)