import type { Metadata } from "next"; import Widget from "@/components/Widget"; import { services, categoryOrder, categoryLabels } from "@/data/services"; export const metadata: Metadata = { title: "Homelab | Tyler Koenig", description: "Production-grade personal homelab: Proxmox, pfSense, 8 VLANs, WireGuard, Caddy, Authentik SSO, and 20+ self-hosted services.", }; const glanceStats = [ { label: "Hypervisor", value: "Proxmox VE" }, { label: "Firewall", value: "pfSense (Intel N100)" }, { label: "Switching", value: "TP-Link Omada (managed)" }, { label: "ISP", value: "AT&T Fiber 1 Gbps" }, { label: "VPN", value: "WireGuard (pfSense)" }, { label: "Reverse Proxy", value: "Caddy + Cloudflare DNS-01" }, { label: "Auth", value: "Authentik SSO" }, { label: "DNS", value: "Pi-hole → Unbound → Cloudflare" }, { label: "Containers", value: "9 LXC + 2 VMs" }, ]; const vlans = [ { id: "1000", name: "MGMT", subnet: "10.0.0.0/24", purpose: "Network equipment only" }, { id: "1010", name: "LAN", subnet: "10.1.0.0/24", purpose: "Trusted personal devices" }, { id: "1020", name: "Homelab", subnet: "10.2.0.0/24", purpose: "All self-hosted services" }, { id: "1030", name: "Guests", subnet: "10.3.0.0/24", purpose: "Internet only, RFC1918 blocked" }, { id: "1040", name: "IoT", subnet: "10.4.0.0/24", purpose: "Smart home, isolated" }, { id: "1050", name: "WFH", subnet: "10.5.0.0/24", purpose: "Work devices, no personal access" }, { id: "DMZ", name: "DMZ", subnet: "10.99.0.0/24", purpose: "Public-facing, hard-blocked internally" }, { id: "VPN", name: "VPN", subnet: "10.200.0.0/24", purpose: "WireGuard clients = LAN access" }, ]; const adrs = [ { title: "AT&T Gateway: IP Passthrough over EAP bypass", decision: "BGW320 stays in-line with IP Passthrough mode. pfSense gets the public IP directly. Gateway WiFi disabled.", why: "AT&T locks 802.1X auth to their gateway hardware. EAP proxy bypass is brittle — breaks on firmware updates and only saves 1–2ms. True bridge mode isn't supported.", }, { title: "Caddy over NGINX Proxy Manager", decision: "Caddy with DNS-01 challenge via Cloudflare API. All subdomains resolve to Caddy internally via Pi-hole. Caddy terminates SSL and proxies to backends.", why: "Single Caddyfile, auto-cert without exposing port 80/443 to the internet. NPM has more UI overhead for the same outcome. Traefik is more complex for no benefit here.", }, { title: "WireGuard over OpenVPN", decision: "WireGuard on pfSense, UDP 51820, VPN subnet 10.200.0.0/24. Clients get LAN + MGMT access, blocked from Guest/IoT/WFH.", why: "Faster, simpler config, better battery life on mobile. ~600–900 Mbps on an N100. OpenVPN has no advantage here. Tailscale adds an external relay dependency.", }, { title: "Pi-hole in Homelab VLAN, not MGMT", decision: "Pi-hole at 10.2.0.11 (VLAN 1020). Firewall allows port 53 inbound from all VLANs. MGMT uses pfSense Unbound as its primary DNS.", why: "Putting Pi-hole in MGMT would require opening MGMT to all VLANs — a larger attack surface. DNS traffic crossing into Homelab VLAN is the lesser risk.", }, { title: "N100 for pfSense", decision: "Intel N100 mini PC: 4-core 3.4 GHz, ~6W idle. Handles 2–3 Gbps routing and 600–900 Mbps WireGuard.", why: "Right-sized for 1 Gbps fiber with headroom. Raspberry Pi can't handle 1 Gbps + VPN. A full rack server wastes power for this role.", }, { title: "Shared Postgres + Redis in apps LXC", decision: "One Postgres instance, multiple databases. One Redis instance. A single init script provisions all schemas on first run.", why: "Avoids 15 separate DB containers. Reduces RAM overhead significantly. All productivity apps share the same LXC (10.2.0.60).", }, { title: "Gitea CI/CD: Self-hosted runner with container build + SSH rsync deploy", decision: "act_runner v0.3.1 on Gitea LXC (10.99.0.22). Push to dev → node:22-alpine container builds Next.js → rsync out/ to Portfolio LXC → SSH docker rebuild.", why: "Keeps the full pipeline internal — no GitHub Actions, no external runners. Build runs in an isolated Alpine container so the Gitea LXC isn't polluted. Portfolio LXC (10.99.0.23) just serves pre-built static files via nginx.", }, ]; export default function HomelabPage() { return ( <> {/* Header */}
Personal infrastructure environment for learning, self-hosting, and operational practice. Running 24/7 on production-grade hardware with real network segmentation, SSO, monitoring, and IaC-style documentation.
{label}
{value}
| VLAN | Name | Subnet | Purpose |
|---|---|---|---|
| {v.id} | {v.name} | {v.subnet} | {v.purpose} |
{categoryLabels[cat]}
{svc.name}
{svc.description}
{adr.title}
decision: {adr.decision}
why: {adr.why}
lerkolabs on GitHub
Full documentation: VLAN maps, runbooks, service registry, config exports, and setup guides.
↗ github.com/lerko96/homelab-wip