Mi centro de mando casero: un panel en la Raspberry Pi con Python y cero dependencias

Un panel web que comprueba en vivo qué servicios de casa responden, lee la última copia y guarda tareas. Unas 200 líneas de Python sin instalar nada.

Iván· Publicado el · 4 minRead in English →

Tengo varias cosas corriendo en casa: una Raspberry Pi con media docena de servicios, un NAS, un portátil para prácticas, una impresora 3D… y la sensación de no saber nunca de un vistazo qué está funcionando.

Existen paneles ya hechos (yo uso Homepage para tener los enlaces a mano), pero quería algo mío que me enseñara tres cosas en una sola pantalla:

  1. Qué responde ahora mismo y qué no.
  2. Cómo fue la copia de seguridad de anoche.
  3. Mis tareas pendientes, que yo y mi asistente de IA podamos leer y escribir.

El resultado es un panel en Python con cero dependencias: solo la librería estándar. Nada de pip install, nada que actualizar.

La idea general

Un único archivo server.py que hace dos cosas:

  • Sirve una página HTML (index.html) con el panel.
  • Ofrece una pequeña API en JSON que la página consulta cada 30 segundos.
GET    /api/estado        ¿qué responde?
GET    /api/copia         ¿cómo fue la última copia?
GET    /api/tareas        lista de tareas
POST   /api/tareas        crear una tarea
PATCH  /api/tareas/<id>   marcarla como hecha
DELETE /api/tareas/<id>   borrarla

Comprobar si algo está vivo, sin ping

Mi primera idea fue hacer ping a cada máquina. Pero ping necesita permisos especiales para enviar paquetes ICMP, y además solo te dice que la máquina está encendida, no que el servicio funcione.

Es mejor intentar conectarse al puerto del servicio. Si acepta la conexión, está vivo:

import socket, time

def check(host, port):
    t0 = time.monotonic()
    try:
        ip = socket.gethostbyname(host)   # también vale un nombre como nas-casa.local
        with socket.create_connection((ip, port), timeout=1.5):
            ok = True
    except OSError:
        ok = False
    return ok, round((time.monotonic() - t0) * 1000)

Con eso compruebo el panel de Homepage (puerto 80), el repositorio Git (3000), Uptime Kuma (3001), el NAS (5000), el router (80), el portátil (22, SSH) y la impresora 3D (1883, MQTT). Como el NAS se busca por nombre, aunque cambie de IP lo sigue encontrando.

Para que doce comprobaciones no tarden doce veces más, las lanzo en paralelo con un ThreadPoolExecutor, y guardo el resultado 20 segundos en caché para no machacar a nadie si tengo el panel abierto en dos pestañas.

Leer la última copia de seguridad

Mi script de copias escribe en un registro líneas como esta:

2026-09-24 03:30:08 === copia OK (248M en el NAS) ===

El panel lee el archivo desde el final hacia atrás y se queda con la primera línea que coincida con una expresión regular. Si pone OK, verde. Si no, rojo. Sencillo y suficiente.

Tareas en un JSON, sin base de datos

Las tareas viven en un tareas.json. Para una lista de decenas de tareas no hace falta más. Solo hay dos cuidados:

  • Un candado (threading.Lock) para que dos peticiones a la vez no se pisen.
  • Escritura atómica: escribo en un archivo temporal y después lo renombro encima del bueno.
def guardar(tareas):
    tmp = TAREAS.with_suffix(".tmp")
    tmp.write_text(json.dumps(tareas, ensure_ascii=False, indent=1))
    tmp.replace(TAREAS)   # atómico: o el viejo entero, o el nuevo entero

Si la Pi se apaga justo mientras guarda, el archivo de tareas nunca queda a medias.

Como es una API normal, mi asistente de IA puede apuntarme tareas con una petición POST desde otro ordenador de casa. Le digo «apúntame en el centro de mando que tengo que…» y aparece en el panel.

Separar el contenido del código

Al principio las alertas («el PC no tiene copias periódicas», «la impresora no tiene IP fija»…) estaban escritas en el HTML. Cada cambio era tocar la página. Ahora viven en un contenido.json que el panel lee cada pocos minutos. Actualizar el panel es editar un JSON, sin reiniciar nada.

Ejecutarlo como servicio (y encerrarlo un poco)

Para que arranque solo con la Pi, un servicio de systemd:

[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

Las líneas de abajo son endurecimiento: el servicio ve casi todo el sistema en solo lectura y solo puede escribir en su propia carpeta. Es un panel casero sin contraseña dentro de mi red, así que si algún día alguien lo engaña, el daño queda limitado a esa carpeta.

¿Y la página?

HTML, CSS y un poco de JavaScript, sin frameworks. Está pensada para verse a pantalla completa en el ordenador: una fila con las casillas de estado en verde o rojo, y debajo tres columnas con alertas y proyectos, entregas pendientes y tareas. Incluso tiene su propio logo, un pequeño marcador con un punto verde en el centro.

Lo que aprendí

  • Menos es más. La librería estándar de Python da para muchísimo: servidor HTTP, JSON, hilos, sockets.
  • Comprueba servicios, no máquinas. Un puerto que responde dice mucho más que un ping.
  • Guarda los datos fuera del código. Tareas y alertas en JSON hacen el panel fácil de mantener.
  • Un panel no sustituye a los avisos. El panel es para cuando lo miras. Para cuando no lo miras están las notificaciones al móvil.
Mini-quiz

¿Te has quedado con la idea?

Tres preguntas rápidas. Cada acierto suma 10 XP.

  1. ¿Cómo comprueba el panel si un servicio está vivo?
  2. ¿Por qué el panel guarda las tareas escribiendo primero en un archivo .tmp?
  3. ¿Qué hace ProtectSystem=strict en el servicio de systemd?
  • #python
  • #raspberry-pi
  • #systemd
  • #dashboard
  • #homelab
Esc