# AgentHub Backup & Restore Runbook **Version**: 1.0 **Date**: 2026-04-30 **Maintainer**: FoundingEngineer **Related ADR**: [ADR-0004 Déploiement](./adr/0004-deploiement-phase1-lan-phase2-coolify.md) --- ## Table of Contents 1. [Backup Strategy](#backup-strategy) 2. [Automated Backups](#automated-backups) 3. [Manual Backup](#manual-backup) 4. [Restore Procedure](#restore-procedure) 5. [Disaster Recovery](#disaster-recovery) 6. [Weekly Encrypted Backups](#weekly-encrypted-backups) 7. [Troubleshooting](#troubleshooting) --- ## Backup Strategy AgentHub utilise une stratégie de backup à deux niveaux : 1. **Backups locaux journaliers** : pg_dump à 03:00 UTC, rétention 14 jours sur `/opt/agenthub/backups` 2. **Backups distants hebdomadaires** : copie chiffrée GPG vers Scaleway Object Storage Paris, rétention 8 semaines **RPO (Recovery Point Objective)** : 24 heures **RTO (Recovery Time Objective)** : < 30 minutes pour un restore standard --- ## Automated Backups Les backups sont orchestrés par **Ofelia** (scheduler Docker) qui exécute le container `backup` quotidiennement. ### Vérifier le statut des backups ```bash # Lister les backups locaux ls -lh /opt/agenthub/backups/ # Vérifier les logs Ofelia docker compose -f compose.lan.yml logs ofelia # Vérifier les logs du dernier backup docker compose -f compose.lan.yml logs backup | tail -50 ``` ### Configuration du backup automatique Le service `backup` est configuré dans `compose.lan.yml` et `compose.coolify.yml` : - **Schedule** : `0 0 3 * * *` (03:00 UTC tous les jours) - **Rétention locale** : 14 jours (gérée automatiquement par le script) - **Format** : PostgreSQL custom format (`-Fc`), optimal pour restore sélectif - **Destination** : `/opt/agenthub/backups/agenthub_YYYYMMDD_HHMMSS.dump` --- ## Manual Backup ### Backup manuel immédiat ```bash # Exécuter un backup manuel docker compose -f compose.lan.yml run --rm backup # Vérifier que le backup est créé ls -lh /opt/agenthub/backups/ | tail -1 ``` ### Backup one-shot hors Docker ```bash # Depuis l'hôte (nécessite psql installé) PGPASSWORD='' pg_dump -Fc \ -h localhost \ -p 5432 \ -U agenthub \ -d agenthub \ -f "/opt/agenthub/backups/manual_$(date -u +%Y%m%d_%H%M%S).dump" ``` --- ## Restore Procedure ### Pré-requis - Fichier de backup disponible (`.dump`) - Accès au serveur Postgres cible - Variables d'environnement Postgres configurées (`PGHOST`, `PGUSER`, `PGPASSWORD`) ### Restore standard (production down) **Cas d'usage** : restaurer la base de production après corruption ou perte de données. ```bash # 1. Arrêter l'application pour éviter les connexions actives docker compose -f compose.lan.yml stop app # 2. Lancer le restore (interactive confirmation) docker compose -f compose.lan.yml run --rm \ -e PGHOST=postgres \ -e PGDATABASE=agenthub \ -e PGUSER=agenthub \ -e PGPASSWORD="${POSTGRES_PASSWORD}" \ backup \ /usr/local/bin/restore.sh /backups/agenthub_20260430_030000.dump # 3. Vérifier l'intégrité après restore docker compose -f compose.lan.yml run --rm postgres \ psql -h postgres -U agenthub -d agenthub -c "SELECT COUNT(*) FROM agents;" # 4. Redémarrer l'application docker compose -f compose.lan.yml start app ``` ### Restore vers base éphémère (test) **Cas d'usage** : valider un backup avant de l'utiliser en production. ```bash # 1. Créer une base de test docker compose -f compose.lan.yml exec postgres \ psql -U agenthub -d postgres -c "CREATE DATABASE agenthub_test OWNER agenthub;" # 2. Restore vers la base de test docker compose -f compose.lan.yml run --rm \ -e PGHOST=postgres \ -e PGDATABASE=agenthub_test \ -e PGUSER=agenthub \ -e PGPASSWORD="${POSTGRES_PASSWORD}" \ -e SKIP_CONFIRMATION=yes \ backup \ /usr/local/bin/restore.sh /backups/agenthub_20260430_030000.dump agenthub_test # 3. Vérifier la restauration docker compose -f compose.lan.yml exec postgres \ psql -U agenthub -d agenthub_test -c "\dt" # 4. Comparer le nombre de tables PROD_TABLES=$(docker compose -f compose.lan.yml exec postgres \ psql -U agenthub -d agenthub -t -c \ "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public';") TEST_TABLES=$(docker compose -f compose.lan.yml exec postgres \ psql -U agenthub -d agenthub_test -t -c \ "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public';") echo "Prod tables: ${PROD_TABLES}" echo "Test tables: ${TEST_TABLES}" # 5. Cleanup docker compose -f compose.lan.yml exec postgres \ psql -U agenthub -d postgres -c "DROP DATABASE agenthub_test;" ``` --- ## Disaster Recovery ### Scénario 1 : Corruption de la base de production **Impact** : Application down, données corrompues 1. Arrêter l'application : `docker compose -f compose.lan.yml stop app` 2. Identifier le dernier backup sain : `ls -lht /opt/agenthub/backups/ | head -5` 3. Restaurer depuis ce backup (voir [Restore standard](#restore-standard-production-down)) 4. Redémarrer : `docker compose -f compose.lan.yml start app` 5. Smoke test : `curl -fsS http://:3000/healthz` **Temps estimé** : 5–10 minutes --- ### Scénario 2 : Perte totale du volume pgdata **Impact** : Volume Docker perdu, base de données inexistante 1. Recréer le volume : `docker volume create _pgdata` 2. Démarrer Postgres : `docker compose -f compose.lan.yml up -d postgres` 3. Attendre que Postgres initialise : `docker compose -f compose.lan.yml logs -f postgres` 4. Restaurer le dernier backup (voir [Restore standard](#restore-standard-production-down)) 5. Redémarrer l'application : `docker compose -f compose.lan.yml up -d` **Temps estimé** : 10–15 minutes --- ### Scénario 3 : Perte totale du serveur **Impact** : Serveur hôte down, backups locaux inaccessibles 1. Provisionner un nouveau serveur (voir `scripts/bootstrap.sh` dans le repo) 2. Télécharger le backup hebdomadaire chiffré depuis Scaleway Object Storage : ```bash aws s3 cp \ "s3://${S3_BUCKET}/weekly/agenthub_.dump.gpg" \ /tmp/backup.dump.gpg \ --endpoint-url "${S3_ENDPOINT}" ``` 3. Déchiffrer le backup : ```bash gpg --decrypt /tmp/backup.dump.gpg > /tmp/backup.dump ``` 4. Copier le backup dans le volume : ```bash mkdir -p /opt/agenthub/backups mv /tmp/backup.dump /opt/agenthub/backups/ ``` 5. Lancer la stack : `docker compose -f compose.lan.yml up -d` 6. Restaurer (voir [Restore standard](#restore-standard-production-down)) **Temps estimé** : 30–60 minutes (selon taille du backup et bande passante) --- ## Weekly Encrypted Backups Les backups hebdomadaires (dimanche 03:00 UTC) sont chiffrés GPG et uploadés vers Scaleway Object Storage. ### Vérifier les backups distants ```bash # Lister les backups sur Scaleway aws s3 ls "s3://${S3_BUCKET}/weekly/" --endpoint-url "${S3_ENDPOINT}" ``` ### Télécharger et déchiffrer un backup distant ```bash # Télécharger aws s3 cp \ "s3://${S3_BUCKET}/weekly/agenthub_20260427_030000.dump.gpg" \ /tmp/backup.dump.gpg \ --endpoint-url "${S3_ENDPOINT}" # Déchiffrer (nécessite la clé privée GPG correspondante) gpg --decrypt /tmp/backup.dump.gpg > /tmp/backup.dump # Restaurer # (voir section Restore Procedure) ``` ### Configuration S3 et GPG Les variables d'environnement suivantes doivent être configurées dans `.env` : ```bash S3_ENDPOINT=https://s3.fr-par.scw.cloud S3_BUCKET=agenthub-backups-paris AWS_ACCESS_KEY_ID= AWS_SECRET_ACCESS_KEY= GPG_RECIPIENT_KEY= ``` --- ## Troubleshooting ### Le backup automatique ne s'exécute pas **Symptômes** : Aucun nouveau backup depuis > 24h 1. Vérifier que le service Ofelia est up : ```bash docker compose -f compose.lan.yml ps ofelia ``` 2. Vérifier les logs Ofelia : ```bash docker compose -f compose.lan.yml logs ofelia | grep backup-daily ``` 3. Vérifier les labels du service `backup` dans `compose.lan.yml` : ```yaml labels: ofelia.enabled: "true" ofelia.job-exec.backup-daily.schedule: "0 0 3 * * *" ofelia.job-exec.backup-daily.command: "/usr/local/bin/backup.sh" ``` 4. Relancer Ofelia : ```bash docker compose -f compose.lan.yml restart ofelia ``` --- ### Le restore échoue avec "permission denied" **Cause probable** : Mauvais utilisateur Postgres ou base verrouillée 1. Vérifier que l'application est arrêtée : ```bash docker compose -f compose.lan.yml stop app ``` 2. Tuer les connexions actives : ```bash docker compose -f compose.lan.yml exec postgres \ psql -U postgres -d postgres -c \ "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='agenthub';" ``` 3. Relancer le restore --- ### Backup file is empty or missing **Cause probable** : pg_dump a échoué (credentials, réseau, espace disque) 1. Vérifier les logs du container backup : ```bash docker compose -f compose.lan.yml logs backup ``` 2. Vérifier l'espace disque sur l'hôte : ```bash df -h /opt/agenthub/backups ``` 3. Tester manuellement pg_dump : ```bash docker compose -f compose.lan.yml run --rm backup \ pg_dump -h postgres -U agenthub -d agenthub --version ``` --- ### GPG encryption fails lors du backup hebdomadaire **Cause probable** : `GPG_RECIPIENT_KEY` manquant ou invalide 1. Vérifier que la clé GPG est importée dans le container : ```bash docker compose -f compose.lan.yml run --rm backup gpg --list-keys ``` 2. Importer la clé publique si manquante : ```bash docker compose -f compose.lan.yml run --rm backup \ gpg --import /path/to/public-key.asc ``` 3. Vérifier la variable d'environnement `GPG_RECIPIENT_KEY` dans `.env` --- ## Drill de restore (procédure mensuelle recommandée) **Objectif** : Valider que les backups sont restaurables et complets. ```bash # 1. Sélectionner un backup récent BACKUP_FILE="/opt/agenthub/backups/$(ls -t /opt/agenthub/backups/ | head -1)" echo "Testing backup: ${BACKUP_FILE}" # 2. Restore vers base éphémère # (voir section "Restore vers base éphémère (test)") # 3. Vérifier le checksum du backup sha256sum "${BACKUP_FILE}" > /tmp/backup-checksum.txt cat /tmp/backup-checksum.txt # 4. Comparer le nombre de tables # (voir section "Restore vers base éphémère (test)") # 5. Cleanup et documenter # Enregistrer le résultat du drill dans un log de suivi ``` **Fréquence recommandée** : 1 fois par mois minimum --- ## Contacts et Escalation - **Responsable technique** : FoundingEngineer - **Documentation source** : `agenthub/docs/RUNBOOK-restore.md` - **Scripts** : `agenthub/scripts/{backup,restore}.sh` - **ADR associé** : [ADR-0004](./adr/0004-deploiement-phase1-lan-phase2-coolify.md)