Skip to content
~/amoghshendre
← back to blog

Letting an AI agent run my Pi-hole maintenance

8 min read
  • homelab
  • agentic-ai
New to AI agents or home servers? Start here (1 min)

A small computer in my house runs Pi-hole, software that blocks ads and trackers for every device on my home network.

Claude Code is an AI agent that can connect to that computer, inspect it, and prepare fixes. That is what makes it agentic: it does not just answer questions, it carries out a multi-step job with real tools, and this post is about doing that safely. The closest analogy is a contractor who can walk through a house and write the work orders but never gets the master key.

That boundary is the story here. The AI could investigate problems and write scripts, but only I had administrator access. It prepared each important change, I read it, and I personally approved and ran it.

The computer was months behind on updates. Over two days, the AI identified the problems and prepared the fixes, and I applied them. One old configuration mistake of mine made a service fail during the update, and we repaired that too.

The lasting benefit was not one successful cleanup. It was the repeatable maintenance routine we set up afterward.

AI inspects
  → AI prepares
  → I review
  → I authorize
  → the system changes

Glossary at the end.

My Raspberry Pi 5 runs Pi-hole for the whole house, along with a few other services. Like most homelab boxes, it worked well enough that I stopped looking at it, right up until I asked Claude Code, running on my Mac, to SSH in and tell me how it was doing.

The answer: 251 pending apt packages, a Pi-hole installation that was one version behind, and a blocklist setup I'd never really tuned. Over the next two days, the agent did the investigation and wrote every script, while I ran every sudo command. That division of labor turned out to be the interesting part.

The guardrail: the agent never gets root

The <user> account on this box deliberately has no passwordless sudo, so there's no path to root without a human typing a password. My first instinct was to grant it so the agent could work freely. Instead, we landed on a narrower fix for the immediate need (reading Pi-hole's query database just required joining the pihole group) and a pattern for everything else:

  1. The agent writes a script to the Pi over SSH, say ~/add-regex.sh
  2. I read it
  3. I run sudo ~/add-regex.sh myself and paste the output back

Every privileged change went through that loop: adding denylist rules, upgrading Pi-hole, installing packages, fixing a broken service. The agent could inspect anything but change nothing that mattered without my hands on the keyboard. It's slower than handing over root, and it's exactly the speed I want for a box that does DNS for my house.

Each script also created its own backups before touching anything (gravity.db, pihole.toml, a full dpkg -l snapshot before the big upgrade), so every step had an undo.

Community regex rules, but measured first

I'd been meaning to try MMotti's community regex denylist: 14 patterns that catch ad-ish subdomains (ads.*, telemetry.*, pixel.*) regardless of which blocklist they're on.

Instead of piping the installer into sudo (don't), the agent tested each pattern with pihole-FTL regex-test against domains from my actual query history. Four of the fourteen had real false positives on my network:

  • ^pixel: matches legitimate Google pixel*.google.com endpoints
  • ^telemetry, ^count, ^stat: each collided with legitimate services I use

So we added the ten safe ones and skipped those four. The lesson generalizes: a community blocklist is a starting point, and your own query log is the test suite.

One more thing the query analysis surfaced: my top "blocked-looking" domain was an internal work domain that only resolves on the office VPN. NXDOMAIN at home is correct for it. Worth knowing before you "fix" something that isn't broken.

The upgrade that looked scarier than it was

pihole -up went cleanly, but the post-upgrade verification script cried wolf. It grepped pihole.toml for port = and matched the DNS port (53) instead of the webserver port, then announced the dashboard config had been reset. It hadn't; the grep was just too greedy. Two minutes of reading the actual config confirmed everything was intact.

A real finding did come out of the upgrade check, though: gravity had dropped from ~93k to ~80k domains, because the box had been leaning on a single blocklist all along. Adding OISD Big took the total to ~350k domains. The expanded setup immediately started blocking things the regex false-positive scan had missed, like an ABP-style tracking-pixel entry that plain domain matching couldn't express.

251 packages at midnight

For the apt backlog, the agent wrote an upgrade script. It ran full-upgrade with --force-confold so my hand-edited configs survive, with service checks and DNS smoke tests afterward and no automatic reboot. A one-shot wrapper installed it into /etc/cron.d, ran at midnight, and deleted its own cron entry when done.

Next morning: the log showed a clean nine-minute run. All 251 packages upgraded, every service up, DNS answering.

Except one thing.

The upgrade casualty: a placeholder in /etc/default/alloy

Grafana Alloy, the agent that ships this box's metrics to Grafana Cloud, failed to configure during the upgrade:

/etc/default/alloy: 18: Syntax error: newline unexpected

The culprit was a line I'd left in that file months ago, from a docs template:

GCLOUD_RW_API_KEY=glc_<your-new-dual-scope-token>

That file is sourced by sh in the package's maintainer scripts, and <...> is a shell redirection. The real token lived elsewhere; this was a leftover placeholder that had been silently harmless, until a package upgrade actually sourced the file and choked. I deleted the line, ran dpkg --configure -a, reinstalled the package, and Alloy was back to shipping metrics within a minute.

It's a good miniature of why unattended upgrades scare people: the failure wasn't the upgrade's fault. It was my old mess, and the upgrade was just the first thing to trip over it.

Making it not my job anymore

Two changes so this doesn't decay again:

Security patches: automatic. I configured unattended-upgrades to use only the Debian security origin, with anything that requires a reboot excluded. Routine CVEs get patched nightly; anything that needs a reboot or could change behavior waits for a human.

Everything else: a monthly ritual with the agent. A self-contained prompt file lives on my Mac describing the box, its quirks, and a read-only checklist (health, services, pending updates, backups, DNS sanity), ending with "don't make any changes without asking." A recurring calendar event on the 1st tells me to run it. When I told the agent "I am not going to remember this," it also saved the trigger phrase to its own persistent memory. A fresh session next month starts already knowing the box.

What I'd tell you

  • Keep sudo human. The stage-a-script, read-it, run-it-yourself loop costs seconds and means the agent's blast radius is zero until you've reviewed the diff.
  • Test regex rules against your own query log before enabling them. My false-positive list won't be yours.
  • One blocklist is not a setup. Check your gravity count; mine had been half of what I assumed for who knows how long.
  • Grep your /etc/default/* files for template placeholders before your next big upgrade. <> in a sourced shell file is a time bomb.
  • The agent's best output wasn't the fixes, it was the system: the prompt file, the calendar event, the memory. Maintenance you'll actually repeat beats maintenance done once, heroically.
Glossary
  • Pi-hole: software that blocks ads and trackers for every device on a network by filtering their DNS lookups.
  • DNS: the system that turns a name like example.com into the numeric address a device connects to.
  • SSH: a secure way to log into and control another computer over the network from a terminal.
  • sudo / root: root is the all-powerful administrator account on a Linux machine, and sudo is the command that runs a single command with that power.
  • apt package: a piece of software installed and updated through apt, the package manager on Debian-based systems like this one.
  • regex: a text pattern that matches many similar strings at once, used here to match whole families of domain names.
  • denylist / blocklist: a list of domains Pi-hole refuses to resolve, which is what actually blocks the ads.
  • gravity: Pi-hole's combined database of every blocked domain pulled from all of its lists.
  • NXDOMAIN: the DNS answer that means "this domain does not exist."
  • false positive: something that was blocked or flagged but should have been allowed.
  • cron: the Linux scheduler that runs commands at set times, like an alarm clock for scripts.
  • unattended-upgrades: a Debian tool that installs security updates automatically on a schedule.
  • Claude Code: Anthropic's AI coding agent, which runs in a terminal and can inspect systems and write scripts.
  • AI agent / agentic AI: an AI that takes multi-step actions with tools rather than only answering questions (the "agentic" in the tag), here working over SSH.

related