Hola,
Me he encontrado con un error que afecta todas las integraciones externas, no solo la mía: el botón Actualizar de una integración externa falla sistemáticamente, y lo que es peor, falla a medias, en silencio.
El síntoma
Después de hacer clic en « Actualizar », Gladys muestra correctamente la nueva versión como instalada. Pero el contenedor, en cambio, nunca se ha recreado:
$ docker inspect --format '{{.Config.Image}}' gladys-ext-<mi-integración>
ghcr.io/<...>:1.0.7 <-- la IMAGEN ANTIGUA, aunque la UI muestra 2.0.0
En otras palabras: el código antiguo se ejecuta bajo el nuevo manifiesto. Si la nueva versión declara funcionalidades que la antigua no implementa (widgets, desencadenadores de escena…), el núcleo las solicita a un contenedor que no sabe cómo responder.
En los registros, en el momento del clic:
<warn> errorMiddleware.js:83 TypeError: Cannot assign to read only property 'start' of object '#<Object>'
at Object.assign (<anonymous>)
at Store.setState (/src/server/lib/state/Store.js:19:12)
at StateManager.setState (/src/server/lib/state/index.js:17:38)
at ExternalIntegration.registerProxyService (/src/server/lib/external-integration/externalIntegration.registerProxyService.js:238:21)
at ExternalIntegration.update (/src/server/lib/external-integration/externalIntegration.update.js:233:8)
at update (/src/server/api/controllers/externalIntegration.controller.js:306:25)
La causa
registerProxyService() construye el proxy con Object.freeze({ start, stop, device, ... }), y StateManager.setState() fusiona en el Store existente (Object.assign) en lugar de reemplazarlo.
- 1er registro: el estado del
Storeaún esnull, el objeto se asigna → OK. - 2do registro:
Object.assign(proxyCongelado, nuevoProxy)→ excepción en su primera clave,start.
Pero init.js registra todas las integraciones externas instaladas al iniciar Gladys. Por lo tanto, en el momento en que se hace clic en « Actualizar », el Store aún existe, y la actualización nunca puede completarse. Cabe señalar que uninstall() hace bien la limpieza (deleteState en las dos claves) — es por eso que desinstalar/reinstalar funciona, mientras que actualizar nunca funciona.
Y la excepción ocurre en el peor lugar: después de detener el contenedor antiguo y reescribir la línea en la base de datos, pero antes de createIntegrationContainer(). De ahí la incoherencia base de datos/contenedor descrita anteriormente, además de las imágenes antiguas nunca limpiadas.
Reproducción mínima del error subyacente:
const StateManager = require('./server/lib/state');
const sm = new StateManager();
const proxy = () => Object.freeze({ start: async () => {}, stop: async () => {} });
sm.setState('service', 'mi-servicio', proxy()); // ok
sm.setState('service', 'mi-servicio', proxy()); // TypeError: Cannot assign to read only property 'start'
La solución temporal mientras tanto
docker rm -f gladys-ext-<mi-integración>
Luego, iniciar la integración desde la UI: start.js ya no encuentra el contenedor y lo recrea a partir de docker_image en la base de datos, por lo tanto, con la versión correcta. Hay que hacerlo cada vez que se actualiza hasta que se corrija.
La corrección
Dos líneas, en registerProxyService(), justo antes de los dos setState — exactamente lo que ya hace uninstall():
this.stateManager.deleteState('service', service.name);
this.stateManager.deleteState('serviceById', service.id);
this.stateManager.setState('service', service.name, proxyService);
this.stateManager.setState('serviceById', service.id, proxyService);
Prefiero esto a una modificación de Store.setState(): cambiar « fusionar » por « reemplazar » en un helper compartido por todo el núcleo es mucho más arriesgado que estas dos líneas locales al error.
He preparado el parche con tres pruebas de no regresión: fallan en master con el TypeError exacto y pasan con la corrección. Las suites external-integration, state y service pasan completamente (809 pruebas), prettier y eslint también. Enviaré la PR a continuación.
Versión: Gladys 5.1.0, instalación Docker Compose.
¡Gracias!