Skip to content

Auditoría acotada de consola de mantenimiento — 2026-10-19

Fuente revisada: ~/Dev/homelab-maint, HEAD 763ef72 al comenzar. No se consultó ni mutó runtime CT212; no se ejecutaron tareas reales, SSH, apagados, backups ni operaciones HA. Fixtures locales en /tmp/homelab-maint-audit-20261019/reproduce.py, con estado/logs temporales y herramientas simuladas. Esta revisión no certifica el inventario vivo.

1. Excepciones y aborto temprano bloqueaban indefinidamente la consola

_run_task sólo trataba timeout y cancelación durante consumo de stdout. Errores de creación del proceso, apertura/escritura del log o persistencia abandonaban RUNS y estado running; al ser exclusivas las tareas, cualquier operación posterior recibía 409. Una cancelación antes de que arrancara la corutina también dejaba el registro sin recoger.

Reproducción original: simulando OSError en create_subprocess_shell, la corutina terminaba pero RUNS=['fixture'], tasks.fixture.status=running y transition.status=running. No hubo proceso real en esta prueba.

Corrección LOCAL preparada por autorización del coordinador: wrapper con cierre y liberación en finally, notificación de fin a SSE, fallo parcial persistido cuando es posible y estado veraz en memoria si el disco falla. Conserva nivel previo y último éxito ante excepciones. Creación protegida con shield: si se cancela durante spawn, recoge y termina el proceso antes de liberar la exclusión. Callback recoge cancelación anterior al arranque de la corutina. Se retiró la espera artificial posterior al fin para no dejar ventana de aborto después de confirmar éxito.

Abortar es idempotente: un segundo POST no vuelve a cancelar la recolección del proceso ni su terminación.

Validación: 17 pruebas locales pasan mediante .venv/bin/python -m unittest discover -s tests -q; incluyen seis nuevas: fallo spawn, fallo persistencia, fallo apertura log, aborto antes de empezar y aborto durante spawn y doble aborto durante spawn. Se mantienen éxito, timeout silencioso/con salida, aborto, niveles y preflight previo existentes. git diff --check limpio. Pendiente revisión/commit/despliegue por coordinador; NO está aplicado a CT212.

Actualización del coordinador: revisado, suite final 17/17, publicado 60a85d5 y desplegado selectivamente. CT212 conserva L0, running=[], transición nula; hash app.py <REDACTED>. Los puntos 2 y 3 siguientes siguen pendientes.

2. Los niveles L2–L4 aún pueden ocultar fallos de comandos críticos

Casos reproducidos extrayendo cuerpos remotos originales de YAML y ejecutándolos localmente con herramientas simuladas que fallan:

  • enter-l2, primer paso: docker stop ... | tr ...; echo devuelve rc0 aunque Docker devuelva rc23. Afecta también a exit-l2 con docker start.
  • enter-l3, paso LXC: cada pct exec devuelve rc23, pero el bucle termina rc0 y sólo imprime 100/251/172/281/280 por su echo. exit-l3 es equivalente.
  • enter-l4, desmontaje VM208: fallo de sudo -n umount -l seguido de echo umounted produce rc0 y mensaje aparente de éxito. En fixture falló el stub sudo antes de llegar a umount; prueba exactamente que cualquier fallo de ese comando queda oculto por el echo final.

No basta con que _run_task propague rc: estos comandos le entregan rc0. La verificación L3 cuenta procesos Docker sin comprobar un valor esperado; no demuestra que las aplicaciones LXC se hayan parado. L2 verifica un solo punto NFS y tampoco demuestra parada de todos los escritores. L4 comprueba NAS sin ping, sin demostrar que los clientes hayan soltado montajes.

Propuesta: reconciliar primero inventario/propietario de cada recurso, después reemplazar cadenas permisivas por agregación explícita de errores y verificaciones que afirmen el estado requerido. Ausente/opcional debe declararse, no inferirse ignorando errores. No se reescribieron tareas ni inventario a ciegas.

3. Reinicio conserva transición interrumpida pero vuelve a admitir operaciones

El arranque hace STATE=load_state() y RUNS={} sin reconciliación. Si el proceso cae o se reinicia durante una transición, el archivo conserva tareas/transición running; la API no las considera al admitir una operación nueva. Además la UI sólo destaca partial_or_failed, por lo que el estado running huérfano no tiene el aviso de cambio parcial.

Fixture: persistir nivel L2 y transición hacia L3 en estado running, recargar estado con RUNS={} y llamar api_run('nas-reboot', confirm=True) con runner simulado devuelve {'started': 'nas-reboot'}. No se ejecutó ningún reinicio NAS.

Relevancia adicional: p51-maint-on avisa explícitamente de que CT212/consola puede reiniciarse al migrarse. Persistencia de estado por sí sola no da garantía de saber qué paso remoto terminó tras ese reinicio.

Propuesta: convertir tareas/transición running heredadas a interrumpidas al arranque y exigir reconciliación explícita para mutaciones posteriores. Registrar paso y resultado para orientar la recuperación, sin reanudar comandos remotos ni elevar nivel automáticamente. No asumir que terminar SSH canceló la operación remota. Este punto queda pendiente; no está cubierto por el wrapper nuevo.