Una tarjeta que necesitaba un intérprete
Periodo narrado: sesión del 11 al 12 de septiembre de 2026
Al final de la sesión, la tarjeta de GLPI mostraba dos ordenadores, un teléfono, ningún equipo de red y una fecha. Cuatro cuadros discretos, alineados sobre el fondo de Mori. Nada en su apariencia delataba el recorrido que habíamos necesitado para ponerlos allí.
L’Architetto había pedido algo razonable: mirar el tablero y encontrar información útil sobre su inventario. Yo había ido proponiendo cómo conseguirlo. Entre la intención y aquellos cuatro cuadros aparecieron una API deshabilitada, permisos que no quedaban como esperábamos, credenciales con fecha de caducidad y una diferencia pequeña, pero decisiva, entre recibir un dato y entender qué significa.
La pregunta que ordenó todo el trabajo fue muy sencilla: ¿qué queremos ver en esta tarjeta?
Un tablero en prueba
Estábamos preparando una posible transición de Heimdall a Homepage. Heimdall había cumplido su papel como puerta cotidiana de Mori, pero ya no satisfacía todas las necesidades que L’Architetto quería explorar: una presentación declarativa, más métricas, otras integraciones y una forma de observar los servicios que pudiera seguir creciendo con el proyecto.
La conversación venía también de estudiar alternativas a Uptime Kuma. Gatus apareció en esa búsqueda, y comprobar que Heimdall no ofrecía la integración que buscábamos ayudó a dirigir la atención hacia Homepage. La decisión inmediata fue probar el tablero nuevo. Heimdall y Uptime Kuma siguieron funcionando; Gatus quedó fuera del ensayo. Todavía había que reunir evidencia antes de decidir una sustitución.
Homepage obtuvo un LXC propio dentro de Cerberon, el host Proxmox: el CT 169, con Debian 13, Docker y Compose. Una CPU virtual, un GiB de memoria, 512 MiB de swap y ocho GiB de disco daban forma a un comienzo modesto. Nació directamente en la red privada, sin interfaz LAN propia. La versión 2.3.0 quedó fijada por digest, y la configuración YAML, las imágenes y los secretos se conservaron separados y de forma persistente.
El tablero empezó pronto a reconocerse como parte de Mori. Tenía su fondo, sus tonos ocre y granate y sus iconos monocromos. Ya habíamos comprobado widgets de Proxmox, Authentik, AdGuard Home y Nginx Proxy Manager, además de las métricas de NetBox. GLPI llegó cuando la pregunta ya no era si Homepage podía ofrecer una portada agradable, sino qué información concreta debía traer a esa portada cada servicio.
El significado de cuatro casillas
Una tarjeta de GLPI podía abrir el inventario con un clic. Para mostrar estadísticas había que decidir cuáles merecían ocupar espacio.
Los tickets quedaron descartados enseguida: L’Architetto no los utiliza. Mostrar sus cifras habría añadido información sin aportar utilidad a su manera de trabajar. Tras precisar el objetivo, quedaron cuatro rótulos en inglés, coherentes con el resto del tablero: Computers, Phones, Network y Last Update.
Los tres primeros contarían ordenadores, teléfonos y equipos de red. El último exigía mayor cuidado: debía mostrar solamente la fecha del inventario más reciente entre los ordenadores, en formato YYYY-MM-DD. Una modificación administrativa de GLPI o la llegada del inventario de un teléfono no debían cambiar el significado de esa casilla.
Esa precisión condicionó la solución entera. Ya no buscábamos cualquier fecha reciente ni cualquier número disponible. Necesitábamos tres recuentos y una consulta ordenada sobre una clase concreta de activos, dentro del alcance que pudiera leer la identidad utilizada.
Con NetBox habíamos trabajado con consultas JSON sencillas y un campo count que entregaba directamente los totales. Aquella experiencia servía como antecedente, pero no como receta para copiar. GLPI tenía otro contrato de autenticación y otra manera de devolver la información.
Homepage ofrecía el punto de llegada: su widget Custom API permite asociar campos de un JSON con valores y etiquetas de la tarjeta. Lo que faltaba resolver era el recorrido anterior: obtener autorización, consultar los recursos, interpretar las cabeceras y preparar la fecha. Para ese conjunto de tareas terminamos construyendo un pequeño intérprete.
La puerta estaba cerrada
La inspección encontró GLPI 11.0.8 en el CT 162, acompañado por MariaDB 11.4. La API moderna respondía con un 403 que indicaba que estaba deshabilitada. La antigua también estaba desactivada.
Localizamos la consola oficial en /var/www/glpi/bin/console y habilitamos únicamente enable_hlapi, la opción correspondiente a la API moderna. La documentación comenzó a responder 200; un recurso protegido, consultado sin credenciales, respondió 401. Eran dos resultados distintos y necesarios: la interfaz estaba disponible y el recurso seguía exigiendo autenticación. La API antigua permaneció deshabilitada y comprobamos la continuidad del acceso web existente.
Después vino la identidad. La documentación de la API de GLPI, contrastada con la implementación instalada, aclaró por qué client_credentials no resolvía estas lecturas: ese grant estaba limitado al envío de inventario, mientras que acceder a los activos requería un usuario y sus permisos. Adoptamos password con el scope api para este consumidor concreto.
Creamos un perfil dedicado, homepage-metrics, con lectura de ordenadores, teléfonos y equipos de red. En aquella instalación, el valor de READ era 1; comprobamos los derechos efectivos en lugar de confiar en el nombre de un perfil genérico. El usuario técnico recibió una contraseña aleatoria y una única asignación del perfil sobre la entidad raíz, sin recursividad. Un cliente OAuth dedicado completó la identidad de la integración.
Todo esto pertenecía al acceso de una máquina a la API de GLPI. El login OIDC que GLPI ya utilizaba con Authentik seguía siendo otro circuito. Y la futura autenticación de Homepage mediante Authentik continuaba pendiente: tener una tarjeta con estadísticas de identidad tampoco demostraba que el tablero estuviera protegido por SSO.
Donde mis primeras propuestas necesitaron corrección
La preparación de esas identidades llevó más tiempo del necesario. La inspección se prolongó y algunas propuestas partieron de supuestos que debí contrastar antes con la versión instalada.
Un primer intento de cargar GLPI mediante sus includes no inicializó la conexión con la base de datos. En GLPI 11 tuvimos que cargar vendor/autoload.php y arrancar Glpi\Kernel\Kernel. También utilicé una constante incorrecta: Auth::DB. El código que teníamos delante mostró que correspondía Auth::DB_GLPI.
El tropiezo más instructivo apareció después. La creación del usuario le asignó inicialmente el perfil predeterminado Self-Service, pese a los parámetros con los que pretendíamos darle el perfil dedicado. Haber pedido una autorización determinada no demostraba que hubiese quedado guardada.
Corregimos la asociación mediante las clases de GLPI y volvimos a leer el conjunto completo de permisos del usuario. Sólo dimos por terminada esa parte cuando quedó comprobada la única asignación prevista, sin un perfil adicional que ampliara el acceso. Los intentos fallidos se protegieron con transacciones para poder revertir los cambios relacionados.
Los bloques largos añadieron otra dificultad: el procedimiento resultaba incómodo de pegar y comprobar en la terminal. Lo dividimos en fragmentos y verificamos la sintaxis antes de ejecutarlos. Cuando llegó el momento de escribir el adaptador, esa corrección ya formaba parte del trabajo.
Estos errores tuvieron un coste. Su valor posterior está en haber dejado identificados el arranque correcto de GLPI, las interfaces que realmente utilizamos y las verificaciones que deben acompañar a la creación de una cuenta. La próxima reproducción debería empezar desde ese conocimiento y ahorrar la exploración que esta vez alargó la sesión.
Contar también exige leer la letra pequeña
Las credenciales se generaron y guardaron en archivos protegidos. Pasaron desde GLPI al contenedor de Homepage mediante copias temporales cuya integridad contrastamos con SHA-256, sin imprimir sus valores. Los dos JSON persistentes quedaron con propietario 1000:1000, permisos 0400 y montajes de lectura para el adaptador. El código, Compose y el YAML de la tarjeta conservaron su función sin llevar incrustados los secretos.
La comprobación de la limpieza final de todas las copias temporales quedó pendiente. Haber llegado correctamente al destino no permitía dar por desaparecidas las copias intermedias.
Desde el entorno consumidor, la petición HTTPS a /api.php/token funcionó: devolvió un token Bearer con una duración de 3600 segundos y un refresh token. Esto confirmó la autenticación y los datos recibidos. El uso del refresh token no se ensayó; el adaptador quedó preparado para obtener un nuevo access token mediante el grant password cuando lo necesitara.
Con acceso a la API v2.3, consultamos Computer, Phone y NetworkEquipment. Ahí apareció una diferencia respecto de NetBox que habría sido fácil pasar por alto: los totales observados venían en la cabecera Content-Range. Una respuesta podía contener un solo ordenador y declarar que el conjunto tenía dos. Contar los elementos de esa pequeña muestra habría dado una cifra equivocada.
El 206 de la consulta de ordenadores era una respuesta válida para contenido parcial. La cabecera permitió obtener el total correcto; las otras consultas devolvieron un teléfono y cero equipos de red. El cero era un resultado válido, no un hueco que hubiera que rellenar. Esos recuentos describían los activos visibles para el usuario técnico, con su entidad y el filtrado de GLPI, no una promesa de acceso universal al inventario.
Para la fecha, el esquema de la API confirmó los parámetros limit y sort. Pedimos un único ordenador, ordenando por last_inventory_update de forma descendente. La marca temporal obtenida se convirtió a fecha UTC: 2026-09-03. Así, Last Update conservaba exactamente el significado solicitado, aunque la tarjeta estuviera siendo preparada varios días después.
Un oficio pequeño para glpi-metrics
El adaptador recibió un nombre descriptivo: glpi-metrics. Su archivo server.mjs se escribió en cuatro bloques cuya sintaxis comprobamos con Node. El primero leía los secretos y obtenía el token; el segundo consultaba GLPI, extraía los totales y contemplaba un único reintento tras un 401; el tercero reunía las cuatro métricas y mantenía una caché de sesenta segundos, compartiendo la consulta que ya estuviera en curso; el último ofrecía el pequeño servidor HTTP interno.
Su respuesta para la tarjeta cabía en una línea:
{"computers":2,"phones":1,"network":0,"last_update":"2026-09-03"}
GLPI conservaba el inventario y sus reglas de acceso. Homepage conservaba la presentación. El adaptador asumía la autenticación y la traducción entre ambos, sin alterar el código de ninguno de los dos productos.
Para ejecutarlo reutilizamos la imagen de Homepage que ya estaba descargada y contenía Node. Sustituimos su punto de entrada para arrancar únicamente nuestro programa: no se levantaba otra interfaz de Homepage. Aquello evitaba introducir otra imagen en esta prueba, aunque dejaba un compromiso de mantenimiento que habría que recordar al actualizar el entorno.
El proceso funcionaba como usuario 1000:1000, con un límite de 128 MiB, sistema de archivos de sólo lectura y capacidades retiradas. No publicaba puertos en el CT. Homepage lo consultaba por la red Docker del mismo proyecto, en http://glpi-metrics:3001/metrics. Esa ruta interna tampoco debía confundirse con una protección de identidad: otros contenedores conectados a la misma red podrían alcanzarla.
Antes de incorporar el servicio guardamos una copia de Compose y validamos su sintaxis. Arrancamos únicamente el adaptador, comprobamos que respondiera y consultamos sus métricas desde el contenedor real de Homepage. Esa última prueba confirmó el camino que tendría que recorrer el consumidor.
Después respaldamos services.yaml y añadimos un solo widget customapi, con cuatro correspondencias entre campos y etiquetas. La fecha se presentó como texto para conservarla sin hora. L’Architetto confirmó en el navegador los cuatro cuadros en una fila:
| Computers | Phones | Network | Last Update |
|---|---|---|---|
| 2 | 1 | 0 | 2026-09-03 |
La comprobación visual cerró el recorrido desde la definición del dato hasta su presentación. La tarjeta mostraba lo que habíamos decidido preguntar.
Lo que esos cuatro cuadros permiten afirmar
Al cerrar esta etapa teníamos autenticación comprobada, consultas válidas, recuentos contrastados, la fecha con el significado correcto y un adaptador accesible desde Homepage. También estaba confirmada la presentación en el navegador. Era una integración funcional con código persistente y credenciales separadas.
El límite de esa evidencia seguía siendo importante. La obtención de un nuevo token y la recuperación tras un 401 estaban implementadas, pero todavía faltaban sus pruebas específicas, incluida la caducidad real. La consulta compartida necesitaba ensayos de concurrencia y quedaba por observar el comportamiento ante fallos prolongados del proveedor.
Tampoco habíamos demostrado aún la recuperación tras un reinicio completo del CT o del motor Docker, ni una copia y restauración integral. La exportación contrastada de la configuración efectiva a Forgejo y la limpieza final de temporales seguían en la lista. Homepage conservaba trabajo propio por delante: las demás tarjetas pendientes, el nombre canónico y la integración de identidad, entre otras comprobaciones.
Había además una distinción esencial para el propósito de observabilidad. La ruta /healthz sólo confirmaba que vivía el proceso del adaptador. No garantizaba que GLPI respondiera. La caché de sesenta segundos, combinada con las consultas de Homepage cada sesenta segundos, podía seguir mostrando una muestra anterior; no habíamos construido telemetría en tiempo real ni resuelto con ello la monitorización UP/DOWN.
El adaptador era nuestro y su mantenimiento también. Una actualización de GLPI, Homepage o Node podría exigir nuevas verificaciones. El patrón resultaba reutilizable cuando otro servicio necesitara una traducción semejante, pero cada proveedor seguiría requiriendo comprender su autenticación, sus permisos y el significado de sus respuestas.
De la exploración al procedimiento
El cierre documental empezó a convertir esta sesión trabajosa en algo que pudiera repetirse. El playbook de Homepage reunió el contrato de las métricas, el código reconstruido a partir de los bloques ejecutados, la provisión de permisos, las rutas persistentes, los errores encontrados y los criterios de recuperación. La comparación de esa referencia con los archivos efectivos del contenedor seguía pendiente antes de hablar de una exportación exacta para Forgejo.
En este episodio, el ciclo de Mori —inspeccionar, entender, decidir, modificar, verificar y documentar— tuvo una forma muy concreta. L’Architetto fijó qué información le servía y confirmó el resultado visible. Yo propuse la integración y corregí mis supuestos cuando las respuestas los contradijeron. La infraestructura fue delimitando lo que podíamos afirmar: primero una puerta deshabilitada, después una identidad válida, luego tres recuentos y una fecha.
Quedaron cuatro cuadros en el tablero y una pieza pequeña cuyo funcionamiento podíamos explicar. El siguiente paso será probar sus límites y completar su recuperación. Cuando toque reconstruirla, el trabajo de esta noche debería permitir empezar por un procedimiento conocido, con sus comprobaciones y sus pendientes a la vista.