CreaRack-SL

Robustez F1+F4: kill de instancias por PATH + auditoría de empaquetado del Agente

Contexto

Reinstalar el .exe del Local Agent de forma manual (descarga + doble click) sobre una instancia viva dejaba instancias duplicadas corriendo (footgun s183). Esto ocurría porque:

  1. psutil no iba empaquetado: la función find_running_agent_processes() hace import psutil pero la librería nunca entró en el onefile (ni requirements-agent.lock ni --hidden-import), por lo que devolvía [] siempre. El kill fallaba silenciosamente en el binario (aunque funcionaba en desarrollo).

  2. La vía de respaldo (wmic) era insegura: cuando psutil fallaba, se usaba wmic process ... get processid | taskkill /PID, pero mataba “todo excepto el pid actual”. En un onefile de PyInstaller el programa es en realidad DOS procesos (bootloader + hijo), así que podía matar a su propio padre o dejar duplicados. Además, wmic está retirado en Windows 11 24H2+.

  3. Filtro por nombre era débil: un instalador descargado a Downloads/CreaRackAgent.exe y otro instalado en %APPDATA%\CreaRackAgent\ tienen el mismo nombre. Matar por nombre (IMAGENAME) no distinguía.

Decisión

Matar instancias por PATH del ejecutable, no por nombre. Psutil viaja empaquetado en el .exe desde la versión 2.13.2 (Agent):

F1 · Kill seguro por PATH

  • Función principal: find_running_agent_processes() filtra por ruta del ejecutable bajo INSTALL_DIR (excluyendo el par de procesos propio: pid + ppid).

    • Solo mata procesos cuyo .exe está en %APPDATA%\CreaRackAgent\ (o la ruta configurada).
    • Nunca toca el instalador que corre desde Descargas (mismo nombre, distinto path).
    • Excluye el par pid+ppid para que el onefile (bootloader + hijo) no se autolesione.
  • Implementación:

    def _is_under_install_dir(exe_path: str) -> bool:
        """True si exe_path está DENTRO del directorio de instalación (case-insensitive)."""
        exe = Path(exe_path).resolve()
        install = INSTALL_DIR.resolve()
        return install == exe.parent or install in exe.parents
    
    def find_running_agent_processes() -> list:
        """Instancias del Agente corriendo desde el directorio de INSTALACIÓN."""
        own_pids = {os.getpid(), os.getppid()}
        processes = []
        for proc in psutil.process_iter(["pid", "name", "exe"]):
            if proc.info["pid"] not in own_pids and _is_under_install_dir(proc.info.get("exe") or ""):
                processes.append(proc)
        return processes
  • Último recurso sin psutil (entornos dev):

    taskkill /F /FI "IMAGENAME eq CreaRackAgent.exe" /FI "PID ne <pid>" /FI "PID ne <ppid>"

    Sin psutil disponible, se usan filtros /FI de taskkill para excluir explícitamente el par de procesos propio. Se retira wmic (obsoleto, inseguro).

  • Dependencia nueva: psutil 7.2.2 (BSD-3-Clause) → entra en requirements-agent.lock + --hidden-import=psutil en build_agent.bat + entrada en LICENSES_AGENT.txt.

F4 · Auditoría de empaquetado (gate de selftest)

El --selftest es el gate que protege cada build y cada swap de auto-update. Se amplía para que importe también los terceros lazy que aparecen en runtime:

  • psutil — usado por el installer al kill.
  • pyasn1.codec.ber — decode de traps SNMP.
  • pysnmp.proto.api — receiver de traps.

Si en una futura compilación se olvida alguno de estos hidden-imports, el selftest lo detecta ANTES de llegar a la flota. Corrige además un drift en ATTRIBUTIONS: aiohttp estaba listado como 3.13.4 cuando el lock empaqueta 3.14.0.

Nota: win10toast NO se empaqueta a propósito — su excepción ImportError tiene fallback a MessageBox nativo.

Cierre de items

  • F1 (Agent 2.13.2): ✅ Cerrado — kill seguro por path, excluyendo pid+ppid propio.
  • F4 (Agent 2.13.2): ✅ Cerrado — selftest importa terceros lazy; auditoría de hidden-imports completada.

El item F2 ya fue cerrado en Agent 2.13.1 (traza persistente del auto-update en update_watchdog.log). El item F3 queda pendiente (firma de la versión ≠ decisión técnica pendiente).

Impacto

  • Usuario final: ninguno visible con esta versión. El beneficio se nota en la próxima reinstalación manual (descarga + doble click). No requiere instalación fresca — el fix viaja por auto-update.
  • Desarrollador: psutil ahora es dependencia explícita del Agente (lock + hidden-import auditable).
  • Build: exige recompilar el .exe (2.13.2). La nueva cadena de build genera LICENSES_AGENT.txt actualizado con BSD-3-Clause de psutil.

Cobertura de tests

6 tests nuevos en tests/agent/test_agent_installer_kill.py:

  • Filtro por path (dentro/fuera de INSTALL_DIR).
  • Exclusión del par pid+ppid propio.
  • Kill + espera (psutil.wait_procs).
  • Degradación sin psutil (taskkill con filtros PID ne).
  • Case-insensitivity en Windows.

Véase también

  • [[entity—terminal—service—agent-installer]]
  • [[entity—terminal—service—agent-selftest]]
  • [[concept—terminal—local-agent-architecture]]
  • [[feature—terminal—agent-auto-update]]
  • [[incident—20260420—agent-duplicados-s183]]