My home command centre: a Raspberry Pi dashboard with Python and zero dependencies
A web dashboard that checks live which home services respond, reads the last backup and stores to-dos. About 200 lines of Python with nothing to install.
I have quite a few things running at home: a Raspberry Pi with half a dozen services, a NAS, a laptop for practice, a 3D printer… and the feeling of never knowing at a glance what’s actually working.
There are ready-made dashboards (I use Homepage to keep links handy), but I wanted something of my own that showed three things on a single screen:
- What responds right now and what doesn’t.
- How last night’s backup went.
- My to-do list, which both I and my AI assistant can read and write.
The result is a Python dashboard with zero dependencies: just the standard library. No pip install, nothing to keep updated.
The overall idea
A single server.py file that does two things:
- Serves an HTML page (
index.html) with the dashboard. - Offers a small JSON API that the page polls every 30 seconds.
GET /api/estado what responds?
GET /api/copia how did the last backup go?
GET /api/tareas task list
POST /api/tareas create a task
PATCH /api/tareas/<id> mark it as done
DELETE /api/tareas/<id> delete it
(The endpoints are in Spanish because, well, so am I.)
Checking whether something is alive, without ping
My first idea was to ping each machine. But ping needs special permissions to send ICMP packets, and it only tells you the machine is on, not that the service works.
It’s better to try connecting to the service’s port. If it accepts the connection, it’s alive:
import socket, time
def check(host, port):
t0 = time.monotonic()
try:
ip = socket.gethostbyname(host) # a name like nas-casa.local works too
with socket.create_connection((ip, port), timeout=1.5):
ok = True
except OSError:
ok = False
return ok, round((time.monotonic() - t0) * 1000)
With that I check the Homepage dashboard (port 80), the Git server (3000), Uptime Kuma (3001), the NAS (5000), the router (80), the laptop (22, SSH) and the 3D printer (1883, MQTT). Because the NAS is looked up by name, it’s still found even if its IP changes.
So that twelve checks don’t take twelve times as long, I run them in parallel with a ThreadPoolExecutor, and cache the result for 20 seconds so I don’t hammer anything if the dashboard is open in two tabs.
Reading the last backup
My backup script writes lines like this to a log:
2026-09-24 03:30:08 === copia OK (248M en el NAS) ===
The dashboard reads the file from the end backwards and keeps the first line that matches a regular expression. If it says OK, green. Otherwise, red. Simple and good enough.
Tasks in a JSON file, no database
Tasks live in a tareas.json. For a list of a few dozen items, you don’t need more. Just two precautions:
- A lock (
threading.Lock) so two simultaneous requests don’t clash. - Atomic writes: write to a temp file, then rename it over the real one.
def save(tasks):
tmp = TASKS.with_suffix(".tmp")
tmp.write_text(json.dumps(tasks, ensure_ascii=False, indent=1))
tmp.replace(TASKS) # atomic: either the whole old file or the whole new one
If the Pi loses power right while saving, the tasks file is never left half-written.
Because it’s a normal API, my AI assistant can add tasks for me with a POST request from another computer at home. I say “put it on the command centre that I need to…” and it shows up on the dashboard.
Separating content from code
At first the warnings (“the PC has no scheduled backups”, “the printer has no fixed IP”…) were hard-coded in the HTML. Every change meant editing the page. Now they live in a contenido.json that the dashboard reloads every few minutes. Updating the dashboard means editing a JSON file, with no restart.
Running it as a service (and locking it down a bit)
To start it automatically with the Pi, a systemd service:
[Service]
User=pi
WorkingDirectory=/home/pi/centro_mando
ExecStart=/usr/bin/python3 server.py
Restart=always
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/pi/centro_mando
PrivateTmp=true
The bottom lines are hardening: the service sees almost the whole system as read-only and can only write to its own folder. It’s a home dashboard with no password inside my network, so if someone ever tricks it, the damage is limited to that folder.
What about the page?
HTML, CSS and a bit of JavaScript, no frameworks. It’s designed to be viewed full-screen on a computer: a row of green or red status tiles, and underneath three columns with warnings and projects, upcoming deadlines and tasks. It even has its own logo, a little gauge with a green dot in the middle.
What I learned
- Less is more. Python’s standard library covers a lot: HTTP server, JSON, threads, sockets.
- Check services, not machines. A responding port says far more than a ping.
- Keep data out of the code. Tasks and warnings in JSON make the dashboard easy to maintain.
- A dashboard doesn’t replace alerts. The dashboard is for when you look at it. For when you don’t, there are phone notifications.
Did it stick?
Three quick questions. Each right answer is worth 10 XP.