Home Assistant, con limen como límite
limen nació para que un agente de IA entrase en nuestras máquinas sin darle una shell. Ahora también lleva su estado a Home Assistant, desde donde gestionamos la oficina, con la misma regla: cada máquina decide qué se puede ejecutar en ella, y limen decide qué sale por MQTT y qué se puede pulsar desde allí.
limen, el límite del agente
limen es un servidor MCP que llega a cada máquina por SSH. Cada máquina dice en su limen.toml
qué ficheros se pueden leer y qué packs de scripts ofrece, y cada script es una herramienta con los
argumentos que declara su cabecera: limen los comprueba antes de ejecutarlo, y no hay shell ni línea de
comandos. Los límites están en la máquina, no en el agente:
cada permiso nuevo pasa por un pull request.
El mismo límite, para Home Assistant
El hub, limen serve, ya hablaba con todas las máquinas. Con [mqtt] se conecta
además a un broker y usa el discovery de Home Assistant: cada máquina aparece como un dispositivo, sin
integración que instalar. Y en el hub decidimos dos cosas:
- Qué se publica: los sensores, scripts que solo leen y que el hub ejecuta cada cierto tiempo.
- Qué se puede llamar: los botones, scripts que el hub ejecuta, con argumentos fijos, cuando alguien los pulsa.
# limen.toml del hub
[mqtt]
enabled = true
url = "mqtt://192.168.1.229:1883"
user = "limen"
password_file = "mqtt-password"
[[mqtt.sensor]]
node = "nas"
id = "disk_used"
script = "disk_used"
args = { path = "/srv" }
every = "60s"
discovery = { unit_of_measurement = "%" }
[[mqtt.button]]
node = "nas"
id = "restart_immich"
script = "restart_container"
args = { name = "immich_server" }
Que una máquina ofrezca un script no lo lleva a Home Assistant: solo llega lo que está escrito en el hub.
Un sensor es un script que imprime un valor
limen no interpreta nada de lo que ejecuta, tampoco aquí: el estado del sensor es la salida del script, ya
redactada. Así que los scripts para sensores imprimen un solo valor, redondeado para que no cambie en cada
lectura. El pack system trae cpu_used, memory_used,
disk_used, temperature y gpu, y el pack docker,
container_state:
# packs/system/disk_used
#!/bin/sh
#: description = "Percent used of the file system that holds a path, as a whole number"
#: read_only = true
#: [args.path]
#: type = "string"
#: default = "/"
#: pattern = '^/[A-Za-z0-9/._-]{0,255}$'
set -eu
df -P -- "$LIMEN_ARG_PATH" | awk 'NR == 2 { sub(/%/, "", $5); print $5 }'
Si hace falta un campo de un JSON, lo saca el value_template de Home Assistant, no limen.
Dónde sigue el límite
- Las máquinas no cambian. El hub ejecuta los scripts por SSH igual que para el agente: la máquina solo acepta los de sus packs, con los argumentos que declara su cabecera.
-
Un sensor solo lee. Su script tiene que decir
read_only = true, o el hub lo rechaza, y la máquina rechaza cada lectura. - Pulsar un botón no elige nada. Se ejecuta el script con los argumentos escritos en el hub, y nunca uno que limen haría confirmar a una persona. Como botones declaramos solo lo que nos parece bien que pulse cualquiera que llegue al broker, como reiniciar un contenedor.
- Una máquina que deja de ofrecer un script se lo lleva. Su sensor o su botón queda no disponible en Home Assistant.
¿Y lnxlink?
lnxlink es el complemento natural para llevar un Linux a
Home Assistant: se instala en cada máquina, publica decenas de sensores y ofrece acciones como apagarla.
Nosotros preferimos que el agente y Home Assistant entren por la misma puerta: no hay nada más que instalar
ni actualizar en las máquinas, y todo lo que Home Assistant ve o pulsa es un script que la máquina ofrece y
que hemos declarado en el hub. El precio: un estado tiene la frescura de su every, cada
lectura es una ejecución por SSH, solo hay los sensores para los que hay un script, y si el hub se para,
Home Assistant ve todo como no disponible.
Los detalles, en la especificación de limen, y cómo escribir un script para un sensor, en su docs/scripts.md.