#!/usr/bin/env bash set -euo pipefail ####################################### # Configuration ####################################### DBS=("sirh" "inventory" "ferme") PGHOST="localhost" PGPORT="5432" PGUSER="backup_liot" PGPASSWORD="backup_liot" IA_SSH="malio-b@192.168.0.179" IA_BASE_DIR="/home/malio-b/backups" SSH_KEY="/home/malio/.ssh/id_ed25519_backup" SSH_OPTS=(-i "$SSH_KEY" -o IdentitiesOnly=yes -o BatchMode=yes -o ConnectTimeout=10) LOG_DIR="/var/log/pg_backup" 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 ####################################### # Discord (Webhook) ####################################### DISCORD_WEBHOOK_URL="https://discord.com/api/webhooks/1478503102888935506/YCtJM09QZiKNMiCe5u7vCQb52VcLjHAd9wwEsKNltlJVcy7sKvoMTOJkvEKOOrk-Wpkh" discord_ping() { local details="${1:-}" [[ -z "${DISCORD_WEBHOOK_URL:-}" ]] && return 0 local color dumps_display users_display if [[ -n "${DUMPS_OK:-}" && -n "${USERS_OK:-}" ]]; then color="🟢" else color="🔴" fi dumps_display=$([[ -n "${DUMPS_OK:-}" ]] && echo "✅" || echo "❌") users_display=$([[ -n "${USERS_OK:-}" ]] && echo "✅" || echo "❌") local msg="**@here BACKUP BDD RECETTE ${color}**\n" msg+="Name: ${BACKUP_DIR_NAME}\n" msg+="Dumps transfer: ${dumps_display}\n" msg+="Users transfer: ${users_display}\n" [[ -n "$details" ]] && msg+="Details: $details" curl -fsS -H "Content-Type: application/json" \ -d "{\"content\":\"$msg\"}" \ "$DISCORD_WEBHOOK_URL" >/dev/null || true } ####################################### # Statuts init ####################################### DUMPS_OK=true USERS_OK=true DUMP_ERRORS="" USER_ERRORS="" ####################################### # Lock (évite 2 backups en même temps) ####################################### LOCK_DIR="/tmp/pg_multi_dump_stream.lock.d" if ! mkdir "$LOCK_DIR" 2>/dev/null; then log "ERROR: Backup déjà en cours (lock: $LOCK_DIR)" DUMPS_OK= USERS_OK= discord_ping "Lock exists: $LOCK_DIR" exit 1 fi trap 'rm -rf "$LOCK_DIR"' EXIT ####################################### # Remote dir ####################################### REMOTE_DIR="${IA_BASE_DIR}/${BACKUP_DIR_NAME}" log "Starting backup process" log "Remote directory: ${REMOTE_DIR}" log "Creating remote directory" if ! ssh "${SSH_OPTS[@]}" "$IA_SSH" "mkdir -p '${REMOTE_DIR}'"; then log "ERROR: Création dossier distant impossible: ${REMOTE_DIR}" DUMPS_OK= USERS_OK= discord_ping "Remote mkdir KO: ${REMOTE_DIR}" exit 1 fi ####################################### # Export PostgreSQL roles (no passwords) ####################################### ROLES_FILE="${TMP_DIR}/roles_${TS}.sql" log "Exporting PostgreSQL roles" set +e psql -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -d postgres -Atq <<'SQL' > "$ROLES_FILE" SELECT format( 'DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = %L) THEN CREATE ROLE %I; END IF; END $$;', rolname, rolname ) FROM pg_roles WHERE rolname !~ '^pg_' ORDER BY rolname; SELECT format( 'ALTER ROLE %I WITH %s%s%s%s%s%s%s%s;', rolname, CASE WHEN rolsuper THEN 'SUPERUSER ' ELSE 'NOSUPERUSER ' END, CASE WHEN rolinherit THEN 'INHERIT ' ELSE 'NOINHERIT ' END, CASE WHEN rolcreaterole THEN 'CREATEROLE ' ELSE 'NOCREATEROLE ' END, CASE WHEN rolcreatedb THEN 'CREATEDB ' ELSE 'NOCREATEDB ' END, CASE WHEN rolcanlogin THEN 'LOGIN ' ELSE 'NOLOGIN ' END, CASE WHEN rolreplication THEN 'REPLICATION ' ELSE 'NOREPLICATION ' END, CASE WHEN rolbypassrls THEN 'BYPASSRLS ' ELSE 'NOBYPASSRLS ' END, 'CONNECTION LIMIT ' || rolconnlimit ) FROM pg_roles WHERE rolname !~ '^pg_' ORDER BY rolname; SQL RET=$? if [[ $RET -ne 0 ]]; then USERS_OK= USER_ERRORS+="roles_export " log "ERROR: Users export failed" else log "Roles export completed: $ROLES_FILE" fi set -e log "Sending roles file to IA server" set +e scp "${SSH_OPTS[@]}" "$ROLES_FILE" "$IA_SSH:${REMOTE_DIR}/" RET=$? if [[ $RET -ne 0 ]]; then USERS_OK= USER_ERRORS+="roles_scp " log "ERROR: Users transfer failed (roles file)" else log "Roles transfer completed" fi set -e ####################################### # Dump des bases + transfert (continue même si KO) ####################################### set +e for DB in "${DBS[@]}"; do FILE="${TMP_DIR}/${DB}_${TS}.dump" log "Dumping database: $DB" pg_dump -h "$PGHOST" -p "$PGPORT" -U "$PGUSER" -Fc --no-owner --no-acl -d "$DB" -f "$FILE" RET=$? if [[ $RET -ne 0 ]]; then DUMPS_OK= DUMP_ERRORS+="${DB} " log "ERROR: Dump failed for $DB" continue fi log "Dump completed: $FILE" log "Sending dump to IA server" scp "${SSH_OPTS[@]}" "$FILE" "$IA_SSH:${REMOTE_DIR}/" RET=$? if [[ $RET -ne 0 ]]; then DUMPS_OK= DUMP_ERRORS+="${DB}(scp) " log "ERROR: Transfer failed for $DB" continue fi log "Transfer completed for $DB" done set -e ####################################### # Nettoyage ####################################### log "Cleaning temporary files" rm -rf "$TMP_DIR" ####################################### # Envoi message Discord (final) ####################################### DETAILS="" [[ -z "${DUMPS_OK:-}" ]] && DETAILS+="Dumps KO: ${DUMP_ERRORS} " [[ -z "${USERS_OK:-}" ]] && DETAILS+="Users KO: ${USER_ERRORS} " discord_ping "$DETAILS" log "Backup finished"