Update: 2026-07-29 16:46:22
This commit is contained in:
@@ -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 <<EOF
|
||||
|
||||
أضفها ثم أعد تشغيل الحاويتين حتى تلتقطا البيئة الجديدة:
|
||||
|
||||
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 <<EOF
|
||||
|
||||
══════════════════════════════════════════
|
||||
✔ migration finished.
|
||||
✔ migration finished. backup: $BACKUP
|
||||
|
||||
backup: $BACKUP
|
||||
|
||||
الخطوة الأخيرة يدوياً — فعّل الكتابة بـ GCM ثم أعد تحميل PHP:
|
||||
echo 'ENCRYPTION_MODE=gcm' >> ~/.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'<root-pw>' $DST_DB < $BACKUP
|
||||
══════════════════════════════════════════
|
||||
EOF
|
||||
|
||||
Reference in New Issue
Block a user