Actualizar un servidor con el agente en una carpeta NO estandar terminaba en
verde y lo dejaba con la version anterior. Todo el camino de Windows
identificaba al agente por NOMBRE, mientras que lo unico que se actualiza se
identifica por RUTA; en cuanto las dos no coincidian, nada fallaba y nada
cambiaba.
- Se alinea la tarea programada con el binario instalado. `Start-ScheduledTask`
ejecuta la ruta registrada en su accion, no el -Prefix: si difieren, se copiaba
el binario nuevo en un sitio y se arrancaba el viejo del otro. Ahora se
reapunta conservando disparador, principal, ajustes y argumentos; si no se
puede corregir, FALLA — arrancar a sabiendas el binario anterior es peor.
- La confirmacion de arranque mira la RUTA del proceso. Un agente viejo que
nunca se detuvo satisfacia igual de bien un `Get-Process -Name`. Si la ruta no
es legible (un proceso de SYSTEM no la expone sin elevacion) se acepta por
nombre y se avisa, en vez de revertir una actualizacion correcta por falta de
informacion.
- Corregido Merge-EnvFile con un config\.env de UNA linea: al asignar la salida
de un `if`, PowerShell desenrolla un array de un elemento a escalar, asi que
$lines.Count reventaba con Set-StrictMode y la siembra abortaba la instalacion.
Nuevo scripts/emular-actualizacion-windows.ps1: monta un agente falso (un .exe
real que se queda vivo), una instalacion en una carpeta y una tarea apuntando a
otra, corre el instalador y dice si la actualizacion surtio efecto. Sin elevacion
y sin tocar la instalacion real de la maquina. Es lo que destapo los dos
defectos: contra el instalador anterior reproduce el sintoma exacto —codigo de
salida 0 y "El agente esta corriendo con el binario nuevo" sobre un servidor
intacto— y contra este confirma que ya surte efecto, sin tocar la tarea cuando
ya estaba bien.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
install.sh recibió la maquinaria de actualización segura y install.ps1 nunca
recibió el equivalente. La asimetría se notaba en producción: actualizar desde
el PANEL dejaba el servidor sin agente.
- -UpdateInPlace: actualiza conservando la tarea y la configuración, sin correr
el bootstrap (una segunda instancia purga el Temp\ de la que está viva).
- No se interrumpe una restauración en curso: sale con 75 (EX_TEMPFAIL), que el
PANEL traduce a "reintenta luego". Windows no tenía esta guarda y una
reinstalación a destiempo dejaba el respaldo vetado y la base en SINGLE_USER.
- Respaldo del binario anterior y reversión automática si el nuevo no arranca.
- Rearranque garantizado en TODOS los modos: la detención corría siempre, pero
solo -Service volvía a arrancar algo.
- Espera de liberación del .exe de 5s a 30s con reintentos de la copia.
- Se distingue "no es administrador" de "es administrador con el token filtrado
por UAC", que es lo que recibe una sesión de OpenSSH. Se veían idénticos y el
remedio es el opuesto.
Y --headless deja de ser un no-op en Windows: _ensure_qt_platform() salía de
inmediato en win32, así que la tarea ONSTART arrancaba como SYSTEM en la sesión 0
con el plugin Qt 'windows' intentando crear una ventana real. En Linux el unit
fija QT_QPA_PLATFORM=offscreen por fuera, y esa asimetría escondió el defecto.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Instalando con sudo, el agente quedaba sin poder leer ni escribir NADA de lo
suyo, y tanto el panel como su sonda lo reportaban como éxito.
El bootstrap ejecuta el binario como root, y ese arranque crea todo el árbol bajo
PREFIX: config/, config/data/app.db, config/logs/, Entrada/, Procesados/,
Fallados/ y Temp/. La siembra escribe config/.env en 0600, también de root. Pero
el unit se registra con User=$SUDO_USER, así que el agente arranca como una
cuenta común que no puede leer su configuración (load_dotenv sin try/except),
ni guardar jobs en su base, ni mover ZIPs entre las carpetas de trabajo.
install.sh no hacía chown en ninguna parte.
Reproducido en un contenedor antes de arreglarlo: .env root:root 600, app.db y
Entrada/ de root, las tres operaciones del agente fallando — y `test -f` de la
sonda del panel dando ok, o sea verde justo en el caso roto.
SERVICE_USER se resuelve ahora al principio (antes se calculaba dentro del case
de --service, después del bootstrap y de la siembra), conservando la misma
cadena de precedencia. El chown va después de ambos pasos, solo cuando el script
corre como root y el servicio no es root, y solo del usuario —no del grupo—;
`chown` no toca los modos, así que el 0600 del .env sobrevive.
Con dos cuidados que no son opcionales:
- Guarda contra un PREFIX de sistema: un `chown -R` sobre / o /opt sería
catastrófico. Se rechazan las rutas de sistema y las de un solo componente,
avisando en vez de abortar una instalación ya hecha.
- Si el chown falla se reporta como ERROR con el comando de arreglo, no se traga.
Es el mismo criterio que el repo ya aplica al icacls de Windows: no se asume
que un comando de endurecimiento tuvo éxito.
Probado: arreglo, guarda de /opt, usuario inexistente, y sin sudo (no intenta
nada). Windows no necesita el arreglo simétrico y queda documentado por qué: la
tarea corre como SYSTEM con RunLevel Highest y install.ps1 no restringe ninguna
ACL, así que hereda de su carpeta padre y puede leer todo lo del Administrador.