Skip to content

[FEATURE] Autofix damaged pack files #1165

Description

@AkinoYuki

Summary

Sometimes when backuping to a remote location, there are errors like:
"message_type":"error","message":"pack 8dff63ceaaef5dee100af69aca77048ed20b4b3ff003a2f120ecd04687794e93: unexpected file size: got 196608, expected 18103192\n"} {"message_type":"error","message":"\nThe repository contains damaged pack files. These damaged files must be removed to repair the repository. This can be done using the following commands. Please read the troubleshooting guide at https://restic.readthedocs.io/en/stable/077_troubleshooting.html first.\n\n"}

It would be great to have an option that would automatically run the different commands that will fix the repo and re-run a failed backup job when there is are detected damaged pack files in a repository.

Motivation / Use case

Sometimes backups are noted successful despite the fact that the repo is flagged as red because of damaged pack files.

Proposed solution

propose an option (a checkbox, to validate backup admin's agreement) that will, if the repo contains damaged pack files, run the following commands:
restic repair packs 23f70d1df70ff0d1eff51...87fac4e9d889477 e686ddb3afa7ea...d2cadd8139c663be
restic repair snapshots --forget
restic check
restic prune

Right now I'm using a script that automates this but it would be better if it was embeded natively.
My script:
#!/usr/bin/env bash

repair-restic-repo

------------------

Vérifie et répare un repository Restic corrompu utilisé par Zerobyte (Docker).

Ce script :

1. Vérifie que les outils nécessaires sont présents (docker, docker compose).

2. Lit le mot de passe Restic depuis un fichier sur l'hôte.

3. Exécute dans le conteneur Zerobyte :

- restic check (scan d'intégrité)

- Si des packs corrompus sont détectés :

* Extrait les IDs des packs depuis la sortie de restic check.

* Demande confirmation pour réparer (sauf si --fix-all).

* Exécute restic repair packs <PACK_IDS>.

* Exécute restic repair snapshots --forget.

* Exécute restic prune.

4. Nettoie le dossier de cache temporaire dans le conteneur.

Prérequis :

- Être exécuté sur l'hôte Docker où tourne le conteneur "zerobyte".

- Avoir un accès en lecture à :

/mnt/something/Backup/Zerobyte - restic.pass

- Avoir docker et docker compose installés et fonctionnels.

- Le repository Restic est supposé être : rclone:pcloud:backups/zerobyte

Usage :

./repair-restic-repo [--fix-all]

Options :

--fix-all Réparer automatiquement sans demander de confirmation.

Sortie :

- Les commandes restic sont exécutées dans le conteneur.

- Le script retourne 0 si tout s'est bien passé, 1 en cas d'erreur ou si l'utilisateur refuse la réparation.

set -euo pipefail

=============================================================================

Configuration

=============================================================================

CONTAINER_NAME="zerobyte"
WORKDIR="/srv/docker/zerobyte"
RCLONE_CONFIG="/app/rclone/rclone.conf"
RESTIC_REPO="rclone:pcloud:backups/zerobyte"
PASSWORD_FILE_HOST="/mnt/something/Backup/Zerobyte - restic.pass"
RESTIC_CACHE_DIR="/tmp/restic-cache"

FIX_ALL=false

=============================================================================

Parse des arguments

=============================================================================

while [[ $# -gt 0 ]]; do
case "$1" in
--fix-all)
FIX_ALL=true
shift
;;
*)
echo "Option inconnue : $1" >&2
echo "Usage: $0 [--fix-all]" >&2
exit 1
;;
esac
done

=============================================================================

Vérifications préliminaires

=============================================================================

if ! command -v docker &>/dev/null; then
echo "Erreur: docker n'est pas installé ou pas dans le PATH." >&2
exit 1
fi

if ! docker compose version &>/dev/null && ! docker-compose version &>/dev/null; then
echo "Erreur: docker compose (ou docker-compose) n'est pas disponible." >&2
exit 1
fi

if [[ ! -f "$PASSWORD_FILE_HOST" ]]; then
echo "Erreur: fichier de mot de passe introuvable : $PASSWORD_FILE_HOST" >&2
exit 1
fi

Déterminer quelle commande docker compose utiliser

if docker compose version &>/dev/null; then
DOCKER_COMPOSE_CMD="docker compose"
else
DOCKER_COMPOSE_CMD="docker-compose"
fi

=============================================================================

Lecture du mot de passe depuis le fichier sur l'hôte

=============================================================================

RESTIC_PASSWORD=$(<"$PASSWORD_FILE_HOST")

=============================================================================

Fonctions utilitaires

=============================================================================

Exécute une commande dans le conteneur avec les variables d'environnement appropriées.

Le mot de passe est passé via RESTIC_PASSWORD (variable d'env).

Usage: exec_in_container [args...]

exec_in_container() {
$DOCKER_COMPOSE_CMD -f "$WORKDIR/docker-compose.yml" exec -T "$CONTAINER_NAME"
env RCLONE_CONFIG="$RCLONE_CONFIG"
RESTIC_REPOSITORY="$RESTIC_REPO"
RESTIC_PASSWORD="$RESTIC_PASSWORD"
RESTIC_CACHE_DIR="$RESTIC_CACHE_DIR"
"$@"
}

Nettoyage du dossier de cache temporaire dans le conteneur

cleanup_cache() {
echo ">> Nettoyage du dossier de cache temporaire dans le conteneur..."
exec_in_container rm -rf "$RESTIC_CACHE_DIR" || true
}

=============================================================================

Trap pour nettoyer le cache à la sortie (succès ou échec)

=============================================================================

trap cleanup_cache EXIT

=============================================================================

Préparation de l'environnement dans le conteneur (cache)

=============================================================================

echo ">> Création du dossier de cache Restic dans le conteneur..."
exec_in_container mkdir -p "$RESTIC_CACHE_DIR"

=============================================================================

Scan d'intégrité du repository (restic check)

=============================================================================

echo ">> Vérification de l'intégrité du repository (restic check)..."
set +e
CHECK_OUTPUT=$(exec_in_container restic check 2>&1)
CHECK_EXIT=$?
set -e

echo "$CHECK_OUTPUT"

if [[ $CHECK_EXIT -eq 0 ]]; then
echo ">> Le repository est sain. Aucune réparation nécessaire."
exit 0
fi

=============================================================================

Extraction des IDs de packs corrompus depuis la sortie de restic check

=============================================================================

PACK_IDS=()

Méthode 1 : Chercher la ligne "restic repair packs ..."

Fonctionne même si la sortie est en JSON, tant que la chaîne est présente.

REPAIR_LINE=$(echo "$CHECK_OUTPUT" | grep -oP 'restic repair packs\s+\K.*' || true)

if [[ -n "$REPAIR_LINE" ]]; then
# Extraire tous les IDs hexa de 64 caractères sur cette ligne
PACK_IDS=($(echo "$REPAIR_LINE" | grep -oP '[0-9a-f]{64}'))
fi

Méthode 2 (fallback) : Si aucune commande "restic repair packs" trouvée,

chercher les lignes de type "pack : unexpected file size" ou "damaged pack"

if [[ ${#PACK_IDS[@]} -eq 0 ]]; then
# Extraire les IDs depuis les messages d'erreur de type :
# pack : unexpected file size: got ...
# pack : ...
PACK_IDS=($(echo "$CHECK_OUTPUT" | grep -oP 'pack\s+\K[0-9a-f]{64}(?=\s*:)' || true))
fi

Vérifier qu'on a bien trouvé des IDs

if [[ ${#PACK_IDS[@]} -eq 0 ]]; then
echo ">> Aucune commande de réparation détectée et aucun pack corrompu identifiable." >&2
echo ">> Le repository semble avoir des erreurs, mais impossible d'extraire les IDs des packs." >&2
echo ">> Sortie brute de restic check :" >&2
echo "$CHECK_OUTPUT" >&2
exit 1
fi

echo ">> Packs corrompus détectés :"
printf ' - %s\n' "${PACK_IDS[@]}"

=============================================================================

Demande de confirmation (sauf si --fix-all)

=============================================================================

if [[ "$FIX_ALL" != true ]]; then
echo ""
read -rp "Voulez-vous réparer ces packs corrompus ? [o/N] " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Oo]$ ]]; then
echo ">> Réparation annulée par l'utilisateur."
exit 1
fi
fi

=============================================================================

Réparation des packs corrompus

=============================================================================

echo ">> Réparation des packs corrompus..."
exec_in_container restic repair packs "${PACK_IDS[@]}"

=============================================================================

Nettoyage des snapshots orphelins / cassés

=============================================================================

echo ">> Nettoyage des snapshots orphelins..."
exec_in_container restic repair snapshots --forget

=============================================================================

Compactage et nettoyage du repository

=============================================================================

echo ">> Compactage du repository (restic prune)..."
exec_in_container restic prune

echo ">> Réparation terminée."
exit 0
root@debian:/srv/docker/zerobyte#

Impact

Medium

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions