#!/bin/bash # Géré par Ansible (rôle sauvegardes) — ne pas modifier à la main. # # Archive {{ sauvegardes_repertoires | join(', ') }} en tar.zst, chiffre avec age # (clé publique du serveur — la clé privée ne quitte jamais le serveur, voir # {{ sauvegardes_age_cle }}), envoie vers Dedibackup en FTP, puis applique la # rotation ({{ sauvegardes_retention_quotidiennes }} quotidiennes, # {{ sauvegardes_retention_hebdomadaires }} hebdomadaires). set -euo pipefail # Les identifiants ne sont jamais affichés ni journalisés : on désactive # l'option xtrace pour cette ligne précise, par précaution. set +x source /root/.dedibackup AGE_KEY="{{ sauvegardes_age_cle }}" AGE_PUB=$(grep -oP '(?<=public key: )age1\S+' "$AGE_KEY") DATE=$(date +%Y%m%d) DOW=$(date +%u) SEMAINE=$(date +%G-W%V) WORKDIR=$(mktemp -d) trap 'rm -rf "$WORKDIR"' EXIT ARCHIVE_QUOT="quotidienne-${DATE}.tar.zst.age" # Portail (étape 13) : export cohérent de sa base (--single-transaction, sans # bloquer le site) et ses fichiers d'état. Le mot de passe root est lu dans # le conteneur : il n'apparaît jamais sur la ligne de commande de l'hôte. # Absent sur un serveur neuf (la sauvegarde arrive avant le portail) : ignoré. EN_PLUS=() if [ "$(docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ sauvegardes_portail_conteneur }} 2>/dev/null)" = "true" ]; then docker exec {{ sauvegardes_portail_conteneur }} sh -c \ 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" exec mariadb-dump -uroot --single-transaction --routines --triggers --databases {{ sauvegardes_portail_base }}' \ > "$WORKDIR/portail.sql" EN_PLUS+=(-C "$WORKDIR" portail.sql) fi # OpenStack (bloc A, 2026-10-12) : export logique des bases de Kolla (Keystone, # Nova, Neutron, Glance, Heat, Placement, Grafana), sans redémarrage ni # verrou (--single-transaction). Les comptes MariaDB (base mysql) ne sont pas # exportés : Kolla les recrée au déploiement. Le mot de passe est lu dans # passwords.yml et passé par l'entrée standard, jamais en argument. if [ "$(docker inspect -f '{% raw %}{{.State.Running}}{% endraw %}' {{ sauvegardes_openstack_conteneur }} 2>/dev/null)" = "true" ]; then kolla_sql() { printf '[client]\nuser=root\npassword="%s"\nhost=%s\n' \ "$(awk '/^database_password:/{print $2}' {{ sauvegardes_openstack_mots_de_passe }})" "{{ sauvegardes_openstack_hote }}" \ | docker exec -i {{ sauvegardes_openstack_conteneur }} "$@" } BASES=$(kolla_sql mariadb --defaults-extra-file=/dev/stdin -N -e \ "SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('mysql','information_schema','performance_schema','sys')") # shellcheck disable=SC2086 # une base par mot kolla_sql mariadb-dump --defaults-extra-file=/dev/stdin --single-transaction \ --routines --triggers --events --databases $BASES > "$WORKDIR/openstack.sql" EN_PLUS+=(-C "$WORKDIR" openstack.sql) fi for chemin in {{ sauvegardes_portail_fichiers | join(' ') }}; do if [ -e "$chemin" ]; then EN_PLUS+=("$chemin") fi done tar --exclude="{{ sauvegardes_age_dir }}" --exclude='/root/.dedibackup' \ -I zstd -cf - {{ sauvegardes_repertoires | join(' ') }} "${EN_PLUS[@]}" \ | age -r "$AGE_PUB" -o "$WORKDIR/$ARCHIVE_QUOT" lftp -u "$DEDIBACKUP_USER,$DEDIBACKUP_PASS" "$DEDIBACKUP_HOST" -e " set ftp:ssl-allow no put $WORKDIR/$ARCHIVE_QUOT quit " logger -t sauvegarde-dedibackup "Quotidienne envoyée : $ARCHIVE_QUOT" # Métrique pour l'alerte Prometheus "SauvegardeAncienne" (étape 7) : écrite # seulement une fois la sauvegarde confirmée envoyée, de façon atomique # (fichier temporaire puis renommage) pour que node_exporter ne lise jamais # un fichier à moitié écrit. DOSSIER_TEXTFILE="{{ sauvegardes_dossier_textfile | default('/var/lib/node_exporter/textfile') }}" mkdir -p "$DOSSIER_TEXTFILE" cat > "$DOSSIER_TEXTFILE/sauvegarde.prom.tmp" << METRIQUE # HELP syllabus_derniere_sauvegarde_reussie_timestamp Horodatage Unix de la dernière sauvegarde Dedibackup réussie. # TYPE syllabus_derniere_sauvegarde_reussie_timestamp gauge syllabus_derniere_sauvegarde_reussie_timestamp $(date +%s) METRIQUE mv "$DOSSIER_TEXTFILE/sauvegarde.prom.tmp" "$DOSSIER_TEXTFILE/sauvegarde.prom" if [ "$DOW" = "{{ sauvegardes_jour_hebdomadaire }}" ]; then ARCHIVE_HEBDO="hebdomadaire-${SEMAINE}.tar.zst.age" cp "$WORKDIR/$ARCHIVE_QUOT" "$WORKDIR/$ARCHIVE_HEBDO" lftp -u "$DEDIBACKUP_USER,$DEDIBACKUP_PASS" "$DEDIBACKUP_HOST" -e " set ftp:ssl-allow no put $WORKDIR/$ARCHIVE_HEBDO quit " logger -t sauvegarde-dedibackup "Hebdomadaire envoyée : $ARCHIVE_HEBDO" fi # --- Rotation : on ne garde que les N plus récentes de chaque catégorie. --- rotation() { local motif="$1" garder="$2" local liste a_supprimer liste=$(lftp -u "$DEDIBACKUP_USER,$DEDIBACKUP_PASS" "$DEDIBACKUP_HOST" -e " set ftp:ssl-allow no cls -1 ${motif} quit " 2>/dev/null | sort) a_supprimer=$(echo "$liste" | head -n -"$garder") if [ -n "$a_supprimer" ]; then local commandes="" while IFS= read -r fichier; do [ -n "$fichier" ] && commandes+="rm $fichier;" done <<< "$a_supprimer" lftp -u "$DEDIBACKUP_USER,$DEDIBACKUP_PASS" "$DEDIBACKUP_HOST" -e " set ftp:ssl-allow no $commandes quit " logger -t sauvegarde-dedibackup "Rotation $motif : supprimé $(echo "$a_supprimer" | wc -l) fichier(s)." fi } rotation "quotidienne-*.tar.zst.age" {{ sauvegardes_retention_quotidiennes }} rotation "hebdomadaire-*.tar.zst.age" {{ sauvegardes_retention_hebdomadaires }}