shephard.cc

This is the infrastructure behind shephard.cc — a homelab that has grown from a stack of spare parts into two routed networks, a domain-joined family device fleet, highly-available DNS, full observability, and backups that power their own storage on and off. Everything below is running right now.

┌──(ashep@shephard)-[~/network]
└─$ ip route show

Network & edge

Two routed segments, three router platforms, no open ports.

CGNAT lands on the UniFi gateway, which runs the primary 10.0.0.0/16 — around thirty devices migrated off an old flat /24 — and directly serves the core boxes: the Debian server, the Frigate NVR, Home Assistant, and the house WiFi. From there, a Netgear GS316EP breaks the network out to a FortiGate 60D, which fronts its own NATed 192.168.1.0/24.

# topology

        ╔═════════════════════════╗
        ║    internet · CGNAT     ║
        ╚════════════╤════════════╝
                     │
        ┌────────────┴────────────┐
        │      unifi gateway      │
        │       10.0.0.0/16       │
        └──┬──────────────────────┘
           ├─ the-shephards · debian · 10.0.1.1 · raid 1 · restic · docker
           ├─ home assistant · smart-home hub
           ├─ wifi · phones + family devices
           │
           └─ netgear gs316ep
              │  wan1 · 10.0.1.6
        ┌─────┴───────────────────┐
        │      fortigate 60d      │
        │      192.168.1.0/24     │
        └──┬──────────────────────┘
           ├─ arch desktop · jetkvm attached
           ├─ raspberry pi · pi-hole (ha node)
           │
           └─ windows server 2025 · hyper-v + ad ds
              ├─ media vm · debian · isolated vpn
              ├─ pi-hole vm · ha pair with the pi
              ├─ local-ai vm
              ├─ opnsense vm · sandbox network
              └─ test vms · whatever's next
  

Behind the FortiGate live the Arch desktop, the physical half of the Pi-hole pair, and Windows Server 2025 — domain controller for the family's devices and Hyper-V host for the VM farm: a VPN-isolated Debian VM for legally sourced media, a Pi-hole VM, RHEL, a local-AI box, test VMs, and OPNsense, which runs its own sandbox network for routing experiments on a quad-port Intel i350-T4.

Because everything sits behind CGNAT, nothing is port-forwarded. Remote access is a Tailscale mesh — the Debian server advertises both subnets, and Tailscale runs in the background on the phone so ad blocking follows it out of the house — plus a JetKVM on the main desktop for true hardware-level access from any browser.

UniFi gateway FortiGate 60D Netgear GS316EP OPNsense Tailscale JetKVM
┌──(ashep@shephard)-[~/compute]
└─$ cat /etc/hosts

Compute

Real metal where it matters, virtual machines for everything else.

the-shephards

Debian · 10.0.1.1

The workhorse. mdadm RAID 1 storage at /mnt/Raid_1, Timeshift btrfs snapshots, systemd timers for backups and NAS power, and Tailscale subnet routing for both networks. This is the station the household relies most heavily on. Serving up media with Jellyfin, pictures with Immich, surveillance with Frigate NVR makes this machine's uptime important for the family to enjoy services they would normally pay a cloud provider for.

mdadm RAID 1subnet routerrestic client

windows-server

Windows Server 2025 · Hyper-V + AD DS

Domain controller for the family's laptops and Windows devices, Hyper-V host for the whole VM farm, and home of the restic REST server for backups.

AD DSHyper-Vrest-server

raspberry-pi(s)

bare metal · Pi-hole + Home Assistant

2 Raspberry Pis perform mission-critical tasks for the household. One runs Pi-hole, which is the household DNS server and ad-blocker. The other is running Home Assistant OS which makes smart home devices simple enough for the kids to use.

Pi-holeHA nodeHome Assistant
┌──(ashep@shephard)-[~/dns]
└─$ dig +short shephard.cc

DNS, in layers

The part of the lab that must never go down — so it can't depend on any one box.

Ad-blocking and internal resolution run on a keepalived-managed HA Pi-hole pair: one virtual, on the Windows Server farm, and one physical, on a Raspberry Pi — with nebula-sync keeping the Pi-hole v6 configuration identical between them. Either half of the lab can fall over completely and the house keeps resolving.

DNS records are added by a one-shot setdns script that hits the UniFi and Pi-hole v6 APIs simultaneously which makes adding new DNS records as simple as typing the name you want it to resolve.

Tailscale is set up to run quietly in the background on the phone, so the Pi-holes keep filtering ads even away from home.

┌──(ashep@shephard)-[~/identity]
└─$ net ads info

Identity — two Active Directories

One in production, one being rebuilt the hard way.

The production directory is Active Directory Domain Services on Windows Server 2025: the family's laptops and Windows devices are domain-joined, with real accounts and real policy — enterprise identity, scaled down to a household.

Running alongside it as a test project is a Samba AD domain, ad.shephard.cc — Although not in use, it serves as a great learning tool for AD management through Linux and offers full-featured Active Directory/Group Policy that can be managed from a Windows device.

┌──(ashep@shephard)-[~/Docker]
└─$ ls

Services

Fronted by Nginx Proxy Manager with a wildcard *.shephard.cc certificate via Cloudflare DNS — every service gets a real name and real TLS.

nginx proxy managerreverse proxy + wildcard TLS for everything below
pi-holenetwork-wide ad blocking in an HA pair (see DNS)
vaultwardenself-hosted password manager for the family
home assistantautomation hub — also powers the backup storage on and off
jellyfinmedia server — movies and TV for the whole house
immichphoto backup that replaced Google Photos for the family
frigateNVR with hardware-accelerated object detection
minecrafta persistent world for the household
freshrssall the news wanted, none of the rest
qbittorrentlegally sourced media, sealed inside a VPN-isolated VM
homepagethe dashboard that ties the whole lab together
portainercontainer management with a UI
otterwikilab documentation — the runbooks live here
speedtestscheduled speed tests to keep the ISP honest
uptime kumastatus checks + push targets for every watchdog
ntfypush notifications to phones, wired for iOS APNs
prometheusmetrics scraped across the fleet (see observability)
grafanathe dashboards on top of prometheus
wazuhSIEM, proxied like everything else
scrutinySMART disk monitoring

The supporting cast — mysql, a letsencrypt store, and the data volumes — rounds out the compose files. Docker's default address pools are pinned to 10.20.0.0/16 in daemon.json — learned the hard way after a container network landed on top of the FortiGate segment.

┌──(ashep@shephard)-[~/monitoring]
└─$ curl -s prometheus:9090/api/v1/targets | jq '.data.activeTargets | length'

Observability & security

If it isn't graphed, alerted, and audited, it isn't done.

Prometheus + Grafana scrape the whole fleet: node_exporter on the Linux boxes, windows_exporter on the server, pihole-exporter for DNS, and Home Assistant's built-in Prometheus integration for the smart-home side — all pulled into a custom-built dashboard.

# grafana → prometheus scrape jobs
up  arch-desktop   up  debian-server   up  windows-server
up  pihole         up  homeassistant   up  prometheus

On the security side, a Wazuh SIEM runs as a single-node Docker Compose stack behind NPM, with agents on the Debian server and on Arch — the latter built from source, because Arch — and tuned ossec.conf rules to keep the false positives quiet. Scrutiny watches SMART data on the storage server, so disks get replaced before they get interesting.

Prometheus Grafana Wazuh SIEM Scrutiny SMART Uptime Kuma ntfy alerts
┌──(ashep@shephard)-[~/backups]
└─$ systemctl list-timers | grep -i nas

Backups: restic + a NAS that turns itself off

The favorite piece of engineering in the lab — 3-2-1 with a power bill in mind.

Backups run on restic from the Debian server to a restic REST server on Windows Server 2025. The twist: the repository lives on a drive that is powered off outside the backup window. A smart plug controlled through Home Assistant's REST API brings the storage up on schedule, and systemd timers orchestrate the whole nightly cycle:

19:00  NAS_Power_On.timer    → Home Assistant flips the smart plug; disks spin up
19:0x  restic-backup         → pre-flight check against rest-server /config,
                               power-cycle retry logic if the target is absent
19:xx  uptime-kuma push      → success heartbeat (silence = an alert)
02:00  NAS_Power_Off.timer   → storage goes dark until tomorrow

On the Windows side, the REST server runs as an NSSM service launched entirely from C:\, set to manual start so a boot-time scheduled task owns the choreography: power the NAS on, wait for the repo drive to appear, start the service, power the NAS back off. Testing confirmed rest-server keeps running happily after its storage disappears underneath it — the drive only has to exist at process start. A PowerShell watchdog watches service events (7034/7036) and pushes to Uptime Kuma if anything falls over. Local safety nets round it out: Timeshift btrfs snapshots on Debian and mdadm RAID 1 under the primary data.

┌──(ashep@shephard)-[~/hardware]
└─$ esphome logs salt-monitor.yaml

Smart home & hardware

Where the lab leaves the rack.

water softener monitor

ESP32-S3 N16R8 · ESPHome

Measures the salt level in the water softener and reports into Home Assistant — Alert is sent as a notification to phone.

leak-response pump

Arduino

An automated water pump that switches on the moment water is detected — flood control that doesn't wait for a human to notice.

wled everywhere

WLED · ESP32s + Raspberry Pis

Several strip lights around the house driven by ESP32s and Raspberry Pis — including a custom Python script to control lights for a mini rack.

lighting logic

motion sensors · smart buttons

Stair lights come on with motion and time out after a couple of minutes of stillness; programmable push buttons act as dimmer and power toggle for lights too awkward to reach.

┌──(ashep@shephard)-[~/workstation]
└─$ hyprctl monitors

The workstation

The desk the lab is run from.

Daily driver is Arch Linux with Hyprland, topped by a custom QuickShell bar written in QML — deep purple, JetBrains Mono, dynamic app launchers driven from a JSON config. (The bar across the top of this page is its web impersonation.) The stack underneath: Kitty, Neovim, zsh with oh-my-zsh, and a cliphist / wl-clipboard / rofi-wayland clipboard pipeline.

Three monitors are input-switched with ddcutil, so one keystroke steals a screen from another machine. A JetKVM sits on the box for true hardware-level access — BIOS and all — from any browser through JetKVM cloud. For downtime there's a Sunshine → Moonlight streaming path to the living-room TV.

Hyprland QuickShell Kitty + Neovim ddcutil JetKVM Sunshine / Moonlight
┌──(ashep@shephard)-[~/lab]
└─$ git status

On the bench

A homelab is never finished; it's just between incidents.

On branch homelab
Changes in progress:
  modified:   identity/samba-ad          # ad.shephard.cc — parallel test domain
  modified:   automation/ansible         # bootstrap plays under evaluation
  modified:   network/opnsense-sandbox   # isolated routing experiments

Untracked files:
  new:        labs/rhel/kickstart        # automated provisioning practice
  new:        vms/local-ai               # model serving experiments

nothing is ever fully committed in a homelab