32, 'WEBHOOK_SECRET_KEY' => 32, 'INTERNAL_SOCKET_KEY' => 32, 'BOT_SECRET_KEY' => 24, 'FCM_INTERNAL_API_KEY' => 32, 'MIGRATION_ADMIN_KEY' => 24, 'PAYMENT_KEY' => 32, ]; /** ‏تدويرها يبطل كل الجلسات القائمة — حدث تشغيلي واعٍ لا روتين. */ const ENV_SESSION_SCOPED = [ 'JWT_SECRET_KEY' => 64, 'SECRET_KEY_HMAC' => 32, ]; /** * ‏⛔ مجمّدة. بيانات مخزَّنة في القاعدة مشتقّة منها: * ENC_KEY — أسماء الركّاب وهواتفهم ولوحات السيارات مشفّرة به * BLIND_INDEX_PEPPER — أعمدة *_bidx محسوبة به ومخزَّنة * TRACKING_SECRET_SALT / FP_PEPPER — قيم مشتقّة مخزَّنة * * ‏تغييرها لا يعيد تشفير شيء: يجعل الصفوف القديمة غير قابلة للقراءة * ‏إلى الأبد، ولا نسخة احتياطية تنفع لأنها مشفّرة بالمفتاح القديم. * ‏تدويرها ترحيلُ بيانات لا تغييرُ إعداد. * * ‏اللوحة تعرض حالتها فقط — لا قيمتها ولا حقل تعديل. */ const ENV_FROZEN = [ 'ENC_KEY', 'ENCRYPTION_KEY_PATH', 'ENCRYPTION_MODE', 'BLIND_INDEX_PEPPER', 'TRACKING_SECRET_SALT', 'FP_PEPPER', ]; /** ‏لا تُولَّد — تُنسخ من لوحة المزوّد. قابلة للتحرير يدوياً. */ const ENV_EXTERNAL = [ 'GEMINI_API_KEY', 'NABEH_API_KEY', 'NABEH_PASSWORD', 'NABEH_OTP_APP_KEY', 'ELEVENLABS_API_KEY', 'CREATOMATE_API_KEY', 'SOCIAL_BOT_TOKEN', 'MAIL_PASSWORD', 'SIRO_SMTP_PASSWORD', 'SMS_PASSWORD_EGYPT', 'VOICE_CALL_API_KEY', 'RASEEL_DRIVER_ACCESS_TOKEN', ]; /** * ‏كلمات مرور القواعد ← اسم المستخدم ومتغيّر اسم القاعدة. * * ‏تغييرها في .env وحده لا يفعل شيئاً: متغيّرات صورة mysql تُقرأ مرة * ‏واحدة عند تهيئة مجلّد بيانات فارغ، وبعدها كلمة المرور تعيش في * ‏mysql.user. الترتيب الصحيح ALTER USER ثم .env — واللوحة تفعله * ‏بهذا الترتيب وتتحقّق قبل أن تكتب. */ const ENV_DB_CREDENTIALS = [ 'DB_PRIMARY_PASS_V2' => ['user' => 'DB_PRIMARY_USER_V2', 'db' => 'DB_PRIMARY_NAME_V2'], 'DB_PRIMARY_PASS' => ['user' => 'DB_PRIMARY_USER', 'db' => 'DB_PRIMARY_NAME'], 'DB_TRACKING_PASS' => ['user' => 'DB_TRACKING_USER', 'db' => 'DB_TRACKING_NAME'], 'DB_RIDE_PASS' => ['user' => 'DB_RIDE_USER', 'db' => 'DB_RIDE_NAME'], 'DB_TRANSIT_PASS' => ['user' => 'DB_TRANSIT_USER', 'db' => 'DB_TRANSIT_NAME'], 'DB_FOOD_PASS' => ['user' => 'DB_FOOD_USER', 'db' => 'DB_FOOD_NAME'], ]; /** ‏يقرأ .env إلى مصفوفة. */ function envRead(string $file): array { if (!is_readable($file)) return []; $out = []; foreach (file($file, FILE_IGNORE_NEW_LINES) as $line) { if (preg_match('/^\s*([A-Z0-9_]+)\s*=\s*(.*)$/', $line, $m)) { $out[$m[1]] = trim($m[2], "\"' "); } } return $out; } /** ‏قيمة نائبة: موجودة شكلاً وغائبة فعلاً. */ function envIsPlaceholder(?string $v): bool { if ($v === null || $v === '') return true; return (bool) preg_match('/^( $value) { $line = "$name=$value"; if (preg_match('/^\s*' . preg_quote($name, '/') . '\s*=/m', $out)) { $out = preg_replace( '/^\s*' . preg_quote($name, '/') . '\s*=.*$/m', str_replace('$', '\\$', $line), $out, 1 ); } else { $append[] = $line; } } if ($append) { $out .= "\n# ── " . date('Y-m-d H:i') . " ──\n" . implode("\n", $append) . "\n"; } if (file_put_contents($file, $out) === false) return null; @chmod($file, 0600); return $backup; } /** ‏سرّ عشوائي بصيغة hex. */ function envGenerate(int $bytes = 32): string { return bin2hex(random_bytes($bytes)); }