$v) { $name = is_int($k) ? $v : $k; $bytes = is_int($k) ? 0 : $v; $exists = array_key_exists($name, $present); $blank = $exists && $isPlaceholder($present[$name]); $state = !$exists ? 'مفقود' : ($blank ? 'نائب' : 'مضبوط'); printf(" %-30s %s\n", $name, $state); if ($seedable && ($state !== 'مضبوط') && $toSeed !== null) { $toSeed[$name] = $bytes ?: 32; } } }; $report('أسرار داخلية (تُولَّد وتُدوَّر بحرية)', ENV_GENERATED, $present, $isPlaceholder, $toSeed, true); $report('مرتبطة بالجلسات (تدويرها يُخرج الجميع)', ENV_SESSION_SCOPED, $present, $isPlaceholder, $toSeed, true); $report('⛔ مجمّدة — لا تُدوَّر (بيانات مخزَّنة مشتقّة منها)', ENV_FROZEN, $present, $isPlaceholder); $report('اعتماد قواعد البيانات (يُغيَّر في MySQL أولاً لا هنا)', ENV_DB_CREDENTIALS, $present, $isPlaceholder); $report('طرف ثالث (تُنسخ من لوحة المزوّد)', ENV_EXTERNAL, $present, $isPlaceholder); echo "\n" . str_repeat('─', 62) . "\n"; if ($command !== 'seed') { echo empty($toSeed) ? "لا ينقص شيء قابل للتوليد.\n" : 'قابل للزراعة: ' . count($toSeed) . " مفتاحاً. شغّل: php env_keys.php seed\n"; exit(0); } // ═══════════════════════════════════════════════════════════════ // الزراعة // ═══════════════════════════════════════════════════════════════ if (empty($toSeed)) { echo "لا ينقص شيء قابل للتوليد.\n"; exit(0); } // ‏لا نلمس مفتاحاً مضبوطاً أبداً: هذه الأداة تملأ الفراغ فقط. التدوير // ‏قرار واعٍ له تبعات (جلسات، خوادم أخرى تحمل نفس السرّ)، ولا يجوز أن // ‏يقع كأثر جانبي لتشغيل أداة جرد. $additions = []; foreach ($toSeed as $name => $bytes) { $value = bin2hex(random_bytes($bytes)); $additions[$name] = $value; echo "توليد $name (" . ($bytes * 2) . " حرفاً)\n"; } if ($dryRun) { echo "\n[dry-run] لم يُكتب شيء.\n"; exit(0); } // ‏نسخة احتياطية قبل أي كتابة. ملف .env تالف يعني توقف كل شيء، وهو // ‏غير موجود في git بطبيعته فلا مصدر آخر لاستعادته. $backup = $envFile . '.bak.' . date('Ymd_His'); if (!copy($envFile, $backup)) { fwrite(STDERR, "تعذّرت النسخة الاحتياطية — أُلغيت العملية.\n"); exit(1); } @chmod($backup, 0600); echo "\nنسخة احتياطية: $backup\n"; $out = rtrim(file_get_contents($envFile), "\n") . "\n"; // ‏نستبدل السطر النائب في مكانه، ونُلحق المفقود في النهاية — الاستبدال // ‏في المكان يبقي التعليقات المجاورة مفيدة. foreach ($additions as $name => $value) { if (array_key_exists($name, $present)) { $out = preg_replace( '/^\s*' . preg_quote($name, '/') . '\s*=.*$/m', "$name=$value", $out, 1 ); unset($additions[$name]); } } if (!empty($additions)) { $out .= "\n# ── زُرعت تلقائياً بـ tools/env_keys.php في " . date('Y-m-d H:i') . " ──\n"; foreach ($additions as $name => $value) { $out .= "$name=$value\n"; } } if (file_put_contents($envFile, $out) === false) { fwrite(STDERR, "تعذّرت الكتابة. النسخة الاحتياطية سليمة: $backup\n"); exit(1); } @chmod($envFile, 0600); echo "\nتمّت الزراعة.\n"; echo "الخطوة التالية — أعد تشغيل حاويات php لتقرأ الجديد:\n"; echo " docker compose restart php php_food php_transit\n\n"; echo "تذكير: WEBHOOK_SECRET_KEY يجب أن يطابق RELAY_SECRET في\n"; echo "sms_relay/app/build.gradle.kts — وهو يحتاج إعادة بناء للتطبيق.\n";