Local-first infrastructure
A smart home system with no cloud, no subscription, no vendor
I replaced a commercial home controller with a system I built myself. It runs on my own hardware, nothing it does leaves the house, and I can ask Tommy to turn the lights off from the other side of the world.
Commercial smart home platforms want your data and a monthly fee, and they break when their servers do. So I built a replacement.
The architecture, deliberately boring
A single Python process. No microservices, no cloud dependency, no build step on the server. A FastAPI backend talks to devices over MQTT and HTTP polling. State lives in SQLite. That is the whole backend.
A YAML rules engine handles automations and hot-reloads when I edit it, so changing behaviour means editing a text file, not redeploying anything. “If the front door opens after 11pm and nobody is home, turn on the hall light and message me” is four lines.
The dashboard is React, mobile-first, organised by room with scenes and device groups, because the real test of a home system is whether it works one-handed in the dark.
Talking to it
The system has a small bridge into the Tommy fleet, so the house is just another thing I can ask about in Telegram. “Is the aircon still on?” “Set the living room to movie.” “What ran overnight?” The house answers in the same thread as everything else.
Production hardening
Structured JSON logging. Environment validation on startup so a missing variable fails loudly instead of silently. Docker Compose for deployment. Automated backups. A self-contained installer that sets the dashboard up in a single command, which matters because the whole thing moves to a new apartment this year.
The bug that justified the whole audit
During hardening I pushed for a deeper pass than the first review offered. That second pass found the entire outbound command path had been silently dead: the system accepted every command, logged it, and did nothing. Every test that checked “did the API respond” passed. No test checked “did the light turn on”.
That became the first Build Log post. Lesson: test the outcome, not the acknowledgement.