Complete implementation ready for Coolify: - Node.js 22 + Fastify + socket.io backend - PostgreSQL 16 + Redis 7 services - Docker Compose configuration - Deployment scripts and documentation Co-Authored-By: Paperclip <noreply@paperclip.ing>
377 lines
11 KiB
Markdown
377 lines
11 KiB
Markdown
# 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='<postgres-password>' 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://<host>: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 <project>_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_<date>.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=<scaleway-access-key>
|
||
AWS_SECRET_ACCESS_KEY=<scaleway-secret-key>
|
||
GPG_RECIPIENT_KEY=<gpg-public-key-id>
|
||
```
|
||
|
||
---
|
||
|
||
## 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)
|