Files
Malio-ops/RecetteScripts/backup-bdd-recette.sh

368 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. applique une rotation distante sur 10 jours ;
# 9. envoie un bilan sur Discord :
# - 1 message global si tout est OK ;
# - en cas derreur partielle :
# * USERS OK -> message simple ;
# * USERS KO -> message détaillé ;
# * DB OK -> message simple ;
# * DB KO -> message détaillé.
###############################################################################
#######################################
# 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
#######################################
read -r -a DBS_ARRAY <<< "$DBS"
IA_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}"
IA_BASE_DIR="${BACKUP_REMOTE_DIR}"
RETENTION_DAYS=10
SSH_OPTS=(
-i "$SSH_KEY"
-o IdentitiesOnly=yes
-o BatchMode=yes
-o ConnectTimeout="${SSH_TIMEOUT}"
)
LOG_DIR="${BACKUP_LOG_DIR}"
mkdir -p "$LOG_DIR"
TS="$(date +'%Y-%m-%d_%H-%M-%S')"
BACKUP_DIR_NAME="backup_${TS}"
LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log"
TMP_DIR="/tmp/pg_dump_${BACKUP_DIR_NAME}"
mkdir -p "$TMP_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; }
export PGPASSWORD
#######################################
# Configuration Discord
#######################################
DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}"
DISCORD_PING="${DISCORD_PING:-@here}"
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
#######################################
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"
}
#######################################
# Messages USERS
#######################################
discord_msg_users_ok_simple() {
local msg="**BACKUP BDD ${ENV_NAME} 🟢**\n"
msg+="Users backup validé"
discord_send "$msg"
}
discord_msg_users_error() {
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 msg="**${DISCORD_PING} BACKUP BDD ${ENV_NAME} 🔴**\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"
}
#######################################
# Messages DB
#######################################
discord_msg_db_ok_simple() {
local db="$1"
local msg="**BACKUP BDD ${ENV_NAME} 🟢**\n"
msg+="Backup validé : ${db}"
discord_send "$msg"
}
discord_msg_db_error() {
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 msg="**${DISCORD_PING} BACKUP BDD ${ENV_NAME} 🔴**\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_error "" "" "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_error "" "" "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=
if [[ -n "$USERS_DETAILS" ]]; then
USERS_DETAILS+=" | roles transfer failed"
else
USERS_DETAILS="roles transfer failed"
fi
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"]=""
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
#######################################
# Rotation distante
#######################################
log "Starting remote rotation: delete backups older than ${RETENTION_DAYS} days"
set +e
ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${REMOTE_DIR}/user' -type f -name 'user_*.dump' -mtime +${RETENTION_DAYS} -delete"
RET=$?
if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for users"
fi
for DB in "${DBS_ARRAY[@]}"; do
ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"
RET=$?
if [[ $RET -ne 0 ]]; then
log "ERROR: remote rotation failed for ${DB}"
else
log "Remote rotation OK for ${DB}"
fi
done
set -e
log "Remote rotation finished"
#######################################
# 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
if [[ -n "${USERS_EXPORT_OK:-}" && -n "${USERS_TRANSFER_OK:-}" ]]; then
discord_msg_users_ok_simple
else
discord_msg_users_error "${USERS_EXPORT_OK:+true}" "${USERS_TRANSFER_OK:+true}" "$USERS_DETAILS"
fi
for DB in "${DBS_ARRAY[@]}"; do
if [[ -n "${DB_DUMP_OK[$DB]:-}" && -n "${DB_TRANSFER_OK[$DB]:-}" ]]; then
discord_msg_db_ok_simple "$DB"
else
discord_msg_db_error "$DB" "${DB_DUMP_OK[$DB]:+true}" "${DB_TRANSFER_OK[$DB]:+true}" "${DB_DETAILS[$DB]}"
fi
done
exit 2