Files
Malio-ops/RecetteScripts/backup-bdd-recette.sh
2026-03-09 09:08:44 +01:00

347 lines
9.1 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
set -euo pipefail
###############################################################################
# backup-bdd-recette.sh
#
# Ce script réalise une sauvegarde logique de plusieurs bases PostgreSQL
# définies dans le fichier .env, exporte également la liste des rôles/users,
# puis transfère lensemble vers une machine distante de stockage.
#
# Fonctionnement global :
# 1. charge la configuration depuis le fichier .env ;
# 2. prépare les chemins, logs et variables de connexion ;
# 3. empêche lexécution simultanée grâce à un verrou ;
# 4. crée les dossiers de destination sur la machine distante ;
# 5. exporte les rôles PostgreSQL ;
# 6. dump chaque base au format personnalisé PostgreSQL ;
# 7. transfère chaque fichier vers le serveur distant ;
# 8. envoie un bilan sur Discord :
# - 1 message global si tout est OK ;
# - 1 message USERS si export/transfert des rôles en erreur ;
# - 1 message par base si dump ou transfert en erreur.
###############################################################################
#######################################
# Chargement du .env
#######################################
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="${SCRIPT_DIR}/.env"
if [[ ! -f "$ENV_FILE" ]]; then
echo "ERROR: fichier .env introuvable : $ENV_FILE" >&2
exit 1
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
#######################################
# Vérification des variables requises
#######################################
: "${ENV_NAME:?Variable ENV_NAME manquante}"
: "${PGHOST:?Variable PGHOST manquante}"
: "${PGPORT:?Variable PGPORT manquante}"
: "${PGUSER:?Variable PGUSER manquante}"
: "${PGPASSWORD:?Variable PGPASSWORD manquante}"
: "${DBS:?Variable DBS manquante}"
: "${BACKUP_REMOTE_USER:?Variable BACKUP_REMOTE_USER manquante}"
: "${BACKUP_REMOTE_HOST:?Variable BACKUP_REMOTE_HOST manquante}"
: "${BACKUP_REMOTE_DIR:?Variable BACKUP_REMOTE_DIR manquante}"
: "${SSH_KEY:?Variable SSH_KEY manquante}"
: "${SSH_TIMEOUT:?Variable SSH_TIMEOUT manquante}"
: "${BACKUP_LOG_DIR:?Variable BACKUP_LOG_DIR manquante}"
#######################################
# Configuration principale
#######################################
# Conversion de la liste des bases en tableau Bash
read -r -a DBS_ARRAY <<< "$DBS"
# Paramètres de connexion SSH vers la machine distante
IA_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
IA_BASE_DIR="${BACKUP_REMOTE_DIR}"
# Clé SSH et options utilisées pour les connexions SSH/SCP
SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout="${SSH_TIMEOUT}")
# Dossier de logs local
LOG_DIR="${BACKUP_LOG_DIR}"
mkdir -p "$LOG_DIR"
# Timestamp unique pour identifier ce jeu de sauvegardes
TS="$(date +'%Y-%m-%d_%H-%M-%S')"
BACKUP_DIR_NAME="backup_${TS}"
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
# Dossier temporaire local où seront générés les dumps avant transfert
TMP_DIR="/tmp/pg_dump_${BACKUP_DIR_NAME}"
mkdir -p "$TMP_DIR"
# Redirige stdout/stderr vers le fichier de log tout en gardant l'affichage console
exec > >(tee -a "$LOG_FILE") 2>&1
# Fonction de log horodaté
log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; }
# Rend le mot de passe PostgreSQL disponible à psql / pg_dump
export PGPASSWORD
#######################################
# Configuration Discord
#######################################
# URL du webhook Discord.
# Si elle est vide, aucune notification ne sera envoyée.
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
DISCORD_PING="${DISCORD_PING:-@here}"
# Envoie un message texte simple sur Discord via webhook
discord_send() {
local msg="$1"
[[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return
curl -fsS -H "Content-Type: application/json" \
-d "{\"content\":\"$msg\"}" \
"$DISCORD_WEBHOOK_URL" >/dev/null || true
}
#######################################
# Message global OK
#######################################
# Envoie un message unique quand tout le process sest bien déroulé
discord_msg_global_ok() {
local msg="**BACKUP BDD ${ENV_NAME} 🟢**\n"
msg+="Name: ${BACKUP_DIR_NAME}\n"
msg+="Dumps transfer: ✅\n"
msg+="Users transfer: ✅"
discord_send "$msg"
}
#######################################
# Message USERS
#######################################
# Envoie un message de statut spécifique à lexport/transfert des rôles/users
# Paramètres :
# $1 = export_ok -> non vide si export OK
# $2 = transfer_ok -> non vide si transfert OK
# $3 = details -> détail textuel de lerreur éventuelle
discord_msg_users() {
local export_ok="$1"
local transfer_ok="$2"
local details="$3"
local export_disp transfer_disp
export_disp=$([[ -n "$export_ok" ]] && echo "✅" || echo "❌")
transfer_disp=$([[ -n "$transfer_ok" ]] && echo "✅" || echo "❌")
local color ping
if [[ -n "$export_ok" && -n "$transfer_ok" ]]; then
color="🟢"
ping=""
else
color="🔴"
ping="${DISCORD_PING} "
fi
local msg="**${ping}BACKUP BDD ${ENV_NAME} ${color}**\n"
msg+="Name: ${BACKUP_DIR_NAME}\n"
msg+="Users export: ${export_disp}\n"
msg+="Users transfer: ${transfer_disp}"
[[ -n "$details" ]] && msg+="\nDetails: ${details}"
discord_send "$msg"
}
#######################################
# Message DB
#######################################
# Envoie un message de statut spécifique à une base donnée
# Paramètres :
# $1 = db -> nom de la base
# $2 = dump_ok -> non vide si dump OK
# $3 = transfer_ok -> non vide si transfert OK
# $4 = details -> détail textuel de lerreur éventuelle
discord_msg_db() {
local db="$1"
local dump_ok="$2"
local transfer_ok="$3"
local details="$4"
local dump_disp transfer_disp
dump_disp=$([[ -n "$dump_ok" ]] && echo "✅" || echo "❌")
transfer_disp=$([[ -n "$transfer_ok" ]] && echo "✅" || echo "❌")
local color ping
if [[ -n "$dump_ok" && -n "$transfer_ok" ]]; then
color="🟢"
ping=""
else
color="🔴"
ping="${DISCORD_PING} "
fi
local msg="**${ping}BACKUP BDD ${ENV_NAME} ${color}**\n"
msg+="Name: ${BACKUP_DIR_NAME}\n"
msg+="Database: ${db}\n"
msg+="Dump: ${dump_disp}\n"
msg+="Transfer: ${transfer_disp}"
[[ -n "$details" ]] && msg+="\nDetails: ${details}"
discord_send "$msg"
}
#######################################
# Variables de statut globales
#######################################
DUMPS_OK=true
USERS_OK=true
USERS_EXPORT_OK=true
USERS_TRANSFER_OK=true
USERS_DETAILS=""
declare -A DB_DUMP_OK
declare -A DB_TRANSFER_OK
declare -A DB_DETAILS
#######################################
# Verrou dexécution
#######################################
LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
log "ERROR: Backup déjà en cours"
discord_msg_users "" "" "Lock already exists"
exit 1
fi
trap 'rm -rf "$LOCK_DIR"' EXIT
#######################################
# Préparation du dossier distant
#######################################
REMOTE_DIR="${IA_BASE_DIR}"
log "Creating remote directories"
if ! ssh "${SSH_OPTS[@]}" "$IA_SSH" "mkdir -p '${REMOTE_DIR}/ferme' '${REMOTE_DIR}/sirh' '${REMOTE_DIR}/inventory' '${REMOTE_DIR}/user'"; then
log "ERROR: remote mkdir failed"
discord_msg_users "" "" "Remote mkdir failed"
exit 1
fi
#######################################
# Export des rôles PostgreSQL
#######################################
ROLES_FILE="${TMP_DIR}/user_${TS}.dump"
set +e
psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -Atq <<'SQL' > "$ROLES_FILE"
SELECT rolname FROM pg_roles WHERE rolname !~ '^pg_';
SQL
RET=$?
if [[ $RET -ne 0 ]]; then
USERS_OK=
USERS_EXPORT_OK=
USERS_DETAILS="roles export failed"
fi
scp "${SSH_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${REMOTE_DIR}/user/"
RET=$?
if [[ $RET -ne 0 ]]; then
USERS_OK=
USERS_TRANSFER_OK=
USERS_DETAILS+=" roles transfer failed"
fi
set -e
#######################################
# Dump des bases
#######################################
set +e
for DB in "${DBS_ARRAY[@]}"; do
FILE="${TMP_DIR}/${DB}_${TS}.dump"
DB_DUMP_OK["$DB"]=true
DB_TRANSFER_OK["$DB"]=true
DB_DETAILS["$DB"]="OK"
log "Dump $DB"
pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"
RET=$?
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
DB_DUMP_OK["$DB"]=
DB_TRANSFER_OK["$DB"]=
DB_DETAILS["$DB"]="dump failed"
continue
fi
scp "${SSH_OPTS[@]}" "$FILE" "$IA_SSH:${REMOTE_DIR}/${DB}/"
RET=$?
if [[ $RET -ne 0 ]]; then
DUMPS_OK=
DB_TRANSFER_OK["$DB"]=
DB_DETAILS["$DB"]="transfer failed"
fi
done
set -e
#######################################
# Nettoyage local
#######################################
rm -rf "$TMP_DIR"
#######################################
# Bilan final Discord
#######################################
MODE_KO=
[[ -z "${DUMPS_OK:-}" ]] && MODE_KO=true
[[ -z "${USERS_OK:-}" ]] && MODE_KO=true
if [[ -z "${MODE_KO:-}" ]]; then
discord_msg_global_ok
exit 0
fi
discord_msg_users "${USERS_EXPORT_OK:+true}" "${USERS_TRANSFER_OK:+true}" "$USERS_DETAILS"
for DB in "${DBS_ARRAY[@]}"; do
discord_msg_db "$DB" "${DB_DUMP_OK[$DB]:+true}" "${DB_TRANSFER_OK[$DB]:+true}" "${DB_DETAILS[$DB]}"
done
exit 2