From c0a20e539603653340d0646f6f3d265d8e561171 Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Wed, 29 Jul 2026 16:46:22 +0300 Subject: [PATCH] Update: 2026-07-29 16:46:22 --- backend/scripts/run_v2_migration.sh | 188 ++++++++++++++++++---------- 1 file changed, 121 insertions(+), 67 deletions(-) diff --git a/backend/scripts/run_v2_migration.sh b/backend/scripts/run_v2_migration.sh index dcbfca3..e80b092 100755 --- a/backend/scripts/run_v2_migration.sh +++ b/backend/scripts/run_v2_migration.sh @@ -1,112 +1,166 @@ #!/usr/bin/env bash # ============================================================ -# run_v2_migration.sh — تشغيل ترحيل intaleqDBV2 → intaleqDB1 على السيرفر +# run_v2_migration.sh — ترحيل بيانات intaleqDBV2 → mainDB داخل Docker # -# يُشغَّل على السيرفر فقط. كل خطوة خطرة مسبوقة بفحص وقائي. +# يُشغَّل من المضيف، في جذر المستودع: +# bash backend/scripts/run_v2_migration.sh # -# bash backend/scripts/run_v2_migration.sh +# البيئة الحقيقية داخل docker/.env (env_file للحاويات)، والمفاتيح على +# /keys داخل الحاوية. لذلك: +# - أوامر SQL تُنفَّذ عبر حاوية mysql (هي وحدها فيها عميل mysql) +# - أوامر PHP تُنفَّذ عبر حاوية php (فيها pdo_mysql والمفاتيح والبيئة) # -# يتوقّع في البيئة (أو في ~/.env): -# SRC_DB_HOST SRC_DB_NAME SRC_DB_USER SRC_DB_PASS ← القديمة (intaleqDBV2) -# DB_PRIMARY_*_V2 ← الجديدة (intaleqDB1) -# ENCRYPTION_KEY_PATH أو ENC_KEY, initializationVector, BLIND_INDEX_PEPPER +# القاعدة القديمة غير موجودة في Docker أصلاً — تُستورد من intaleqDBV2.sql +# إلى قاعدة منفصلة داخل نفس خادم MySQL ثم تُقرأ منها. # ============================================================ set -euo pipefail -HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -BACKEND="$(dirname "$HERE")" +REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +DC="docker compose -f $REPO/docker/docker-compose.yml" +ENV_FILE="$REPO/docker/.env" +DUMP="$REPO/intaleqDBV2.sql" STAMP="$(date +%Y%m%d-%H%M%S)" -BACKUP="$HOME/backup_intaleqDB1_before_migration_$STAMP.sql" +BACKUP="$HOME/backup_mainDB_before_migration_$STAMP.sql" -# ── تحميل .env إن وُجد ─────────────────────────────────────── -for f in "$HOME/.env" "$BACKEND/.env"; do - if [[ -f "$f" ]]; then set -a; . "$f"; set +a; echo "loaded env: $f"; break; fi -done +SRC_DB="intaleqDBV2" # تُنشأ الآن داخل حاوية mysql من الملف +DST_DB="mainDB" # الهدف — نفس ما يشير إليه DB_PRIMARY_NAME_V2 -SRC_DB_HOST="${SRC_DB_HOST:-localhost}" -: "${SRC_DB_NAME:?SRC_DB_NAME is not set — it must be the OLD database (intaleqDBV2)}" -: "${SRC_DB_USER:?SRC_DB_USER is not set}" -: "${DB_PRIMARY_NAME_V2:?DB_PRIMARY_NAME_V2 is not set — it must be the NEW database (intaleqDB1)}" -: "${BLIND_INDEX_PEPPER:?BLIND_INDEX_PEPPER is not set}" +[[ -f "$ENV_FILE" ]] || { echo "✘ $ENV_FILE not found"; exit 1; } +[[ -f "$DUMP" ]] || { echo "✘ $DUMP not found"; exit 1; } -DST="$DB_PRIMARY_NAME_V2" -DST_HOST="${DB_PRIMARY_HOST_V2:-localhost}" -DST_USER="${DB_PRIMARY_USER_V2:?DB_PRIMARY_USER_V2 is not set}" -DST_PASS="${DB_PRIMARY_PASS_V2:-}" +ROOT_PW="$(grep -E '^MYSQL_ROOT_PASSWORD=' "$ENV_FILE" | cut -d= -f2- | tr -d '"'"'"'')" +APP_USER="$(grep -E '^DB_PRIMARY_USER_V2=' "$ENV_FILE" | cut -d= -f2- | tr -d '"'"'"'')" +CFG_DST="$(grep -E '^DB_PRIMARY_NAME_V2=' "$ENV_FILE" | cut -d= -f2- | tr -d '"'"'"'')" +[[ -n "$ROOT_PW" ]] || { echo "✘ MYSQL_ROOT_PASSWORD missing from $ENV_FILE"; exit 1; } -echo -echo "══════════════════════════════════════════" -echo " SOURCE (read-only) : $SRC_DB_NAME @ $SRC_DB_HOST" -echo " TARGET (written) : $DST @ $DST_HOST" -echo "══════════════════════════════════════════" - -# ── حارس الانعكاس: لا يجوز أن يكون الاثنان نفس القاعدة ────── -if [[ "$SRC_DB_NAME" == "$DST" ]]; then - echo "✘ ABORT: source and target are the same database ($DST)." - echo " SRC_DB_NAME must be the OLD db; DB_PRIMARY_NAME_V2 the NEW one." +# حارس الانعكاس: ما يشير إليه الباك إند هو ما نكتب فيه، لا غير +if [[ "$CFG_DST" != "$DST_DB" ]]; then + echo "✘ ABORT: DB_PRIMARY_NAME_V2 is '$CFG_DST' but this script writes to '$DST_DB'." + echo " Fix docker/.env or this script — do not guess." exit 1 fi +if [[ "$SRC_DB" == "$DST_DB" ]]; then echo "✘ ABORT: source == target"; exit 1; fi -# ── حارس إضافي: الهدف يجب أن يكون شبه فارغ ────────────────── -mysql_dst() { mysql -h "$DST_HOST" -u "$DST_USER" ${DST_PASS:+-p"$DST_PASS"} "$DST" "$@"; } -DRIVERS_IN_TARGET=$(mysql_dst -N -B -e "SELECT COUNT(*) FROM driver" 2>/dev/null || echo "?") -echo " target currently holds $DRIVERS_IN_TARGET driver row(s)" -if [[ "$DRIVERS_IN_TARGET" != "0" && "$DRIVERS_IN_TARGET" != "?" ]]; then - echo " ⚠️ target is NOT empty — --truncate would delete those rows." +# دوال التنفيذ داخل الحاويات +sql_root() { $DC exec -T mysql mysql -uroot -p"$ROOT_PW" "$@"; } +php_in() { $DC exec -T php php "$@"; } + +echo "══════════════════════════════════════════" +echo " SOURCE : $SRC_DB (imported from intaleqDBV2.sql, read-only)" +echo " TARGET : $DST_DB (DB_PRIMARY_NAME_V2)" +echo "══════════════════════════════════════════" + +# ── 0) الحاويات تعمل؟ ─────────────────────────────────────── +echo +echo "── [0/8] checking containers" +$DC ps --status running --services | grep -qx mysql || { echo "✘ mysql container is not running"; exit 1; } +$DC ps --status running --services | grep -qx php || { echo "✘ php container is not running"; exit 1; } +echo " ✔ mysql + php are up" + +# ── 1) متغيّرات الترحيل في docker/.env ─────────────────────── +echo +echo "── [1/8] checking migration env vars" +MISSING=() +for v in BLIND_INDEX_PEPPER SRC_DB_HOST SRC_DB_NAME SRC_DB_USER SRC_DB_PASS; do + grep -qE "^$v=" "$ENV_FILE" || MISSING+=("$v") +done +if (( ${#MISSING[@]} )); then + echo "✘ missing from docker/.env: ${MISSING[*]}" + cat <> docker/.env <<'ENV' +SRC_DB_HOST=mysql +SRC_DB_NAME=$SRC_DB +SRC_DB_USER=$APP_USER +SRC_DB_PASS=<نفس DB_PRIMARY_PASS_V2> +BLIND_INDEX_PEPPER=<64 خانة hex> +ENV + + docker compose -f docker/docker-compose.yml up -d php +EOF + exit 1 fi +echo " ✔ all present" + +# ── 2) استيراد القاعدة القديمة ────────────────────────────── +echo +echo "── [2/8] importing $SRC_DB from the dump" +EXISTS=$(sql_root -N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$SRC_DB'" 2>/dev/null || echo 0) +if [[ "$EXISTS" != "0" ]]; then + echo " source already present ($EXISTS tables) — skipping import" +else + sql_root -e "CREATE DATABASE IF NOT EXISTS \`$SRC_DB\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci" + $DC exec -T mysql mysql -uroot -p"$ROOT_PW" "$SRC_DB" < "$DUMP" + echo " ✔ imported" +fi +# صلاحية قراءة فقط لمستخدم التطبيق — الترحيل لا يكتب في المصدر إطلاقاً +sql_root -e "GRANT SELECT ON \`$SRC_DB\`.* TO '$APP_USER'@'%'; FLUSH PRIVILEGES;" +sql_root -N -B -e "SELECT CONCAT(' drivers=',(SELECT COUNT(*) FROM \`$SRC_DB\`.driver), + ' passengers=',(SELECT COUNT(*) FROM \`$SRC_DB\`.passengers), + ' cars=',(SELECT COUNT(*) FROM \`$SRC_DB\`.CarRegistration))" + +# ── 3) الهدف يحمل المخطط؟ ─────────────────────────────────── +echo +echo "── [3/8] checking target schema" +HAS=$(sql_root -N -B -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='$DST_DB' AND table_name='driver'") +if [[ "$HAS" == "0" ]]; then + echo " target has no 'driver' table — applying schema_primary.sql" + $DC exec -T mysql mysql -uroot -p"$ROOT_PW" "$DST_DB" < "$REPO/backend/schema_primary.sql" +fi +ROWS=$(sql_root -N -B -e "SELECT COUNT(*) FROM \`$DST_DB\`.driver") +echo " target currently holds $ROWS driver row(s)" +[[ "$ROWS" == "0" ]] || echo " ⚠️ NOT empty — the migration runs with --truncate and will delete them." read -r -p $'\nType the TARGET database name to confirm: ' CONFIRM -[[ "$CONFIRM" == "$DST" ]] || { echo "✘ mismatch — aborted."; exit 1; } +[[ "$CONFIRM" == "$DST_DB" ]] || { echo "✘ mismatch — aborted."; exit 1; } -# ── 1) نسخة احتياطية ──────────────────────────────────────── +# ── 4) نسخة احتياطية ──────────────────────────────────────── echo -echo "── [1/6] backing up target → $BACKUP" -mysqldump -h "$DST_HOST" -u "$DST_USER" ${DST_PASS:+-p"$DST_PASS"} \ - --single-transaction --routines --triggers "$DST" > "$BACKUP" +echo "── [4/8] backing up $DST_DB → $BACKUP" +$DC exec -T mysql mysqldump -uroot -p"$ROOT_PW" --single-transaction --routines --triggers "$DST_DB" > "$BACKUP" [[ -s "$BACKUP" ]] || { echo "✘ backup is empty — aborted."; exit 1; } echo " ✔ $(du -h "$BACKUP" | cut -f1)" -# ── 2) إصلاحات المخطط ─────────────────────────────────────── +# ── 5) إصلاحات المخطط ─────────────────────────────────────── echo -echo "── [2/6] applying schema fixes" -mysql_dst < "$BACKEND/migrations/2026_07_29_v2_migration_schema_fixes.sql" +echo "── [5/8] applying schema fixes" +$DC exec -T mysql mysql -uroot -p"$ROOT_PW" "$DST_DB" \ + < "$REPO/backend/migrations/2026_07_29_v2_migration_schema_fixes.sql" echo " ✔ applied" -# ── 3) فحص المفتاح ────────────────────────────────────────── +# ── 6) فحص المفتاح ────────────────────────────────────────── echo -echo "── [3/6] probing legacy key/IV (must report FAILED=0)" -php "$HERE/migrate_v2_reencrypt.php" --probe +echo "── [6/8] probing legacy key/IV (must report FAILED=0)" +php_in /var/www/backend/scripts/migrate_v2_reencrypt.php --probe -# ── 4) تشغيل جاف ──────────────────────────────────────────── +# ── 7) تشغيل جاف ثم التنفيذ ───────────────────────────────── echo -echo "── [4/6] dry run" -php "$HERE/migrate_v2_reencrypt.php" --dry-run +echo "── [7/8] dry run" +php_in /var/www/backend/scripts/migrate_v2_reencrypt.php --dry-run read -r -p $'\nDry run looks correct? proceed with the real migration [yes/N]: ' GO [[ "$GO" == "yes" ]] || { echo "stopped before writing. Backup kept at $BACKUP"; exit 0; } -# ── 5) التنفيذ ────────────────────────────────────────────── -echo -echo "── [5/6] migrating" -php "$HERE/migrate_v2_reencrypt.php" --truncate +php_in /var/www/backend/scripts/migrate_v2_reencrypt.php --truncate -# ── 6) التدقيق ────────────────────────────────────────────── +# ── 8) التدقيق ────────────────────────────────────────────── echo -echo "── [6/6] verifying" -php "$HERE/migrate_v2_reencrypt.php" --verify +echo "── [8/8] verifying" +php_in /var/www/backend/scripts/migrate_v2_reencrypt.php --verify cat <> ~/.env - sudo systemctl reload php8.3-fpm + الخطوة الأخيرة — فعّل الكتابة بـ GCM: + echo 'ENCRYPTION_MODE=gcm' >> docker/.env + docker compose -f docker/docker-compose.yml up -d php للتراجع: - mysql -u $DST_USER -p $DST < $BACKUP + docker compose -f docker/docker-compose.yml exec -T mysql \\ + mysql -uroot -p'' $DST_DB < $BACKUP ══════════════════════════════════════════ EOF