diff --git a/Sources/Homelab/2026-05-25-wireguard-postgres-access-initial-deploy.md b/Sources/Homelab/2026-05-25-wireguard-postgres-access-initial-deploy.md new file mode 100644 index 0000000..f472608 --- /dev/null +++ b/Sources/Homelab/2026-05-25-wireguard-postgres-access-initial-deploy.md @@ -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 _private.key | wg pubkey > _public.key' +sudo cat /etc/wireguard/_public.key +``` + +Capture the public key output. Replace `` 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] +# +PublicKey = +AllowedIPs = 10.99.0./32 +``` + +Where `` 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/_private.key) +Address = 10.99.0./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) \ No newline at end of file