Skip to content

Immich y originales — revisión nocturna 19 octubre 2026

Se cerró una copia lógica independiente del dump19 en Synology. Tras un reinicio imprevisto de pmx50 se reanudó una vez la tarea existente de originales para terminar pics19, conservando evidencia anterior. No se inició restauración DB, contenedor, poda, scrub o aplicación. Los apartados finales contienen el estado más reciente; las lecturas anteriores se conservan con sus horas. No se han leído fotografías/vídeos privados ni modificado el copiador activo. La única mutación autorizada posterior fue ampliar el receiver con grupo de dumps y consulta readonly, mediante reemplazo atómico; se detalla abajo. Sin commit/push.

Copia que sigue activa y cómo continuar

Comprobación autoritativa con sudo en VM208, 23:47:16 CEST:

  • Python root PID197552, padre197551, start_ticks399646; estado S/do_wait.
  • rsync PID395430, padre197552, start_ticks838444; estado S/do_select.
  • rsync rchar 41.508.160.938, wchar 41.518.869.044. Muestras anteriores rchar37.230.208.541 y38.248.473.856 demuestran avance, no un proceso atascado. Una muestra D durante NFS no basta para diagnosticar bloqueo: luego volvió S y siguió aumentando I/O.
  • last.json generación20261019: documents/proyectos/photos complete, pics copying. Photos terminó23:15:31; pics comenzó23:15:35. No duplicar, señalizar ni reiniciar.

Este proceso se lanzó con backend antiguo y no escribe PID/heartbeat en last.json. Leer /proc/197552/{stat,io} y /proc/395430/{stat,io} con sudo; monxas no puede leer sus I/O. Comprobar start_ticks y PPID para evitar confundir PID reutilizado. Cuando terminen, leer last.json y generación remota cerrada antes de proceder. El observador existente conoce este modo legado; no se ha cambiado su fuente.

Qué acreditan los manifiestos

Photos apunta a /volume1/backups/terramaster/snapshots-originales/photos/20261019, con tres hashes SHA256 y lecturas de restauración aisladas registradas. Las tres muestras son encoded-video, derivados; ninguna es un original upload/library. Por tanto no acreditan fotos originales recuperadas. Pics aún no tiene muestras completas.

No existe photos-20261019.json individual en el estado local; el resultado está conservado en last.json legado. No se fabricó manifiesto retrospectivo. El backend nuevo crea manifiestos por grupo, pero el proceso que sigue activo ejecuta la versión anterior. Preservar last.json al cierre, sin sobrescribirlo.

La política futura ya está en backup_originals.py mediante el trabajo paralelo publicado en1925763: reserva muestras de upload/library. Esta revisión reutiliza su función photo_original_area en tests; se retiró el módulo duplicado propuesto. Véase night-originals-sampling-20261019.md. No se instaló esa política sobre el copiador activo ni se reinterpretan sus tres muestras de derivados.

Generación de base de datos elegida

Fuente viva: /home/monxas/scripts/pg-dumps.sh, mantenida en scripts/vm208/pg-dumps.sh. Exporta pg_dumpall a temporal, comprueba gzip y publica atómicamente sin sustituir archivo diario existente.

Dump elegido: immich-db.2026-10-19.sql.gz, 183.486.870 bytes, terminado 19oct01:30:31 CEST (18oct23:30:31Z). Se ha leído gzip completo y comprobado CRC; SHA256 comprimido: <REDACTED>. Procede de PostgreSQL16.10.

El análisis de COPY asset del dump devuelve 31.595 assets:

Árbol respaldado Assets dump Assets vivos consultados Checksum de Immich
photos, montado /usr/src/app/upload 10.803 10.832 sha1 del contenido
pics, montado /mnt/pics 20.792 20.792 sha1-path: hash de ruta, no del contenido

Las 29 fotos nuevas posteriores al dump son diferencia de RPO, no prueba de pérdida. Recuperar ese dump conserva el punto01:30, aunque los árboles se hayan copiado muchas horas después. Los archivos posteriores pueden existir sin fila en ese dump. Lo contrario —filas cuyo archivo fue borrado/movido antes de la copia— solo se detecta cotejando cada referencia contra la generación cerrada.

Fingerprint anónimo de referencias COPY, en orden del dump: <REDACTED>. No se muestran IDs, nombres, propietarios o rutas personales.

immich-recovery-inventory.py reproduce este inventario, detecta gzip/COPY truncado, raíces no cubiertas y escapes de ruta; solo devuelve agregados. No lee bytes de fotos ni afirma cobertura de archivos o restauración.

Cobertura independiente del dump pendiente

El dump19 está en /home/monxas/appdata/_pgdumps, local a VM208. El script appdata incluiría esa carpeta en un tar; sin embargo el destino vivo por defecto /mnt/nfs_media/backups/appdata contiene únicamente un tar publicado appdata-2026-09-08.tar.gz de23.438.860.208 bytes y dos artefactos corruptos. No hay tar de octubre en ese destino. No se ha descomprimido23GB ni disparado backup.

Contexto acreditado después por el coordinador: el tar appdata se retiró intencionalmente de la programación02:00 desde20260908 tras comprobar recuperación PBS; Healthchecks pausado y RunWisp sin appdatajob. No es una regresión ni se ha reactivado. PBS protege la VM, pero acreditar el dump concreto dentro de PBS requiere su extracción/verificación. No se afirma que la base carezca globalmente de protección. La nueva copia lógica Synology complementa PBS y permite identificar ese dump de manera independiente; originales19 no contiene el dump por sí sola.

immich-recovery-receiver-extension.patch documenta la ampliación revisada: grupo separado immich-dumps y operación status estrictamente readonly. Tras12 pruebas sobre árbol temporal, se aplicó la misma adición a fuente y receiver vivo por autorización del coordinador, mediante reemplazo atómico y backup privado. No reutilizar proyectos para el dump: contaminaría la selección de previous/link-dest de su siguiente generación. No se cambiaron claves, configuración SSH ni ejecución rsync activa. El receiver instalado conserva sus restricciones de rutas, staging y generaciones cerradas.

Secuencia revisada por el coordinador para la copia lógica dedicada:

  1. Extensión revisada/instalada atómicamente sin tocar procesos del rsync existente. status immich-dumps20261019 ya demuestra espacio real sin crear staging: 241.145.114.624 bytes libres, staging=false, complete=false. Exigir reserva64GiB más tamaño dump y staging. No usar prepare como consulta de espacio.
  2. Staging local privado0700 con copia inmutable dump0400 y SHA256 comprobado. Manifest privado asocia hash/timestamp/assets a photos19 y pics19; registrar coherence_verified=false hasta cotejo de referencias. Nunca publicar secretos.
  3. Preparar grupo immich-dumps/generación20261019 por canal restringido existente, transferir únicamente staging privado con rsync limitado, verificar SHA256 remoto, restaurar una copia privada acotada y verificarlo de nuevo; entonces complete. No reabrir ni escribir generaciones photos/pics ya cerradas.
  4. Registrar manifiesto vinculante local más copia remota. Solo después acreditar protección independiente del dump; no prometer consistencia de archivos todavía.

Receiver runtime /var/services/homes/monxas/.ssh/originals-receiver.py, uid1026, modo0700; .ssh y base snapshots también0700. Hash anterior <REDACTED>, nuevo <REDACTED>. Backup privado0600 originals-receiver.py.before-immich-dumps-20261019T235315. Instalado por canal administrador existente con credencial específica solo en memoria, sin imprimirla ni ampliar claves. Status posterior se ejecutó con la clave restringida normal desde208 y terminó0. No prepare ni transferencia.

Ensayo DB aislado preparado y bloqueado por recursos

Imagen DB exacta existente: sha256:<REDACTED>. DB viva634.048.995 bytes; extensiones vectors0.2.1, vector0.8.1 y vchord0.4.3. Preload vchord.so,vectors.so. UID/GID postgres verificado999:999.

Fuente immich-recovery-db-plan.py y ejecutor immich-recovery-db-drill.py:

  • Por defecto plan; --execute explícito, imagen local exacta --pull=never.
  • Red none, sin puertos, servidor Immich/Redis/ML/jobs no arrancan.
  • Rootfs readonly, usuario999, todas capacidades retiradas, no-new-privileges. Solo pgdata privada nueva RW; ningún original o volumen de producción montado.
  • Bypass del entrypoint Immich que escribe /etc/postgresql, usando entrypoint PostgreSQL estándar. /tmp y socket de bootstrap en tmpfs acotados.
  • Memoria1GiB sin swap de contenedor, CPU0,5, pids128, shm64MiB, shared_buffers128MiB.
  • Preflight exige MemAvailable≥3GiB para reservar2GiB, disco libre≥32GiB para workspace≤8GiB y reserva24GiB. Watchdog de bloques realmente asignados y espacio libre cada segundo; detiene si workspace≥8GiB, free<24GiB, MemAvailable<2GiB, SwapFree disminuye o timeout20min. Es vigilancia de consumo real, no cuota de FS: puede haber pequeño sobrepaso entre muestras; no se presenta como hard quota.
  • Copia privada inmutable dump/CRC/hash antes de crear contenedor. Restore SQL ON_ERROR_STOP, logs0600; solo query de conteos/raíces/checksumAlgorithm al terminar.
  • Inspect solo Image/Labels/límites/State, sin env. Timeouts10s en init/query/inspect. Cleanup comprueba name+token aunque docker run falle antes de devolver éxito; hijos TERM→KILL+wait; errores de limpieza conservados en evidencia.

No ejecutado: a23:47 VM208 MemAvailable2.740.928.512 bytes, inferior3GiB; SwapFree2.419.310.592 de6.424.817.664. No se consume margen N‑1 para conseguir una prueba. Disco local40.823.451.648 libres y TerraMaster5.210.X.X.X libres. Synology status posterior241.145.114.624 libres; esto no compensa la RAM.

El ejecutor tiene pruebas, pero no es una restauración PostgreSQL realizada. Comprobar recursos de nuevo y revisión del coordinador antes de --execute. No se ha creado workspace ni contenedor del ensayo. 12 tests locales de recuperación y12 del receiver pasan, incluyendo recursos, copias corruptas, rutas, anonimización, muestras originales, timeouts/cleanup selectivo y terminación de hijos. Sin pruebas sobre producción.

Criterio para una recuperación completa

Una DB restaurada no basta. Tras cerrar ambas generaciones y proteger el dump: restaurar DB aislada, exportar referencias a fichero privado y comprobar todas contra snapshots readonly. Exigir presencia/size consistente y comprobar SHA1 de los10.803 originales subidos. Para20.792 externos sha1-path no sirve de hash de bytes: añadir verificación independiente SHA256 y muestras de originales reales, sin tratar las tres muestras encoded-video como originales. No crear la app ni desencadenar análisis durante la prueba.

Limpieza posterior: solamente contenedor cuyo nombre y label figuren en result.json, después de confirmar detenido; preservar dump inmutable, informe y logs. Retirar únicamente pgdata del workspace creado por el ensayo cuando ningún proceso lo use. Nunca rm genérico de Docker, volúmenes de producción ni carpetas de backup.

Copia independiente del dump cerrada23:57:24

Por autorización concreta del coordinador se completó la secuencia anterior, sin esperar ni tocar el rsync de originales:

  • Staging local /home/monxas/.local/state/immich-recovery-dump/202610190700, copia dump0400, manifest0600. Hash del dump verificado antes de transferencia.
  • Transferencia única nice19, bwlimit4096KiB/s, al grupo dedicado immich-dumps; no retry automático ni modificación de photos/pics/proyectos/documents.
  • Administrador verificó rutas literales, uid1026 y directorios0700; chmod0600 solo a los dos archivos exactos del staging, nada recursivo.
  • Hash remoto coincidente. Lectura de restauración del archivo183.486.870 bytes hacia /home/monxas/.local/state/immich-recovery-dump/restore-20261019, privado0700, archivo0400; SHA256 coincide y gzip completo/CRC correcto.
  • Solo entonces complete immich-dumps20261019 cerró la generación. Destino: /volume1/backups/terramaster/snapshots-originales/immich-dumps/20261019.
  • Status posterior: staging=false, complete=true, espacio235.759.337.472 libres.
  • Hash manifest remoto/local: <REDACTED>. Vincula dump01:30/31.595assets y generaciones photos19/pics19, conservando coherence_verified=false y pics copying en momento del binding.
  • Evidencia privada0600: /home/monxas/.local/state/immich-recovery-dump/evidence-20261019.json. Preservados dump inmutable, manifest, archivo leído de vuelta y evidencia.

Fuente reutilizable versionada immich-recovery-copy-dump.py: plan por defecto, --stage o --finish --permissions-reviewed; valida hashes de generaciones ya cerradas sin sobrescribirlas, no rehace automáticamente transferencias parciales, no lanza DB ni aplicación. Las pruebas cubren idempotencia y requisito de permisos. No se añadió cron ni se integró todavía en la tarea de originales11:10.

Última observación23:57:31: pics sigue copying, PID395430/ppid197552/start838444, rchar55.438.858.397 y wchar55.453.416.595, claramente avanzado. No se tocó last.json.

Esto acredita copia independiente y recuperación del archivo de dump, no una restauración de PostgreSQL ni recuperación coherente completa de Immich. La DB aislada sigue pendiente de margen de RAM/revisión y el cotejo de todos los originales sigue pendiente de cerrar pics y usar hashes de contenido apropiados.

Receptores alternativos: consulta readonly, sin arrancar plataformas

Bazzite192.168.0.XXX respondió TCP22; solo después se efectuó una conexión SSH sin reintentos y con host trust estricto. Estaba ya encendido. Datos agregados: MemAvailable26.016.210.944 bytes, disco home277.706.403.840 libres, swap16.771.444.736 completamente libre. Cumple presupuesto3GiB RAM/32GiB disco, pero docker no está instalado en PATH y no hay proceso dockerd/podman. No se acreditó receptor Docker activo ni se buscó una nueva plataforma. No se movieron imagen o dump.

Mac: docker CLI existente, contexto desktop-linux; docker info con timeout5s terminó1, daemon no disponible. Colima CLI ausente. No se arrancó Docker Desktop ni ninguna VM/runtime. Al no haber receptor elegible dentro del alcance, se cerraron las consultas; VM208 conserva guard/reserva2GiB y no se lanzó DB drill.

Podman existente acreditado; ensayo pausado por reinicio imprevisto

La consulta inicial de Docker no descartaba un motor daemonless. Tras revisión expresa se comprobó Podman ya instalado5.8.7, rootless uid/gid1000, amd64, cgroupv2/systemd con cpu/io/memory/pids, overlay, SELinux y seccomp activos. podman info terminó0 sin arrancar socket/daemon. La imagen exacta769aa… no existía en ese store. Bazzite tenía26GB RAM/277GB disco libres y swap sin uso. No se instaló plataforma nueva ni se tocó AI/GPU.

Adaptación fuente immich-recovery-podman-plan.py y db-drill.py --engine podman: misma imagen/SQL/watchdog, rootless keep-id999→host1000, pgdata privada :Z, rootfs readonly, tmpfs explícitos acotados, networknone, memoria1GiB/CPU0,5, pids128 y swap0. Antes de cualquier SQL se comprueban valores reales de memory.max, memory.swap.max, cpu.max y pids.max, imagen, readonly, red y UIDmap; si difieren o una opción falla, se detiene solamente el contenedor propio. Inputs transferidos y DB workspace se suman para el límite vigilado8GiB.

Fundamento revisado: ayuda instalada5.8.7 y documentación oficial Podman run, que explica keep-id, etiquetado SELinux y tmpfs readonly. La prueba efectiva de cgroup es obligatoria, especialmente ante diferencias de versiones sobre memory-swap.

Se autorizó exportar solamente imagen existente769aa… desde208, sin pull, y transferir el dump desde la lectura privada Synology verificada. Herramienta versionada immich-recovery-transfer-bazzite.py: SSH estricto, nice19/gzip1, 4MiB/s, archivos0400/stage0700, hashes de transporte, deadline20min y limpieza TERM→KILL; guard de consumo agregado del staging, sin retry automático.

La exportación abortó antes de recibir datos: a00:03 el alias media-208 vía pmx50 devolvió No route to host. En Bazzite quedó únicamente /home/monxas/.local/state/immich-recovery-input/20261020/postgres-image.tar.gz de 0 bytes. No se transfirió dump, no se hizo podman load y no se creó ningún contenedor. El coordinador confirmó reinicio inesperado de pmx50, no una acción de mantenimiento autorizada. No se atribuye causa al intento de exportación. Se pausaron exportación, traslados y ensayo; no hay reinicios/reintentos por este agente.

Evidencia y ledger después del reinicio,20oct00:04

VM208 respondió por su IP directa con el mismo host trust. A00:04:22 uptime40s; PID197552 y395430 anteriores desaparecidos, ningún backup_originals.py nuevo. last.json sigue diciendo pics copying, pero es estado persistido obsoleto: la copia quedó interrumpida. Un rsync nuevo9214/ppid9197(sshd) no tenía sourcepics, ni target snapshots-originales, ni .incomplete19; no se intervino ese proceso ajeno.

Consultas readonly al receiver tras arranque:

Grupo19 Staging Complete
pics sí no
photos no sí
immich-dumps no sí

Espacio Synology229.571.403.776 bytes. Ningún prepare, complete o relanzamiento posterior al reinicio. El dump y su evidencia previa siguen presentes; no se repitieron hashes completos ni se iniciaron copias durante el diagnóstico.

Ledger durable nuevo0600, con fsync de archivo y directorio: /home/monxas/.local/state/immich-recovery-dump/interruption-20261020T0004.json. Registra estado obsoleto, procesos desaparecidos, stagingpics conservado, generación dump cerrada/coherencefalse y ensayo pausado0bytes/0load/0containers. No modifica last.json ni manifiestos del copiador.

El siguiente paso requiere que el coordinador cierre el diagnóstico de estabilidad; no reanudar automáticamente ni publicar pics incompleto. La herramienta se niega a pisar staging previo. Las fuentes de recuperación tienen 16 tests locales, más12 de receiver; ejecución real de DB sigue pendiente.

Reanudación acotada de pics19 a00:11:13

Por autorización del coordinador, tras comprobar estabilidad de arranque y sin reanudar exportación/DB:

  1. Corregido backup_originals.py: captura previous=dict(estado) antes de preparing. Si prepare devuelve already_complete, falta manifiesto individual y el estado previo era complete del mismo destino, conserva exactamente evidencia anterior. Otro destino no hereda muestras. No crea manifiestos ni declara nueva verificación. 17 tests backend pasan.
  2. Preflight: ningún productor de backup, lock libre; RunWisp healthy, scheduling activo, config_stale=false y originals_backup sin ejecución previa. Pics source 73.785.622.528 bytes, destino229.571.403.776 libres: pasa incluso reserva conservadora de fuente completa+64GiB. El sizing se realizó con sudo como el backend; el usuario sin sudo no tiene acceso a algunos subdirectorios privados.
  3. Backup privado600+fsync de script y last.json en UTC221009: backup_originals.py.before-pics-resume-20261019T221009Z y last.json.before-pics-resume-20261019T221009Z. Script previo; last previo .
  4. Despliegue atómico del backend mantenido, hash <REDACTED>. Se comprobó que last.json no cambió durante el despliegue.
  5. Una invocación real mediante CLI --daemon hacia daemon existente y tarea originals_backup; no nuevo daemon/job/cron. RunID 01M5B3G0E7CFGMZJVC7T6SJZ7Y, start20oct00:11:13+02, triggerapi. La tentativa anterior de obtener password automático falló antes de invocar exec y no disparó otro job; el canal local no necesitó revelar credenciales. Horario11:10, overlap skip, timeout18000 y bwlimit49152KiB/s conservados.

Documentos/proyectos/photos se saltaron como ya completos. Photos quedó exactamente igual al backup legado (tres muestras encoded-video y timestamps); ninguna reinterpretación como muestras nuevas de originales. Pics reutiliza su staging19; backend31548, rsync31621/parent31548/start_ticks45428, heartbeat10s. A00:15:14 rchar5.264.794.931/wchar5.266.737.349, avanzando.

Sesión rsync remota huérfana neutralizada sin cleanup

El chequeo posterior a la invocación encontró dos árboles rsync para el mismo TARGET: viejo7779→7780(parent7778, start2051788032/2051788074) y nuevo9498→9499. Las lecturas sudo mostraron que el viejo estaba inerte en poll_schedule_timeout, I/O invariable y con un archivo temporal abierto; el nuevo escribía activamente. El productor208 antiguo se había perdido por el reinicio.

El coordinador autorizó acción específica tras revisar riesgo de que TERM/cleanup renombrara un temporal viejo sobre la copia actual. Se revalidaron UID1026, commrsync, target exacto, PPID y startticks; SIGSTOP al hijo7780 y luego7779, confirmaciónT en ambos, SIGKILL a ambos sin TERM ni cleanup. No señales a sshd, PGIDs o sesión nueva. Ambos desaparecieron y quedó solamente9498/9499.

Temporal viejo 3.145.728 bytes preservado, existe y mantiene inode. Ningún archivo borrado ni renombrado. Evidencia con rutas/FD/I/O reales se conserva privada600 en Synology: /var/services/homes/monxas/.ssh/orphan-pics-20261020T001434.json. El informe público solo retiene metadatos anónimos. El temporal puede quedar como artefacto extra de la generación; no se ha podado.

Ledger durable600+fsync en208: /home/monxas/.local/state/immich-recovery-dump/resume-20261020T001113.json. Conserva runID, hashes, handles, estado anterior y acción exacta de huérfanos. No se volvió a modificar el receiver mientras la copia actual sigue abierta; el hash vivo de la extensión es.

Fallo de lectura de muestra y corrección del transporte SSH

El primer run terminó a00:20:30 con exit1/CalledProcessError después de completar la transferencia principal, durante la lectura aislada de una muestra. No se cerró la generación ni se fabricó verificación. El argumento remoto se construía con shlex.quote dentro de un argv subprocess; rsync3.2.7 ya protege argumentos SSH y aquellas comillas adicionales llegaban literalmente al receiver, que rechazó el comando. No era evidencia de fallo de los originales.

Se corrigió una línea: argv conserva el path remoto sin prequoting. Fixture real mínimo VM208 rsync3.2.7/protocol31 → Synology rsync3.0.9/protocol30, misma clave y receiver vigente: tres archivos sintéticos con espacio, apóstrofo y Unicode se copiaron y restauraron con hashes iguales3/3; construcción antigua falla12. Fixture privado700/600, sin complete, bajo staging separado .incomplete-quote-fixture-20261020t0022-96f6e190; no se tocó pics19 durante el ensayo. La limpieza selectiva de estos sintéticos queda pendiente del cierre. La regresión usa transporte rsync real cuando existe3.2+; en Mac antiguo se omite.

Revisión y autorización del coordinador: preflight task anterior endedfailed, sin proceso productor y cero receivers NAS de pics19. Copias0600+fsync de script965 y last.json antes del reemplazo atómico22:28:41Z: backup_originals.py.before-quote-fix-20261019T222841Z y last.json.before-quote-fix-20261019T222841Z. Estado previo SH. Backend desplegado SHA256 <REDACTED>.

Se reutilizó una sola vez la tarea originals_backup existente a00:28:52+02, runID 01M5B4GB39GECPYM97ZZR9T41C. La fase main saltó rápidamente los bytes iguales y pasó verifying a22:28:56Z. Resultado todavía pendiente: solo el cierre nativo tras hashes remotos y restauraciones aisladas permite acreditar complete. No exportación de imagen ni ensayo DB durante esta recuperación.

Cierre acreditado de pics19 a00:30:08

RunWisp 01M5B4GB39GECPYM97ZZR9T41C terminó success/exit0 a 20oct00:30:08.372+02, duración75.402ms. El backend cerró nativamente pics a 22:30:08.312Z después de cuatro muestras con hash del origen, hash del destino y restauración privada aislada con hash igual; tamaños anónimos906.978, 5.113.691,4.828.134 y713.298.013 bytes. No complete manual ni poda.

Estado Synology readonly posterior: pics19 complete_exists=true, staging_exists=false,217.287.405.568 bytes libres. Los cuatro grupos figuran complete; photos conserva exactamente la evidencia legada de tres derivados. No quedan procesos backup/rsync en208. Manifiesto pics19 SHA256 <REDACTED>. Ledger privado0600+fsync: /home/monxas/.local/state/immich-recovery-dump/pics-closed-20261020T003008.json. El temporal huérfano preservado puede quedar como artefacto extra; no se eliminó. El fixture sintético separado sigue privado y no representa otra generación real.

Este resultado acredita muestras reales restauradas de pics y cierre del conjunto de copias de archivos. No acredita todavía restauración PostgreSQL ni coherencia entre el dump y ambas generaciones, ni originales Immich recuperados end-to-end.

Nueva pausa del ensayo DB tras aviso de segundo reboot

El coordinador autorizó continuar DB-only después del cierre de pics. Preflight Bazzite: UID1000/Podman5.8.7, MemAvailable26.006.122.496 bytes, SwapFree16.771.444.736 y disco277.697.945.600 libres. Se preservó stage antiguo 20261020 con imagen0B y se inició una transferencia bounded a stage separado 20261020-retry0032,0700, archivo0400; límite4MiB/s/deadline1200s/reserva24GiB y presupuesto agregado8GiB conservados. Fuente exacta769aa… ya disponible en208.

El coordinador avisó de un segundo reboot pmx50 y ordenó pausa antes de cualquier SQL. Se interrumpió solamente el orquestador propio con SIGINT; su finally cerró ambos SSH propios mediante TERM→KILL+wait. Exit130. Comprobación posterior: ambas imágenes staging 0 bytes, ningún dump transferido, ningún proceso docker image save de nuestra imagen en208. No podmanload, contenedor ni SQL. No se reintentará hasta nueva autorización; inputs se conservan sin borrado. El cierre pics posterior al segundo boot sigue acreditado por run ended y NAS.

Propuesta siguiente: copia lógica diaria durable, solo análisis de fuente

Pendiente de revisión; nada implementado ni desplegado. Fuentes leídas: scripts/vm208/pg-dumps.sh, ansible/host_vars/media-208.yml, ansible/roles/homelab_tools/templates/runwisp.toml.j2, scripts/original-backups/runwisp-task.toml, backend originals y helper dump19. No consultas NAS/runtime adicionales para este diseño.

La tarea pg_dumps existente está a01:30, catch_up=skip/on_overlap=skip. Exporta seis DB, no despierta contenedores, protege con flock, publica por archivo temporal tras pipeline+gzipCRC y conserva un archivo final por día sin sobrescribirlo. Solo aplica retención local KEEP=5 si ninguna DB falla. Su exportación actual es la fuente: la nueva copia no debe ejecutar pg_dump ni consultar la aplicación. La copia externa puede fallar independientemente de otras cinco DB; no conviene acoplar el acceso NAS ni su retención a ese bucle de exportación.

Se propone invocar un publicador acotado una vez al final de originals_backup 11:10, después del bucle de cuatro grupos, incluso cuando se omiten como ya completos. Se conserva la misma tarea RunWisp, horario, catch_up y overlap; se mantiene el flock originals durante publicación. No nueva tarea/cron. Si falla un grupo y el flujo no llega al final, la publicación queda pendiente y visible; el dump local ya generado se preserva. Una futura política de rescate de backlog requiere revisión propia; no copiar todos los días históricos automáticamente.

Contrato propuesto para la primera implementación:

  1. Seleccionar un solo dump final Immich del día local esperado, no ocultos, enlaces ni .failed/.verified; exigir archivo regular, origen permitido, tamaño acotado y propietario/permisos privados. Si falta el de hoy, fallar de forma visible sin convertir ayer en éxito de hoy. No ampliar la retención local ni exportar otro dump como reacción. Abrir/fstat y copiar a staging700 con fichero0400, hashSHA256 y CRCgzip completo; volver a comprobar identidad del origen. Publicar manifiesto600+fsync y directorio fsync antes de red.
  2. Identidad de generación, compatible con validador receptor: d20261020-<sha256_completo64hex> (74 caracteres; límite80). Lleva fecha del dump y hash completo, por lo que otro contenido del mismo día obtiene otro destino. El dump19 ya cerrado conserva su nombre legado20261019. Nunca sobrescribir, renombrar o reutilizar otra generación publicada.
  3. Manifiesto inmutable con nombre/tamaño/hash/CRC, fecha del archivo dump, hora de staging, esquema de formato y referencias exactas a generaciones photos/pics realmente observadas en evidencia local. No asignar automáticamente fecha dump a generaciones de archivos. Contadores anónimos del dump pueden venir del parser existente, sin SQLlive ni rutas personales en logs públicos. coherence_verified=false, database_restored=false y originals_restored=false permanecen explícitos. Su publicación no demuestra coherencia aunque las tres fechas coincidan.
  4. Bajo el canal restringido existente: status readonly, reserva remota64GiB además de bytes entrantes; prepare únicamente la identidad derivada; sin link-dest/delete/inplace. Copiar dump+manifiesto a4MiB/s/nice19, deadline y presupuesto local explícitos. Hash remoto de ambos; restauración privada del dump entero (unos184MB actualmente), igualdadSHA256 y CRC antes de complete. Esto acredita recuperación de archivo, nunca recuperación PostgreSQL. Complete nativo solo después de terminar rsync y bajo lock remoto revisado.
  5. Idempotencia: si destino complete existe, comparar ambos hashes con la evidencia inmutable local; coincidencia devuelve éxito already_verified sin otra copia. Diferencia aborta sin sobrescribir. Staging existente solo se reanuda para misma identidad/manifiesto; sin relanzamiento ciego ni procesos paralelos. Ledger privado marca fases staging/hash/readback/published; no escribir last.json de los cuatro grupos ni alterar su evidencia.
  6. Retención externa inicial append-only sin poda automática: jamás eliminar generaciones anteriores ni incompletas ajenas. Si se alcanza reserva64GiB, fallar y avisar para revisión humana. Retención local pg_dumps5 permanece con su propietario actual. Staging privado de publicación fallida se preserva para diagnóstico; su limpieza selectiva y cualquier retención externa son cambios independientes revisables. Informar edad del último dump externo, estado y bytes mediante el informe/tarea existentes, sin nuevo daemon.

Bloqueos concretos antes de automatizar: el helper actual está atado a fecha, hash y contadores de dump19; no reutilizarlo como programa diario. El finish actual requiere revisión de permisos por administrador; para un flujo sin nueva credencial se debe revisar primero umask077 aplicado solamente a rsync receptor immich-dumps y demostrar directorios700/archivos600 con el mismo canal. El receiver candidato de locks/gramática aún no equivale a receiver instalado; conservar live5bab hasta revisión y despliegue explícitos del coordinador. La fecha fija del helper dump19 es evidencia histórica y no debe cambiar para “hacerlo diario”. Adaptación nueva necesita tests de fecha/hash/idempotencia, colisión, CRC, readback y fallo antes de complete sobre fixtures privados.

Revisión del helper de transferencia DB antes de publicarlo

immich-recovery-transfer-bazzite.py sigue siendo un one-shot de la incidencia, no una tarea automática. Su STAGE cambió de20261020 a20261020-retry0032 para preservar el0B previo; el segundo stage también acabó0B y se conserva. El helper actual volverá a fallar cerrado si se invoca otra vez porque rechaza stage existente. No quitar esa guarda ni reutilizar esos directorios. También se explicitan ProxyJump=none/ProxyCommand=none manteniendo StrictHostKeyChecking=yes para acceso directo existente, sin nuevas claves.

Antes de promoverlo a herramienta reutilizable: sustituir STAGE fijo por argumento obligatorio validado bajo raíz privada fija, registrar origen/destino exactos y exigir --execute con plan por defecto; permitir únicamente crear un stage nuevo, sin auto-rename/retry/cleanup. Imagen/dump deben continuar ligados a ID/hash revisados. Conservar deadline1200s,4MiB/s y presupuesto agregado8GiB. El SIGINT del orquestador sí ejecuta finally y cierra hijos; no presentar una muerte abrupta como garantía de terminar procesos remotos. Tras interrupción, comprobar solo nuestros procesos exactos y artefactos, como se hizo aquí. No implementado; no transferencia, DB ni SQL adicionales tras la segunda pausa.

Fixture final del receiver: a28c832, sin instalación

Por autorización expresa, el candidato exacto SHA256 <REDACTED> se ensayó directamente en NAS a00:34:31, Python2.7.18 y rsync3.0.9/protocol30. Esta prueba es distinta de la anterior90bb5de; no implica que aquel hash cubriera las correcciones posteriores de gramática. BASE sustituido únicamente en una copia privada temporal; ningún acceso a208/pmx50 ni instalación de producción.

Resultado PASS: cuatro variantes con operandos extra rechazadas exit1; flags standalone -L, -s, -rtL, -rs, --copy-links, --secluded-args, --delete y --inplace rechazados exit1. Los perfiles actuales -ltre.iLsfxCIvu y sender -te.LsfxCIvu alcanzaron rsync real, esperaron su protocolo y terminaron43 al cerrar EOF, sin enviar datos de aplicación.

FD3 del lock permaneció heredado tras exec; segundo writer y complete concurrentes retornaron75. Sender readonly no quedó bloqueado. Tras cerrar el writer, complete funcionó, repetir complete y escribir tarde se rechazaron; hash del archivo sintético completo siguió igual. Lockdir700 y lockfile600. Ningún PID remanente; BASE temporal propio eliminado. Solo tres bytes sintéticos locales, ninguna transferencia ni archivo real del homelab leído en esta prueba.

Evidencia privada600+fsync en NAS: /var/services/homes/monxas/.ssh/receiver-fixture-a28c832-20261019T223431Z.json. Harness SHA256 <REDACTED>. El receiver live no se modificó durante el ensayo. Despliegue pendiente de nueva instrucción del coordinador; DB y transferencias siguen pausados.

Despliegue autorizado del receiver final a00:35:13

Tras revisar el fixture final, el coordinador autorizó instalar exactamente <REDACTED>. Precondiciones satisfechas: hash live5bab exacto y cero writers rsync de originals, sin señales a procesos. Backup privado0600+fsync del script anterior: /var/services/homes/monxas/.ssh/originals-receiver.py.before-a28c832-20261019T223513Z. Reemplazo atómico, fsync del directorio y hash posterior correcto; live conserva UID1026 y permisos0700. Evidencia privada600+fsync: /var/services/homes/monxas/.ssh/receiver-deploy-a28c832-20261019T223513Z.json.

Cinco status readonly mediante la clave restringida existente desde208: documents, proyectos, photos, pics e immich-dumps de20261019 tienen complete_exists=true y staging_exists=false; libres217.287.405.568 bytes. No cambios a datos/generaciones, archivos lock existentes, sshd, claves ni perfiles. Rollback: previa comprobación de ausencia de writers, verificar hash del backup5bab, reemplazar atómicamente el script por esa copia con UID1026/mode0700 y fsync del directorio; nunca borrar o reinicializar locks para volver atrás.

Este despliegue protege writers futuros con lock NAS y gramática rsync cerrada. No incorpora el publicador diario propuesto ni permisos específicos para dumps; no vuelve a ejecutar backups. DB y transferencias siguen pausados.

Publicador diario implementado en fuente, todavía desactivado

Módulo nuevo scripts/original-backups/immich-dump-publisher.py, API publish_existing_dump(report, channel=None), sin efectos al importar ni exportaciones SQL. La integración fuente opt-in pertenece al agente de mantenimiento; no se cambió RunWisp ni se desplegó el módulo. El estado por defecto sigue desactivado. El CLI separado muestra plan y rechaza ejecutar fuera del lock/tarea originales; no añade un segundo scheduler.

Selecciona exclusivamente el nombre final de hoy según el reloj local del exportador, independientemente de report.stamp. Resuelve productor monxas mediante pwd y exige UID1000, mientras staging pertenece al caller root del job sudo. Rechaza symlinks en padres/origen, ficheros públicos/no regulares/hardlinks, mtime de más36h o más5min futuro. Fuente comprimida máxima2GiB; copia solamente bytes del fstat inicial con detector de crecimiento, reserva local8GiB+2dump y watchdog300s; no usa la reserva remota64GiB como requisito local. CRC expandido máximo8GiB/300s. Generación fecha+hash completa de74 caracteres, manifiesto inmutable600, dump400/directorios700 y fsync. Conserva referencias concretas a photos/pics y declara siempre coherence/database/originals_restored=false.

El canal exige capabilities immich-dump-private-v1 y auditoría de permisos del receiver antes de upload, antes de complete y después; ausencia o false abortan. El prerequisito nuevo sigue sin desplegar al escribir este apartado; livea28 no acredita esa capability y no permite activar esta integración. Hash de ambos archivos y readback privado del dump completo+CRC preceden a complete nativo. Destino publicado existente solo devuelve éxito tras comparar hashes; no borra ninguna generación ni poda externa. Conserva stages fallidos para diagnóstico.

SSH/rsync locales usan sesiones propias y timeout; cancelación/timeout ejecutan TERM→KILL del grupo propio y reap. SIGTERM tiene handler temporal solo dentro del publisher en mainthread; cleanup ignora brevemente SIGTERM/SIGINT repetidos y restaura handlers al terminar. Esto no promete eliminar procesos remotos tras una caída del host: el lock NAS protege futuras conexiones mientras sobrevivan. El readback monitoriza también temporales hermanos de rsync cada0,5s, tamaño esperado y reserva local8GiB; es watchdog con posible tránsito entre muestras, no cuota dura de filesystem. No se ha ejecutado contra NAS.

Validación local: 36 tests dedicados PASS, incluidos callerroot/productor1000, 40.422.686.720 bytes locales admitidos/reserva remota64GiB intacta, origen cambiante, CRC/fecha/identidad/permisos, idempotencia, reserva, readback corrupto y pérdida de privacidad antes/después de complete. Fixtures reales de árbol de procesos prueban timeout, KeyboardInterrupt y señales repetidas sin descendientes vivos, fallo al crear proceso y restauración de handlers. Backend integrado:25 tests PASS, uno omitido por rsync antiguo del Mac. Ninguna prueba usó SSH ni datos reales.

Pendiente antes de enable: revisar/desplegar prerequisito privacy del receiver, revisar módulo+hook, verificar contrato UID/rutas desplegadas y documentar flag opt-in en la tarea existente. Ningún cambio de runtime, cron, credenciales, DB o retención se autoriza mediante este apartado.

Ensayo del prerequisito privacy6dcc: transferencia local NAS bloqueada

El candidato fuente <REDACTED> aún no se desplegó. Un fixture aislado Python2 intentó usar rsync Synology como sender local hacia un shim/candidate privado: root devuelve44 userdisabled y monxas52 servicedisabled, antes de transmitir el payload sintético. Preservar el entorno SSH no cambió52. No se habilitó servicio ni se modificó autenticación. Esto es una limitación del sender Synology del harness, no un resultado PASS ni un fallo demostrado del receiver. Todos los temporales propios se eliminaron, sin PIDs restantes; producción permanece a28c832. Evidencias privadas0600 en .ssh/receiver-fixture-6dcc7cca-20261019T224356Z.json,224417Z,224435Z y224452Z. Se comunicó al coordinador antes de cambiar método; DB/publicador pausados.

Prerequisito privacy: fixture real y despliegue a00:48:34

La limitación del sender NAS se resolvió usando el canal administrador existente Mac→NAS, autorizado por el coordinador, sin208 ni cambios de servicio/configuración. El destino /tmp del primer transporte también fue rechazado por política vendor; se usó únicamente un directorio NUEVO privado bajo el share permitido /volume1/backups/terramaster/.receiver-private-mac-fixture-*, separado de snapshots-originales. Nunca se cambiaron permisos de los padres/share. Un primer input600 no distinguía la umask; el definitivo usó archivo sintético644 dentro de directorio privado700 y payload de tres bytes. Openrsync Mac2.6.9→NAS3.0.9 terminó0 en ambos envíos; --dirs redundante se normalizó solo en el shim del fixture. La gramática de producción no se relajó.

PASS final a00:48:13, candidato exacto6dcc7cca: receiver ejecutado como UID1026 con umask heredada022 produce dump0600 y pics0644, hashes sintéticos iguales. Status acredita capability y private_permissions true/false/null; rechaza archivo0644, directorio0750, symlink y más de dos archivos. Tras complete del fixture, complete=true/staging=null. Monitor administrador solo leyó FD porque DSM restringe /proc al propio usuario del rsync; cada receiver siguió con UID1026. FD3, writer/complete75, sender readonly, perfiles vigentes, rechazo de operandos extra/flags peligrosos y complete inmutable también PASS. Sin PIDs remanentes, temporales propios eliminados. Evidencia privada600+fsync: /var/services/homes/monxas/.ssh/receiver-fixture-6dcc7cca-mac-20261019T224813Z.json.

Con precondiciones livea28 exacto y cero writers originals, despliegue atómico autorizado a00:48:34, fsync del directorio y verificación de hash: <REDACTED>, UID1026, modo0700. Rollback privado0600+fsync del a28 anterior: /var/services/homes/monxas/.ssh/originals-receiver.py.before-6dcc7cca-20261019T224834Z. Evidencia privada del despliegue: /var/services/homes/monxas/.ssh/receiver-deploy-6dcc7cca-20261019T224834Z.json.

Status readonly del dump19 por receiver instalado, como UID1026: complete_exists true, staging_exists false, capability immich-dump-private-v1, private_permissions complete=true/staging=null; libres217.287.401.472 bytes. No se cambiaron permisos previos de datos, generaciones, locks, sshd ni claves. La nueva umask se limita a futuras operaciones immich-dumps; no hace chmod de copias anteriores. Publicador diario/flag de job, DB y transferencias masivas siguen pausados hasta decisión conjunta posterior del coordinador.

Método nuevo de inputs DB, sin VM208: pendiente de adquisición

El coordinador autorizó sustituir la exportación de imagen desde208 por la cadena verificada en night-immich-db-image-digest-20261020.md: manifest plataforma <REDACTED> referencia config/ImageID exacto769aa218…, linux/amd64,21 capas comprimidas208.950.383 bytes. Para respetar4MiB/s también en imagen se descarta podman pull nativo (sin limitador equivalente) y se elige un único método OCI serial HTTPS, sin tags ni fallback. No se usa208, TerraMaster.20 ni los directorios0B de intentos anteriores.

Fuentes nuevas: immich-recovery-oci.py, immich-recovery-offhost-inputs.py y test_offhost_inputs.py.14 tests fakes/locales PASS; revisión independiente del agente update_retries PASS antes de ejecución. El formato sigue OCI Image Layout y descriptores OCI. Manifest/config/cada capa exigen SHA256 y tamaño exactos; config prueba plataforma. Token anónimo readonly solo RAM, HTTPS obligatorio y redirects limitados a GHCR/githubusercontent; Authorization se elimina al cambiar host. Lecturas read1 con watchdog y pacing64KiB sin crédito por espera;4MiB/s, alarm1200s. OCIarchive sin tags, load rootless y verificación posterior de ID769/amd64/linux obligatoria. No se crea un Dockerarchive adicional: el runner existente inspecciona imagen local exacta y no consume archivos de imagen directamente.

Dump19 inmutable183.486.870 bytes/SHA6c3b… y manifiesto701 bytes se recuperan por rsync administrador NAS→Mac, credencial existente solo RAM, hosttrust estricto; stageMac700/dump400, hash y CRC expandido≤8GiB/300s, fsync, reservaMac2GiB. Después se transmiten a stage Bazzite NUEVO exclusivo,≤4MiB/s y hash exacto. Prerrequisitos Bazzite3GiB RAM/32GiB disco, reserva durante operación2GiB/24GiB, sin swap adicional para faseOCI; stages≤8GiB agregado. El imagestore rootless está fuera de esos8GiB, cubierto por guardia global de espacio libre24GiB. Se preservan stages fallidos; ningún prune ni eliminación de capas compartidas. Ownership privado registra si la imagen existía antes del load; rollback manual solo puede retirar el ID importado propio si antes no existía y no tiene ningún consumidor. No se promete rollback atómico del imagestore.

El primer intento 20261020-oci0055 terminó antes del primer evento de stages, en0,29s; el stage Mac no existe, por lo que no alcanzó lectura NAS, streaming ni faseOCI/load. El mensaje genérico no conservó clase/fase del preflight; se corrigió fuente para registrar únicamente fase y clase de error sin secretos ni URLs. No reintento automático ni sondeo repetido de host offline. Pendiente distinguir TCPoffline de preflight remoto mediante comprobación acotada del coordinador. No contenedor ni SQL; publisher diario continúa OFF.

Causa del preflight y adquisición en curso

La lectura diagnóstica acotada confirmó Bazzite online, UID1000, sin procesoOCI ni stage0055 existente. El segundo intento con fase/clase identificó el fallo antes del mkdir en preflight remoto. Evidencia de metadatos: /home es symlink root-owned a /var/home; input root canónico /var/home/monxas/.local/state/immich-recovery-input es UID1000/mode700. La guarda contra padres symlink rechazaba correctamente la ruta alias, sin que ello acreditara host offline. No se relajó: ambas constantes pasaron a ruta canónica explícita, manteniendo las mismas restricciones. MemAvailable26.002.321.408, SwapFree16.771.444.736 y disco277.697.892.352 bytes.

Delta revisado independientemente PASS,14 tests de nuevo PASS. Hashes revisados: offhost; OCI. El intento posterior creó stages privados NUEVOS20261020-oci0055 y pasó a lectura NAS→Mac limitada4MiB/s. Los intentos anteriores al preflight no dejaron ninguno. Resultado final todavía pendiente; no se ha autorizado contenedor ni SQL.

Inputs dump completos; OCI detenido antes de load

El método nuevo recuperó el dump NAS→Mac:183.486.870 bytes, SHA6c3b… y CRC completos verificados; manifiesto701 bytes/SHA55014e15… iguales. Ambos se transmitieron a /var/home/monxas/.local/state/immich-recovery-input/20261020-oci0055 con tamaño exacto/hash y fsync; stageUID1000/mode700. El stage Mac también conserva los dos archivos privados400 bajo .local/state/immich-recovery-offhost/20261020-oci0055.

OCI falló durante la config antes de capas: manifest4281 bytes/SHA886090 correcto, config0B, dos blobs en total, sin archive, sin image-ownership ni verified-record. Por orden del código no se ejecutó podmanload. Allocated stage183.517.184 bytes. Nada de SQL/contenedores, ni cambios a208. Se preserva este stage completo, incluido el blob0B; no se sobrescribe ni se vuelve a invocar a ciegas.

Una única consulta diagnóstica sin destino de archivo recuperó después la misma config769 correctamente:15.677 bytes, hash igual, redirect único a pkg-containers.githubusercontent.com. No se relajó allowlist/auth/TLS. El tipo original no se conservó; no se afirma una causa HTTP concreta ni se da el ensayo por completo. Se añadió observabilidad fuente de fase/digest/clase/statusHTTP, sin URLs firmadas ni token. Pendiente decisión del coordinador para un NUEVO stageOCI que aproveche el dump ya acreditado sin duplicar tráfico NAS.

Inputs finales acreditados; todavía sin contenedor ni SQL

La fase0105 descargó/verificó manifest, config y21 capas, creó OCIarchive y cargó la imagen una sola vez. Falló después por una plantilla propia incompatible: Podman5.8.7 usa .ID, no .Id. Lectura correcta mostró el ID completo esperado, amd64/linux; no era un mismatch de imagen. Se corrigió formato y normalización estricta64hex en fuente futura. No se repitió fetch/load ni se eliminó el failure histórico de0105. Root revisó y autorizó UNA reconciliación de solo lecturas.

immich-recovery-reconcile.py SHA <REDACTED> rehaseó manifest/config/21capas/archive y dump+manifiesto, repitió CRC con identidad before/after estable, inspeccionó .ID y escribió un NUEVO combinedproof0105. 21 tests de inputs/reconcile PASS. Resultado inputs_verified_after_inspect_format_fix:

  • Imagen exacta sha256:<REDACTED>, linux/amd64. No existía antes del load; ninguna retirada automática de imagen.
  • OCIarchive208.998.400 bytes, SHA256 <REDACTED>.
  • Dump0055 conserva183.486.870 bytes/SHA6c3b…, CRC verificado y nodo estable; manifiesto701 bytes/SHA55014e15…; sin cambios a0055 ni nuevas lecturas NAS.
  • Padre input-root agregado601.653.248 bytes; disco libre276.318.248.960, MemAvailable25.980.825.600 y SwapFree16.771.444.736 bytes.
  • Python existente 3.14.8, sin instalar ni cambiar herramientas.

Proof remoto0400/UID1000: /var/home/monxas/.local/state/immich-recovery-input/20261020-oci0105/combined-inputs-verified.json, SHA256<REDACTED> (1746bytes). Copia Mac0600: ~/.local/state/immich-recovery-offhost/20261020-oci0105/inputs-verified.json, SHA256<REDACTED> (1747bytes; mismo JSON, newline final adicional).

El siguiente runner debe usar input-root padre canónico /var/home/monxas/.local/state/immich-recovery-input y work como hermano, para contabilizar0055+0105+stages0B. El imagestore sigue fuera del presupuesto8GiB, con reserva global24GiB. Esto acredita inputs exactos disponibles; no acredita restore PostgreSQL, originales ni coherencia dump+archivos. Contenedor/SQL siguen pendientes de revisión del runner y autorización posterior del coordinador. No se tocaron los informes de coordinación/layout que mantiene Root.

Ensayo DB-only 1: rechazado antes de crear contenedor, sin SQL

Tras publicación4e6a091 y autorización explícita del coordinador se instalaron cuatro fuentes en NUEVO code stage privado20261020-code0120, verificando hashes: runner; guard5050ccad…; db-plan3b2d70be…; podman-plan256566ea…. PYTHONDONTWRITEBYTECODE=1 evita caches de código. Work NUEVO y hermano del input-root: /var/home/monxas/.local/state/immich-recovery-db-20261020-0120.

Plan/preflight pasó: MemAvailable25.999.097.856, SwapFree16.771.444.736, disco libre276.318.175.232, inputs601.690.112 bytes y blockers=[]; originals_mounts=[]. Se ejecutó una sola vez el runner congelado con fuente0055 SHA6c3b… y presupuesto padre canónico. Terminóexit1 antes de kernelproof.

Causa concreta del log privado (60bytes): Podman5.8.7 rechaza uid=999 en opciones --tmpfs, durante el parseo de /var/run/postgresql. No se creó contenedor: podman container exists immich-recovery-da28cf68e463 devolvió1, ausencia verificada por lectura independiente. No hay kernel-proof.json y no se alcanzó SELECT1 ni restore del dump ni ningún SQL. No límites kernel/UID/GID efectivos que acreditar porque nunca existió el objeto.

Resultado privado600 en work/result.json: statusfailed, restore_completefalse, stoppedfalse, cleanup_errors=[container_state_unavailable]. Este código describe que el inspect no encontró objeto; no se presenta como contenedor parado. La lectura independiente exists1 acredita ausencia y explica ese diagnóstico. Ownership token quedó en el resultado, no se usó para señalar ningún proceso ajeno. No señales ni podas.

Work conserva input copiado privado400, pgdata nueva, restore.log y result.json; inputs0055/0105 y proof029f… se conservan íntegros. No reintento automático ni relajación de límites. El coordinador prepara corrección mínima del formato tmpfs Podman; fuente revisada/publicada y nueva autorización serán necesarias para otro work. Imagen/dump siguen acreditados como inputs, no como restauración DB lograda.

Ensayo DB-only 2: aislamiento acreditado, bootstrap detenido por socket

Tras publicación765dad4 y autorización explícita, una única ejecución nueva usó code0125 privado con runnerc52391e0 y podman-pland07b5f92; guard/baseplan sin cambios. Work hermano nuevo /var/home/monxas/.local/state/immich-recovery-db-20261020-0125. No descargas/load ni cambios en0120/0055/0105. Preflight: MemAvailable25.989.709.824, SwapFree16.771.444.736, disco276.134.608.896, inputs601.722.880 bytes, sin blockers.

El gate efectivo acreditó /bin/sh y argv exacto, control bind RO separado de PGDATA vacío. kernel-proof.json0600/fsync precedió cualquier initdb/SQL: imagen769aa… exacta, rednone, rootfsRO, User999:999, UID/GID999→1000, memory.max1073741824, swap.max0, cpu.max50000/100000 y pids.max128. Sólo después se publicó start0400/fsync y arrancó el entrypoint original.

Bootstrap falló antes del stream del dump: el PostgreSQL temporal escuchaba en /tmp, mientras psql interno del entrypoint buscó /var/run/postgresql. Contenedor propio immich-recovery-6921df27d3e7 salió2, OOMfalse, 01:25:24.855–01:25:29.649+02. El runner terminóexit1 tras fallar la espera init; result statusfailed, stoppedtrue, cleanup_errors=[], sin conteos restaurados. No se confunde SQL de bootstrap tras proof con importación del dump, que no comenzó.

Log propio bootstrap-container.log600/fsync preservado en work0125:2580bytes, SH. Postflight leído: MemAvailable25.971.892.224, SwapFree16.771.444.736 sin cambio, disco275.911.122.944. Todo el intento se conserva, sin restart/retry automático. Siguiente ajuste propuesto: PGHOST=/tmp para el psql interno del entrypoint, sujeto a fuente revisada/publicada y nueva autorización. DB restore aún pendiente; no se han verificado originales ni coherencia de archivos.

Kernelproof0125:980bytes, UID1000/mode0600, SHA256<REDACTED>.

Lectura posterior del entrypoint exacto desde el contenedor propio detenido: 14179bytes, copia privada600/fsync en work0125/docker-entrypoint.original.sh, SHA256<REDACTED>. Confirma que docker_process_sql limpia PGHOST/PGHOSTADDR: la propuesta envPGHOST por sí sola queda descartada. Ajuste fuente propuesto por Root: socketserver en /tmp,/var/run/postgresql, ambos tmpfs ya revisados, preservando runner-h/tmp. Pendiente revisión/publicación y nueva autorización; ningún restart/exec/SQL.

Ensayo DB-only 3: restauración PostgreSQL verificada

Tras publicar5bbc06c y revisión Root+maint18+17tests, una única ejecución nueva usó code/work0132: runnerc52391e0, guard5050ccad, baseplanc65ed776 y podman-pland07b5f92. Se reutilizaron imagen exacta769aa… y dump0055SHA6c3b…, sin fetch/load ni cambios sobre intentos0120/0125 o inputs0055/0105. Ambos sockets privados permitieron el bootstrap original. Gate argv/controlRO/PGDATA vacío y proof kernel duradero precedieron cualquier initdb/SQL:1GiB,swap0,.5CPU,pids128,rednone,rootfsRO, User999:999 y mappings UID/GID999→1000. database_restored=true.

Runnerexit0/statusdb_restored, conteos exactos del dump19: assets31595, photos10803, pics20792, content_sha110803, path_sha120792. Contenedor propio immich-recovery-c920ccf0f843: stoppedtrue, cleanup_errors=[], lectura independiente runningfalse/exit0/OOMfalse y ownershiplabel exacto. Postflight sin blockers: MemAvailable25.805.119.488, SwapFree16.771.444.736 sin cambio, disco274.693.378.048, inputs+work1.943.101.440bytes. Los anteriores workspaces se preservan también en disco y quedan cubiertos por reserva global24GiB.

Evidencia UID1000/mode0600 bajo /var/home/monxas/.local/state/immich-recovery-db-20261020-0132:

  • kernel-proof.json1000B SHA256<REDACTED>.
  • result.json872B SHA256<REDACTED>.
  • restore.log9871B SHA256<REDACTED>.

El campo global restore_complete=false del runner se mantiene: este resultado acredita sólo la restauración de base de datos, no la aplicación Immich completa. originals_verified=false y coherence_verified=false; no originales montados, app/Redis/ML/GPU ni red. Los29assets posteriores al dump siguen siendo desfaseRPO, no pérdida acreditada. No se elimina ningún workspace/contenedor/imagen automáticamente. Rollback selectivo posterior sólo debe retirar artefactos propios con label/identidad revalidados y procesos parados, preservando dump/proofs/logs; nunca prune global.

Productor natural20: lectura01:31, pendiente de finalizar

Después del DBdrill, UNA lectura directa208 a01:31:16+02 (sin proxy, confianzaSSH existente) encontró dump20final ausente, un temporal agregado58.982.400bytes y productor pg-dumps.sh PIDs87308/87312. RunWisp systemunit active/running, Usermonxas/MainPID830. Evidencia local privada metadata-only; no contenido, hash, export, HTTP ni tarea manual. Estado PENDING, sin retryloop ni activación del flag publisher. Publicación diaria seguirá OFF hasta validar productor natural final/frescura y revisiónCAS del coordinador.

Manifiesto privado de referencias y productor natural20

Assembler fuente publicada e5ce2e7, revisión16tests sintéticos: primera lectura falló cerrada sin output por una línea ajena aasset>1MiB. Diagnóstico acotado confirmó in_assetfalse/is_COPYprefixfalse; corrección revisada descarta únicamente esas líneas por chunks1MiB+1, manteniendo SHA/CRC completos,8GiB/300s y límites estrictos sobre asset/COPY/EOF. No se ampliaron límites ni se leyeron fotos.

UNA ejecución corregida local produjo NUEVO manifiesto privado0600 en ~/.local/state/immich-recovery-references/20261020-0144/references.ndjson (parent0700), sin alterar dump0055. DumpSHA6c3b…/CRC/identidad antes-después PASS. Referencias31595 (photos10803,pics20792), únicasU31595, directoriosD10266; operaciones metadata estimadas4D+U+64=72723≤80000. NDJSON4219168B, SHA256<REDACTED>; bodySHA256<REDACTED>. Se descartó1línea ajena de5047430bytes. Rutas/filenames sólo dentro del artefacto privado; salidas e informe contienen agregados. NAS filesystem coverage pendiente.

Checker fuent compiló (NO se ejecutó) en memoria con NAS/usr/bin/python2.7.18/euid1026 a01:40:25+02, sin helperwrites/pyc/walk. Capabilities fchdir/fstat/O_DIRECTORY16384/O_NOFOLLOW32768; O_CLOEXEC no expuesto. Nueve ancestros fijados por metadata exacta previa, incluidos dos directorios0777 que no se alteraron; no se asume que todos seanUID1026.

Segunda lectura metadata natural autorizada01:41:05+02: dump20final existe, regularUID1000/mode0600/nlink1,183629054bytes, mtime01:31:41.053609+02; temporales0, productorPIDs[], RunWispactive/runningmonxasPID830. Sin contenido/hash/export/trigger/retryloop. Productor natural final/frescura metadata PASS, sin acreditar aún CRC del dump20 ni su copia externa. Publisher no se activa por este agente y conserva condición de revisiónCAS del coordinador.

Cobertura metadata NAS31595: PASS y publicación automática habilitada

Tras publicar8d88baf y reviewsRoot+maint, UNA ejecución del bridge96464a0 con checker e6c2d7 y manifiesto0144pinned terminóexit0/statusmetadata_match. ASKPASSforce autenticó UID1026 directo, sin sudo ni ficheros remotos nuevos; stdinPIPE exacto/EOF separado de autenticación. Shimlocal700 sin secreto/PYc eliminado al salir; recibo local privado0600/fsync. No fotos abiertas ni NASwrites.

coverage_complete=true, processed_references31595/pending0/unique31595, root_changed=false tras reanchor final. Photos10803 y pics20792: todos archivos regulares, no vacíos y propietario esperado. Missing/symlink/unexpected_type/ unexpected_uid/empty_file/unavailable=0 en ambos grupos. 72461 operaciones metadata≤80000, ritmo≤100/s, peak_owned_fds8≤60; 900s NAS/1000s transporte. Manifiesto3bffd1ff…/bodyb409bb2e…/dump6c3b… idénticos. No hubo retry ni relajación de límites. content_verified=false y coherence_verified=false: existencia/tipo/propiedad no prueban todos los bytes ni restauración completa Immich.

Recibo local1340bytes bajo ~/.local/state/immich-recovery-references/20261020-check0149/receipt.json, SHA256<REDACTED>.

Candidatos de originales, todavía sin leer sus archivos

Una ejecución local sourcecb72a5f releyó exclusivamente dump0055SHA/CRC/identidad PASS y produjo manifest idéntico más candidatos privados en NUEVOstage0158: library0/upload100, JSON0600 SHA256 <REDACTED>. Contienen sólo rutas upload y checksums SHA1 del dump para futura selección; no fotos leídas/copias/stat adicionales ejecutados. Las muestras históricas3encoded-video siguen acreditando derivados; no se sustituyen por originales sin ensayo de bytes.

Estado actual de la publicación diaria

El coordinador habilitó el flag en fuente e4d4cfb y runtime RunWisp por CAS: configSHA256<REDACTED>, backend/helper hashes intactos,19jobs/otros18intactos, reloadrc0. RPC01:48:59+02 healthytrue/schedulingtrue/config_stalefalse/TZEuropeMadrid; originals_backup siguiente11:10. Último estado613875… intacto y rollback privado ~/runwisp/rollbacks/dump-publication-q8_uhj04. Esto reemplaza el estadoOFF previo sin reescribir su historia. Primera publicación automática PENDING11:10, sin job manual; las5métricas aún reflejan último enabled0 hasta completar la tarea.

Tres originales upload recuperados: PASS02:05CEST

Fuentes336d44f, revisión32tests+integración y nativecompileNASPython2.7.18/euid1026 antes de leer originales. UNAejecución real sesión76249 terminóexit0: samples_verified3 / sample_bytes6098264 / sample_content_verifiedtrue. OrigenDBdump19SHA6c3… + NASphotosgeneración20261019/pool100uploadSHA80bf…; SHA1 del dump verificado en origen y en tres relecturaslocales, más SHA256 remoto/local. No son las muestras encoded-video históricas: éstos son tres originalesupload.

Nuevo stage local privado700 ~/.local/state/immich-recovery-references/20261020-samples0210: archive.tar/tresanónimos/proof/receipt600;0210identifica stage, hora real02:05. ReceiptSHA256<REDACTED>. Máximo16MiBporarchivo/48MiBtotal,4MiB/s combinado,120sNAS/180sparent, FDnofollow/pins/reanchor; TARUSTARREG-only verificado y extracciónmanual exclusiva, sinNASwrites ni cambios de producción. Artefactos y pruebas previas se conservan.

originals_verified=false y coherence_verified=false: resultado sólo de3muestras, no todos los bytes ni restauración completa de Immich. DB-only31.595y cobertura metadata31.595yaPASS son evidencias separadas. Publicación diaria habilitada, primera automática11:10aún pendiente. Detalles y fuentes en informe de originales.

Raíz de entrada contabilizada y compatibilidad de limpieza: 20 octubre, 19:16 CEST

La auditoría local encontró que guard.observe omitía raíces extra inexistentes: un nombre equivocado se contaba como cero bytes y podía dejar pasar la guardia de recursos. El runner rootless exigía declarar input-root, pero no acreditaba que el dump perteneciera a ese árbol. Se reprodujeron ambos casos con temporales, sin motor o datos reales.

PR81 exige raíz existente y recorrido de componentes de directorio no-follow, dump regular dentro de esa raíz y rechazo de .. antes de normalizar. Esto evita validar la ruta normalizada y copiar una distinta a través de un symlink seguido de ... Los rechazos preceden a observación de recursos, comprobación de imagen y creación de workspace. Se mantiene rechazo de solapamiento work/input-root; el guard ya no considera ausencias como cero. No es aislamiento atómico ante cambios concurrentes posteriores ni una reescritura del recorrido recursivo.

La primera CI Linux pasó Python3.12 y alertas, pero Python3.10 descubrió un fallo previo de limpieza: add_note no existe en esa versión y podía ocultar la excepción original. La corrección conserva el mismo objeto/tipo de error, retiene notas y emite un aviso fijo de limpieza no confirmada sin mensajes privados. La regresión simula esa ausencia también en Python reciente y verifica que se intentan todos los hijos y se restauran señales/máscara.

La revisión independiente y la repetición local pasan 24 pruebas de recuperación y18 de ciclo de vida. Las dos suites se añaden al workflow Linux existente, con triggers para los módulos Immich importados; no se añade otro job o daemon. La CI corregida39464581944 pasa Python3.10/3.12 y contratos/alertas PBS. PR81 quedó fusionada a las 19:17:43 CEST en <REDACTED>. La CI posterior39464818300 también terminó correctamente. No se ejecutaron motores, SSH, NAS, dumps reales, restauraciones o instalación runtime para estas pruebas. La restauración completa sigue pendiente.