diff --git a/.gitea/workflows/ci-prod.yml b/.gitea/workflows/ci-prod.yml index ef23864..98669f8 100644 --- a/.gitea/workflows/ci-prod.yml +++ b/.gitea/workflows/ci-prod.yml @@ -151,6 +151,7 @@ jobs: KEY_B64=$(printf "%s" "${RAW_KEY}" | tr -d '\r' | base64 | tr -d '\r\n') SCRIPT_B64=$(base64 scripts/backup_db.sh | tr -d '\r\n') + REFRESH_SCRIPT_B64=$(base64 scripts/refresh_uat_db.sh | tr -d '\r\n') [ -f .env ] && ENV_B64=$(base64 .env | tr -d '\r\n') || ENV_B64="" docker run --rm --net=host \ @@ -160,6 +161,7 @@ jobs: -e DEST_DIR="${DEST_DIR}" \ -e KEY_B64="${KEY_B64}" \ -e SCRIPT_B64="${SCRIPT_B64}" \ + -e REFRESH_SCRIPT_B64="${REFRESH_SCRIPT_B64}" \ -e ENV_B64="${ENV_B64}" \ alpine:latest sh -c ' apk add --no-cache openssh-client rsync coreutils && \ @@ -173,6 +175,7 @@ jobs: echo "" >> /tmp/working_key && \ chmod 600 /tmp/working_key && \ (echo "$SCRIPT_B64" | base64 -d 2>/dev/null || echo "$SCRIPT_B64" | base64 --decode) > /tmp/backup_db.sh && \ + (echo "$REFRESH_SCRIPT_B64" | base64 -d 2>/dev/null || echo "$REFRESH_SCRIPT_B64" | base64 --decode) > /tmp/refresh_uat_db.sh && \ if [ -n "$ENV_B64" ]; then \ (echo "$ENV_B64" | base64 -d 2>/dev/null || echo "$ENV_B64" | base64 --decode) > /tmp/.env ; \ else \ @@ -186,9 +189,10 @@ jobs: SSH_CMD="ssh -p ${PORT} -i /tmp/working_key -o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o ConnectTimeout=15" && \ echo "=== 🚀 EXECUTING REMOTE COMMAND ===" && \ $SSH_CMD "${TARGET_USER}@${TARGET_HOST}" "mkdir -p ${DEST_DIR}/scripts" && \ - echo "=== 📩 RSYNC TRANSFER SCRIPT & ENV ===" && \ + echo "=== 📩 RSYNC TRANSFER SCRIPTS & ENV ===" && \ rsync -avz -e "$SSH_CMD" /tmp/backup_db.sh "${TARGET_USER}@${TARGET_HOST}:${DEST_DIR}/scripts/backup_db.sh" && \ + rsync -avz -e "$SSH_CMD" /tmp/refresh_uat_db.sh "${TARGET_USER}@${TARGET_HOST}:${DEST_DIR}/scripts/refresh_uat_db.sh" && \ rsync -avz -e "$SSH_CMD" /tmp/.env "${TARGET_USER}@${TARGET_HOST}:${DEST_DIR}/.env" && \ - $SSH_CMD "${TARGET_USER}@${TARGET_HOST}" "chmod +x ${DEST_DIR}/scripts/backup_db.sh" && \ - echo "SUCCESS: Backup script and .env deployed successfully to ${DEST_DIR} on DB Server!" + $SSH_CMD "${TARGET_USER}@${TARGET_HOST}" "chmod +x ${DEST_DIR}/scripts/backup_db.sh ${DEST_DIR}/scripts/refresh_uat_db.sh" && \ + echo "SUCCESS: Backup and UAT refresh scripts and .env deployed successfully to ${DEST_DIR} on DB Server!" ' diff --git a/scripts/backup_db.sh b/scripts/backup_db.sh index cf6375a..3234661 100755 --- a/scripts/backup_db.sh +++ b/scripts/backup_db.sh @@ -31,14 +31,6 @@ DB_HOST="${POSTGRES_HOST:-localhost}" DB_PORT="${POSTGRES_PORT:-5432}" CONTAINER_NAME="${DOCKER_CONTAINER_NAME:-astalange_db}" -# Variables UAT -ENABLE_UAT_RESTORE="${ENABLE_UAT_RESTORE:-true}" -UAT_DB_HOST="${UAT_DB_HOST:-10.8.0.2}" -UAT_DB_PORT="${UAT_DB_PORT:-5433}" -UAT_DB_NAME="${UAT_DB_NAME:-astalange}" -UAT_DB_USER="${UAT_DB_USER:-myuser}" -UAT_DB_PASSWORD="${UAT_DB_PASSWORD:-mypassword}" - # Configuration du transfert distant vers le VPS (optionnel) ENABLE_REMOTE_COPY="${ENABLE_REMOTE_COPY:-false}" REMOTE_HOST="${REMOTE_HOST:-}" @@ -111,83 +103,6 @@ if [ "$STATUS" = "SUCCESS" ]; then fi fi -# 2b. Restauration et Anonymisation vers la BDD UAT (10.8.0.2) -if [ "$STATUS" = "SUCCESS" ] && [ "$ENABLE_UAT_RESTORE" = "true" ]; then - log "DĂ©marrage de la restauration et l'anonymisation sur UAT (${UAT_DB_USER}@${UAT_DB_HOST}:${UAT_DB_PORT}/${UAT_DB_NAME})..." - - CLEAN_SCHEMA_SQL=" -DO \$\$ DECLARE r RECORD; BEGIN FOR r IN (SELECT oid FROM pg_largeobject_metadata) LOOP PERFORM lo_unlink(r.oid); END LOOP; EXCEPTION WHEN OTHERS THEN NULL; END \$\$; -DROP SCHEMA IF EXISTS public CASCADE; -CREATE SCHEMA public; -GRANT ALL ON SCHEMA public TO ${UAT_DB_USER}; -GRANT ALL ON SCHEMA public TO public; -" - - ANONYMIZE_SQL=" -UPDATE adherent SET - nom = 'Nom_Adherent_' || id, - prenom = 'Prenom_Adherent_' || id, - email = 'adherent' || id || '@test.local', - telephone = '06' || LPAD((id)::text, 8, '0'), - lieu_naissance_ville = 'Ville_' || id, - representant_legal = CASE - WHEN representant_legal IS NOT NULL AND TRIM(representant_legal) <> '' - THEN 'Representant_' || id - ELSE representant_legal - END; - -UPDATE pre_inscription SET - nom = 'Nom_PreInscr_' || id, - prenom = 'Prenom_PreInscr_' || id, - email = 'preinscr' || id || '@test.local', - telephone = '06' || LPAD((id)::text, 8, '0'), - lieu_naissance_ville = 'Ville_' || id, - representant_legal = CASE - WHEN representant_legal IS NOT NULL AND TRIM(representant_legal) <> '' - THEN 'Representant_PreInscr_' || id - ELSE representant_legal - END; - -UPDATE educateur SET - nom = 'Nom_Educ_' || id, - prenom = 'Prenom_Educ_' || id, - email = 'educ' || id || '@test.local', - telephone = '06' || LPAD((id)::text, 8, '0'); -" - - RESTORE_SUCCESS=false - - if command -v psql >/dev/null 2>&1; then - log "RĂ©initialisation du schĂ©ma public sur UAT..." - PGPASSWORD="${UAT_DB_PASSWORD}" psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$CLEAN_SCHEMA_SQL" >> "$LOG_FILE" 2>&1 - - log "Restauration du dump Prod vers UAT..." - if gunzip -c "$DUMP_PATH" | PGPASSWORD="${UAT_DB_PASSWORD}" psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" >> "$LOG_FILE" 2>&1; then - log "ExĂ©cution des requĂȘtes d'anonymisation sur UAT..." - if PGPASSWORD="${UAT_DB_PASSWORD}" psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$ANONYMIZE_SQL" >> "$LOG_FILE" 2>&1; then - RESTORE_SUCCESS=true - fi - fi - elif command -v docker >/dev/null 2>&1; then - log "RĂ©initialisation du schĂ©ma public sur UAT via Docker..." - docker run --rm --net=host -e PGPASSWORD="${UAT_DB_PASSWORD}" postgres:15-alpine psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$CLEAN_SCHEMA_SQL" >> "$LOG_FILE" 2>&1 - - log "Restauration du dump Prod vers UAT via Docker..." - if gunzip -c "$DUMP_PATH" | docker run --rm -i --net=host -e PGPASSWORD="${UAT_DB_PASSWORD}" postgres:15-alpine psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" >> "$LOG_FILE" 2>&1; then - log "ExĂ©cution des requĂȘtes d'anonymisation sur UAT via Docker..." - if docker run --rm --net=host -e PGPASSWORD="${UAT_DB_PASSWORD}" postgres:15-alpine psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$ANONYMIZE_SQL" >> "$LOG_FILE" 2>&1; then - RESTORE_SUCCESS=true - fi - fi - fi - - if [ "$RESTORE_SUCCESS" = "true" ]; then - log "✅ Restauration et anonymisation UAT rĂ©ussies avec succĂšs !" - else - log "⚠ AVERTISSEMENT: Impossible d'effectuer la restauration/anonymisation UAT." - fi -fi - # 3. Transfert sĂ©curisĂ© RSYNC / SCP vers le serveur VPS (Optionnel) if [ "$STATUS" = "SUCCESS" ] && [ "$ENABLE_REMOTE_COPY" = "true" ] && [ -n "$REMOTE_HOST" ]; then log "Transfert du dump vers le serveur VPS distant (${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_BACKUP_DIR})..." diff --git a/scripts/refresh_uat_db.sh b/scripts/refresh_uat_db.sh new file mode 100755 index 0000000..eb9775f --- /dev/null +++ b/scripts/refresh_uat_db.sh @@ -0,0 +1,204 @@ +#!/usr/bin/env bash +# ============================================================================== +# Script de rafraĂźchissement et anonymisation (RGPD) de la base de donnĂ©es UAT +# Projet : AS Talange +# Description : +# 1. RĂ©cupĂšre un dump PostgreSQL spĂ©cifiĂ© en paramĂštre ou le dernier dump produit +# 2. RĂ©initialise le schĂ©ma public de la base de donnĂ©es UAT de test +# 3. Restaure le dump de production vers la base UAT +# 4. Anonymise l'ensemble des donnĂ©es personnelles (AdhĂ©rents, PrĂ©-inscriptions, Éducateurs) +# +# Usage : +# ./scripts/refresh_uat_db.sh [chemin_vers_dump.sql.gz] +# ============================================================================== + +set -u + +# Localisation du rĂ©pertoire du script et du projet +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +# Chargement du fichier .env si prĂ©sent dans le projet +if [ -f "${PROJECT_DIR}/.env" ]; then + set -a + source "${PROJECT_DIR}/.env" + set +a +fi + +# Configuration du rĂ©pertoire de sauvegarde +BACKUP_DIR="${BACKUP_DIR:-${PROJECT_DIR}/backups}" + +# Configuration de la base de donnĂ©es UAT +UAT_DB_HOST="${UAT_DB_HOST:-10.8.0.2}" +UAT_DB_PORT="${UAT_DB_PORT:-5433}" +UAT_DB_NAME="${UAT_DB_NAME:-astalange}" +UAT_DB_USER="${UAT_DB_USER:-myuser}" +UAT_DB_PASSWORD="${UAT_DB_PASSWORD:-mypassword}" + +# DĂ©termination du fichier dump Ă  restaurer +DUMP_PATH="${1:-}" + +# Si aucun dump n'est passĂ© en argument, on cherche le plus rĂ©cent dans BACKUP_DIR +if [ -z "$DUMP_PATH" ]; then + if [ -d "$BACKUP_DIR" ]; then + DUMP_PATH=$(ls -t "${BACKUP_DIR}"/astalange_db_backup_*.sql.gz 2>/dev/null | head -n 1) + fi +fi + +# Journalisation +TIMESTAMP=$(date +"%Y-%m-%d_%H%M%S") +DATE_HUMAN=$(date +"%d/%m/%Y Ă  %H:%M:%S") +LOG_FILE=$(mktemp /tmp/astalange_refresh_uat_${TIMESTAMP}_XXXXXX.log) + +log() { + echo "[$(date +'%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE" +} + +log "======================================================================" +log " RafraĂźchissement & Anonymisation de la Base UAT / Test AS Talange" +log "======================================================================" +log "Date d'exĂ©cution : ${DATE_HUMAN}" +log "Cible UAT : ${UAT_DB_USER}@${UAT_DB_HOST}:${UAT_DB_PORT}/${UAT_DB_NAME}" + +# VĂ©rification de l'existence du fichier dump +if [ -z "$DUMP_PATH" ] || [ ! -f "$DUMP_PATH" ]; then + log "❌ ERREUR: Fichier dump introuvable ou non spĂ©cifiĂ©." + log "Veuillez fournir le chemin d'un dump valide en argument ou vĂ©rifier le dossier ${BACKUP_DIR}." + log "Exemple d'utilisation : $0 /chemin/vers/astalange_db_backup_XXXX.sql.gz" + rm -f "$LOG_FILE" + exit 1 +fi + +if [ ! -s "$DUMP_PATH" ]; then + log "❌ ERREUR: Le fichier dump spĂ©cifiĂ© est vide : ${DUMP_PATH}" + rm -f "$LOG_FILE" + exit 1 +fi + +DUMP_SIZE=$(du -h "$DUMP_PATH" | cut -f1) +log "Fichier dump source: ${DUMP_PATH} (${DUMP_SIZE})" + +# RequĂȘtes SQL de nettoyage du schĂ©ma +CLEAN_SCHEMA_SQL=" +DO \$\$ DECLARE r RECORD; BEGIN FOR r IN (SELECT oid FROM pg_largeobject_metadata) LOOP PERFORM lo_unlink(r.oid); END LOOP; EXCEPTION WHEN OTHERS THEN NULL; END \$\$; +DROP SCHEMA IF EXISTS public CASCADE; +CREATE SCHEMA public; +GRANT ALL ON SCHEMA public TO ${UAT_DB_USER}; +GRANT ALL ON SCHEMA public TO public; +" + +# RequĂȘtes SQL d'anonymisation RGPD (avec gestion dynamique des colonnes) +ANONYMIZE_SQL=" +DO \$\$ +BEGIN + -- 1. Anonymisation des adhĂ©rents + UPDATE adherent SET + nom = 'Nom_Adherent_' || id, + prenom = 'Prenom_Adherent_' || id, + email = 'adherent' || id || '@test.local', + telephone = '06' || LPAD((id)::text, 8, '0'), + lieu_naissance_ville = 'Ville_' || id, + representant_legal = CASE + WHEN representant_legal IS NOT NULL AND TRIM(representant_legal) <> '' + THEN 'Representant_' || id + ELSE representant_legal + END; + + -- Anonymisation de l'adresse si les colonnes existent + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'adherent' AND column_name = 'adresse') THEN + EXECUTE 'UPDATE adherent SET adresse = ''1 rue de Test'', code_postal = ''57525'', commune = ''Talange'''; + END IF; + + -- 2. Anonymisation des prĂ©-inscriptions + UPDATE pre_inscription SET + nom = 'Nom_PreInscr_' || id, + prenom = 'Prenom_PreInscr_' || id, + email = 'preinscr' || id || '@test.local', + telephone = '06' || LPAD((id)::text, 8, '0'), + lieu_naissance_ville = 'Ville_' || id, + representant_legal = CASE + WHEN representant_legal IS NOT NULL AND TRIM(representant_legal) <> '' + THEN 'Representant_PreInscr_' || id + ELSE representant_legal + END; + + IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'pre_inscription' AND column_name = 'adresse') THEN + EXECUTE 'UPDATE pre_inscription SET adresse = ''1 rue de Test'', code_postal = ''57525'', commune = ''Talange'''; + END IF; + + -- 3. Anonymisation des Ă©ducateurs + UPDATE educateur SET + nom = 'Nom_Educ_' || id, + prenom = 'Prenom_Educ_' || id, + email = 'educ' || id || '@test.local', + telephone = '06' || LPAD((id)::text, 8, '0'); +END \$\$; +" + +RESTORE_SUCCESS=false + +# ExĂ©cution soit via le client psql local, soit via conteneur Docker Ă©phĂ©mĂšre +if command -v psql >/dev/null 2>&1; then + log "Mode d'exĂ©cution : Client psql local dĂ©tectĂ©." + + log "1/3. RĂ©initialisation du schĂ©ma public sur la base UAT..." + if PGPASSWORD="${UAT_DB_PASSWORD}" psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$CLEAN_SCHEMA_SQL" >> "$LOG_FILE" 2>&1; then + log "✅ SchĂ©ma public rĂ©initialisĂ©." + else + log "❌ ERREUR lors de la rĂ©initialisation du schĂ©ma sur UAT." + fi + + log "2/3. Restauration du dump vers UAT..." + if gunzip -c "$DUMP_PATH" | PGPASSWORD="${UAT_DB_PASSWORD}" psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" >> "$LOG_FILE" 2>&1; then + log "✅ Dump restaurĂ© avec succĂšs." + + log "3/3. ExĂ©cution de l'anonymisation RGPD sur UAT..." + if PGPASSWORD="${UAT_DB_PASSWORD}" psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$ANONYMIZE_SQL" >> "$LOG_FILE" 2>&1; then + log "✅ DonnĂ©es personnelles anonymisĂ©es avec succĂšs." + RESTORE_SUCCESS=true + else + log "❌ ERREUR lors de l'anonymisation RGPD." + fi + else + log "❌ ERREUR lors de la restauration du dump sur UAT." + fi + +elif command -v docker >/dev/null 2>&1; then + log "Mode d'exĂ©cution : psql absent, utilisation de Docker (postgres:15-alpine)." + + log "1/3. RĂ©initialisation du schĂ©ma public sur la base UAT via Docker..." + if docker run --rm --net=host -e PGPASSWORD="${UAT_DB_PASSWORD}" postgres:15-alpine psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$CLEAN_SCHEMA_SQL" >> "$LOG_FILE" 2>&1; then + log "✅ SchĂ©ma public rĂ©initialisĂ©." + else + log "❌ ERREUR lors de la rĂ©initialisation du schĂ©ma sur UAT." + fi + + log "2/3. Restauration du dump vers UAT via Docker..." + if gunzip -c "$DUMP_PATH" | docker run --rm -i --net=host -e PGPASSWORD="${UAT_DB_PASSWORD}" postgres:15-alpine psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" >> "$LOG_FILE" 2>&1; then + log "✅ Dump restaurĂ© avec succĂšs." + + log "3/3. ExĂ©cution de l'anonymisation RGPD sur UAT via Docker..." + if docker run --rm --net=host -e PGPASSWORD="${UAT_DB_PASSWORD}" postgres:15-alpine psql -h "${UAT_DB_HOST}" -p "${UAT_DB_PORT}" -U "${UAT_DB_USER}" -d "${UAT_DB_NAME}" -c "$ANONYMIZE_SQL" >> "$LOG_FILE" 2>&1; then + log "✅ DonnĂ©es personnelles anonymisĂ©es avec succĂšs." + RESTORE_SUCCESS=true + else + log "❌ ERREUR lors de l'anonymisation RGPD." + fi + else + log "❌ ERREUR lors de la restauration du dump sur UAT." + fi +else + log "❌ ERREUR: Ni psql ni docker ne sont installĂ©s sur cette machine." +fi + +log "======================================================================" +if [ "$RESTORE_SUCCESS" = "true" ]; then + log "🎉 OpĂ©ration terminĂ©e avec succĂšs : la base UAT est rafraĂźchie et anonymisĂ©e !" + rm -f "$LOG_FILE" + exit 0 +else + log "❌ ÉCHEC : Le rafraĂźchissement ou l'anonymisation UAT a rencontrĂ© des erreurs." + log "Consultez les logs ci-dessus pour plus de dĂ©tails." + rm -f "$LOG_FILE" + exit 1 +fi