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.
Ryzen 7 5800XT · 31 GB RAM · RTX 5060 8 GB · Unraid · OPNsense · UniFi switching and Wi-Fi 7
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.
Everything lives in one PowerShell data file: 48 hosts, 67 aliases and 49 firewall flows. Two scripts read it.
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.# 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
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:
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.
| Symptom | Root cause | Fix |
|---|---|---|
| Game panel agent stuck in a restart loop | From 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 address | A 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 Loki | The 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 gateway | The ICMP allow sat below a broader block in rule order. | Moved the rule up. Zero failed probes since. |
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.
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.
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.
Container and host logs, parsed firewall syslog, NetFlow, metrics, and health checks against each app's own API.
16 hard rules, log-template mining with per-source baselines, firewall aggregates, and silence detection.
qwen3 8B on the shared GPU, read-only tools (logs, metrics, topology, notes), answers in a fixed JSON schema.
The AI picks from a small action catalog. The owner approves or denies in Discord.
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.
| Transcodes | Model | Runs on |
|---|---|---|
| 0–1 | qwen3 8B | GPU |
| 2 | qwen3 4B | GPU |
| 3+ | qwen3 4B, critical only | CPU |
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.
| Model | Device | Output tok/s | Full triage |
|---|---|---|---|
| qwen3 8B | GPU | 68 | 5.7 s |
| qwen3 4B | GPU | 102 | 4.2 s |
| qwen3 4B | CPU | 9 | ~100 s |
3.3k-token prompt of real restart-loop logs, Plex idle. All three found the cause and returned valid JSON.
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.
Wings can't reach the panel. Replies are being dropped by the firewall (DMZ → host, asymmetric path). Started after the image update.
| What went wrong | What 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. |
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.
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.
I take on home server, self-hosting and small-office network jobs. See services and prices or get in touch.