diff --git a/BackupVaultWarden/backup-vaultwarden.sh b/BackupVaultWarden/backup-vaultwarden.sh index 2f39468..fe31f5e 100755 --- a/BackupVaultWarden/backup-vaultwarden.sh +++ b/BackupVaultWarden/backup-vaultwarden.sh @@ -113,11 +113,14 @@ mkdir -p "$LOCAL_BACKUP" ####################################### # Notification Discord ####################################### -discord_ping() { +send_discord() { local success="$1" local details="${2:-}" + local payload="" [[ -z "$DISCORD_WEBHOOK_URL" ]] && return 0 + require_cmd jq || return 0 + require_cmd curl || return 0 local icon status_line if [[ "$success" == "true" ]]; then @@ -136,7 +139,6 @@ discord_ping() { msg+="Data transfer: ${status_line}\n" [[ -n "$details" ]] && msg+="Détails: ${details}" - local payload payload="$(jq -n --arg content "$msg" '{content: $content}')" curl -fsS -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL" >/dev/null || true } @@ -147,7 +149,7 @@ discord_ping() { fail() { local detail="$1" log "ERROR: $detail" - discord_ping "false" "$detail" + send_discord "false" "$detail" exit 1 } @@ -165,6 +167,7 @@ if ! mkdir "$LOCK_DIR" 2>/dev/null; then fi cleanup() { + rm -f "${LOCAL_BACKUP_FILE:-}" rm -rf -- "$LOCK_DIR" } trap cleanup EXIT @@ -227,5 +230,5 @@ rm -f "$LOCAL_BACKUP_FILE" || fail "Impossible de supprimer le backup local $LOC # Fin ####################################### log "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR" -discord_ping "true" "Backup envoyé avec succès vers $REMOTE_HOST" +send_discord "true" "Backup envoyé avec succès vers $REMOTE_HOST" echo "Backup $BACKUP_NAME terminé et envoyé sur $REMOTE_HOST:$REMOTE_DIR" diff --git a/CHANGELOG.md b/CHANGELOG.md index ca87d16..f0ee94e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ et applique le versionnement semantique. ## [Unreleased] +### Changed +- Harmonisation des fonctions d'envoi Discord dans les scripts de sauvegarde, de supervision et de reconstruction. +- Ajout d'un fichier de log dedie a l'orchestrateur `RebuildBdd/run-rebuild-bdd.sh`. +- Renforcement de la journalisation et de la gestion des erreurs dans `RecetteScripts/backup-bdd-recette.sh`. + +### Fixed +- Nettoyage du backup local temporaire dans `BackupVaultWarden/backup-vaultwarden.sh` en sortie de script. +- Gestion plus robuste des dependances shell et des verifications de disponibilite PostgreSQL dans les scripts `RebuildBdd`. +- Acceptation explicite du flag `--non-interactive` dans `RebuildBdd/Checkup/check-target-readiness.sh` pour compatibilite de workflow. +- Validation des noms de base et refus des cles SSH symboliques dans les scripts de reconstruction. +- Suppression des notifications Discord fragiles quand `jq` ou `curl` sont absents, avec envoi silencieux en secours. +- Detection et nettoyage des verrous perimes dans `RecetteScripts/backup-bdd-recette.sh`. +- Filtrage des privileges `SUPERUSER` lors de la restauration des roles dans `RecetteScripts/rebuild-bdd-recette.sh`. + ## [1.0.0] - 2026-03-18 ### Added diff --git a/CheckStorage/check-storage.sh b/CheckStorage/check-storage.sh index 3084667..2e974bd 100755 --- a/CheckStorage/check-storage.sh +++ b/CheckStorage/check-storage.sh @@ -52,6 +52,20 @@ if [[ -n "${DISCORD_WEBHOOK_URL:-}" ]]; then require_cmd curl fi +send_discord() { + local message="$1" + local payload="" + + [[ -n "${DISCORD_WEBHOOK_URL:-}" ]] || return 0 + + payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0 + + curl -fsS \ + -H "Content-Type: application/json" \ + -d "$payload" \ + "$DISCORD_WEBHOOK_URL" >/dev/null || true +} + ############################################################################### # RÉCUPÉRATION DES INFORMATIONS DISQUE ############################################################################### @@ -77,15 +91,7 @@ if [ "$usage" -ge "$limit" ]; then msgLimit="${DISCORD_PING}\n**CHECK STOCKAGE :red_circle:**\nLimite autorisée : ${limit}%\nUtilisation actuelle : ${usage}%\nEspace restant : ${free}%\nUtilisé / total : ${used_gb} GB / ${total_gb} GB\nDisponible : ${avail_gb} GB\nHeure : $(date)" - if [[ -n "${DISCORD_WEBHOOK_URL:-}" ]]; then - payload="$(jq -n --arg content "$msgLimit" '{content: $content}')" - - curl -fsS \ - -H "Accept: application/json" \ - -H "Content-Type: application/json; charset=utf-8" \ - -d "$payload" \ - "$DISCORD_WEBHOOK_URL" >/dev/null || true - fi + send_discord "$msgLimit" fi diff --git a/RebuildBdd/Checkup/check-postgresql.sh b/RebuildBdd/Checkup/check-postgresql.sh index 6bd9c31..61c27af 100755 --- a/RebuildBdd/Checkup/check-postgresql.sh +++ b/RebuildBdd/Checkup/check-postgresql.sh @@ -41,19 +41,19 @@ fail() { exit 1 } -require_cmd() { +has_cmd() { command -v "$1" >/dev/null 2>&1 } postgres_server_ready() { - require_cmd postgres || return 1 - require_cmd pg_ctlcluster || return 1 - require_cmd pg_lsclusters || return 1 + has_cmd postgres || return 1 + has_cmd pg_ctlcluster || return 1 + has_cmd pg_lsclusters || return 1 return 0 } ensure_postgres_cluster() { - if ! require_cmd pg_lsclusters || ! require_cmd pg_createcluster; then + if ! has_cmd pg_lsclusters || ! has_cmd pg_createcluster; then return 0 fi @@ -66,7 +66,7 @@ ensure_postgres_cluster() { version="$(find /etc/postgresql -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | LC_ALL=C sort -V | tail -n 1)" fi - if [[ -z "$version" ]] && require_cmd psql; then + if [[ -z "$version" ]] && has_cmd psql; then version="$(psql --version 2>/dev/null | awk '{print $3}' | cut -d. -f1)" fi @@ -82,15 +82,15 @@ collect_postgres_diagnostics() { if "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager >/dev/null 2>&1; then diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: OK; " - elif require_cmd systemctl; then + elif has_cmd systemctl; then diagnostics+="systemctl status ${POSTGRES_SERVICE_NAME}: $( "$SUDO_BIN" systemctl status "$POSTGRES_SERVICE_NAME" --no-pager 2>/dev/null | tail -n 5 | tr '\n' ' ' ); " fi - if require_cmd pg_lsclusters; then + if has_cmd pg_lsclusters; then diagnostics+="pg_lsclusters: $(pg_lsclusters --no-header 2>/dev/null | tr '\n' ' '); " fi - if require_cmd journalctl; then + if has_cmd journalctl; then diagnostics+="journalctl: $( "$SUDO_BIN" journalctl -u "$POSTGRES_SERVICE_NAME" -n 10 --no-pager 2>/dev/null | tr '\n' ' ' ); " fi @@ -102,11 +102,11 @@ start_postgres_service() { return 0 fi - if require_cmd service && "$SUDO_BIN" service "$POSTGRES_SERVICE_NAME" start >/dev/null 2>&1; then + if has_cmd service && "$SUDO_BIN" service "$POSTGRES_SERVICE_NAME" start >/dev/null 2>&1; then return 0 fi - if require_cmd pg_lsclusters && require_cmd pg_ctlcluster; then + if has_cmd pg_lsclusters && has_cmd pg_ctlcluster; then local version cluster while read -r version cluster _; do [[ -n "$version" && -n "$cluster" ]] || continue @@ -143,7 +143,7 @@ read -r -a POSTGRES_PACKAGES <<< "$POSTGRES_PACKAGE_LIST" export PGPASSWORD -if ! require_cmd "$SUDO_BIN"; then +if ! has_cmd "$SUDO_BIN"; then fail "sudo absent sur la cible" fi @@ -157,7 +157,7 @@ fi POSTGRES_INSTALLED="no" -if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb || ! postgres_server_ready; then +if ! has_cmd psql || ! has_cmd pg_restore || ! has_cmd createdb || ! has_cmd dropdb || ! postgres_server_ready; then [[ "${AUTO_INSTALL_POSTGRES,,}" == "yes" ]] || fail "PostgreSQL absent et AUTO_INSTALL_POSTGRES=no" log "PostgreSQL absent : installation en cours..." @@ -181,15 +181,17 @@ else fi log "Vérification de la disponibilité de PostgreSQL..." +PG_READY=false for _ in {1..20}; do if "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then + PG_READY=true log "PostgreSQL répond correctement." break fi sleep 1 done -if ! "$SUDO_BIN" -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then +if [[ "$PG_READY" != true ]]; then fail "PostgreSQL ne répond pas correctement" fi diff --git a/RebuildBdd/Checkup/check-target-readiness.sh b/RebuildBdd/Checkup/check-target-readiness.sh index cd6f618..8dbd361 100755 --- a/RebuildBdd/Checkup/check-target-readiness.sh +++ b/RebuildBdd/Checkup/check-target-readiness.sh @@ -30,6 +30,7 @@ while [[ $# -gt 0 ]]; do shift 2 ;; --non-interactive) + # Flag accepté pour compatibilité avec les autres scripts du workflow. NON_INTERACTIVE="yes" shift ;; diff --git a/RebuildBdd/rebuild-bdd-core.sh b/RebuildBdd/rebuild-bdd-core.sh index 41ed462..25ed421 100755 --- a/RebuildBdd/rebuild-bdd-core.sh +++ b/RebuildBdd/rebuild-bdd-core.sh @@ -97,6 +97,10 @@ fail() { } require_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1" +} + +has_cmd() { command -v "$1" >/dev/null 2>&1 } @@ -137,6 +141,13 @@ sql_escape_literal() { printf "%s" "$s" } +validate_db_name() { + local db_name="${1:-}" + + [[ -n "$db_name" ]] || return 1 + [[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || return 1 +} + build_excluded_roles_regex() { local roles_string="${1:-}" local role @@ -168,6 +179,22 @@ build_excluded_roles_regex() { printf '%s' "$joined" } +send_discord() { + local message="$1" + local payload="" + + [[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0 + has_cmd jq || return 0 + has_cmd curl || return 0 + + payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0 + + curl -fsS "$DISCORD_WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -d "$payload" \ + >/dev/null || true +} + cleanup() { rm -f \ "${LOCAL_DB_DUMP_FILE:-}" \ @@ -252,7 +279,7 @@ else fi for cmd in ssh scp psql pg_restore createdb dropdb python3 grep sed find basename curl; do - require_cmd "$cmd" || fail "commande requise absente : $cmd" + require_cmd "$cmd" done CHECK_SCRIPT="${SCRIPT_DIR}/Checkup/check-postgresql.sh" @@ -264,6 +291,7 @@ fi [[ -f "$SSH_KEY" ]] || fail "clé SSH source backup introuvable : $SSH_KEY" [[ -r "$SSH_KEY" ]] || fail "clé SSH source backup non lisible : $SSH_KEY" +[[ ! -L "$SSH_KEY" ]] || fail "clé SSH source backup ne doit pas être un lien symbolique : $SSH_KEY" export PGPASSWORD @@ -319,7 +347,7 @@ for candidate in "${DBS_ARRAY[@]}"; do done [[ -n "$DB" ]] || fail "base refusée : non présente dans DBS" -[[ "$DB" =~ ^[a-zA-Z0-9_]+$ ]] || fail "nom de base invalide" +validate_db_name "$DB" || fail "nom de base invalide" log "Environnement : $ENV_NAME" log "Base cible : $DB" @@ -479,27 +507,13 @@ pg_restore \ "$LOCAL_DB_DUMP_FILE" \ >>"$LOG_FILE" 2>&1 || fail "échec restauration base ${DB}" -send_discord_message() { - local message="$1" - local payload="" - - [[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0 - - payload="$(python3 -c 'import json,sys; print(json.dumps({"content": sys.argv[1]}))' "$message")" || return 0 - - curl -sS -X POST "$DISCORD_WEBHOOK_URL" \ - -H "Content-Type: application/json" \ - -d "$payload" \ - >/dev/null || true -} - SUCCESS_MESSAGE="✅ REBUILD BDD ${ENV_NAME} Base restaurée : ${DB} Hôte PostgreSQL : ${PGHOST}:${PGPORT} Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP") Log : ${LOG_FILE}" -send_discord_message "$SUCCESS_MESSAGE" +send_discord "$SUCCESS_MESSAGE" log "Restauration terminée avec succès pour ${DB}" print_json_and_exit "success" "restauration terminée avec succès" 0 diff --git a/RebuildBdd/run-rebuild-bdd.sh b/RebuildBdd/run-rebuild-bdd.sh index b95e350..8b462ee 100755 --- a/RebuildBdd/run-rebuild-bdd.sh +++ b/RebuildBdd/run-rebuild-bdd.sh @@ -124,6 +124,10 @@ REQUESTED_DB="${CLI_DB:-${REQUESTED_DB:-}}" ALLOW_OVERWRITE_RAW="${CLI_OVERWRITE:-${ALLOW_OVERWRITE:-no}}" RESTORE_ROLES_RAW="${CLI_RESTORE_ROLES:-${RESTORE_ROLES:-yes}}" REQUEST_ID="${CLI_REQUEST_ID:-${REQUEST_ID:-$(date '+%Y%m%d%H%M%S')_$RANDOM}}" +LOG_DIR="${RUN_REBUILD_BDD_LOG_DIR:-${SCRIPT_DIR}/logs}" +mkdir -p "$LOG_DIR" +LOG_FILE="${LOG_DIR}/run_rebuild_bdd_${REQUEST_ID}.log" +exec > >(tee -a "$LOG_FILE") 2>&1 ALLOW_OVERWRITE="$(to_bool_yes_no "$ALLOW_OVERWRITE_RAW")" || fail "ALLOW_OVERWRITE invalide" RESTORE_ROLES="$(to_bool_yes_no "$RESTORE_ROLES_RAW")" || fail "RESTORE_ROLES invalide" diff --git a/RecetteScripts/backup-bdd-recette.sh b/RecetteScripts/backup-bdd-recette.sh index e06487b..2743098 100755 --- a/RecetteScripts/backup-bdd-recette.sh +++ b/RecetteScripts/backup-bdd-recette.sh @@ -162,17 +162,22 @@ TS="$(date +'%Y-%m-%d_%H-%M-%S')" BACKUP_DIR_NAME="backup_${TS}" LOG_FILE="${LOG_DIR}/${BACKUP_DIR_NAME}.log" +exec > >(tee -a "$LOG_FILE") 2>&1 + TMP_DIR="$(mktemp -d /tmp/pg_dump_XXXXXX)" || { echo "ERROR: impossible de créer le dossier temporaire" >&2 exit 1 } -exec > >(tee -a "$LOG_FILE") 2>&1 +log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } -log() { echo "---- $(date +'%Y-%m-%d %H:%M:%S') ---- $*"; } +fail() { + log "ERROR: $*" + exit 1 +} require_cmd() { - command -v "$1" >/dev/null 2>&1 + command -v "$1" >/dev/null 2>&1 || fail "commande manquante : $1" } safe_remove_dir() { @@ -192,10 +197,7 @@ export PGPASSWORD ####################################### for cmd in ssh scp curl jq pg_dump pg_dumpall mktemp; do - require_cmd "$cmd" || { - echo "ERROR: commande manquante : $cmd" >&2 - exit 1 - } + require_cmd "$cmd" done [[ -f "$SSH_KEY" ]] || { @@ -222,14 +224,14 @@ chmod 600 "$SSH_KEY" || true DISCORD_WEBHOOK_URL="${DISCORD_WEBHOOK_URL:-}" DISCORD_PING="${DISCORD_PING:-@here}" -discord_send() { +send_discord() { local msg="$1" + local payload [[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0 - local payload payload="$(jq -n --arg content "$msg" '{content: $content}')" || { log "ERROR: impossible de construire le payload JSON Discord" - return 1 + return 0 } curl -fsS \ @@ -251,7 +253,7 @@ Dumps transfer: ✅ Users transfer: ✅ EOF )" - discord_send "$msg" + send_discord "$msg" } ####################################### @@ -265,7 +267,7 @@ discord_msg_users_ok_simple() { Users backup validé EOF )" - discord_send "$msg" + send_discord "$msg" } discord_msg_users_error() { @@ -297,7 +299,7 @@ EOF )" fi - discord_send "$msg" + send_discord "$msg" } ####################################### @@ -312,7 +314,7 @@ discord_msg_db_ok_simple() { Backup validé : ${db} EOF )" - discord_send "$msg" + send_discord "$msg" } discord_msg_db_error() { @@ -347,7 +349,7 @@ EOF )" fi - discord_send "$msg" + send_discord "$msg" } ####################################### @@ -370,11 +372,36 @@ declare -A DB_DETAILS ####################################### LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d" +LOCK_PID_FILE="${LOCK_DIR}/pid" if ! mkdir "$LOCK_DIR" 2>/dev/null; then - log "ERROR: Backup déjà en cours" - discord_msg_users_error "" "" "Lock already exists" - exit 1 + stale_lock="no" + existing_pid="" + + if [[ -f "$LOCK_PID_FILE" ]]; then + existing_pid="$(<"$LOCK_PID_FILE")" + fi + + if [[ "$existing_pid" =~ ^[0-9]+$ ]] && kill -0 "$existing_pid" 2>/dev/null; then + log "ERROR: Backup déjà en cours (PID ${existing_pid})" + discord_msg_users_error "" "" "Lock already exists (PID ${existing_pid})" + exit 1 + fi + + stale_lock="yes" + log "WARNING: lock périmé détecté, nettoyage en cours" + rm -rf -- "$LOCK_DIR" + + mkdir "$LOCK_DIR" 2>/dev/null || fail "impossible de recréer le lock après nettoyage" +fi + +echo $$ > "$LOCK_PID_FILE" || { + rm -rf -- "$LOCK_DIR" + fail "impossible d'écrire le PID du lock" +} + +if [[ "${stale_lock:-no}" == "yes" ]]; then + log "Lock périmé nettoyé." fi cleanup() { @@ -406,18 +433,18 @@ fi ROLES_FILE="${TMP_DIR}/user_${TS}.sql" -set +e - log "Export des rôles PostgreSQL" -pg_dumpall \ +if pg_dumpall \ -h "$PGHOST" \ -p "$PGPORT" \ -U "$PGUSER" \ --globals-only \ - > "$ROLES_FILE" - -RET=$? + > "$ROLES_FILE"; then + RET=0 +else + RET=$? +fi if [[ $RET -ne 0 ]]; then USERS_OK= @@ -428,8 +455,11 @@ else fi if [[ -n "${USERS_EXPORT_OK:-}" ]]; then - scp "${SCP_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/user/" - RET=$? + if scp "${SCP_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/user/"; then + RET=0 + else + RET=$? + fi if [[ $RET -ne 0 ]]; then USERS_OK= @@ -444,14 +474,10 @@ if [[ -n "${USERS_EXPORT_OK:-}" ]]; then fi fi -set -e - ####################################### # Dump des bases ####################################### -set +e - for DB in "${DBS_ARRAY[@]}"; do FILE="${TMP_DIR}/${DB}_${TS}.dump" @@ -461,8 +487,11 @@ for DB in "${DBS_ARRAY[@]}"; do log "Dump $DB" - pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE" - RET=$? + if pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc -d "$DB" -f "$FILE"; then + RET=0 + else + RET=$? + fi if [[ $RET -ne 0 ]]; then DUMPS_OK= @@ -472,8 +501,11 @@ for DB in "${DBS_ARRAY[@]}"; do continue fi - scp "${SCP_OPTS[@]}" "$FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/${DB}/" - RET=$? + if scp "${SCP_OPTS[@]}" "$FILE" "$IA_SSH:${BACKUP_REMOTE_DIR}/${DB}/"; then + RET=0 + else + RET=$? + fi if [[ $RET -ne 0 ]]; then DUMPS_OK= @@ -482,18 +514,17 @@ for DB in "${DBS_ARRAY[@]}"; do 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 '${BACKUP_REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete" -RET=$? +if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/user' -type f -name 'user_*.sql' -mtime +${RETENTION_DAYS} -delete"; then + RET=0 +else + RET=$? +fi if [[ $RET -ne 0 ]]; then log "ERROR: remote rotation failed for users" @@ -502,8 +533,11 @@ else fi for DB in "${DBS_ARRAY[@]}"; do - ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete" - RET=$? + if ssh "${SSH_OPTS[@]}" "$IA_SSH" "find '${BACKUP_REMOTE_DIR}/${DB}' -type f -name '${DB}_*.dump' -mtime +${RETENTION_DAYS} -delete"; then + RET=0 + else + RET=$? + fi if [[ $RET -ne 0 ]]; then log "ERROR: remote rotation failed for ${DB}" @@ -512,8 +546,6 @@ for DB in "${DBS_ARRAY[@]}"; do fi done -set -e - log "Remote rotation finished" ####################################### diff --git a/RecetteScripts/check-statut-recette.sh b/RecetteScripts/check-statut-recette.sh index 84cfc59..3d8ce59 100755 --- a/RecetteScripts/check-statut-recette.sh +++ b/RecetteScripts/check-statut-recette.sh @@ -135,7 +135,7 @@ add_summary_line() { ####################################### # Envoi du message Discord récapitulatif ####################################### -send_discord_summary() { +send_discord() { [[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0 local header_icon ping_prefix="" @@ -154,7 +154,7 @@ send_discord_summary() { done local payload - payload="$(jq -n --arg content "$msg" '{content: $content}')" + payload="$(jq -n --arg content "$msg" '{content: $content}')" || return 0 curl -fsS -H "Content-Type: application/json" \ -d "$payload" \ @@ -228,7 +228,7 @@ main() { done FAILURES="$failures" - send_discord_summary + send_discord if [[ "$failures" -gt 0 ]]; then exit 2 diff --git a/RecetteScripts/rebuild-bdd-recette.sh b/RecetteScripts/rebuild-bdd-recette.sh index 6d6d157..ac7b107 100644 --- a/RecetteScripts/rebuild-bdd-recette.sh +++ b/RecetteScripts/rebuild-bdd-recette.sh @@ -120,6 +120,10 @@ cleanup() { trap cleanup EXIT require_cmd() { + command -v "$1" >/dev/null 2>&1 || fail "commande requise absente : $1" +} + +has_cmd() { command -v "$1" >/dev/null 2>&1 } @@ -130,11 +134,10 @@ sql_escape_literal() { } validate_db_name() { - local db_name="$1" + local db_name="${1:-}" - [[ -n "$db_name" ]] || fail "nom de base vide" - [[ "$db_name" =~ ^[A-Za-z0-9_]+$ ]] || \ - fail "nom de base invalide : seuls les lettres, chiffres et underscores sont autorisés" + [[ -n "$db_name" ]] || return 1 + [[ "$db_name" =~ ^[a-zA-Z0-9_]+$ ]] || return 1 } build_excluded_roles_regex() { @@ -158,29 +161,20 @@ build_excluded_roles_regex() { # Envoi simple d'un message texte via webhook Discord. # Si DISCORD_WEBHOOK_URL n'est pas défini, on ignore silencieusement l'envoi. ############################################################################### -send_discord_message() { +send_discord() { local message="$1" local payload="" - [[ -n "$DISCORD_WEBHOOK_URL" ]] || { - log "DISCORD_WEBHOOK_URL non défini : notification Discord ignorée." - return 0 - } + [[ -n "$DISCORD_WEBHOOK_URL" ]] || return 0 + has_cmd jq || return 0 + has_cmd curl || return 0 - if ! require_cmd curl; then - log "curl absent : notification Discord ignorée." - return 0 - fi + payload="$(jq -n --arg content "$message" '{content: $content}')" || return 0 - payload="$(jq -n --arg content "$message" '{content: $content}')" || { - log "Impossible de construire le payload JSON Discord." - return 0 - } - - curl -sS -X POST "$DISCORD_WEBHOOK_URL" \ + curl -fsS "$DISCORD_WEBHOOK_URL" \ -H "Content-Type: application/json" \ -d "$payload" \ - >/dev/null || log "Échec d'envoi de la notification Discord." + >/dev/null || true } ############################################################################### @@ -188,6 +182,7 @@ send_discord_message() { ############################################################################### [[ -f "$SSH_KEY" ]] || fail "clé SSH introuvable : $SSH_KEY" [[ -r "$SSH_KEY" ]] || fail "clé SSH non lisible : $SSH_KEY" +[[ ! -L "$SSH_KEY" ]] || fail "clé SSH ne doit pas être un lien symbolique : $SSH_KEY" [[ "$PGPORT" =~ ^[0-9]+$ ]] || fail "PGPORT invalide" [[ "$BACKUP_REMOTE_SSH_PORT" =~ ^[0-9]+$ ]] || fail "BACKUP_REMOTE_SSH_PORT invalide" [[ "$PGUSER" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || fail "PGUSER invalide" @@ -221,7 +216,7 @@ REMOTE_SSH="${BACKUP_REMOTE_USER}@${BACKUP_REMOTE_HOST}" ############################################################################### POSTGRES_INSTALLED=false -if ! require_cmd psql || ! require_cmd pg_restore || ! require_cmd createdb || ! require_cmd dropdb; then +if ! has_cmd psql || ! has_cmd pg_restore || ! has_cmd createdb || ! has_cmd dropdb; then log "PostgreSQL absent : installation en cours..." sudo apt update >>"$LOG_FILE" 2>&1 || fail "échec de apt update" @@ -248,15 +243,17 @@ fi # Attente disponibilité PostgreSQL ############################################################################### log "Vérification de la disponibilité de PostgreSQL..." +PG_READY=false for _ in {1..20}; do if sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then + PG_READY=true log "PostgreSQL répond correctement." break fi sleep 1 done -if ! sudo -u postgres psql -d postgres -c "SELECT 1;" >/dev/null 2>&1; then +if [[ "$PG_READY" != true ]]; then fail "PostgreSQL ne répond pas correctement" fi @@ -303,7 +300,7 @@ else read -r -p "Nom exact de la base à restaurer : " DB fi -validate_db_name "$DB" +validate_db_name "$DB" || fail "nom de base invalide" log "Environnement : $ENV_NAME" log "Base cible sélectionnée : $DB" @@ -424,6 +421,8 @@ if [[ -n "$LOCAL_ROLES_FILE" ]]; then cp "$LOCAL_ROLES_FILE" "$FILTERED_ROLES_FILE" fi + sed -i -E '/^ALTER ROLE .* (NO)?SUPERUSER\b/d' "$FILTERED_ROLES_FILE" + log "Fichier des rôles filtré généré : ${FILTERED_ROLES_FILE}" sed -nE 's/^CREATE ROLE "?([^" ;]+)"?;$/\1/p' "$FILTERED_ROLES_FILE" \ @@ -504,4 +503,4 @@ Hôte PostgreSQL : ${PGHOST}:${PGPORT} Dump utilisé : $(basename "$LAST_REMOTE_DB_DUMP") Log : ${LOG_FILE}" -send_discord_message "$SUCCESS_MESSAGE" +send_discord "$SUCCESS_MESSAGE"