fix : t 021 a 033 fait
This commit is contained in:
@@ -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"
|
||||
|
||||
#######################################
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user