#!/usr/bin/env bash # ============================================================ # run_v2_migration.sh — ترحيل بيانات intaleqDBV2 → mainDB داخل Docker # # يُشغَّل من المضيف، في جذر المستودع: # bash backend/scripts/run_v2_migration.sh # # البيئة الحقيقية داخل docker/.env (env_file للحاويات)، والمفاتيح على # /keys داخل الحاوية. لذلك: # - أوامر SQL تُنفَّذ عبر حاوية mysql (هي وحدها فيها عميل mysql) # - أوامر PHP تُنفَّذ عبر حاوية php (فيها pdo_mysql والمفاتيح والبيئة) # # القاعدة القديمة غير موجودة في Docker أصلاً — تُستورد من intaleqDBV2.sql # إلى قاعدة منفصلة داخل نفس خادم MySQL ثم تُقرأ منها. # ============================================================ set -euo pipefail 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_mainDB_before_migration_$STAMP.sql" [[ -f "$ENV_FILE" ]] || { echo "✘ $ENV_FILE not found"; exit 1; } [[ -f "$DUMP" ]] || { echo "✘ $DUMP not found"; exit 1; } # آخر تعريف يفوز — نفس ما يفعله docker compose عند تكرار المفتاح في env_file env_get() { grep -E "^$1=" "$ENV_FILE" | tail -n1 | cut -d= -f2- | tr -d '"'"'"'' ; } ROOT_PW="$(env_get MYSQL_ROOT_PASSWORD)" APP_USER="$(env_get DB_PRIMARY_USER_V2)" APP_PASS="$(env_get DB_PRIMARY_PASS_V2)" # الهدف يُقرأ من البيئة ولا يُثبَّت هنا: الهدف هو بالتعريف ما يتكلم معه # الباك إند، وأي اسم مكتوب في السكربت سيكون تخميناً يخالف النشر الفعلي. DST_DB="$(env_get DB_PRIMARY_NAME_V2)" SRC_DB="$(env_get SRC_DB_NAME)" SRC_USER="$(env_get SRC_DB_USER)" SRC_PASS="$(env_get SRC_DB_PASS)" [[ -n "$ROOT_PW" ]] || { echo "✘ MYSQL_ROOT_PASSWORD missing from $ENV_FILE"; exit 1; } [[ -n "$DST_DB" ]] || { echo "✘ DB_PRIMARY_NAME_V2 missing from $ENV_FILE"; exit 1; } [[ -n "$SRC_DB" ]] || SRC_DB="intaleqDBV2" # حارس الانعكاس — الوحيد الذي لا يُتجاوز if [[ "$SRC_DB" == "$DST_DB" ]]; then echo "✘ ABORT: source and target are the same database ($DST_DB)." echo " SRC_DB_NAME must be the OLD db; DB_PRIMARY_NAME_V2 the NEW one." exit 1 fi # بيانات دخول المصدر يجب أن تكون لمستخدم موجود فعلاً — القيم المنسوخة من # .env.example لا تعمل على نشر غيّر أسماء المستخدمين. if [[ "$SRC_USER" != "$APP_USER" ]]; then echo "⚠️ SRC_DB_USER ('$SRC_USER') differs from DB_PRIMARY_USER_V2 ('$APP_USER')." echo " Unless that user really exists, set SRC_DB_USER/SRC_DB_PASS to the app user." fi # دوال التنفيذ داخل الحاويات 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 '$SRC_USER'@'%'; FLUSH PRIVILEGES;" # تأكيد أن بيانات الدخول تعمل فعلاً قبل الوصول إلى مرحلة الكتابة $DC exec -T mysql mysql -u"$SRC_USER" -p"$SRC_PASS" -N -B -e "SELECT 1 FROM \`$SRC_DB\`.driver LIMIT 1" >/dev/null 2>&1 \ || { echo "✘ SRC_DB_USER/SRC_DB_PASS cannot read $SRC_DB — fix them in docker/.env, then: docker compose -f docker/docker-compose.yml up -d php"; exit 1; } 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_DB" ]] || { echo "✘ mismatch — aborted."; exit 1; } # ── 4) نسخة احتياطية ──────────────────────────────────────── echo 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)" # ── 5) إصلاحات المخطط ─────────────────────────────────────── # تُطبَّق عبارة-عبارة وتتجاوز فقط "موجود مسبقاً" — يجعل التشغيل قابلاً # لإعادة المحاولة إن كانت المايغريشن قد طُبِّقت جزئياً من قبل. echo echo "── [5/8] applying schema fixes" bash "$REPO/backend/scripts/apply_sql_tolerant.sh" mysql "$ROOT_PW" "$DST_DB" \ "$REPO/backend/migrations/2026_07_29_v2_migration_schema_fixes.sql" # ── 6) فحص المفتاح ────────────────────────────────────────── echo echo "── [6/8] probing legacy key/IV (must report FAILED=0)" php_in /var/www/backend/scripts/migrate_v2_reencrypt.php --probe # ── 7) تشغيل جاف ثم التنفيذ ───────────────────────────────── echo 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; } php_in /var/www/backend/scripts/migrate_v2_reencrypt.php --truncate # ── 8) التدقيق ────────────────────────────────────────────── echo echo "── [8/8] verifying" php_in /var/www/backend/scripts/migrate_v2_reencrypt.php --verify cat <> docker/.env docker compose -f docker/docker-compose.yml up -d php للتراجع: docker compose -f docker/docker-compose.yml exec -T mysql \\ mysql -uroot -p'' $DST_DB < $BACKUP ══════════════════════════════════════════ EOF