Dump -Fc avec vérification de relecture et rotation, test de restauration qui compare les comptages table par table, extraction par instance, contrôle de fraîcheur, units systemd et procédure de restauration écrite. Le dump Mongo est volontairement en lecture seule : aucun mongorestore, pour qu'aucune erreur de manipulation ne puisse écrire dans la prod. Ce qui manque pour que ce soit une sauvegarde : BACKUP_DEST n'est pas tranché. Tant qu'il est vide, le dump reste sur la machine qui héberge la base et ne protège de rien — le script le dit à chaque exécution. Et notify.sh ne fait qu'un logger, donc les trois surveillances écrivent dans un journal que personne ne lit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
54 lines
2.0 KiB
Bash
54 lines
2.0 KiB
Bash
#!/bin/sh
|
|
# Sauvegarde applicative de la base Postgres, en complément du snapshot OVH.
|
|
# Lecture seule vis-à-vis de la base : pg_dump prend un snapshot MVCC, il ne verrouille rien.
|
|
set -eu
|
|
|
|
CONTAINER=${CONTAINER:-myim_postgres}
|
|
DB=${POSTGRES_DATABASE:-my_info_mate}
|
|
DB_USER=${POSTGRES_USER:-mym}
|
|
OUT=${OUT:-./backups}
|
|
|
|
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
|
|
FILE="$OUT/${DB}_${STAMP}.dump"
|
|
|
|
mkdir -p "$OUT"
|
|
|
|
# Les embeddings sont régénérables depuis le contenu CMS : on garde le schéma, pas les vecteurs.
|
|
docker exec "$CONTAINER" pg_dump \
|
|
--username="$DB_USER" --dbname="$DB" \
|
|
--format=custom --compress=6 --no-owner --no-privileges \
|
|
--exclude-table-data='public."ContentEmbeddings"' \
|
|
> "$FILE"
|
|
|
|
# Un dump tronqué ne doit jamais entrer dans la rotation.
|
|
if ! docker exec -i "$CONTAINER" pg_restore --list < "$FILE" > /dev/null; then
|
|
echo "Dump illisible, supprimé : $FILE" >&2
|
|
rm -f "$FILE"
|
|
exit 1
|
|
fi
|
|
|
|
echo "$FILE ($(du -h "$FILE" | cut -f1))"
|
|
|
|
# Copie hors du VPS. C'est cette étape qui fait la différence entre un fichier
|
|
# et une sauvegarde : un dump posé sur la machine qui héberge la base disparaît
|
|
# avec elle. BACKUP_DEST vide = rien n'est protégé, le script le dit.
|
|
if [ -z "${BACKUP_DEST:-}" ]; then
|
|
echo "BACKUP_DEST vide : le dump reste sur cette machine, il ne protege de rien." >&2
|
|
else
|
|
# Un remote rclone s'ecrit "nom:chemin", sans double slash — d'ou le test sur
|
|
# le prefixe de chemin et non sur "://", qui ne matchait aucun remote.
|
|
case "$BACKUP_DEST" in
|
|
/*|./*|../*) mkdir -p "$BACKUP_DEST" && cp "$FILE" "$BACKUP_DEST/" ;;
|
|
*:*) rclone copy --immutable "$FILE" "$BACKUP_DEST" ;;
|
|
*) mkdir -p "$BACKUP_DEST" && cp "$FILE" "$BACKUP_DEST/" ;;
|
|
esac
|
|
echo "copie -> $BACKUP_DEST"
|
|
fi
|
|
|
|
# Rétention : 7 quotidiens. Les hebdo/mensuels sont gérés côté stockage objet
|
|
# par une lifecycle rule, pas ici — le VPS ne doit pas être le gardien de l'historique.
|
|
ls -1t "$OUT"/${DB}_*.dump 2>/dev/null | tail -n +8 | while read -r old; do
|
|
echo "rotation: $old"
|
|
rm -f "$old"
|
|
done
|