El restore robaba los medios de Signage del org origen al restaurar en la misma instalación (v1.77.2)
Cuándo
20-08-2026, durante los ensayos de restauración del task #236 (ventana de features Modo Actualizar / selección por árbol) sobre la organización de pruebas. Detectado el mismo día por Edu al ver vacía la pestaña Content de Signage. Recuperación completada el 20/21-08; fix definitivo commiteado el 21-08-2026 (v1.77.2, PR #410, commit fe33bee9).
Síntomas visibles
La biblioteca de Signage de la organización real Esferic apareció vacía: 14 vídeos (231 MB) y sus miniaturas habían desaparecido del disco. La limpieza nocturna del servidor borró después la única copia que quedaba, agravando la pérdida hasta que se recuperó desde el ZIP de la Papelera.
Causa raíz
Al restaurar un backup en la MISMA instalación (org origen y destino comparten MEDIA_ROOT), el flujo de restore hacía dos pasos separados: (1) extraía los ficheros del ZIP en la ruta ORIGINAL (con el org_id de origen incrustado en la ruta signage/<org_id>/...), y (2) una función aparte, relocate_signage_files(), recolocaba esos ficheros a la ruta del org destino con os.replace() — mover, no copiar. El fallo: esa función no distinguía “fichero recién extraído del ZIP” de “fichero VIVO que ya estaba en esa ruta perteneciente al org origen” — ambos comparten la misma ruta cuando origen y destino están en la misma instalación. Cuando el backup se generaba SIN vídeos (la opción recomendada desde v1.72.0, ver [[feature—backup-restore—signage-videos-opcionales-v1-72-0]]), la función igualmente recorría los signage_media_relpaths(data) declarados y movía cualquier fichero que encontrara en la ruta de origen — que eran los ORIGINALES vivos, no extracciones del ZIP — hacia la carpeta del org destino. Con thumbnails sí incluidos en el ZIP, el mismo mecanismo también los machacaba y movía.
Fix aplicado
Commit fe33bee9 (PR #410, v1.77.2, 21-08-2026):
- La extracción del ZIP remapea ahora la ruta DIRECTAMENTE al org destino (
_remap_signage_path()aplicado en el momento de extraer, no después) —racks/api/export/restore.py. relocate_signage_files()queda RETIRADA por completo deracks/api/export/restore_domains.py.- Los ficheros que no viajan en el ZIP (por ejemplo cuando el backup se hizo sin vídeos) se COPIAN (
shutil.copy2) desde el org origen al crear cadaMediaAsset, en_ensure_media_file()— nunca se mueven. - Test de regresión
test_restore_without_videos_never_moves_source_files(tests/api/test_backup_scope.py) fija el contrato: tras un restore sin vídeos, el fichero original (vídeo Y thumbnail) sigue existiendo en la ruta del org origen, y el destino tiene su propia copia utilizable. Rojo con el código anterior, verde con el fix. Área: 36 tests + mypy en verde.
Recuperación de los datos
El ZIP de 244 MB del backup del 20-08 se rescató de la Papelera de reciclaje de Edu; los 38 ficheros se repusieron en el volumen de producción y se verificaron 38/38 contra los registros de la base de datos.
Lecciones
- Un
os.replace()sobre una ruta compartida entre org origen y destino (misma instalación) es una operación destructiva disfrazada de “recolocación”: mover y copiar no son intercambiables cuando la ruta de origen puede contener datos vivos ajenos a la operación en curso. - Los medios de producción siguen sin copia fuera del servidor (deuda ya fichada en infraestructura) — esta vez la Papelera de reciclaje de Edu permitió la recuperación, pero fue casualidad, no una red de seguridad diseñada.
- El bug llevaba latente desde que
relocate_signage_files()se introdujo (v1.70.0, [[feature—backup-restore—extended-domains-v1-70-0]]) sin manifestarse hasta que coincidieron dos condiciones a la vez: restaurar en la MISMA instalación y un backup generado SIN vídeos.
Preventivos futuros
- Copia de los medios de producción fuera del servidor (deuda de infraestructura ya conocida, sin fecha fijada).
- Cualquier operación de filesystem sobre rutas derivadas de un
org_idvariable debería copiar por defecto y solo mover cuando el llamador puede garantizar que la ruta de origen contiene exclusivamente material recién extraído, nunca datos vivos de otra organización.
Véase también
- [[feature—backup-restore—signage-videos-opcionales-v1-72-0]]
- [[entity—racks—service—apply-full-restore-from-zip]]
- [[entity—signage—model—mediaasset]]
- [[feature—backup-restore—extended-domains-v1-70-0]]
- [[feature—backup-restore—modo-actualizar-v1-75-0]]
- [[feature—backup-restore—restauracion-arbol-v1-76-0]]
- [[crearack-tech—guides—seguridad-y-backups]]