Recuperación NFS y hold de mantenimiento — 20 octubre 2026¶
Continuación de las comprobaciones de capacidad y snapshots. Trabajo en checkout separado; Vera y sus commits siguen bajo el control de Ramon.
Corrección del arranque del lock, 22 octubre 16:30–16:32 CEST¶
El monitor registraba NFS sano en sus35 últimas líneas, pero salía75 con
NFS_RECOVERY_UNKNOWN. Faltaba /tmp/homelab-ctl.lock tras el reboot: las tareas
watch/update de RunWisp están retenidas y no crean el archivo. El perfil completo
sigue siendo el HELD conocido; no se cambió ni exportó. El pendiente se conserva.
Se instaló /etc/tmpfiles.d/homelab-ctl.conf, root0644, con esta regla nativa:
Es el mismo pathname usado por ambos flock de RunWisp en media208 y por la
guarda NFS. El lock de /run/lock del controlador central CT212 es distinto.
La operación f crea si falta, conserva un inode existente y puede ajustar
permisos/propiedad; no es f+ ni una sustitución de locks. Véase el
manual de systemd249.
La creación nativa y una segunda aplicación conservaron el inode abierto;
owner1000:1000 y modo0600 comprobados. No se encontró un descriptor abierto con
el pathname antiguo o eliminado antes de crear el archivo ausente.
A las16:32 el siguiente ciclo ordinario del monitor devolvió
NFS_RECOVERY_HELD, conservando75 y el pendiente. Esto corrige la inicialización
y distingue la retención intencional del error UNKNOWN anterior; no habilita
recuperación, no limpia la alerta ni acredita reparar NFS. No se ejecutó el
monitor, una copia, un rebind o una notificación como prueba. Un primer intento
se detuvo antes de escribir por fijar un boot histórico; se conservó su registro.
Fuente en ansible/roles/homelab_tools/{tasks/lifecycle_lock.yml,files/homelab-ctl-tmpfiles.conf},
limitada a media208. El rol conserva never; no se espera convergencia ordinaria.
Para reconstruir sólo esta configuración, revisar primero el estado actual y usar:
ansible-playbook -i ansible/inventory.yml ansible/playbook.yml --limit media-208 --tags homelab_tools_lifecycle_lock --skip-tags always
La selección --list-tasks se verificó localmente; la instalación real fue
selectiva sobre ese archivo y el lock, sin ejecutar el playbook completo ni
tocar el TOML privado. El archivo queda disponible para el tmpfiles de arranque;
no se provocó un reboot para demostrar esa fase. Recibos privados:
nfs-health-failure-20261022/{guard-metadata.json,apply-lock-v2.json,after-ordinary.json}.
Problema y comportamiento preparado¶
Retener watch/update en RunWisp no detenía el monitor NFS. Sus rutas de recuperación todavía podían ejecutar Compose, reiniciar Docker/autofs o arrancar contenedores. Eso también podía aplicar el checkout de Compose durante el hold.
La recuperación y el rebind pasan ahora por una guarda común. La reconciliación sólo solicita permiso si existe un pendiente. La guarda conserva el mismo flock existente del actualizador durante toda la operación y las llamadas anidadas. Cada punto de mutación vuelve a verificar el descriptor y el perfil activo. Un permiso desconocido, un archivo inseguro, un lock ocupado o un hold activo impiden la reparación y devuelven75; no se crean ni sustituyen locks.
La comprobación sana sin pendientes sigue recogiendo estado. Las lecturas y snapshots no autorizan por sí mismas ninguna reparación. Si la reparación queda bloqueada o parcial se conserva el pendiente; el chequeo no reemplaza entonces el baseline. Un rebind fallido ya no genera el mensaje de recuperación completa. Una inspección fallida o inválida tampoco se interpreta como contenedor parado.
Perfil y límite de disponibilidad¶
VM208 tiene Python3.10.12; no se presupone tomllib ni se instala un parser. La guarda usa perfiles completos revisados de RunWisp, mediante SHA256 del archivo privado completo; no imprime el documento ni valores de credenciales.
- Unheld revisado:
<REDACTED>. - Held conocido:
<REDACTED>. - Cualquier otro contenido, fragmentos adicionales o metadatos inesperados: desconocido, sin reparación automática.
Un cambio legítimo futuro del TOML también puede bloquear la recuperación NFS. Debe revisarse el perfil completo y actualizarse explícitamente la guarda; no basta con copiar un hash nuevo ni eliminar su comprobación. Las tareas de backup y los horarios de RunWisp no se modifican por esta guarda.
Mientras el hold impida reparar, una avería NFS necesita intervención en una ventana revisada: la barrera sacrifica recuperación automática para impedir reinicios o despliegues durante el diagnóstico de estabilidad. No se presenta esa situación como disponibilidad normal.
Fuente y runtime, antes de instalar¶
Lectura pasiva de metadatos alrededor de08:54 CEST:
- Servicio nfs-health-monitor inactivo, último resultado success/0, User root.
- Script regular1000:1000/0755, hash
<REDACTED>. - El runtime coincide exactamente con la fuente anterior a c7eccd2 salvo la asignación NTFY_URL. Esa diferencia se conserva en la copia privada y deberá preservarse en cualquier candidato de instalación; no se publica su valor.
Copia privada local de revisión:
~/.local/state/nfs-recovery-review-20261020/runtime-before.sh, modo0600.
Esta observación no acredita salud NFS, un montaje remoto real ni ausencia de
una ejecución posterior. No se ha ejecutado el monitor completo, ningún
recover/rebind ni una notificación como prueba.
Trabajo que sigue pendiente¶
La guarda no convierte toda la recuperación en segura: cuando el perfil permite actuar, la selección histórica aún usa nombres, hay rebind sobre todos los binds y Compose puede arrancar servicios dormidos. Para resolverlo hacen falta IDs inmutables y un diario de autorizaciones preavería, con coordinación Sablier; una lectura running seguida de restart no elimina esa carrera.
También quedan pendientes la prueba de montaje NFS real, la promoción atómica de pendientes y un presupuesto global de tiempo. No liberar el hold para probar esas rutas ni declarar la recuperación completa por tener esta guarda.
Validación de la fuente¶
Revisión independiente de Bash y del módulo Python correcta.35 casos NFS (13 snapshots,8 flujos de reparación y14 de guarda) pasan con dobles locales. Incluyen un hijo Bash benigno real que verifica la herencia del descriptor y una llamada anidada, sin ejecutar el monitor de producción. Los ocho puntos de mutación tienen checkpoint previo; se prueban bloqueo, fallo de inspección, conservación de pendientes/contador y ausencia de recuperación verde falsa. Junto a15 casos N‑1 y6 balloon son56 pruebas de la CI específica para Python3.10 y3.12. No se ejecutan Docker, SSH, NFS ni notificaciones reales.
La guarda no tiene deadline global ni limpieza de grupos de procesos: tras cancelar el padre puede quedar un hijo vivo con el lock. Un timeout de observación no acredita que la reparación haya terminado; comprobar el proceso antes de actuar de nuevo, sin borrar el lock para forzar otra operación.
Estado al preparar esta publicación: fuente validada, instalación pendiente. El candidato de instalación debe preservar NTFY_URL, ambos archivos y sus permisos; bajo un perfil held conocido, publicar primero el Bash protegido deja las reparaciones bloqueadas incluso si aún falta el módulo. Dos renames no constituyen una transacción: un fallo parcial requiere lectura de hashes y recibo antes de intervenir, sin restaurar automáticamente el Bash sin guarda.
Convergencia natural de normalidad, alrededor de09:04 CEST¶
Una lectura mínima de cada host confirmó el helper N‑1 publicado previamente,
hash <REDACTED>:
- Pmx50: pull08:58:30–08:59:16, success, status0, inactivo y MainPID0.
- Pmx51: pull08:59:59–09:01:23, success, status0, inactivo y MainPID0.
No se forzó el pull ni se ejecutó normalidad completo. Esta convergencia acredita la versión instalada, no el margen N‑1 actual ni compatibilidad funcional con toda salida futura de HA. La guarda NFS publicada en0f55754 tiene CI39384104208 correcta:15+6+35 casos en cada Python3.10 y3.12.
Instalación selectiva y comprobación nativa,09:08–09:09 CEST¶
Instalación finalizada alrededor de09:08:11 CEST, sin ejecutar recover/rebind, notificaciones ni jobs, y sin parar o reprogramar timers. El instalador comprobó el perfil held completo e inalterado, el flock existente, servicio inactivo, MainPID0 y cero procesos monitor/guarda; conservó la URL local de avisos.
- Monitor1000:1000/0755:
<REDACTED>. - Helper nuevo1000:1000/0644:
<REDACTED>. - Backup privado y recibo:
/root/rollback-nfs-guard-2xv1390g/en VM208;before.shconserva el monitor anterior,receipt.jsonacredita ambos renames. - Instalador local revisado:
~/.local/state/nfs-recovery-review-20261020/install_guard.py, hash<REDACTED>. Cinco fixtures adicionales extraen exactamente su clasificador de procesos por AST y pasan sin SSH. Compilación Python y sintaxis Bash nativas correctas.
A09:09:17 CEST se cargó el helper instalado con nombre de módulo de validación (sin main), se abrió el lock existente y se llamó exclusivamente a verify. Resultado HELD bajo Python3.10.12 y configuración real, sin invocar ninguna operación de recuperación. La lectura de metadatos de09:08:25 encontró timer activo y último ciclo09:08:00–09:08:04 success/0: ese ciclo fue anterior a la instalación; no se usa como prueba del nuevo monitor.
Para recuperar, leer primero hashes y recibo. Restaurar automáticamente el Bash anterior reabriría el bypass del hold: antes hay que revisar la ventana, la barrera y el estado de las operaciones. No eliminar ni reemplazar el lock. El proceso de instalación no ofrece exclusión absoluta frente a una ejecución manual o un timer antiguo que no respeten ese lock entre comprobación y rename; las comprobaciones de inactividad reducen esa ventana, sin declararla imposible.
Primer ciclo natural posterior confirmado¶
Lectura pasiva a09:10:56,513685 CEST: ciclo09:10:00–09:10:04, success, ExecMainStatus0, MainPID0 e inactivo. El ExecStart del servicio contiene la ruta del monitor instalado. Timer activo, siguiente ejecución09:12:00 CEST. Este ciclo sí es posterior al recibo de instalación09:08:11. No se forzó ningún job. Acredita ejecución del monitor después del reemplazo; no es un ensayo de recuperación frente a una avería, ni demuestra un montaje NFS real por sí solo.
Seguimiento: montaje efectivo y pendientes íntegros¶
Fuente preparada después de09:13 CEST, con revisión independiente. El chequeo
ya no acredita NFS por encontrar un directorio accesible: el helper readonly
mount usa el FD de ubicación y su mnt_id para seleccionar el registro del
montaje efectivo. Exige el target exacto, raíz /, tipo nfs/nfs4 y export esperado;
una segunda apertura fresca debe conservar el mismo ID. No elige la primera
línea ni el mayor ID. Se aceptan capas autofs+NFS cuando el FD señala NFS; se
rechaza NFS oculto por una capa local. Remount exige la comprobación completa.
Fuentes primarias: open(2), O_PATH y proc_pid_fdinfo(5), mnt_id. El descriptor no lee contenidos; los accesos ls/stat previos del monitor se conservan. La muestra no congela el namespace ni garantiza integridad de datos. Una fixture privada en VM208 encontró util-linux2.37.2 y varias filas para un stack sintético con findmnt; no se asumió el comportamiento de una versión nueva. No se crearon montajes ni se contactó el NAS en esa fixture.
Lecturas exclusivas de /proc/self/mountinfo de VM208 alrededor de09:13 y09:18
confirmaron registro del target /mnt/nfs_media, raíz /, nfs y source esperado.
Esas lecturas se usaron para comprobar la configuración, no como prueba de
accesibilidad ni de visibilidad efectiva bajo carreras.
La promoción de pendientes copia completamente el baseline a un temporal0600 del mismo directorio y lo publica mediante GNU ln-T sin sobrescritura. Un pendiente regular existente, incluido uno vacío, conserva sus bytes e inode; una colisión durante publicación tampoco lo sustituye. Se rechazan enlaces, archivos ilegibles y baseline ausente cuando no hay un pendiente anterior. El estado final como symlink se rechaza antes de aceptar pendientes existentes. La recuperación no avanza si falla esa promoción. La reconciliación exige lectura completa y retirada comprobada antes de anunciar finalización.
Validación preparada:75 pruebas NFS, más15 N‑1 y6 balloon (96 en total).
Mac ejecutó74 de las75 NFS; sólo omite la fixture nativa O_PATH, que se ejecuta
en Linux. El adaptador de ln usa os.link en Mac y GNU /bin/ln -T real en CI
Linux. La revisión de ambas partes no encontró bloqueadores tras el ajuste del
symlink de STATE_DIR. La validación Linux y la instalación de este seguimiento
se deben registrar aparte: todavía no se acreditan por esta preparación.
Límites conservados: ancestros y escritores privilegiados del estado son de confianza; publicación atómica no equivale a durabilidad fsync frente a caída del host. No se ha implementado todavía el diario por ID ni coordinación Sablier, y no se libera el hold.
Aplicación del seguimiento y corrección de compatibilidad nsfs¶
Fuente f979feb, CI39386347385:96 casos correctos en cada Python3.10/3.12, incluidos O_PATH y GNU ln-T nativos. La primera instalación se detuvo antes de modificar archivos, coincidiendo con el ciclo09:26. Se releyeron hashes, perfil y servicio inactivo antes de una nueva ejecución, sin reintento ciego.
El segundo intento terminó09:26:37 CEST, con helper compatible primero y Bash
después, preservando NTFY local y1000:1000/0755+0644. Backup de ambos originales
y recibo: /root/rollback-nfs-mount-pending-flbtwhx1/. Monitor instalado:
<REDACTED>.
Instalador privado revisado install_mount_pending.py, hash
<REDACTED>.
No se invocaron recover/rebind, notificaciones ni cambios de timers.
La prueba nativa detectó un defecto no cubierto por aquella CI: el parser
rechazaba raíces opacas net:[…] de montajes nsfs ajenos al NFS. El FD seleccionado
sí correspondía al target, raíz, tipo y export esperados. Se corrigió para
mantener la exigencia de raíz absoluta sólo sobre el registro seleccionado,
sin relajar expected_mount ni el hold. Dos regresiones nuevas cubren ese caso;
fuente5a28045, CI39386930115:98 casos correctos en cada Python3.10/3.12, sin skips.
Helper corregido instalado09:30:13, hash
<REDACTED>.
Antes del reemplazo, la versión candidata acreditó EXPECTED_NFS en el namespace
real y HELD con el lock/config reales. Backup del helper previo y recibo:
/root/rollback-nfs-nsfs-t58d7dff/.
Los ciclos09:28 y09:30 crearon dos fallos de prueba y un pendiente falso; el
hold bloqueó la reparación. Se comprobó que las dos incidencias eran de la
prueba de montaje, sin timeout ls/stat ni mountpoint ausente; el pendiente
coincidía byte a byte con el baseline y sus19 contenedores seguían running.
Después de verificar EXPECTED_NFS+HELD y comprobar identidad/metadatos, se
archivaron reversiblemente el pendiente y el contador09:31:52 en
/root/rollback-nfs-false-proof-_wnjo2i9/, con recibo. No se tocó el baseline ni
se arrancó o reinició ningún contenedor.
Estado observado al cerrar este seguimiento¶
La prueba de montaje efectiva corregida pasa en VM208; el hold sigue activo.
El ciclo09:32 obtuvo NFS OK pero no pudo completar snapshot: conservó el baseline.
Una fixture de diagnóstico con las mismas dos funciones reales, lecturas Docker
y un STATE_DIR privado terminó correctamente con19 running, sin escribir el
estado real; evidencia privada /root/nfs-snapshot-diagnostic-pk_pznqj/.
La colección directa de metadatos también mostró90 contenedores,24 con binds
NFS y ninguna inspección fallida o estado inválido en esa lectura posterior.
Eso no explica retrospectivamente el fallo del ciclo.
El baseline real se actualizó naturalmente09:34:04 con19 registros. El ciclo 09:36 volvió a terminar con1 y conservó ese baseline; no había pendiente ni contador de fallo NFS, y el timer permanecía activo. Queda pendiente diagnosticar este fallo intermitente de snapshot, sin atribuirlo a Docker, permisos o I/O sin evidencia. No se declara todo el monitor resuelto por la CI o por la prueba de montaje. Continúan pendientes IDs/Sablier y el presupuesto global de tiempo.
Inventario por ID y reintento completo (09:49 CEST)¶
La comparación acotada de los ciclos09:32/09:34/09:36 encontró una diferencia útil: los dos fallidos consultaron91 montajes y sólo llegaron a una inspección Running; el correcto consultó90 montajes y24 estados. El primer nombre de los dos ciclos fallidos era el mismo y ya no existía en la lectura posterior. Esto apoya una carrera por desaparición entre consultas; como el stderr histórico se descartó, no demuestra retrospectivamente una causa única.
El subcomando readonly snapshot sustituye esa colección por ps -aq --no-trunc
y un único inspect por IDs completos, con proyección JSONL limitada a ID,
nombre, Running y orígenes de montajes. Valida todas las filas y exige el
conjunto exacto de IDs, claves únicas, nombres válidos y booleanos estrictos
antes de emitir resultado. Una colección fallida se descarta por completo y
se permite un único reintento desde un inventario nuevo. Un error persistente
no publica stdout ni sustituye el baseline. Inventario vacío correcto no
invoca inspect sin argumentos. Se conserva la selección Running=true con
origen NFS exacto o subruta, excluyendo colisiones de prefijos.
Se fija el socket Docker local y se retiran cinco variables de control Docker sólo del entorno de los subprocesos. Root consulta Docker directamente; monxas usa sudo-n; otros usuarios fallan de forma conservadora. El caso normal requiere dos CLI, el vacío uno y un reintento completo como máximo cuatro. Cada CLI tiene timeout10s y se aceptan hasta256 IDs y2MiB por salida capturada; este último límite es de aceptación después de capturar, no un límite duro de memoria. No es una fotografía atómica del estado Docker. El baseline sigue guardando nombres y no resuelve el diario futuro por ID ni Sablier.
Revisión independiente sin bloqueadores. Pruebas:91 NFS (90 ejecutadas en Mac,
una nativa Linux pendiente en CI), más15 N-1 y6 balloon:112 casos en total.
La candidata helper
Instalación del inventario y primer ciclo natural (09:52 CEST)¶
Fuent
La lectura09:50:24 confirmó hashes anteriores51de6aa8/40cb4998 y perfil heldf7b3a3ad.
El servicio estaba failed/failed con MainPID0 tras el ciclo09:50, que conservó
el baseline09:46. El instalador revisado acepta sólo inactive/dead/0 o
failed/failed/0 y exige además cero procesos del monitor/helper; no ejecuta
reset-failed ni altera el timer. Tres pruebas de la función idle exacta y
cinco del clasificador /proc exacto pasaron. Instalador privado
install_snapshot_collection.py, SHA
<REDACTED>.
Instalación terminada09:51:42, primero helper compatible y después monitor,
con CAS de pareja, bloqueo compartido, configuración exacta held, identidad y
metadatos comprobados antes de cada reemplazo. Se preservó NTFY local sin
publicar su valor. Backup de ambos archivos y recibo:
/root/rollback-nfs-snapshot-collection-b472997z/.
Monitor runtime:
<REDACTED>.
Helper runtime:
<REDACTED>.
Propietario1000:1000 y modos0755/0644 conservados. No se invocó reparación,
reinicio de aplicaciones, notificación sintética ni ejecución manual del monitor.
Los dos reemplazos no constituyen una transacción atómica frente a escritores
externos; no hubo rollback ni reintento automático de instalación.
A09:52:01 el helper instalado confirmó EXPECTED_NFS y HELD usando el namespace, config y lock reales. El ciclo natural09:52 empezó y terminó dentro de ese segundo, Resultsuccess/ExecMainStatus0. El baseline se renovó09:52:00.573240 con 19 registros/208bytes; no existían pending-restore ni contador de fallos. La lectura de su journal contó un NFS OK y cero errores de snapshot. El timer seguía active/waiting con próxima ejecución09:54. Es el primer ciclo posterior correcto, no una garantía de ausencia futura de carreras o fallos. Continúan pendientes seguimiento sostenido, diario de recuperación por ID/Sablier y presupuesto global de recuperación.
Fuente pendiente: plazo de notificación¶
En una corrección posterior sólo de fuente, notify() añade a curl
--connect-timeout 5 --max-time 20. El envío sigue siendo best-effort: un fallo
no cambia el resultado de recuperación y no provoca reintentos. No se envió
ninguna notificación de prueba ni se instaló este cambio.
Estos límites acotan cada envío de notificación; no constituyen un presupuesto global de recuperación ni resuelven los posibles bloqueos de NFS o del kernel. La suite local descubre91 pruebas NFS:90 pasan y una se omite en macOS porque requiere Linux/O_PATH. La sintaxis Bash y la revisión del diff pasan.
Notificaciones acotadas instaladas y seguimiento (10:00 CEST)¶
Fuente78fceeb publicada en main. CI39390007368 pasó112 casos porPython3.10/3.12;
documentación39390007450 correcta. Instalador privado de un solo reemplazo
install_notify_bound.py, SHA
<REDACTED>,
revisado por dos agentes y con8 pruebas exactas de idle/proc correctas.
Instalación09:59:45: sólo Bash, preservando NTFY y0755/1000:1000;
helper32589781 mantuvo bytes, inode y metadatos completos. Perfil held exacto,
lock, pareja de hashes, ausencia de proceso y servicio detenido comprobados.
Backup y recibo: /root/rollback-nfs-notify-bound-k2g16cz5/.
Monitor runtime:
<REDACTED>.
A09:59:07 el journal confirmó NFS OK sin errores de snapshot en los cuatro ciclos09:52/09:54/09:56/09:58. A10:00:09 se confirmó EXPECTED_NFS+HELD y el primer ciclo natural posterior al límite de notificación:10:00:00, RC0, baseline20registros actualizado10:00:00.766134, sin pendientes ni contador. La variación19/20 de registros es una observación del inventario, no una lista fija ni prueba de avería. No se enviaron avisos de prueba ni se ejecutó reparación. El ciclo sano no ejercita el timeout HTTP; acredita continuidad del monitor.
Siguiente reparación: identidad y grupos bajo demanda¶
La revisión de fuentes detecta que repair_media_services todavía usa una
lista fija y puede escalar a reinicio global Docker; rebind_nfs_containers_impl
selecciona ps-a y podría arrancar contenedores dormidos. El hold instalado
impide esas reparaciones mientras permanece aplicado. No se considera cerrada
la política de recuperación por haber corregido su inventario.
Sablier tiene proveedor Docker mediante su proxy sin montar el lock de VM208
(homelab-stacks/sablier/compose.yaml). La allowlist
services/control-plane-access/sablier-socket.conf permite start/stop/wait sin
esa barrera. El enrutador deCT212 usa otro lock /run/lock/homelab-ctl.lock,
frente a /tmp/homelab-ctl.lock deVM208. No hay cooperación demostrada entre ellos.
La clasificación debe proteger también todos los integrantes de
tunnel.lazy.names y rutas estáticas lazy, no sólo el contenedor etiquetado;
Jellyfin Share incluye su DB. Contratos on-demand y sus dependencias también
son protección conservadora. Una foto running antigua no autoriza despertar
un grupo Sablier cuyo TTL haya caducado. Se prepara un manifiesto/plan por ID
completo con estado manual explícito para identidades cambiadas, grupos bajo
demanda y clasificación desconocida. Esa preparación no está integrada ni
instalada y todavía requiere contrastar la política con las fuentes vivas.
Planificador por IDs preparado en fuente (10:03 CEST)¶
Nuevo módulo puro scripts/vm208/nfs-recovery-plan.py, sin integración con el
monitor ni instalación. make_manifest(snapshot,now) conserva ID completo,
nombre y conjunto canónico de orígenes NFS de contenedores running y no paused.
plan_recovery(prior,current,policy,now) sólo propone restart/start por el ID
anterior cuando coinciden identidad, nombre y orígenes NFS y la clasificación
explícita es always sin protección. IDs ausentes/reutilizados, renombres,
montajes cambiados, paused y grupos protegidos/desconocidos quedan manuales.
Contenedores nuevos no ganan permiso por aparecer en el inventario actual.
El CLI recibe JSON acotado a2MiB por stdin, rechaza claves duplicadas y esquemas
incoherentes, y sólo escribe JSON. No descubre archivos, llama Docker, adquiere
locks ni ejecuta acciones. Su restored siempre es false; plan_complete
sólo indica ausencia de filas manuales. Un plan mixto contiene propuestas y
pendientes, nunca una recuperación exitosa. La política contiene afirmaciones
explícitas del caller, no prueba de contratos vivos ni del cierre de grupos
Sablier. Validar cronología UTC no concede autorización por edad del snapshot.
La revisión encontró una incompatibilidad antes de publicar: Source vacío de un montaje tmpfs ajeno a NFS se rechazaba. Se permite como noNFS, conservando validación de rutas no vacías, límites, NUL y componentes relativos en NFS. Diecisiete pruebas nuevas cubren identidades, fuentes, paused, clasificación, JSON, límites y ausencia de procesos; revisión independiente sin bloqueadores. La suite NFS pasa108 casos descubiertos en Mac (107 ejecutados, uno O_PATH nativo omitido). La CI incluye el nuevo módulo en sus disparadores push/PR.
Pendiente para integrar: el colector actual no proyecta paused; no se debe inventar false. Faltan el adaptador de inventario y evidencia de política viva, la migración explícita del baseline por nombres y un ejecutor con comprobaciones frescas de hold/montaje/identidad y tratamiento de carreras. El nuevo módulo no modifica ni sustituye todavía el camino de reparación instalado.
Adaptador de inventario preparado y consulta nativa (10:07 CEST)¶
El helper fuente añade el subcomando readonly inventory para producir el
snapshot versionado del planificador: IDs completos, nombre normalizado,
running, paused obligatorio y orígenes de montajes. La fecha UTC se asigna
al terminar la colección completa. Un inventario vacío válido produce JSON;
una colección fallida no produce datos parciales. Comparte el único colector
con el snapshot anterior, incluidos socket local fijo, UID, límites y reintento.
Se conserva literalmente el formato anterior de cuatro campos y su salida por
nombres; funciones de lock/perfil/run/verify/mount idénticas por AST.
Doce pruebas nuevas, incluida la cadena inventario→manifiesto→plan y pausas sin
valor implícito. Revisión independiente sin bloqueadores; suite NFS120 casos
(Mac119 ejecutados, uno O_PATH nativo omitido). El helper candidato
Lecturas separadas de metadatos10:06:16/39 hallaron24 contenedores NFS,19 running, y11 contratos cuyos campos seleccionados coinciden con Git. Diez contratos locales concuerdan con servicio y archivo Compose de sus etiquetas Docker; Audiobookshelf tiene execution_host personal-218. No se aplica ese contrato al homónimo antiguo deVM208. Candidatos primarios always por contrato/propietario: immich-server,plundrio,plundrio-onepacerr,plundrio-scanner,radarr,sonarr,trip-planner. No son una autorización de reinicio ni una política final; etiquetas/grupos bajo demanda y rutas activas pueden protegerlos y los servicios sin cobertura quedan sin clasificar. No se infiere always por ausencia de etiqueta ni para dependencias.
A10:07 ambos archivos managed.caddy de270/271 eran idénticos, SHA
Política contrastada y adaptador instalado (10:15 CEST)¶
Fuente2a382a0 publicada; CI39391293640 pasó141 casos porPython3.10/3.12, sin omisiones. Docs39391293588 correcta. La revisión independiente del adaptador confirmó ABI anterior y funciones de guardia sin cambios.
El contraste de rutas quedó resuelto por lecturas de la API local de configuración
Caddy, sin petición a aplicaciones. Ambos nodos devolvieron HTTP200 y JSON idéntico,
SH
La copia de trabajo deVM208 también coincide, SHA1cf0d635…e722ae. El estático
no es YAML general válido, pero no se atribuye una avería: el dialecto plano del
parser real del controlador (código6ac25c0c…f268 idéntico aGit) procesa sus27
entradas y devuelve27 rutas/0lazy. No se modificó ese archivo. La proyección
restringida de nombres quedó privada0600, sin configuración completa, en
active-sablier-groups-20261020T081317Z.json, SHA
Root validó estructuralmente los11 contratos fuente con validate(), sus campos
relevantes contra la proyección runtime, y los10 propietarios locales por
archivo/servicio Compose. Un plan privado histórico sobre el inventario10:06,
uniendo contratos, etiquetas y grupos activos contrastados, produjo19 entradas:
6 propuestas de restart,0start y13 manuales (11 sin clasificación suficiente y2
protegidas). plan_complete=false/restored=false. No se ejecutó ninguna propuesta.
No es una autorización actual ni prueba de recuperación. Evidencia privada
candidate-plan.json, SHA
Se instaló únicamente el helper10:15:27 mediante reemplazo atómico, con perfil
held exacto, lock, inactividad/procesos0, CAS de ambos archivos y metadatos.
Bash5152d0d1 mantuvo bytes e identidad completa, incluida su notificación local.
Backup de ambos originales y recibo:
/root/rollback-nfs-inventory-adapter-s5g9n73i/.
Instalador privado install_inventory_adapter.py, SHA
La invocación nativa del CLI instalado10:15:48 devolvió schema1/media-208,
90 contenedores/0paused; EXPECTED_NFS y HELD confirmados con fuentes reales.
Helper instalado:
<REDACTED>.
El planner y el ejecutor por IDs no se han desplegado; el monitor mantiene su
baseline por nombres y su reparación retenida. Se están auditando los11 casos
running sin clasificación antes de proponer cobertura automática adicional.
Primer ciclo automático posterior al helper nuevo:10:16:00→10:16:00, Resultsuccess/RC0; baseline actualizado10:16:00.564828 con19registros, sin pendiente ni contador. Timer active/waiting a10:16:33. No se forzó el ciclo.
Refinamiento de clasificación y cobertura del monitor (10:30 CEST)¶
El primer plan privado protegía conservadoramente cualquier lazy_names,
incluso si lazy era la cadena false. Se corrigió antes de integrar un ejecutor:
Trip Planner tiene tunnel.lazy="false", contrato always y ningún grupo activo
Sablier con su nombre. Un nombre de grupo residual no equivale a lazy=true.
La protección usa la unión activa Caddy, contratos on-demand y grupos de etiquetas
sólo con bandera positiva; etiquetas no reconocidas no acreditan always.
Sobre el mismo inventario histórico10:06, el plan v2 produce7 propuestas de
restart/0start y12 manuales (11 sin clasificación suficiente y1 protegida),
restored=false. No ejecuta propuestas ni elimina el plan anterior. Artefacto
privado candidate-plan-v2.json, SHA
Los11 sin contrato de recuperación suficiente tienen propietarios coherentes
con media/tools; no se acreditó una copia migrada entre esos11. La revisión de
fuentes distingue nueve candidatos a disponibilidad permanente, Mylar3 sin
señal explícita del monitor y OnePacerr con intención de pipeline pendiente.
restart: unless-stopped por sí solo no autoriza una reparación. En procesos
con colas, consumo/OCR o grabación, disponibilidad y admisión de reinicio son
comprobaciones distintas. El arranque de nopor-ingest, por ejemplo, marca
trabajos interrumpidos e inicia auto-drain según su configuración; eso no prueba
trabajos actuales ni lo convierte en un servicio bajo demanda.
La omisión de Mylar3 se resolvió en el monitor, no en la política de arranque: fuente cd0c315 y lista instalada43 servicios, con publicación natural10:29 comprobada en Pushgateway y Prometheus. No se arrancó/reinició Mylar3 ni se usó la nueva lista como permiso de recuperación. La recuperación por IDs aún requiere diario por generación, ejecutor, admisión y migración del baseline.
Diario por generación preparado en fuente¶
Nuevo módulo nfs-recovery-journal.py, aún sin actuador ni integración: una
única generación activa mantiene el manifiesto completo, sus hashes de
manifiesto/política, metadatos legacy manuales y estado por ID. Exige disponer
de todos los IDs del manifiesto; no permite sustituir generación, identidad,
montajes o política para eludir intentos anteriores. Estados automáticos:
ready→inflight→done/unknown. Al reabrir, inflight pasa a unknown; manual, done y
unknown no vuelven automáticamente a ready. Un pendiente antiguo conserva sus
bytes fuera del módulo; sólo se registra su hash como manual. Incluso con cero
IDs, legacy mantiene el resultado parcial.
La persistencia exige callback del caller que verifique el flock existente, archivo0600/directorio privado0700, CAS de identidad completa/hash, temporal fsync, publicación exclusiva o reemplazo atómico, fsync de directorio y relectura. El directorio privado debe existir y haber sido persistido por su inicializador; crear un directorio nuevo requiere además fsync del padre antes de actuar. No crea otro lock. Cualquier excepción al guardar requiere releer/reconciliar, sin reintento ciego; si se intentó publicar se informa CommitUncertain. Esto sigue dependiendo de escritores cooperativos y fuentes de confianza; no excluye escritores privilegiados ni la ventana entre comprobar y reemplazar.
Once pruebas nuevas ejercitan disposiciones, inmutabilidad, intentos, pérdida
del lock, enlaces/permisos, escrituras/fsync fallidos, cancelación a ambos lados
del reemplazo y colisión de creación. Revisión independiente sin bloqueadores.
done sólo representará la operación y comprobación limitada que aporte el
futuro ejecutor, nunca salud completa de la aplicación. El módulo no ejecuta
Docker, migra pendientes reales ni crea diarios en producción.
Compilador de política preparado en fuente¶
Nuevo módulo puro nfs-recovery-policy.py, sin descubrimiento ni acciones.
Recibe proyecciones estrictas de contenedores/propietarios/contratos y de ambos
Caddy; exige IDs completos y conjuntos de propietarios exactos, nodos270/271,
observaciones completas, hash de configuración coincidente, endpoint Sablier
conocido y ausencia de selectores Group. Normaliza Names por comas/espacios y
comprueba la unión de nombres de ambas réplicas. No fija los actuales9/17 como
límites de topología. Contratos remotos no autorizan homónimos locales.
Sólo primarios locales con coincidencia de archivo/servicio Compose son always; contratos on-demand/dependencias, Caddy y grupos lazy positivos son protección. Booleanos alineados con el controlador central:1/true/yes/on y0/false/no/off, con strip/case. False ignora nombres lazy residuales; valores desconocidos no conceden always. Los metadatos Compose ausentes de un contenedor no gestionado lo dejan unknown, sin invalidar a los propietarios válidos ni otorgarle permiso. Tipos o rutas no vacías malformados sí se rechazan.
Dieciocho pruebas nuevas cubren esos cruces, réplicas, propietarios, nombres compuestos, JSON y límites. La revisión detectó antes de publicar que el primer borrador trataba yes/1 como desconocidos, omitiendo proteger sus hijos sin etiqueta; corregido y contrastado con parse_bool del controlador central. El envoltorio incluye procedencia y execution_admission_proven=false. Sus flags validan las proyecciones, no prueban frescura, autorización del caller, colas inactivas ni coordinación con Sablier. Esta fuente y el diario todavía no están desplegados ni conectados al camino de recuperación Bash.
Verificación conjunta offline con las proyecciones reales retenidas10:06/10:13: el compilador produce exactamente la política v2 revisada; planner y diario aceptan19 entradas,7 ready y12 manuales, partial=true/restored=false. El diario no se persistió y no hubo acciones. Suite conjunta149 pruebas NFS en Mac (148 ejecutadas, una O_PATH nativa omitida); más15N-1 y6balloon:170 casos previstos por versión en Linux. La CI vigila ambos módulos nuevos en push/PR.
Vista de montajes de los contenedores: observación20oct10:50–10:53¶
Diagnóstico puntual de metadatos, sin instalar timer ni ejecutar comandos dentro de contenedores. Inventario Docker por IDs completos con Running/Paused/PID/ StartedAt y proyección Mounts Source/Destination/Type; procdir, namespace y raíz fijados mediante FDs, recorrido O_PATH|NOFOLLOW por componentes y selección del registro mountinfo por el mnt_id del FD real. Segunda apertura y relectura de tablas, identidad PID/namespace/raíz y proyección Docker después. Rechaza descender dentro de NFS/autofs intermedios; ambiguos quedan unknown. Sólo fstat de namespaces locales de proc, no getattr ni contenidos de archivos de aplicaciones. No usa exec/nsenter, no cambia montajes ni arranca/reinicia servicios.
Primera observación10:50:28,183902 CEST:90contenedores,19running NFS,0pausados, 24bindings. Cinco coinciden estrictamente con origen/tipo/raíz/dispositivo del NFS del host;17 difieren y2 quedan unknown. Segunda consulta10:53:04,242915 con recuentos de motivos conserva esos totales: los17 difieren simultáneamente en source y root, ninguno en tipo de filesystem. Los2unknown eran excepciones aún sin clasificar en esa versión. Host EXPECTED_NFS antes/después, Docker sin cambios observados, RC2 por observación incompleta y stderr vacío.
Eso NO acredita17montajes averiados u obsoletos: source es específico del filesystem y root es relativo a su raíz; distintas exportaciones pueden requerir un contraste adicional. Tampoco las cinco coincidencias prueban RPC, handles frescos, bytes originales ni salud de aplicaciones. No se modifica el planificador ni se conceden permisos de recuperación a partir de estos recuentos. Semántica mountinfo, vista del proceso, bind y submontajes.
Candidatos ejecutados por stdin y retirados al terminar, sin copia instalada:
Tercera observación10:58:56,703509 CEST, candidato
Fuente reproducible: scripts/vm208/nfs-container-views.py, con20pruebas
sintéticas (FDs y proc completamente simulados) y revisión independiente.
Pruebas con guard hermano, sin rutas absolutas del Mac. Para la observación
puntual revisada se puede pasar por stdin:
Sin --execute sólo muestra el plan; no consulta Docker ni namespaces. Fija
el hash del guard2f48e6a5 y falla como unknown ante otra versión. RC0 significa
observación sin unknown, NO salud NFS; puede incluir diferencias conocidas.
RC2 señala observación incompleta/unknown. No instala archivos, diario o
cron ni cambia el monitor existente. La CI ejecuta los20casos junto al resto NFS.
La comprobación offline de la proyección histórica10:06 no encontró fuentes NFS no canónicas:30/30 válidas,24 en running; no explica los2unknown actuales y no prueba sus destinos, que esa proyección no contiene.
Una consulta adicional sólo a Docker,11:00:29,804974 CEST (ps+inspect en lote, sin abrir proc/namespaces), mantiene90contenedores/19running NFS/24bindings: 0fuentes/destinos no canónicos,0tipos distintos de bind y0afectados por esas validaciones previas. No explica los2unknown; siguen pendientes de diagnóstico dentro del recorrido de metadatos. No se repitió ningún arranque ni reparación.
Fuente publicada en main
Error ESTALE identificado20oct11:06–11:08¶
La instrumentación puntual de excepciones, tras23pruebas sintéticas y revisión
de AST (sólo añadido de recuentos en catch existente), observó11:06:05,985430
CEST OSError/ESTALE en descend al abrir un componente O_PATH. Los recuentos
se ponderan por TODOS los bindings del contenedor cuando se interrumpe su
inspección: valor2 NO demuestra dos syscalls fallidas ni dos contenedores.
Consulta posterior11:08:05,368713 CEST, con25pruebas y descarte de afectados ante cambios Docker posteriores: un único ID afectado, correspondiente a onepacerr, con dos bindings marcados unknown. No es plundrio-onepacerr. Host EXPECTED_NFS antes/después, mismo inventario90/19running NFS/24bindings, 5coincidencias estrictas y17diferencias léxicamente compatibles como antes. Sólo se proyectaron ID/Name después para identificar al propietario; no se leyeron archivos de usuario, se ejecutaron procesos dentro ni se cambió estado.
Esto acredita el errno ESTALE en ese recorrido, no su causa histórica ni el
estado completo del pipeline. Se investiga preservación de trabajos antes de
cualquier recuperación. No se ha reiniciado el contenedor ni liberado el hold.
Los candidatos instrumentados privados tienen hashes
Preservación de trabajos OnePacerr: revisión estática20oct11:25–11:32¶
Imagen actual8af9aac3 y revisión OCI
El nombre real del método es stoptWatching(): pone watchingfalse y borra
handler, pero después procesa torrents completados, pudiendo copiar/unlink y
cambiar categorías. No es una pausa inerte. gracefulClose() sólo termina con
process.exit(0), sin esperar importaciones o pipeline. El informe en memoria
getReport() no acredita que el importador independiente esté inactivo.
Express registra sólo HealthController/StatusController con GET de salud/estado; no se encontró operación de pausa/drain/quiescencia en esa superficie registrada. El prefijo API_BASE no se resolvió inequívocamente en la proyección permitida, por lo que no se supuso una URL ni se llamó a ninguna API. Actividad actual del pipeline desconocida; no se reinició OnePacerr ni se invocó stoptWatching. La recuperación pendiente debe conservar tanto pipeline como importaciones y evitar interpretar DONE o Docker running como admisión suficiente.
Diseño de pausa y conservación OnePacerr (fuente, 20 octubre, 14:05 CEST)¶
No se implementa todavía una pausa parcial ni una nueva API. El proceso actual sigue sin un contrato de drain acreditado; la recuperación ESTALE continúa pendiente. Esta revisión no consulta la aplicación, NFS, Docker ni sus datos.
Fuentes y alcance comprobado¶
La API oficial de GitHub identifica eltharynd/OnePacerr, revisión exacta
<REDACTED> del label OCI ya observado. Se guardaron
privadamente 42 blobs (143.403 bytes) de src, Dockerfile, package.json y
configuración TypeScript, verificando cada SHA Git. No se instalaron paquetes
ni se ejecutó el entrypoint. TypeScript 5.9.3 ya disponible reproduce byte a
byte los cuatro dist capturados de index, Express, PipelineController y
PipelineReport. Esto corrobora la revisión sin equiparar cualquier upstreammain
con el runtime. Metadata/library se investigan desde ese commit corroborado,
pero sus dist desplegados no se han leído ni verificado individualmente.
TorrentController sigue siendo el patch propio <REDACTED>.
Además de resolver directorios, difiere de upstream en importación de bundles y
hardlinks/copia. El diseño debe conservar esas diferencias, así como el cliente
Transmission propio; no sustituirlos por una compilación upstream sin revisión.
Fuentes primarias fijadas: metadata, pipeline, torrent, biblioteca.
Riesgo reproducido y superficie completa¶
El reproducer local scripts/onepacerr-review/reproduce-drain-gaps.mjs carga sólo
las tres clases dist con hashes fijados mediante módulos VM e imports sintéticos.
No carga index/environment reales; cualquier fs/util de archivos se sustituye
por rechazo y no se proporciona transporte de red. Tres casos pasan:
stoptWatching()pone watching=false y dispara processCompletedTorrents; no es una pausa sin trabajo nuevo.- Pipeline DONE/isRunning=false coexiste con una importación independiente que aún espera: consultar sólo el reporte no acredita quiescencia.
start()puede terminar dejando un episodio READY y un timer de retry vivo; esperar esa promesa no permite descartar la cola ni declarar todo terminado.
Son fixtures de comportamiento de esas clases con colaboradores sintéticos, no pruebas de actividad ni de quiescencia de la aplicación desplegada.
La cobertura necesaria incluye cuatro productores: refresh periódico de
metadata, updates del WebSocket de metadata, watcher/importador torrent y
pipeline/retry. sendToPipeline() genera metadata/NFO y lanza start sin await;
el flag unprocessedMetadata puede quedar false mientras sigue ese trabajo.
LibraryController añade escrituras y solicitudes bajo esos caminos. También
hay callbacks de scans Emby/Jellyfin, listeners Plex y un reconnect Plex que no
pueden sobrevivir ocultos a una supuesta cuenta de operaciones cero. Quitar un
interval no cancela una callback async ya en vuelo. Un timeout de scan tampoco
prueba finalización de la tarea remota.
Propuesta completa, antes de escribir la feature¶
- Admisión y ownership únicos. Un coordinador del mismo proceso mantiene
OPEN/DRAINING/QUIESCED/UNKNOWN y generación. DRAINING se fija sin await antes
de cerrar entradas: refresh/update, watcher, pipeline start/retry y nuevas
importaciones. Las llamadas internas ya admitidas conservan un permiso
explícito hasta terminar; timers futuros no heredan permiso válido de una
operación que ya terminó. Se registran también las promesas hoy lanzadas sin
await. No usar
isRunning()como contador: PRE/READY son trabajo pendiente. - Parada de productores sin trabajo final. Un método nuevo inerte detiene nuevas admisiones del watcher, guarda/limpia sus handles reales y evita rearmado al volver de await. No reutiliza stoptWatching. Metadata conserva actualización pendiente, desconecta su socket sin reconexión automática y registra cualquier callback ya activa. Se cancelan futuros retries de pipeline preservando los episodios READY y su próximo intento. Los clientes de biblioteca retiran listeners/polls/reconnect en finally y contabilizan sus callbacks activas; no cerrar un socket y permitir que close lo reabra.
- Drain finito sin forzar I/O. Se espera a que las unidades mutadoras ya iniciadas alcancen un límite seguro y sus hijos registrados terminen. No se interrumpe copy/unlink/cambio de categoría a mitad de unidad. Error ESTALE, timeout o cancelación dejan UNKNOWN con la admisión cerrada; nunca un recibo listo para reiniciar ni un retry automático. Un deadline no autoriza matar un proceso para afirmar drain. Una tarea remota de scan no confirmada queda registrada como no resuelta, no como completada por desaparecer el listener.
- Checkpoint local obligatorio. No existe actualmente un bind de estado local para OnePacerr: sus dos mounts de datos son NFS. Se requiere un directorio dedicado en appdata local, privado, independiente del NAS averiado, antes de habilitar esta feature. Schema versionado con generación, revisión de código/política y hash del cuerpo; metadata/unprocessed/firstRun, monitored/NFO necesarios, reporte actual, episodios restantes, historial y retry pendiente. Por operación: identidad estable, paso anterior/posterior y resultado done/unknown; no guardar secretos del entorno ni objetos socket/promesas. Cuerpo máximo propuesto16MiB, sin truncar historial/cola para caber: si no cabe se rechaza QUIESCED y se conserva el estado anterior para revisión. Retry registra intervalo e intención pendiente; no se persiste una lectura monotónica como si fuera válida tras reiniciar. Archivo0600, dir0700, temporal exclusivo, fsync archivo, rename, fsync del directorio y relectura. Fallo de escritura/fsync no produce QUIESCED; resultado de commit incierto exige relectura/conciliación, no reintento ciego.
- Reinicio cerrado y restauración explícita. El proceso nuevo carga y valida el checkpoint ANTES de library.init/metadata refresh/watcher. Inflight se reabre como unknown, no como ready; done no se repite. Metadata y PipelineReport se rehidratan como objetos propios, no se confía en métodos deserializados. READY/retry permanecen aparcados hasta una autorización explícita contra la misma generación. Una admisión repetida no duplica unidades o categorías. Para operaciones no idempotentes ambiguas hace falta conciliación del paso, no volver a copiar/unlink a ciegas. El drain no vuelve atómicas las operaciones destructivas históricas del importador ni restaura archivos ya eliminados.
- Recibo ligado al proceso. Sólo publicar QUIESCED si productores cerrados, operaciones/callbacks activas cero, ningún resultado desconocido sin resolver y checkpoint durable revalidado, ligados a PID/StartedAt/generación y hashes. Un estado readonly puede mostrar ese agregado; no equivale a una autorización de reparación NFS. Cualquier interfaz mutadora requiere canal local restringido y control de generación; no añadir un endpoint público sin autenticación.
Cambios previsibles: coordinador/checkpoint nuevo, index, metadata, pipeline, TorrentController propio y lifecycle de clientes de biblioteca, más tests de integración con colaboradores sintéticos. Es un cambio de aplicación y de persistencia, no una sola corrección de Compose. Debe revisarse antes de implementarlo y mantener los patches existentes byte por byte fuera del delta.
Bootstrap y aceptación que siguen bloqueando recuperación¶
Cambiar un bind JS no actualiza módulos ya cargados. Reiniciar ahora para cargar el gate nuevo perdería primero el READY/retry/metadata que se pretende conservar. El programa viejo no tiene exportación/rehidratación completa ni un drain acreditado. Por ello este diseño no autoriza recrearlo para instalar la protección ni demuestra una migración sin pérdida. Antes del primer despliegue hace falta una estrategia revisada de transición del estado del proceso viejo; los GET de reportes existentes, por sí solos, no cubren al importador.
Aceptación local necesaria antes de considerar ese despliegue: carreras de refresh/WebSocket/watcher/retry al cerrar gate; importer activo mientras pipeline DONE; start que retorna READY; tarea hija no awaited; callback async después de clearInterval; reconexión Plex; copia/rename/categoría en vuelo; fsync/rename y cancelación en cada lado del checkpoint; reapertura con inflight→unknown; dos peticiones de pausa/reanudación de la misma generación; bootstrap con checkpoint incompleto o incompatible; preservación exacta del READY/retry e invariantes de los patches propios. Todo con temporales locales y sin productos. No hay todavía feature implementada, endpoint, imagen construida ni despliegue.