Tecnología · GUÍA PRÁCTICA
Restic informa de packs ausentes: cree un registro antes de cambiar el repositorio
Conserve las pruebas, clasifique el resultado documentado por restic y elija el siguiente paso menos destructivo antes de reparar índices o instantáneas.

Un pack ausente o dañado de restic no es una simple advertencia de limpieza. Hasta que una comprobación completa demuestre lo contrario, las restauraciones pueden fallar y una instantánea nueva quizá tampoco sea recuperable. Detenga backup, forget y prune programados, conserve la salida original y copie el repositorio —o al menos index y snapshots— antes de reparar.
Esta guía crea un registro de reparación de solo lectura a partir del resumen documentado de `restic check --json`, stderr guardado por separado y un dry-run de reparación de instantáneas. El analizador no abre el repositorio, ejecuta restic, borra datos ni autoriza cambios.
El registro distingue packs dañados, una sugerencia de reparar el índice, otros errores pendientes, limpieza opcional y un resumen sin errores. Aun así, siga la instrucción exacta de su versión, investigue fallos de backend o hardware y demuestre la recuperación con una restauración representativa.
1. Detenga cambios y conserve la primera prueba
Desactive backup, forget y prune programados para este repositorio. No continúe un prune fallido, elimine un pack a mano, reconstruya el índice, quite un bloqueo activo ni inicie reparación de instantáneas mientras clasifica el fallo. Un error de bloqueo y un bloqueo obsoleto confirmado son asuntos distintos.
Guarde versión exacta de restic, comando, código de salida, stdout y stderr. El primer fallo puede incluir un ID de pack o error del backend que los comandos posteriores ya no muestran. Oculte URL, usuario y rutas antes de publicar registros; nunca incluya contraseñas, tokens ni credenciales cloud.
Haga una copia completa del repositorio si puede. El mínimo oficial es copiar index y snapshots para revertir una reparación fallida. Un segundo repositorio para backups nuevos reduce presión operativa, pero no demuestra que el dañado esté reparado.
2. Capture el resumen sin perder stderr
Configure repositorio y contraseña con su método seguro habitual y elija la comprobación adecuada al coste y tiempo. Check predeterminado valida estructura; `--read-data` lee todos los packs y puede consumir mucho tiempo y ancho de banda. Las muestras sirven para mantenimiento, no para cerrar un incidente que exige cobertura completa.
Conserve stdout y stderr separados: la interfaz JSON documentada pone el resumen final en stdout y errores en stderr. Este ejemplo POSIX los captura; la línea del código de salida debe seguir inmediatamente al check.
```sh restic version > restic-version.txt restic check --json > check-summary.json 2> check-errors.jsonl printf '%s\n' "$?" > check-exit.txt ```
Compruebe que `check-summary.json` contiene un único resumen completo. Conserve `check-errors.jsonl` aunque el resumen parezca suficiente. El texto libre puede cambiar: automatice con campos documentados y deje la evidencia original para diagnóstico humano.
3. Cree un registro de decisión de solo lectura
Guarde todo el código siguiente como `restic_check_ledger.py`. Solo lee el resumen guardado y rechaza un enlace simbólico en el componente final o un archivo no regular. Lee como máximo 1 MiB más un byte de detección y rechaza el exceso. Decodifica UTF-8 explícitamente y rechaza claves JSON duplicadas. No lee credenciales, contacta el backend ni modifica evidencia o repositorio.
```python #!/usr/bin/env python3 """Validate a saved `restic check --json` summary and print a read-only ledger.""" import argparse import json import re import sys from pathlib import Path PACK_ID = re.compile(r"^[0-9a-f]{64}$") def unique_object(pairs): result = {} for key, value in pairs: if key in result: raise ValueError("duplicate JSON property") result[key] = value return result def load_summary(path: Path) -> dict: if not path.is_file() or path.is_symlink(): raise ValueError("input must be a regular file, not a symlink") with path.open("rb") as stream: raw = stream.read(1024 * 1024 + 1) if len(raw) > 1024 * 1024: raise ValueError("input exceeds the 1 MiB audit limit") try: value = json.loads(raw.decode("utf-8"), object_pairs_hook=unique_object) except (UnicodeDecodeError, json.JSONDecodeError) as exc: raise ValueError("input is not one complete UTF-8 JSON document") from exc if not isinstance(value, dict) or value.get("message_type") != "summary": raise ValueError("requires the stdout summary object from restic check --json") errors = value.get("num_errors") if "broken_packs" not in value: raise ValueError("broken_packs is required, including when it is null") packs = value["broken_packs"] if packs is None: packs = [] # Restic v0.19.1 encodes an empty Go slice as JSON null. index = value.get("suggest_repair_index") prune = value.get("suggest_prune") if type(errors) is not int or errors < 0: raise ValueError("num_errors must be a nonnegative integer") if not isinstance(packs, list) or any( not isinstance(item, str) or not PACK_ID.fullmatch(item) for item in packs ): raise ValueError("broken_packs must contain complete lowercase pack IDs") if len(packs) != len(set(packs)): raise ValueError("broken_packs contains duplicates") if type(index) is not bool or type(prune) is not bool: raise ValueError("repair suggestions must be booleans") return { "numErrors": errors, "brokenPacks": packs, "suggestRepairIndex": index, "suggestPrune": prune, } def classify(summary: dict) -> dict: if summary["brokenPacks"]: status = "BROKEN_PACKS" next_step = "follow the exact restic check instruction for repair packs" elif summary["suggestRepairIndex"]: status = "INDEX_REPAIR_SUGGESTED" next_step = "follow the exact restic check instruction for repair index" elif summary["numErrors"]: status = "OTHER_ERRORS" next_step = "stop and review saved stderr or ask the restic support channel" elif summary["suggestPrune"]: status = "PRUNE_OPTIONAL_AFTER_HEALTH_REVIEW" next_step = "treat prune as cleanup, not damage repair" else: status = "NO_ERRORS_REPORTED" next_step = "retain evidence and perform a representative restore rehearsal" return { "status": status, **summary, "mutationApproved": False, "nextStep": next_step, "requiredBeforeMutation": [ "preserve full stdout, stderr, exit status and restic version", "freeze backup, forget and prune jobs", "copy the repository or at least index and snapshots when possible", "investigate storage or hardware faults before repair", ], } def main() -> int: parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("summary", type=Path, help="saved stdout summary JSON") args = parser.parse_args() try: ledger = classify(load_summary(args.summary)) except (OSError, ValueError) as exc: print(json.dumps({"status": "INCONCLUSIVE", "reason": str(exc)})) return 2 print(json.dumps(ledger, indent=2, sort_keys=True)) return 0 if ledger["status"] == "NO_ERRORS_REPORTED" else 1 if __name__ == "__main__": raise SystemExit(main()) ```
Ejecute `python3 restic_check_ledger.py check-summary.json > repair-ledger.json`. Solo `NO_ERRORS_REPORTED` devuelve 0; categorías accionables no limpias devuelven 1; entrada inválida o no admitida devuelve 2 como `INCONCLUSIVE`. La redirección crea otro informe: use otro nombre y preserve originales.
La implementación inspeccionada de restic v0.19.1 inicializa `broken_packs` como un segmento Go vacío que puede codificarse como JSON `null`. El analizador acepta `null` y `[]` como ausencia de paquetes dañados enumerados, pero rechaza una clave ausente o ID inválidos. Las 19 pruebas sintéticas cubren cinco resultados, nulo y matriz, prioridades, claves duplicadas, rechazo de UTF-16, lectura limitada, enlaces, booleanos, ID inválidos y salidas CLI0/1/2 con entradas sin modificar. Conserve aparte la salida original de check. No es un aislamiento contra carreras de rutas ni un diagnóstico de un repositorio real.
4. Interprete los cinco resultados
`BROKEN_PACKS` significa que el resumen enumera IDs completos de packs dañados. Conserve la lista y siga solo la instrucción `repair packs` de ese check y versión. La guía oficial la prioriza sobre reparar el índice porque también lo actualiza.
`INDEX_REPAIR_SUGGESTED` indica que check fijó `suggest_repair_index` y no enumeró packs dañados. `OTHER_ERRORS` no queda resuelto por campos documentados: revise stderr y consulte soporte, sin adivinar comandos.
`PRUNE_OPTIONAL_AFTER_HEALTH_REVIEW` es limpieza, no reparación. Packs sin referencia pueden sugerir prune y no equivalen a packs necesarios ausentes. `NO_ERRORS_REPORTED` es el informe del resumen, no una prueba de restauración ni de cobertura histórica total.
Estado | Qué establece | Límite de cambio BROKEN_PACKS | check enumeró IDs dañados | use solo repair-packs indicado tras copia y revisión de causa INDEX_REPAIR_SUGGESTED | check pidió reparar índice | solo después de conservar copia OTHER_ERRORS | fallo no resuelto por campos | detenerse y revisar stderr/soporte PRUNE_OPTIONAL_AFTER_HEALTH_REVIEW | quizá conviene limpiar | no tratar prune como reparación NO_ERRORS_REPORTED | este resumen no informa errores | ensayar una restauración representativa
5. Corrija la causa antes de los metadatos
La corrupción repetida apunta a almacenamiento, transporte, memoria, sistema de archivos o hardware hasta descartarlos. Reparar metadatos mientras el backend trunca o cambia archivos puede dañar otra vez el repositorio. Registre backend, versión y primer fallo de almacenamiento para soporte.
No elimine manualmente todo pack con tamaño inesperado. La guía actual manda seguir `check` y `repair packs` cuando detecta packs dañados. Soluciones históricas de foros pertenecen a su versión y contexto, no son una receta universal.
Si no puede pausar backups, inicialice otro repositorio mediante su proceso aprobado y conserve el dañado offline para investigarlo. No reutilice URL ni credenciales en ejemplos públicos ni llame reparación al reemplazo.
6. Elija solo la rama que check solicitó
Para packs dañados enumerados, use el comando `repair packs` y los IDs que indique la salida guardada. Si no hay broken packs y sí una sugerencia explícita, use la ruta documentada del índice. No ejecute ambas por reflejo ni repare un índice sano “por si acaso”.
Con el índice correcto, repetir los backups originales puede reponer blobs que aún existen en origen y sanar instantáneas antiguas que referencian el mismo contenido. No recrea bytes que ya no existen, y una snapshot nueva correcta no demuestra que todas las antiguas estén completas.
Repita un check estructural tras repair o backup healing. Si quedan errores, guarde el resultado como conjunto nuevo y no sustituya el inicial. Un error distinto muestra progreso, no permiso para borrar el historial del incidente.
7. Mapee archivos antes de aceptar pérdida en snapshots
Use `restic repair snapshots --dry-run` solo después de corregir el índice e intentar backup healing. Revise cada `would save new snapshot` y registre snapshot original, ruta afectada, copia fuente disponible y responsable de la decisión.
El dry-run es un mapa de impacto: no repara datos ni aprueba `--forget`. La reparación real elimina contenido inaccesible de snapshots reescritas; `--forget` también elimina las originales dañadas. Es una decisión explícita de pérdida, no limpieza rutinaria.
Deténgase si aparecen rutas importantes, la salida está incompleta, no buscó copias fuente o nadie está autorizado a aceptar la pérdida. Conserve snapshots antiguas y consulte soporte si hay dudas.
8. Demuestre la ruta con check y restore
El cierre oficial es un `restic check --read-data` completo que termina sin errores. En backends grandes o caros, planifique ventana y coste; una serie de porcentajes aleatorios no garantiza cobertura total.
Restaure además una snapshot representativa en un destino nuevo y vacío. Incluya un directorio valioso, un archivo grande y un árbol pequeño rico en metadatos; compare legibilidad de aplicación o hashes independientes. No sobrescriba la única copia fuente.
Cierre el registro solo cuando identifique check final, ensayo restore, inventario de snapshots/rutas y copia del repositorio. Anote lo no probado. “Sin errores” y “podemos restaurar lo necesario” son afirmaciones relacionadas pero distintas.
Preguntas frecuentes
¿Puedo ejecutar prune para quitar el error? No si faltan packs necesarios o quedan errores. Prune limpia; siga primero la reparación exacta indicada por check.
¿`repair index` recupera bytes ausentes? No. Corrige el índice. Repetir backups puede reponer contenido que siga en origen.
¿Por qué guardar stderr si tengo el resumen JSON? Los objetos de error documentados se escriben en stderr y conservan detalle. Ramifique por campos estables, pero mantenga errores originales para diagnóstico.
¿`repair snapshots --dry-run` es destructivo? El dry-run revisa impacto. La reparación posterior escribe snapshots nuevas y `--forget` borra originales; son decisiones separadas.
¿Un check estructural limpio prueba todos los bytes? No. La verificación completa requiere `--read-data`; una restauración representativa añade otra prueba de recuperación.
