Mathew Hufford · Homelab

My home server, run like a production system.

My family uses this server every day for movies, music and local AI. I build and run it with Claude Code. Below are the main projects and the decisions behind them.

17containers running
28PRs merged on my app
166commits to my config repo
5local LLMs tested
Nightlybackups, restore tested

How I use AI on it

Claude Code works on this server every day. These are the rules it works under.

Read anything, change nothing without a yes

It can read logs and configs freely. Any change needs my OK first, and each OK covers one action.

Every change request covers risk

What it will run, why, what could break, and how to undo it.

Least privilege

Read-only sudo, and it's blocked from secret files like .env.

Session handoffs

Each session keeps a log with exact errors and commands, so the next one starts from facts, not memory.

Projects

Problem, what I did, result.

tuneseerr: a music request app built with Claude Code

Claude CodePythonREST APIsOAuthGitHub CIDocker
Problem

My family wanted to add music to our Jellyfin server without asking me.

What I did

Built a web app that talks to Jellyfin, Lidarr, Soulseek and Spotify through their APIs (Spotify via OAuth). Every change goes issue, branch, PR, CI (tests and secret scanning), then release. The server pulls new releases on its own. Dev and prod run separately, so testing never touches the real library. Before making it public I added login lockout, CSRF protection, hashed session tokens and auto IP bans.

Result

4 releases, 28 merged PRs, used daily. Each request also lands in the requester's own playlist.

Testing 5 local LLMs, and deciding not to build something

AI evaluationPython
Problem

I planned a setup where a fast model hands big jobs to a large, slow one. First I needed to know which model was good at what.

What I did

Wrote a test harness: 8 research questions, 7 scoring measures, graded blind. While grading I found a bug in my own harness that was dropping one model's answers, and fixed it before trusting any scores. I also flagged every made-up fact.

Result

The large model scored lower (195 vs 248) and ran 2.5x slower than the best small one, so I dropped the plan. The numbers didn't support building it.

Fixing an IP ban that never banned anyone

DebuggingLogsNetworkingSecurity
Problem

I set up fail2ban to block IPs with repeated failed logins. It said it was running.

What I did

Tested it from outside instead of trusting the status. The logs showed two problems: the app wraps IPs in quotes, so the match pattern never fired, and Docker traffic skips the firewall chain the bans went into. A later review found that IPv6 visitors would have looked like internal traffic and could never be banned.

Result

Fixed all three and confirmed with a real ban. Lesson: test the outcome, not the status.

Backups I've actually restored

OperationsDataAutomation
Problem

The apps keep their data in SQLite DBs, and copying a DB while it's being written can give you a broken backup.

What I did

The nightly job (restic) stops the apps for about 35 seconds so every DB is copied cleanly, then keeps 7 daily, 4 weekly and 6 monthly encrypted snapshots. Failures email me. I didn't call it done until I'd restored from it.

Result

Restore test passed: a 382 MB DB came back with a clean integrity check.

This site

Self-hostedDockernginx
Problem

Put a public page on a home server without opening anything else up.

What I did

Static files only: no login, no DB, no scripts. nginx runs as a non-root user, read-only, with all Linux capabilities dropped. It has no open ports of its own. The only way in is through the reverse proxy (Traefik). I tested it on an internal-only address first, then made it public.

Result

You're reading it. It doesn't list IPs, hostnames or software versions, on purpose.

How it's built

InternetHTTPS only My devicesprivate VPN Reverse proxyTLS, rate limits, headers Public apps (4) Internal toolshome network only Local AI modelsGPU, API key required Backupsseparate disk Alertsemail on failure dashed: blocked unless you're at home or on the VPN InternetHTTPS only Reverse proxyTLS, rate limits, headers Public apps (4) Internal toolshome network only dashed: blocked unless you're at home or on the VPN My devicesprivate VPN Local AI modelsGPU, API key required Backupsseparate disk Alertsemail on failure nightly, for everything above
One way in from the internet, and it only reaches the public apps. Everything else is home network or VPN only.