feature/cras-update-in-place #22

Merged
acazares merged 3 commits from feature/cras-update-in-place into development 2026-07-31 13:28:45 +00:00
Member
No description provided.
acazares changed target branch from main to development 2026-07-31 13:28:35 +00:00
acazares added 3 commits 2026-07-31 13:28:35 +00:00
Dos bloqueos que impedían avanzar con la instalación en Linux.

1. La validación de PANEL_PUBLIC_URL solo comparaba que coincidiera con ORIGIN,
   y eso deja pasar el peor caso: que AMBAS valgan localhost, que es justo lo
   que produce el compose de desarrollo. Ahí no avisaba nada y la instalación
   sembraba loopback en el config/.env del destino, donde localhost es el propio
   destino y el agente acabaría hablando consigo mismo.

   Ahora se valida lo que de verdad importa —que la URL sea alcanzable desde
   otra máquina— y solo después el desajuste con ORIGIN. El mensaje también
   dejaba un consejo peligroso: "comenta PANEL_PUBLIC_URL para que tome ORIGIN"
   solo vale si ORIGIN sirve para sembrar; con ORIGIN en localhost, seguirlo
   empeora las cosas. Ese consejo ahora es condicional.

   De paso se corrige el docstring, que afirmaba que un ORIGIN equivocado se
   habría manifestado por la protección CSRF de adapter-node. No es cierto:
   svelte.config.js tiene csrf.checkOrigin en false, así que el Origin de los
   POST nunca se valida y ese razonamiento llevaba a conclusiones falsas.

2. El formulario fuerza mode='update' en cuanto el servidor tiene versión
   instalada, y una actualización exige que en la ruta destino ya viva algo.
   Eso hacía imposible mover una instalación a otra carpeta —por ejemplo al
   home, para instalar sin privilegios—: abortaba con "no hay una instalación".

   Se agrega una confirmación explícita siguiendo el molde de platformAck: el
   ack lleva la RUTA confirmada y no un booleano, así que una casilla marcada
   deja de valer si después se cambia el destino, y el servidor revalida en vez
   de confiar en la UI. La casilla advierte además que el agente anterior sigue
   corriendo: los dos reportarían con el mismo instance_key y se pisarían la
   carpeta de entrada registrada, que es por donde el panel enruta los
   respaldos.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
En entornos donde no se usa root, ninguna de las dos vías que había servía: la
elevación abortaba con 409 y `user-service` exige mover la instalación al home,
que en un servidor con el agente ya instalado en /opt pide un paso privilegiado
para retirar el unit viejo.

Se agrega una tercera vía para ACTUALIZACIONES: dejar el unit como está y solo
reemplazar el binario, que es lo único que una actualización necesita. Corre
`install.sh --update-in-place` sin prefijo de elevación.

Tres precondiciones, comprobadas por SSH ANTES de subir 270 MB, cada una con su
propio motivo de rechazo porque cada una tiene un remedio distinto:

- El directorio de instalación debe ser escribible por la cuenta SSH. Es el único
  permiso que hace falta: `install` desvincula el destino antes de crearlo, así
  que un binario en ejecución no es obstáculo (eso es cosa de `cp`).
- El unit debe correr con ese mismo usuario. Si quedó con User=root —alguien
  instaló desde un `sudo -i`— la cuenta no puede señalizar el proceso. Un User
  vacío se trata como root, que es lo que hace systemd.
- El unit debe tener Restart=always, que es quien vuelve a levantarlo. Sin eso,
  señalizarlo lo dejaría muerto.

Y se rechaza si hay una restauración en curso, aquí y otra vez en el destino.

Dos correcciones de robustez en la verificación posterior:

- El sello config/.version se sondea en vez de leerse una vez. En esta vía se
  omite el bootstrap y el sello lo escribe el proceso al reiniciarse, así que
  durante unos segundos sigue teniendo la versión ANTERIOR: la lectura única
  convertía una actualización correcta en "reporta 1.0.0, se esperaba 1.1.1".
- `systemctl is-active` puede devolver 'activating' justo tras el reinicio, así
  que la evidencia que manda es el proceso vivo con el binario nuevo.

El código 75 (EX_TEMPFAIL) de install.sh se traduce a un 409 con el motivo real
—"está restaurando, reintenta"— en vez del 502 genérico que hacía pensar que la
instalación se había roto.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dos huecos de diagnóstico que hacían pasar por sano lo que no lo estaba.

1. El check `config` usaba `test -f`, que comprueba existencia y no lectura, así
   que salía verde exactamente en el caso roto: una instalación con sudo deja
   config/.env en 0600 de root mientras el unit corre como una cuenta común, que
   no puede leerlo. El agente no arranca y la pantalla decía "Configuración
   presente: ok".

   Ahora se comprueba lectura y, además, el dueño frente al User= del unit —
   porque la sonda entra con la cuenta SSH, que no siempre es la del servicio, y
   un `test -r` desde la sesión no responde por el agente. El check se renombra a
   "Configuración legible por el servicio", que es lo que de verdad mide, y falla
   nombrando a los dos usuarios para que el remedio sea obvio.

   El cálculo del bit de lectura octal sale a una función pura probada: 0600 no
   deja leer a nadie más que al dueño, se mira el bit 4 y no el valor (620 y 611
   no son lectura), se ignora el dígito de setuid, y un modo ilegible NO se
   interpreta como permisivo — asumir lectura cuando stat devuelve '-' sería el
   error caro.

   La lectura de propiedades del unit se extrae a un helper compartido con el
   instalador. Ya hubo una divergencia por copiar esta lógica: la resolución de
   elevación existía duplicada y las dos pantallas acabaron diciendo cosas
   distintas del mismo servidor.

2. Ningún rechazo de token de servicio dejaba rastro: los seis endpoints solo
   logueaban el 500 de "token no configurado". Con el token rotado, todos los
   agentes quedan mudos —dejan de reportar versión, resolver rutas y registrar
   resultados— y desde el panel se ve igual que un agente apagado; además el
   trace_id que se le devuelve al agente no existía del lado servidor, así que
   era imposible correlacionar.

   Se agrega un helper que loguea el rechazo con trace_id, ruta e instancia, y
   distingue "sin header Authorization" de "token no coincide", que son un agente
   sin configurar y un token rotado: dos problemas con remedios distintos. El
   token NUNCA se registra, ni un fragmento suyo — es el mismo valor en todos los
   agentes, así que un prefijo en los logs ya acota el espacio de búsqueda. Hay
   prueba de eso.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
acazares merged commit c4de2f1438 into development 2026-07-31 13:28:45 +00:00
acazares deleted branch feature/cras-update-in-place 2026-07-31 13:28:45 +00:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ADUANASOFT/PANEL_BASES_ANEXO24#22
No description provided.