Infrastructure portfolio

Interstellar Homelab

One server in an apartment, run like a small production environment: segmented network, firewall rules generated from code, full observability, and a local AI that explains incidents and asks before it touches anything.

Two case studies from November 2026, with the numbers and the failures.

12network segments (VLANs)
42containers on one host
49firewall flows, all from code
~5.6klines of IaC + Sentinel code
~150 Wwhole rack at idle

Ryzen 7 5800XT · 31 GB RAM · RTX 5060 8 GB · Unraid · OPNsense · UniFi switching and Wi-Fi 7

Case study 01

Re-segmenting a live network in one maintenance window

OPNsense · UniFi · Unraid · PowerShellWindow: ~2 hoursRan 2026-11-07

The problem

The network had grown by accretion. VLAN IDs had no scheme, most services lived on the old flat 192.168.1.x range, and firewall rules had been clicked together in the GUI over time. Nobody could say from a file which host was allowed to talk to which.

The goal was a plan anyone could read: the VLAN ID tells you the subnet, every allowed flow is written down, and the firewall is generated from that file.

10MGMTFirewall, switches, server, admin desktop. Wired only.
20TRUSTEDPersonal devices, main Wi-Fi
30MEDIAMedia server and library apps
31DOWNLOADSDownload clients, isolated
32MONITORINGPrometheus, Loki, Grafana, Sentinel
33AILocal LLM runtime
40EDGEReverse proxy, tunnel, game panel (DMZ)
41GAMESGame servers
42HONEYHoneypot
50IOTTVs and smart home, casting via mDNS repeater
51GUESTInternet only
60LABExperiments
Admin Internal services Internet-facing Untrusted clients Lab Rule: VLAN N → 192.168.N.0/24, gateway .1

One source of truth

Everything lives in one PowerShell data file: 48 hosts, 67 aliases and 49 firewall flows. Two scripts read it.

  • Firewall script talks to the OPNsense API with plan, apply and verify modes. It backs up the config first, and new rules go live behind a 60-second automatic rollback, so a rule that locks out the API undoes itself.
  • Server script stages the host IP, VLAN interfaces, Docker networks, 30+ container templates and 4 VM NICs to take effect at the next reboot. It then rewrites old addresses inside app configs, keeping a copy of every file it changes.
# network.psd1 (simplified)
@{ Seq = 40; From = 'C_Sentinel'
   To  = 'C_Ollama'; Port = '11434'
   Desc = 'LLM' }

# preview, apply, then prove it matches
opnsense-iac.ps1 plan
opnsense-iac.ps1 apply
opnsense-iac.ps1 verify   # in sync

The ordering puzzle

UniFi refuses two networks with the same VLAN ID, and every SSID and port profile is attached to the existing network objects. Rather than delete and recreate them, each network was renumbered in place, in an order where every move frees the ID the next move needs:

  1. 60 → 41
  2. 66 → 42
  3. 50 → 51
  4. 40 → 60
  5. 30 → 50
  6. 24 → 40
  7. 23 → 33
  8. 22 → 32
  9. 21 → 31
  10. 20 → 30
  11. 10 → 20

The runbook split each step into scripted and hands-on actions and gave every phase a written way back: console recovery for the firewall, a staged copy of the server's network config, and a full restore path if the window ran out.

33 / 33containers back on their planned network and address
0crash loops after the cutover
In syncfirewall verified against the source file after apply
9problems found in the post-cutover audit, all fixed the same day

What the audit caught

SymptomRoot causeFix
Game panel agent stuck in a restart loopFrom Docker's default bridge, the panel's replies came back to the firewall by a different path than the requests (asymmetric routing) and were dropped.Moved the agent to host networking so both directions take the same path. Tested both ways.
Game panel web UI redirected to a dead addressA container environment variable overrode the app's own config file and still held the pre-cutover URL.New container template that uses the DNS name instead of an IP.
Server logs stopped reaching LokiThe log shipper still pointed at the old address, and an old allow rule was never carried into the new rule set.Translated the config and added a narrow rule: one host, four ports.
Uptime probes couldn't ping any gatewayThe ICMP allow sat below a broader block in rule order.Moved the rule up. Zero failed probes since.
Lesson

Addresses hide in more places than config files: environment variables, apps' own databases, hard-coded defaults in scripts. These six real failures became the evaluation set for the next project.

Case study 02

Sentinel: a local AI that watches the lab and asks before it acts

Python · Loki · Prometheus · Ollama · Discord~4.2k linesSix phases, built 2026-11-08 to 11-09

The problem

The monitoring stack already collected everything, but alerts arrived as a stream of phone notifications that were easy to ignore. In its first hour live, Sentinel surfaced problems that had been firing quietly for days: the appdata backup had failed and was 4+ days stale, the weekly restore test was failing, the array had no parity disk, and an SFP+ link was logging about 600 receive errors every 15 minutes.

The goal: one feed that explains what is wrong in plain English and proposes a fix, with three hard rules. No cloud AI. Nothing changes without the owner's click. The media server always wins the GPU.

Design: a funnel, not a chatbot reading logs

A local model reads a few thousand tokens per second; the firewall alone writes thousands of lines per minute. So deterministic code does the reading, and the model only sees what is new, unusual or broken, already grouped and counted.

millions of lines / day

Collect

Container and host logs, parsed firewall syslog, NetFlow, metrics, and health checks against each app's own API.

hundreds of events / day

Detect, no AI

16 hard rules, log-template mining with per-source baselines, firewall aggregates, and silence detection.

5–20 incidents / day

Reason, local LLM

qwen3 8B on the shared GPU, read-only tools (logs, metrics, topology, notes), answers in a fixed JSON schema.

a few actions / day

Act, with approval

The AI picks from a small action catalog. The owner approves or denies in Discord.

Sharing an 8 GB GPU with 4K transcoding

The same RTX 5060 transcodes 4K HDR for the media server. A GPU arbiter checks the transcode count every few seconds and moves the model out of the way before the next call. 2 GB of VRAM stays reserved at all times.

TranscodesModelRuns on
0–1qwen3 8BGPU
2qwen3 4BGPU
3+qwen3 4B, critical onlyCPU

Measured with a live 2160p HDR transcode: 8B model plus transcode used 6.2 of 8.1 GB, the transcode stayed on hardware with no errors, and the model ran only about 5% slower.

Benchmarks, realistic triage prompt

ModelDeviceOutput tok/sFull triage
qwen3 8BGPU685.7 s
qwen3 4BGPU1024.2 s
qwen3 4BCPU9~100 s

3.3k-token prompt of real restart-loop logs, Plex idle. All three found the cause and returned valid JSON.

Safety: logs are attacker-controlled

Firewall and proxy logs contain text from the internet. Someone can put "ignore previous instructions" in an HTTP header. So the model never gets a shell or write access to anything.

  • It can only choose from a catalog: restart, start, prune dangling images. It never writes commands.
  • A separate executor service holds the Docker socket. It has no LLM and accepts only HMAC-signed requests from one address.
  • Every change needs a one-time approval ID, created only when the owner's Discord account clicks Approve. It is tied to one action and one target and expires after 2 hours.
  • Allowlisted targets, a 10-minute rate limit per target, and a circuit breaker that pauses everything after 5 runs in 15 minutes.
  • Firewall, switch config, disks and deletes are never in the catalog. For those, Sentinel explains and links the runbook.
CRITICALpelican-wingsrestart loop · 4× in 6 min

Wings can't reach the panel. Replies are being dropped by the firewall (DMZ → host, asymmetric path). Started after the image update.

Proposed fix: restart_container(pelican-wings)
ApproveDenyDetailsSnooze 24h
After the click the message updates in place: approved → restarted → healthy after 40 s → error stopped.

Guardrail tests: 15 / 15

  • valid restart runs
  • replayed approval
  • bad signature
  • 3-hour-old approval
  • no approver
  • deny-listed target
  • game-server container
  • unknown action
  • testbox; rm -rf /
  • rate limit
  • caller from another address
  • breaker trips on 5th run

Results, including the weak parts

3–4 of 6real past incidents diagnosed correctly by the 8B model (3–5 by keyword score, varies by run)
80–95%self-reported confidence on nearly everything, so it is not trusted until calibrated against owner ratings
~5 swarm triage per incident on the GPU
0actions taken without an approval
What went wrongWhat changed
During a bulk download that saturated the 1 Gbit WAN, the Ask box answered "not saturated". It didn't know which metric to read.Added a short metrics cheat sheet to the knowledge base. Same question, correct answer: 1,014 Mbit/s and which client was using it.
The AI proposed restarting two healthy services because they "stopped sending logs". Both log in bursts every 30 minutes.Silence detection now learns bursty sources, and log silence can never lead to a restart proposal. Caught in self-review before the proposals reached the owner.
Fix verification used an in-memory timer, so a service restart lost it.Verification moved to the persistent housekeeping loop. Interrupted approvals are marked failed, never silently re-run.
Lesson

The detection layer and written operator notes moved accuracy more than prompt tweaks or model size. Every fact written down made the matching answers correct. A 14B-class model would need a 16 GB card; the notes were free.

Process

How it was built

I set the requirements and made the design decisions below. Most of the scripts and Sentinel's code were written with Claude Code, an AI coding agent, working from my plans. I tested every phase on the live system and run it day to day. Every firewall change went through the source file and needed my explicit OK.

Plex keeps the GPUAI work drops to a smaller model or the CPU while people are watching.
Every fix asks firstNo automatic remediation. Approvals come only from my account.
No cloud AILogs and topology never leave the house. Chat messages carry summaries only, with keys and public IPs stripped.
Home-only dashboardReachable from admin and trusted segments, never published through the tunnel.
Quiet hours23:00–08:00 only critical alerts get through; the rest wait for one morning summary.
Rollback before changeBackups, staged configs and auto-rollback written before anything is applied.

Tools and skills

  • VLAN and firewall design
  • OPNsense API
  • UniFi
  • Infrastructure as code
  • PowerShell
  • Python
  • Docker
  • Unraid
  • Prometheus
  • Alertmanager
  • Loki and Alloy
  • Grafana
  • NetFlow
  • Ollama
  • LLM evaluation
  • Prompt-injection threat modeling
  • Runbooks and change windows
  • Incident root-cause analysis
Want something like this for your setup?

I take on home server, self-hosting and small-office network jobs. See services and prices or get in touch.