Signature
@router.get("/backup/full/download/{job_id}")
def backup_full_download(request, job_id: str):
"""GET /api/racks/backup/full/download/{job_id} - Descarga el ZIP de un backup async listo."""
Método: GET
Path: /api/racks/backup/full/download/{job_id}
Permisos: racks.admin
Respuesta: 200 OK (ZIP file, streaming) | 404 (no listo / no existe)
Descripción
Descarga el ZIP del backup completado, aislado por organización (RLS + ORM). El ZIP se auto-limpia tras servirse (POSIX unlink).
Response (200)
Content-Type: application/zip
Content-Disposition: attachment; filename="crearack_backup_ORGNAME_20260716_110418.zip"
[ZIP binary stream, ~50-500 MB típico]
- Streaming: FileResponse no carga el ZIP en RAM. Evita el guard
PayloadBudgetMiddleware(hard_limit 5 MB).
Response (404)
{
"error": "Backup not ready" // o "Backup file expired"
}
"Backup not ready": Job no encontrado o no estádoneaún."Backup file expired": Job estádonepero el ZIP ya no existe en disco (se borró después de otra descarga).
Lógica interna
- Valida
require_perm(request, "racks", "admin"). - Obtiene
org = get_current_org(request). - Query aislada por org + RLS:
job = AsyncJob.objects.filter( job_id=job_id, organization=org, kind=AsyncJob.Kind.BACKUP_FULL ).first() - Verifica:
- Job existe y es de esta org.
- Job está en status
"done". - Archivo ZIP existe en
ASYNC_BACKUP_DIR.
- Sirve el ZIP con
FileResponse(streaming). - Auto-limpia con
os.unlink(zip_path)inmediato (en POSIX, la inode se mantiene viva mientras el filehandle de FileResponse esté abierto).
Seguridad
Multi-tenancy
- ORM filter: Job debe ser de
organization=org(tabla FK). - RLS: Via
get_current_org(request)que respeta las políticas de RLS en Postgres. - Resultado: Un admin de Org A no puede descargar backups de Org B.
Almacenamiento
- ZIP guardado en
MEDIA_ROOT/backups/async/{job_id}.zip. - Solo la tarea
run_full_backup()puede escribir (no hay endpoint GET de listado). - Auto-limpieza al servirse.
Contenido
- Datos: ~13 modelos, creds SNMP en claro (T1 decision, reversible al restaurar).
- Uploads: Solo archivos referenciados por esta org (no cross-tenant bleed).
Consideraciones
- Timeout descarga: 30 minutos típico (depende del size del ZIP y la red).
- Reintentos: Si la descarga falla a mitad, el cliente debe re-hacer polling y re-intentar (el ZIP no se borra en el primer fallo).
- Expiración: Sin lógica explícita de TTL (puede permanecer días). Future: agregar cron de limpieza.
Véase también
- [[entity—racks—endpoint—backup-full-start]]
- [[entity—racks—service—run-full-backup]]
- [[entity—core—model—asyncjob]]
- [[feature—racks—backup-restore-async-v156]]
- [[concept—saas—multi-tenancy]]
Referenciado desde
- Copia de seguridad y restauración completa en segundo plano (v1.56.0 · ADR T2 2/2)
- El backup adelgaza: los vídeos de Signage son opcionales (v1.72.0)
- El backup cubre ahora cables, salas DCIM, ITSM y Signage (v1.70.0)
- Endpoint POST /api/racks/backup/full/start — Encolar backup completo (v1.56.0)
- Manifest y verificación de integridad al crear un backup (v1.69.0 · task #236 PR-1)
- Manifest y verificación de integridad de backups (core/services/backup_verify.py)
- Restauración aditiva con confirmación (v1.66.2 · Fix integridad datos)
- Servicio / Tarea Huey `run_full_backup()` — Generar ZIP de backup (v1.56.0)