Aller au contenu

Sauvegarder et restaurer Repod

Les données relationnelles de Repod (utilisateurs, index des manifestes, inventaire, jobs d'installation, recherche de paquets, empreintes SSH ssh_known_hosts) résident dans PostgreSQL (DATABASE_URL, volume postgres_data). Le système de fichiers sous /repos/ contient tout le reste : binaires des paquets, manifestes JSON, arborescences de métadonnées de dépôt, trousseau GPG, caches CVE et journaux d'audit. Ce guide explique quoi sauvegarder, comment exécuter et planifier backup.sh, comment copier les sauvegardes hors site, et comment effectuer une restauration complète.


1. Que faut-il sauvegarder

Donnée Emplacement Importance Notes
Base de données applicative PostgreSQL (DATABASE_URL, volume postgres_data) Critique Utilisateurs, index des manifestes, inventaire, jobs d'installation, recherche de paquets, empreintes SSH TOFU. Sauvegarder avec pg_dump -F c (format custom, restaurable avec pg_restore).
Paramètres /repos/settings.json Critique LDAP, OIDC, politique CVE, scheduler, configuration mirror.
Trousseau GPG /repos/gnupg/ Critique Clé privée de signature. Sa perte implique de générer une nouvelle clé et de reconfigurer chaque machine cliente.
Journaux d'audit /repos/audit/*.jsonl Élevée Fichiers JSONL en ajout seul, un par jour. Nécessaires pour la conformité et les enquêtes d'incident.
Données sécurité/CVE /repos/security/ Élevée Décisions de revue CVE du RSSI et caches KEV/EPSS.
Manifestes de paquets /repos/manifests/ Moyenne Métadonnées JSON de chaque paquet. Peuvent être reconstruites en ré-indexant le pool de paquets, mais cela prend du temps.
Pool de paquets /repos/pool/ Moyenne Stockage canonique des .deb/.rpm/.apk. Volumineux, mais inclus par défaut par backup.sh. Peut être reconstruit en ré-uploadant ou en ré-important les paquets depuis l'amont.

La perte du trousseau GPG est douloureuse

Si vous perdez le trousseau GPG sans sauvegarde, vous devez générer une nouvelle clé de signature et la distribuer à chaque machine cliente qui fait confiance à votre dépôt. C'est une charge opérationnelle significative sur de grandes flottes. Traitez le répertoire gnupg/ comme une infrastructure critique.


2. Utiliser backup.sh

Le script backup.sh est fourni à la racine du dépôt Repod. Il exécute pg_dump sur DATABASE_URL (format custom, -F c) et l'archive avec les chemins critiques de /repos/ dans un fichier .tar.gz horodaté.

Utilisation de base :

cd /opt/repod
DATABASE_URL=postgresql://repod:CHANGE_MOI@localhost:5432/repod ./backup.sh

Par défaut, l'archive est écrite dans ./backups/ :

backups/repod_backup_20260601_020000.tar.gz
├── postgres.dump      ← pg_dump -F c (toutes les données relationnelles)
├── pool/
├── settings.json
├── audit/
├── security/
├── manifests/
└── gnupg/

Configuration via les variables d'environnement :

Variable Défaut Description
DATABASE_URL Obligatoire. Chaîne de connexion PostgreSQL. Le script échoue (fail()) si elle est absente ou si pg_dump n'est pas installé — il ne produit jamais d'archive sans dump de base de données.
BACKUP_DIR ./backups Répertoire où les fichiers d'archive sont écrits.
REPOS_DIR ./repos Répertoire contenant les données non relationnelles de /repos/.
BACKUP_RETENTION_DAYS 30 Les archives plus anciennes que ce nombre de jours sont supprimées automatiquement. Définir à 0 pour désactiver la purge.

Exemple avec des chemins personnalisés :

DATABASE_URL=postgresql://repod:CHANGE_MOI@localhost:5432/repod \
BACKUP_DIR=/mnt/nas/repod-backups \
BACKUP_RETENTION_DAYS=90 \
./backup.sh

Exécution à blanc (dry run) — affiche ce qui serait sauvegardé sans créer d'archive :

./backup.sh --dry-run

Connexion à PostgreSQL depuis l'hôte

Le service db (PostgreSQL) n'est pas publié sur l'hôte par défaut. Soit vous publiez temporairement le port 5432 du conteneur, soit vous exécutez pg_dump à l'intérieur du conteneur repod-db et copiez le dump avant d'archiver :

docker exec repod-db pg_dump -U repod -F c -f /tmp/postgres.dump repod
docker cp repod-db:/tmp/postgres.dump ./postgres.dump

Installations SQLite historiques

Si /repos/auth/users.db existe encore (une installation antérieure à la migration PostgreSQL qui n'a pas été nettoyée), backup.sh le sauvegarde aussi par sécurité via sqlite3 .backup. Les installations actuelles n'ont pas ce fichier.

Alternative intégrée

Repod propose également une sauvegarde déclenchable par un admin qui produit le même format d'archive via l'API (POST /api/v1/backup/) ou Paramètres → Sauvegardes, ainsi qu'un job planifié quotidien (backup_daily). Voir Sauvegarde & Restauration (opérations) pour le détail du mécanisme intégré et de la procédure de restauration.


3. Planification avec cron

Exécutez les sauvegardes automatiquement en ajoutant une entrée à la crontab de l'utilisateur propriétaire des données Repod :

crontab -e

Ajoutez une sauvegarde quotidienne à 02h00 UTC, avec journalisation de la sortie dans un fichier :

# Sauvegarde quotidienne Repod — 02h00 UTC
0 2 * * * cd /opt/repod && DATABASE_URL=postgresql://repod:CHANGE_MOI@localhost:5432/repod \
  BACKUP_DIR=/mnt/nas/repod-backups BACKUP_RETENTION_DAYS=60 \
  ./backup.sh \
  >> /var/log/repod-backup.log 2>&1

Vérifiez que le job cron est bien enregistré :

crontab -l

Vérifiez le journal après la première exécution planifiée :

tail -50 /var/log/repod-backup.log

Tip

Faites tourner (logrotate) le fichier de journal pour éviter qu'il ne grossisse indéfiniment. Créez /etc/logrotate.d/repod-backup avec une politique de rotation hebdomadaire et une rétention de 4 semaines.


4. Copie hors site

Une sauvegarde qui réside sur le même hôte que les données qu'elle protège n'est pas une vraie sauvegarde. Copiez les archives vers un emplacement séparé après chaque exécution.

# Ajouter ceci après ./backup.sh dans votre job cron, ou dans un script d'encapsulation
rsync -avz --delete \
  /mnt/nas/repod-backups/ \
  nas-user@nas.example.com:/volume1/backups/repod/

Utilisez l'authentification par clé SSH pour que cela s'exécute sans surveillance. Restreignez l'utilisateur NAS au SFTP et au répertoire de sauvegarde.

Installez rclone et configurez un remote nommé s3-backup :

rclone config
# Suivre les invites : choisir le fournisseur "s3", saisir l'endpoint, la clé d'accès, la clé secrète

Puis synchronisez après chaque exécution de sauvegarde :

rclone sync \
  /mnt/nas/repod-backups/ \
  s3-backup:your-bucket/repod/ \
  --s3-storage-class STANDARD_IA \
  --log-level INFO

Une entrée cron combinée :

0 2 * * * cd /opt/repod && \
  DATABASE_URL=postgresql://repod:CHANGE_MOI@localhost:5432/repod \
  BACKUP_DIR=/mnt/nas/repod-backups BACKUP_RETENTION_DAYS=60 \
  ./backup.sh \
  && rclone sync /mnt/nas/repod-backups/ s3-backup:your-bucket/repod/ \
  >> /var/log/repod-backup.log 2>&1

Tip

Pour les cibles compatibles S3, activez le versionnement d'objets ou le verrouillage d'objets (object lock) sur le bucket, afin qu'un événement de type rançongiciel sur l'hôte Repod ne puisse pas écraser vos copies hors site.


5. Procédure de restauration

Suivez ces étapes pour restaurer Repod à partir d'une archive de sauvegarde. Lisez l'intégralité de la procédure avant de commencer.

Étape 1 — Arrêter la stack

cd /opt/repod
docker compose down

Étape 2 — Identifier et extraire l'archive

ls -lht /mnt/nas/repod-backups/
# Choisir la sauvegarde propre la plus récente, par ex. :
ARCHIVE=repod_backup_20260601_020000.tar.gz

mkdir -p /tmp/repod-restore
tar -xzf /mnt/nas/repod-backups/$ARCHIVE -C /tmp/repod-restore
ls /tmp/repod-restore/

Étape 3 — Restaurer la base de données PostgreSQL

docker compose up -d db
sleep 5   # attendre que PostgreSQL accepte les connexions

# --clean --if-exists supprime et recrée les objets en toute sécurité sur une base vide
BACKUP_NAME="${ARCHIVE%.tar.gz}"
docker exec -i repod-db pg_restore -U repod -d repod --clean --if-exists \
  < "/tmp/repod-restore/$BACKUP_NAME/postgres.dump"

Étape 4 — Recopier les données /repos/

Les chemins de montage des volumes dépendent de votre docker-compose.yaml. Ajustez les chemins de destination selon votre configuration :

RESTORE_DIR="/tmp/repod-restore/$BACKUP_NAME"

# Paramètres
cp "$RESTORE_DIR/settings.json" /opt/repod/repos/settings.json

# Journaux d'audit (fusion, pas d'écrasement, pour préserver les journaux écrits depuis la sauvegarde)
cp -n "$RESTORE_DIR"/audit/*.jsonl /opt/repod/repos/audit/

# Trousseau GPG
rm -rf /opt/repod/repos/gnupg/
cp -r "$RESTORE_DIR/gnupg/" /opt/repod/repos/gnupg/
chmod 700 /opt/repod/repos/gnupg/

# Manifestes, données de sécurité, pool de paquets
cp -r "$RESTORE_DIR/manifests/." /opt/repod/repos/manifests/
cp -r "$RESTORE_DIR/security/."  /opt/repod/repos/security/
cp -r "$RESTORE_DIR/pool/."      /opt/repod/repos/pool/

Warning

Définissez le bon propriétaire sur les fichiers restaurés avant de démarrer les conteneurs. Si le conteneur backend s'exécute avec un utilisateur non root (UID 1000 par défaut), assurez-vous que les fichiers restaurés appartiennent bien à cet UID : chown -R 1000:1000 /opt/repod/repos/

Étape 5 — Démarrer la stack et ré-initialiser les métadonnées du dépôt

docker compose up -d

Les arborescences de métadonnées du dépôt APT/RPM/APK (dists/, rpm/, apk/) ne font pas partie de la sauvegarde — elles sont régénérées à partir de pool/ et de la base de données restaurée :

TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/token \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"VotreMotDePasse"}' | jq -r .access_token)

curl -X POST http://localhost:8000/api/v1/distributions/init \
  -H "Authorization: Bearer $TOKEN"

Étape 6 — Vérifier

# Vérifier que tous les conteneurs sont sains
docker compose ps

# Confirmer que l'API répond
curl -s http://localhost:8000/health | jq .

# Confirmer que les paquets sont visibles
curl -s http://localhost:8000/api/v1/packages/?distribution=jammy | jq '.total'

# Tester apt update sur une machine cliente
sudo apt update

6. Tester les restaurations

Une sauvegarde que vous n'avez jamais testée est une sauvegarde à laquelle vous ne pouvez pas faire confiance.

Testez les restaurations trimestriellement en suivant cette checklist :

  • Démarrer une VM ou un conteneur séparé avec une installation Docker propre.
  • Copier l'archive de sauvegarde la plus récente sur la VM de test.
  • Suivre la procédure de restauration ci-dessus sur la VM de test.
  • Confirmer que l'API renvoie le nombre de paquets attendu.
  • Confirmer que apt update (ou dnf/zypper/apk update) réussit contre l'instance de test.
  • Confirmer que l'interface web est accessible, que la connexion fonctionne, et que les paramètres semblent corrects.
  • Documenter le temps écoulé entre la copie de l'archive et la restauration vérifiée.

Consignez les résultats dans votre runbook opérationnel. Si une restauration prend plus de temps que votre RTO (recovery time objective, objectif de délai de reprise), examinez si la taille de l'archive, la vitesse réseau ou la procédure peuvent être optimisées.


7. Avertissement sur le trousseau GPG

Sauvegarde GPG perdue = rotation de clé obligatoire

Si votre restauration échoue parce que la sauvegarde du trousseau GPG est absente ou corrompue, vous ne pouvez pas récupérer la clé de signature d'origine. Vous devez :

  1. Générer une nouvelle clé GPG via Paramètres → GPG → Générer une clé.
  2. Distribuer la nouvelle clé publique à chaque machine cliente.
  3. Retirer l'ancienne clé du trousseau de confiance de chaque machine cliente (/etc/apt/trusted.gpg.d/, rpm --import, /etc/apk/keys/, selon le format).

C'est un effort significatif sur de grandes flottes. Voir Rotation des clés de signature GPG pour la procédure complète. Il est fortement recommandé de prévenir cette situation en vérifiant que votre sauvegarde de gnupg/ est bien incluse dans chaque archive.