mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-15 13:23:13 +08:00
feat: add homelab config skills (VLAN segmentation, Pi-hole DNS, WireGuard VPN) (#1838)
* feat: add homelab config skills (VLAN, Pi-hole, WireGuard) Adds three homelab configuration skills, extracted from the stale PR #1413 with the same safety treatment applied to the previously accepted batch: - homelab-vlan-segmentation: IoT/guest/trusted/server VLAN design for UniFi, pfSense/OPNsense, and MikroTik. All firewall rules add isolation, not remove protections. Added change-window guidance and AP trunk port clarification. - homelab-pihole-dns: Pi-hole install, blocklists, DNS-over-HTTPS, local DNS records, troubleshooting. Docker is now the lead install method; bare-metal uses inspect-first pattern before running the installer script. - homelab-wireguard-vpn: WireGuard server, peer config, split tunnel, DDNS. Replaced broad iptables FORWARD ACCEPT with scoped directional rules (wg0→eth0 forward + established return only). Credentials moved to env files with explicit notes against inline secrets and version control. Continues the contribution from PR #1413; the eight skills/agents from that PR are already in main via #1729 and #1731. * docs: harden homelab skill pack --------- Co-authored-by: Affaan Mustafa <affaan@dcube.ai>
This commit is contained in:
305
skills/homelab-wireguard-vpn/SKILL.md
Normal file
305
skills/homelab-wireguard-vpn/SKILL.md
Normal file
@@ -0,0 +1,305 @@
|
||||
---
|
||||
name: homelab-wireguard-vpn
|
||||
description: WireGuard VPN server setup, peer configuration, key generation, split tunneling vs full tunnel routing, and remote access to a home network from mobile and laptop clients.
|
||||
origin: community
|
||||
---
|
||||
|
||||
# Homelab WireGuard VPN
|
||||
|
||||
WireGuard is a fast, modern VPN protocol. It is the right choice for remote access to a
|
||||
home network — simpler to configure than OpenVPN and faster than most alternatives.
|
||||
|
||||
All configuration examples show common setups. Review each command — especially the
|
||||
iptables forwarding rules and key file permissions — before applying them to your
|
||||
system, and make changes in a maintenance window.
|
||||
|
||||
## When to Use
|
||||
|
||||
- Setting up WireGuard server on a Raspberry Pi, Linux host, pfSense, or router
|
||||
- Generating WireGuard keypairs and writing peer config files
|
||||
- Configuring remote access from a phone or laptop to a home network
|
||||
- Explaining split tunneling (route only home traffic) vs full tunnel (route all traffic)
|
||||
- Troubleshooting WireGuard connections that will not come up
|
||||
- Automating peer configuration generation for multiple clients
|
||||
|
||||
## How WireGuard Works
|
||||
|
||||
```
|
||||
Your phone (WireGuard client)
|
||||
│
|
||||
│ Encrypted UDP tunnel (port 51820)
|
||||
│
|
||||
Your home router (WireGuard server — needs a public IP or DDNS)
|
||||
│
|
||||
Your home network (192.168.1.0/24, NAS, Pi, etc.)
|
||||
|
||||
Every device has a keypair (public + private key).
|
||||
The server knows each client's public key.
|
||||
The client knows the server's public key + endpoint (IP:port).
|
||||
Traffic is encrypted end-to-end with no central server or certificate authority.
|
||||
```
|
||||
|
||||
## Server Setup (Linux)
|
||||
|
||||
```bash
|
||||
# Install WireGuard
|
||||
sudo apt update && sudo apt install wireguard -y
|
||||
|
||||
# Generate server keypair — create files with private permissions from the start
|
||||
sudo mkdir -p /etc/wireguard
|
||||
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server_private.key'
|
||||
sudo sh -c 'wg pubkey < /etc/wireguard/server_private.key > /etc/wireguard/server_public.key'
|
||||
|
||||
# Write server config — substitute the actual private key value
|
||||
# Do not store private keys in version control or share them
|
||||
sudo tee /etc/wireguard/wg0.conf << 'EOF'
|
||||
[Interface]
|
||||
Address = 10.8.0.1/24 # VPN subnet — server gets .1
|
||||
ListenPort = 51820
|
||||
PrivateKey = <paste_server_private_key_here>
|
||||
|
||||
# Scoped forwarding rules: allow VPN traffic in/out, not a blanket FORWARD ACCEPT
|
||||
PostUp = iptables -A FORWARD -i wg0 -o eth0 -j ACCEPT
|
||||
PostUp = iptables -A FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||
PostUp = iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
|
||||
PostDown = iptables -D FORWARD -i wg0 -o eth0 -j ACCEPT
|
||||
PostDown = iptables -D FORWARD -i eth0 -o wg0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
|
||||
PostDown = iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
|
||||
|
||||
[Peer]
|
||||
# Phone — replace with the actual phone public key
|
||||
PublicKey = <phone_public_key>
|
||||
AllowedIPs = 10.8.0.2/32
|
||||
|
||||
[Peer]
|
||||
# Laptop — replace with the actual laptop public key
|
||||
PublicKey = <laptop_public_key>
|
||||
AllowedIPs = 10.8.0.3/32
|
||||
EOF
|
||||
sudo chmod 600 /etc/wireguard/wg0.conf
|
||||
|
||||
# Replace eth0 with your actual outbound interface name
|
||||
# Check with: ip route show default
|
||||
|
||||
# Enable IP forwarding (required for routing traffic through the server)
|
||||
echo "net.ipv4.ip_forward=1" | sudo tee /etc/sysctl.d/99-wireguard.conf
|
||||
sudo sysctl --system
|
||||
|
||||
# Start WireGuard and enable on boot
|
||||
sudo wg-quick up wg0
|
||||
sudo systemctl enable wg-quick@wg0
|
||||
```
|
||||
|
||||
## Client Configuration
|
||||
|
||||
```bash
|
||||
# Generate a unique keypair for each client device
|
||||
# Run on the client, or on the server and transfer the private key securely — never in plaintext
|
||||
umask 077
|
||||
wg genkey | tee phone_private.key | wg pubkey > phone_public.key
|
||||
|
||||
# Client config file (phone_wg0.conf):
|
||||
[Interface]
|
||||
PrivateKey = <phone_private_key>
|
||||
Address = 10.8.0.2/32
|
||||
DNS = 192.168.1.2 # Optional: use Pi-hole for DNS over the tunnel
|
||||
|
||||
[Peer]
|
||||
PublicKey = <server_public_key>
|
||||
Endpoint = your-home-ip.ddns.net:51820 # Your public IP or DDNS hostname
|
||||
AllowedIPs = 192.168.1.0/24 # Split tunnel: only home network traffic
|
||||
# AllowedIPs = 0.0.0.0/0, ::/0 # Full tunnel: all traffic through VPN
|
||||
|
||||
PersistentKeepalive = 25 # Keep NAT hole open (required for mobile clients)
|
||||
```
|
||||
|
||||
## Split Tunnel vs Full Tunnel
|
||||
|
||||
```
|
||||
# Split tunnel: AllowedIPs = 192.168.1.0/24
|
||||
Only traffic destined for your home network goes through the VPN.
|
||||
Internet traffic (YouTube, Spotify) goes directly — better performance on mobile.
|
||||
Best for: "I just want to reach my NAS and Pi from anywhere."
|
||||
|
||||
# Full tunnel: AllowedIPs = 0.0.0.0/0, ::/0
|
||||
ALL traffic goes through your home internet connection.
|
||||
Useful for: piggybacking home DNS/Pi-hole ad blocking.
|
||||
Downside: home upload speed becomes your bottleneck everywhere.
|
||||
|
||||
# Multi-subnet split tunnel (most common homelab use case):
|
||||
AllowedIPs = 192.168.10.0/24, 192.168.20.0/24, 192.168.30.0/24, 10.8.0.0/24
|
||||
Routes all your VLANs through the tunnel; internet stays direct.
|
||||
```
|
||||
|
||||
## Key Generation and Peer Management
|
||||
|
||||
```python
|
||||
import subprocess
|
||||
|
||||
def generate_keypair() -> tuple[str, str]:
|
||||
"""Generate a WireGuard keypair. Returns (private_key, public_key)."""
|
||||
private = subprocess.check_output(["wg", "genkey"]).decode().strip()
|
||||
public = subprocess.run(
|
||||
["wg", "pubkey"], input=private.encode(), capture_output=True
|
||||
).stdout.decode().strip()
|
||||
return private, public
|
||||
|
||||
def generate_preshared_key() -> str:
|
||||
return subprocess.check_output(["wg", "genpsk"]).decode().strip()
|
||||
|
||||
def build_client_config(
|
||||
client_private_key: str,
|
||||
client_vpn_ip: str, # e.g. "10.8.0.3"
|
||||
server_public_key: str,
|
||||
server_endpoint: str, # e.g. "home.example.com:51820"
|
||||
allowed_ips: str = "192.168.1.0/24",
|
||||
dns: str = "",
|
||||
) -> str:
|
||||
dns_line = f"DNS = {dns}\n" if dns else ""
|
||||
return f"""[Interface]
|
||||
PrivateKey = {client_private_key}
|
||||
Address = {client_vpn_ip}/32
|
||||
{dns_line}
|
||||
[Peer]
|
||||
PublicKey = {server_public_key}
|
||||
Endpoint = {server_endpoint}
|
||||
AllowedIPs = {allowed_ips}
|
||||
PersistentKeepalive = 25
|
||||
"""
|
||||
|
||||
def build_server_peer_block(
|
||||
client_public_key: str,
|
||||
client_vpn_ip: str,
|
||||
comment: str = "",
|
||||
) -> str:
|
||||
comment_line = f"# {comment}\n" if comment else ""
|
||||
return f"""
|
||||
{comment_line}[Peer]
|
||||
PublicKey = {client_public_key}
|
||||
AllowedIPs = {client_vpn_ip}/32
|
||||
"""
|
||||
```
|
||||
|
||||
Keep private keys out of source control. If you use this script, write key material
|
||||
to files with mode 600 and never log or print it.
|
||||
|
||||
## pfSense / OPNsense WireGuard
|
||||
|
||||
```
|
||||
# pfSense: VPN → WireGuard → Add Tunnel
|
||||
Interface Keys: Generate (creates keypair automatically)
|
||||
Listen Port: 51820
|
||||
Interface Address: 10.8.0.1/24
|
||||
|
||||
# Add Peer (one per client):
|
||||
Public Key: <client public key>
|
||||
Allowed IPs: 10.8.0.2/32
|
||||
|
||||
# Assign the WireGuard interface:
|
||||
Interfaces → Assignments → Add (select wg0)
|
||||
Enable interface, no IP needed (it is set in the tunnel config)
|
||||
|
||||
# Firewall rules:
|
||||
WAN → Allow UDP port 51820 inbound (so clients can reach the server)
|
||||
WireGuard interface → Allow traffic to LAN networks you want reachable
|
||||
```
|
||||
|
||||
## DDNS (Dynamic DNS) for Home Servers
|
||||
|
||||
Most home internet connections have a dynamic IP. Use DDNS so your VPN endpoint
|
||||
stays reachable after an IP change.
|
||||
|
||||
```bash
|
||||
# Option 1: Cloudflare DDNS — store credentials in a secrets file, not inline
|
||||
# docker-compose entry using an env file:
|
||||
ddns-updater:
|
||||
image: qmcgaw/ddns-updater
|
||||
env_file: ./ddns.env # store zone_id and token here, not in compose
|
||||
restart: unless-stopped
|
||||
|
||||
# ddns.env (chmod 600, not committed to git):
|
||||
# SETTINGS_CLOUDFLARE_ZONE_ID=your_zone_id
|
||||
# SETTINGS_CLOUDFLARE_TOKEN=your_api_token
|
||||
|
||||
# Option 2: DuckDNS (free, simple)
|
||||
Sign up at duckdns.org → get a token and subdomain (myhome.duckdns.org)
|
||||
Store token in /etc/ddns.env (mode 600), then use a small root-owned script:
|
||||
|
||||
# /usr/local/bin/update-duckdns
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
. /etc/ddns.env
|
||||
curl --fail --silent --show-error --max-time 10 \
|
||||
--get "https://www.duckdns.org/update" \
|
||||
--data-urlencode "domains=myhome" \
|
||||
--data-urlencode "token=${DUCKDNS_TOKEN}" \
|
||||
--data-urlencode "ip="
|
||||
|
||||
# Cron job:
|
||||
*/5 * * * * /usr/local/bin/update-duckdns >/dev/null 2>&1
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
```bash
|
||||
# Check WireGuard status and last handshake
|
||||
sudo wg show
|
||||
|
||||
# If "latest handshake" is never or very old, the tunnel is not connected.
|
||||
# Check:
|
||||
# 1. Is UDP port 51820 open on the router/firewall?
|
||||
sudo ufw status # or check pfSense/UniFi firewall rules
|
||||
|
||||
# 2. Is the server public key in the client config correct?
|
||||
sudo wg show wg0 public-key # Compare to what is in the client config
|
||||
|
||||
# 3. Is IP forwarding enabled on the server?
|
||||
cat /proc/sys/net/ipv4/ip_forward # Should be 1
|
||||
|
||||
# 4. Does the client AllowedIPs cover the IP you are trying to reach?
|
||||
# If AllowedIPs = 192.168.1.0/24 and you are trying to reach 192.168.3.5, it will not route.
|
||||
|
||||
# Check kernel logs for WireGuard errors
|
||||
dmesg | grep wireguard
|
||||
|
||||
# Restart WireGuard
|
||||
sudo wg-quick down wg0 && sudo wg-quick up wg0
|
||||
```
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
```
|
||||
# BAD: Storing private keys in version control or sharing them
|
||||
# Private keys are equivalent to passwords — never commit them to git
|
||||
|
||||
# BAD: Using AllowedIPs = 0.0.0.0/0 on mobile without considering the impact
|
||||
# Full tunnel routes all mobile traffic through your home upload — usually slow
|
||||
|
||||
# BAD: Not setting PersistentKeepalive on mobile clients
|
||||
# Mobile clients behind NAT drop idle tunnels without it
|
||||
|
||||
# BAD: Opening port 51820 in the firewall but forgetting IP forwarding on the server
|
||||
# Tunnel connects but no traffic routes — confusing to debug
|
||||
|
||||
# BAD: Sharing a keypair across multiple client devices
|
||||
# Each device must have its own unique keypair — shared keys break the security model
|
||||
|
||||
# BAD: Using a broad "FORWARD ACCEPT" iptables rule
|
||||
# Scope forwarding rules to the wg0 interface and direction only
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
- Generate a unique keypair per client device — never reuse keys
|
||||
- Use split tunneling (`AllowedIPs = <home subnets>`) for mobile
|
||||
- Set `PersistentKeepalive = 25` on all mobile clients
|
||||
- Use DDNS if your ISP assigns a dynamic IP; store credentials in env files, not inline
|
||||
- Use scoped iptables forwarding rules (inbound on wg0 only) rather than a blanket FORWARD ACCEPT
|
||||
- Add Pi-hole's IP as `DNS =` in client configs to get ad blocking over the VPN
|
||||
- Rotate the server keypair periodically and update all client configs
|
||||
|
||||
## Related Skills
|
||||
|
||||
- homelab-network-setup
|
||||
- homelab-vlan-segmentation
|
||||
- homelab-pihole-dns
|
||||
Reference in New Issue
Block a user