Gestiona el Tiempo de Ordenador de tu Hijo con mqttNanny

En mi último artículo configuré mi ODROID-H2 para que fuera el primer ordenador (ejecutando Linux) de mi hijo de 7 años. Como sabes, un gran poder conlleva una gran responsabilidad, esto significa que tengo que ser capaz de imponer algunos límites de tiempo de uso del ordenador. Particularmente dado que, mi hijo pasará las vacaciones de verano con sus abuelos. Es la excusa perfecta para crear un sistema de gestión y monitorización de tiempo en Linux e integrarlo en Home Assistant

Cómo funciona

El software es esencialmente un script en Python 3 que se ejecuta como un demonio. Tiene dos modos de funcionamiento: local y remoto. Se inicia en modo local cuando no puede conectarse a un agente MQTT. En este modo, carga sus límites desde un archivo y es menos flexible. Cuando se ejecuta en modo remoto, el usuario actual obtiene un permiso desde un agente MQTT permitiendo que el padre lo controle (y siga lo que el usuario está haciendo) dinámicamente. Básicamente, el modo remoto ofrece todas las características, mientras que el modo local es una alternativa en caso de problemas de conectividad.

El programa principal se ejecuta cada minuto en bucle y recopila (y envia) información, como:

  • ¿Cuál es el TTY activo?
  • ¿Qué usuario ha iniciado sesión con X11 en el TTY actual?
  • ¿Se está ejecutando el protector de pantalla?
  • ¿Cuál es el título de la aplicación activa?

Para el usuario conectado en ese momento, se activa un temporizador de cuenta atrás que va restando minutos mientras que el protector de pantalla no esté funcionando. Se envían algunas notificaciones al usuario (notificación-envío con sonido) cuando quedan 10/5/1 minutos. Cuando se agota el tiempo, el protector de pantalla se activa y la cuenta de usuario se deshabilita (el inicio de sesión con contraseña falla). Si el protector de pantalla no se activa a la quinta 5 vez consecutiva, el sistema se apaga. Una vez que la cuota asignada sea mayor que cero, el demonio restaura la contraseña de esa cuenta.

MQTT te permite ver los datos recopilados, cambiar la asignación de tiempo para cada usuario y también solicitar capturas de pantalla de la sesión de escritorio del usuario. Para seguir adelante, necesitarás conocer y tener un agente MQTT y también ejecutar Home Assistant; estas cuestiones no las trataremos en este artículo, aunque ya han sido analizadas en anteriores artículos.

Instalación y configuración

Puedes descargar e instalar el código desde mi página de GitHub:

$ sudo apt-get install git
$ cd /usr/local/share
$ sudo git clone https://github.com/mad-ady/mqttNanny.git
$ cd mqttNanny
$ sudo cp mqtt Nanny.yaml /etc/mqttNanny.yaml
$ sudo cp mqttNanny.service /etc/systemd/system/mqttNanny.service
Necesitarás instalar también algunas dependencias:
$ sudo apt-get install python3-pip python3-yaml python3-notify2 \ 
    espeak xscreensaver xdotool imagemagick
$ sudo pip3 install paho-mqtt
Ten en cuenta que, actualmente, solo admite sistemas Linux, aunque el código está escrito para que también se pueda extender a otros sistemas operativos (las peticiones son bienvenidas). El sistema Linux necesita ejecutar Xorg (Xwayland probablemente necesita muchos cambios) y el programa del protector de pantalla debe ser Xscreensaver (no mate-screensaver, xfce-screensaver, etc.). No obstante, en el futuro se podría añadir soporte para otros protectores de pantalla (bienvenidas son las peticiones). El código ha sido probado en un ODROID-H2 (x86_64), ODROID-XU4 (armhf) y un ODROID-N2 (arm64). Para pasar de mate-screensaver a xscreensaver, puede hacer lo siguiente:
$ sudo apt-get purge mate-screensaver
$ xscreensaver-demo
Mientras se ejecuta xscreensaver-demo, puedes seleccionar qué protectores de pantalla quieres usar y también activar la opción "Lock screen after" para forzarlo a solicitar una contraseña. Deberás hacer que el entorno de escritorio inicie el protector de pantalla automáticamente copiándolo en la carpeta autostart:
$ mkdir ~/.config/autostart
$ cp /usr/share/applications/xscreensaver-properties.desktop \
     ~/.config/autostart/
$ sed -i 's/xscreensaver-demo/xscreensaver/' \ 
     ~/.config/autostart/xscreensaver-properties.desktop
Además, asegúrate de que la hora del sistema esté configurada correctamente en el arranque (ya sea a través de un RTC, NTP o fake-hwclock), de lo contrario, el temporizador en modo local no funcionará correctamente.

Necesitarás editar la configuración (/etc/mqttNanny.yaml) y fijar tus valores por defecto más relevantes. Asegúrate de que el sangrado sea el correcto en el archivo (puedes validarlo en http://www.yamllint.com/), de lo contrario, el programa no se iniciará. Las opciones disponibles son las siguientes:

  • mqttServer - el nombre ip/dns de tu agente MQTT
  • mqttPort - el puerto TCP en el que se ejecuta tu agente (por defecto 1883)
  • mqttUser/mqttPass - tus credenciales MQTT. Si tu agente no usa autentificación, simplemente omite las líneas
  • baseTopic - un prefijo para crear los temas utilizados para enviar/recibir mensajes. Yo personalmente uso ha//
  • mqttTimeTopicSuffix - el tiempo restante para cada usuario se transmitirá en un tema creado desde //. En mi caso es algo así como ha/pc/odroid/timeRemaining
  • mqttScreenshot - el tema donde deseas recibir imágenes de captura de pantalla (MQTT también puedes transmitir datos binarios)
  • mqttScreenshotCommand - el tema donde puede solicitar capturas de pantalla o no. Esto se puede asignar a un parámetro en Home Assistant para activar o desactivar las capturas de pantalla
  • mqttScreenshotDuration - tiempo hasta que la función de captura de pantalla se apague. Si lo quieres siempre activado, configúralo en 0
  • mqttScreenshotInterval - con qué frecuencia (en segundos) debería tomar capturas de pantalla
  • screenshotHeight - cambia el tamaño de la captura de pantalla a esta altura (manteniendo la relación de aspecto), por razones de eficiencia
  • checkInterval - con qué frecuencia se debe marcar el reloj interno del script. Un valor de 60 segundos significa que la asignación se verifica cada minuto. También recibes cambios de aplicación cada minuto..
  • externalNotify - debe ser False o apuntar a un programa/script que toma una cadena como argumento y te envía el mensaje. Por ejemplo, yo lo configuré con /usr/local/bin/telegram-send y recibo notificaciones de eventos a través de un robot de telegram

Figura 1. Notificaciones externas

  • no-signal - debe apuntar a un archivo de imagen que se muestra cuando se desactivan las capturas de pantalla
  • users - contiene una lista de usuarios para monitorizar. Si un usuario no está en la lista, su tiempo no se gestionará, pero los datos sobre su sesión y capturas de pantalla serán reportados a través de MQTT
  • defaultOfflineTime - cuántos minutos tiene el usuario al inicio del programa en el caso de que el programa se ejecute en modo local, sin una conexión con el agente MQTT. Si hay un archivo con la asignación actual del usuario en /usr/local/share/mqttNanny//, ese valor será el que se cargue en su lugar. Este archivo se actualiza con cada cambio/disminución de la cuota asignada.

Figura 2. Configuración de ejemplo para dos usuarios.

Para hacer frente a posibles problemas de seguridad, haz que el archivo de configuración sea legible solo por root:

$ sudo chown root:root /etc/mqttNanny.yaml
$ sudo chmod 400 /etc/mqttNanny.yaml
Se puede usar un archivo de servicio para controlar el demonio:
$ cat /etc/systemd/system/mqttNanny.service

[Unit]
Description=mqttNanny
After=network.target

[Service]
ExecStart=/usr/local/share/mqttNanny/mqttNanny.py
Type=simple
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
Luego ejecuta los siguientes comandos:
$ sudo systemctl enable mqttNanny
$ sudo systemctl start mqttNanny
Puedes seguir los registros log para solucionar posibles problemas ejecutando:
$ sudo journalctl -f -u mqttNanny
Figura 3. Registros Logs

Integración de Home Assistant

Una vez que mqttNanny se ejecute en el ordenador de destino, estaría bien poder controlarlo desde una interfaz web. Descubrí que Home Assistant es la interfaz perfecta para domótica y scripts personalizados/creados por uno mismo.

Añadiremos algunos componentes a configuration.yaml de Home Assistant.yaml que se comunicará con el script a través de MQTT. Consulta la documentación del componente correspondiente para más detalles.

camera:
  - platform: mqtt
    name: N2-PC Display
    topic: 'ha/pc/screenshot'

switch:
  - platform: mqtt
    command_topic: 'ha/pc/screenshot/command'
    state_topic: 'ha/pc/screenshot/command'
    payload_on: 'enable'
    payload_off: 'disable'
    name: N2-PC Enable screenshot
    retain: true

sensor:
  - platform: mqtt
    state_topic: 'ha/pc/activeUser'
    name: N2 Active User
  - platform: mqtt
    state_topic: 'ha/pc/display'
    name: N2 Active display
  - platform: mqtt
    state_topic: 'ha/pc/odroid/timeRemaining'
    name: N2 Odroid time remaining
    value_template: '{{ value | int }}'
    unit_of_measurement: 'minutes'
  - platform: mqtt
    state_topic: 'ha/pc/application'
    name: N2 Active application

binary_sensor:
  - platform: mqtt
    state_topic: 'ha/pc/screensaver'
    name: N2 Screensaver
    payload_on: True
    payload_off: False
Una vez que se vuelva a cargar la configuración de Home Assistant, podrás agregar los nuevos elementos en la interfaz web de Lovelace. A continuación, se muestra una configuración de ejemplo basada en los anteriores valores.

Añade un panel de entidades con esta configuración:

entities:
  - entity: sensor.n2_active_user
  - entity: sensor.n2_active_display
  - entity: sensor.n2_active_application
  - entity: binary_sensor.n2_screensaver
  - entity: input_number.pc_odroid_time_remaining
  - entity: sensor.n2_odroid_time_remaining
  - entity: switch.n2_pc_enable_screenshot
show_header_toggle: false
title: N2
type: entities
Añade un panel de entidad imagen con esta configuración:
camera-view: live
entity: camera.n2_pc_display
type: picture-entity
El resultado final debería ser como el de la siguiente imagen:

Figura 4. Control de Home Assistant

También necesitarás configurar algo de automatización para poder cambiar el tiempo permitido para un determinado usuario. Realiza tus cambios en automations.yaml y reinicia Home Assistant:

- id: '1557839383'
  alias: N2 change remaining time for Odroid
  trigger:
  - entity_id: input_number.pc_odroid_time_remaining
  platform: state
  action:
  - data:
    payload: '{{ states(input_number.pc_odroid_time_remaining) | int }}'
    retain: true
    topic: ha/pc/odroid/timeRemaining
  service: mqtt.publish

- id: '1562675409185'
  alias: N2 get remaining time for Odroid
  trigger:
  - platform: mqtt
  topic: ha/pc/odroid/timeRemaining
  condition: []
  action:
  - data_template:
    entity_id: input_number.pc_odroid_time_remaining
    value: '{{ trigger.payload }}'
  service: input_number.set_value

- id: '1562675848675'
  alias: N2 set daily time allowance for user Odroid
  trigger:
  - at: 00:15:00
  platform: time
  condition: []
  action:
  - data:
    payload: 35
    retain: true
    topic: ha/pc/odroid/timeRemaining
  service: mqtt.publish
La última automatización fija el tiempo diario permitido para el usuario odroid en 35 minutos y se ejecuta a las 00:15. Esto es simplemente un ejemplo. Puede diseñar tus propias automatizaciones que definen cuándo el usuario puede usar el ordenador. Por ejemplo, si solo deseas dar acceso durante un intervalo de tiempo, puede ejecutar una automatización para agregar la asignación a la hora de inicio deseada y una automatización diferente para eliminar la asignación antes de la hora de irse a dormir. Puede controlar la cantidad de tiempo que se asigna a cada usuario en función de determinadas cosas como los días de colegio (https://www.home-assistant.io/components/workday/) o tal vez si ha terminado o no sus deberes (me gustaría saber cómo podríamos medir esto de forma automática).

Fallos y futuras mejoras

Ninguno de los códigos que escribo es perfecto y éste no es una excepción. Hay algunos problemas y formas de evitar mqttNanny que he identificado hasta ahora (parches/ideas son bienvenidos). El programa está destinado a bloquear a una persona con poca experiencia en Linux, pero no será muy eficaz con un administrador de sistemas experimentado. Cuando se ejecuta en modo local, no hay un mecanismo integrado que permite conceder más tiempo al usuario. Si el ordenador está encendido durante un período prolongado de tiempo, se debe reiniciar el servicio mqttNanny para añadir la correspondiente asignación diaria. Por lo tanto, podría no funcionar como cabría esperar, si se suspende el ordenador en lugar de apagarlo. Al cambiar entre los modos local/remoto, los datos recibidos del agente MQTT tienen mayor prioridad. Por ejemplo, el niño podría empezar sin conexión a la red, agotar su asignación predeterminada y luego reiniciar la conexión de red al agente. Recibiría su asignación remota como si nada hubiera pasado. Cuando se ejecuta en modo local, la protección es bastante pobre si el usuario puede cambiar la hora del sistema. Podría "pedir prestado" tiempo de otros días en el futuro/pasado. Solo es monitorizada/bloqueada la sesión X11. El usuario puede iniciar sesión en un TTY antes del bloqueo, o mediante ssh con autentificación de clave y puede seguir usando el sistema (basado en terminal).. Si el usuario mata el proceso del protector de pantalla, no hay nada que bloquee la sesión. El proceso intenta bloquear el protector de pantalla 5 veces consecutivas y, si falla, se apagará el sistema. Pero el usuario podría usar el exploit en el intento número 4 y desbloquear el protector de pantalla desde la línea de comandos (podría ejecutar un script para desbloquearlo continuamente). Si el ordenador no se apaga correctamente (por ejemplo, se desenchufa directamente de la corriente), no tendrás el estado correcto en MQTT. El archivo /etc/mqttNanny.yaml utilizado por el código debe ser legible solo por root, ya que contiene sus credenciales MQTT. De lo contrario, el usuario podría conocerlas y usar mosquitto_pub para cambiar su tiempo asignado Los archivos que almacenan la asignación diaria utilizada en /usr/local/share/mqttNanny// no se limpian automáticamente y eventualmente pueden llenar su disco en unos pocos siglos (1,5 MB / año). Una tarea cron para eliminar archivos antiguos sería de gran utilidad.

Espero que las versiones futuras del código agreguen soporte para otros protectores de pantalla para Linux, así como soporte para MacOS y Windows (el código específico del sistema operativo es modular y debería ser fácilmente ampliable). Esperamos nuevas versiones/ parches.

Referencias

https://www.home-assistant.io/

Be the first to comment

Leave a Reply