٣ — التجهيز التلقائي
ما يحدث بين ضغط العميل على «تسجيل» ووجود نظامه جاهزًا.
الكود في مشروع لوحة PHP: app/Services/TenantProvisioningService.php · bin/provision_worker.php
الفحص قبل أي شيء
قبل أول مستأجر حقيقي، من جذر اللوحة:
php bin/provision_doctor.php --install-schema
يفحص كل ما يعتمد عليه المسار — امتدادات PHP، قاعدة البيانات، Portainer، اتصال SSH، مسار vhost، وجود الصورة، وتصادم المنافذ — ويطبع OK / WARN / FAIL لكل بند مع ما يجب فعله في الفاشل. يقرأ config/db.php بنفسه، فلا تمرّ كلمة مرور في سطر الأوامر (حيث تُسجَّل في ~/.bash_history وتظهر في ps لكل مستخدم على الخادم).
كل خطوة من الخطوات أدناه تحتاج شيئًا خارج PHP. حين يغيب أحدها، يظهر الفشل بعد خطوات بالرسالة الخطأ. هذا الأمر يسأل كلًّا منها مباشرةً.
التسجيل لا ينتظر
handleRegister() يضع مهمة في الطابور ولا ينفّذها. إنشاء stack وانتظار إقلاع الحاويات وإصدار شهادة يستغرق دقائق، والزائر يمسك طلب HTTP مفتوحًا.
وفشل وضع المهمة في الطابور لا يُفشِل التسجيل: الحساب والترخيص موجودان فعلًا، ويُسجَّل الفشل في السجل ليُعاد.
العميل الذي اختار الاستضافة الذاتية لا يدخل الطابور إطلاقًا — لا stack ولا نطاق ولا شهادة. يُفتح له التحميل بدلًا من ذلك.
العامل
php bin/provision_worker.php
يسحب المهام المعلّقة وينفّذها. شغّله من كرون على خادم اللوحة:
* * * * * /usr/bin/php /[مسار-اللوحة]/bin/provision_worker.php >> /var/log/hr_provision.log 2>&1
CLI فقط — يرفض العمل عبر HTTP.
للتشغيل بمسار Traefik وشهادة اختبارية:
PROVISION_MODE=traefik LETSENCRYPT_STAGING=1 php bin/provision_worker.php
الخطوات
PROVISION_MODE يختار المسار. الفرق ليس تجميلًا: أربع خطوات تختفي لأن لا شيء يحتاج أن ينفّذها.
مسار Traefik — ثلاث خطوات
| # | الخطوة | تفعل |
|---|---|---|
| ١ | stack | نداء واحد لـ Portainer ينشئ حاويات المستأجر بالـ labels |
| ٢ | wait | تنتظر تقرير الحاويات بأنها سليمة |
| ٣ | verify | تنادي عنوان المستأجر فعلًا وتنتظر ردًّا |
مسار بلِسك — ستّ خطوات
ports ← stack ← wait ← subdomain ← ssl ← nginx
ما اختفى في Traefik: تخصيص المنافذ (لا منافذ منشورة)، إنشاء النطاق (wildcard يكفي)، إصدار الشهادة (من الـ labels)، وتوجيهات nginx (الـ labels هي التوجيه).
خطوة verify — ولماذا أُضيفت
تنادي https://<الشركة>.onz.one/login وتظلّ تحاول حتى تردّ.
بدونها يستطيع مستأجر أن ينهي التجهيز ومعه شهادة صالحة وبلا مسار يعمل، ولا يكتشف أحد ذلك حتى يحاول العميل الدخول. الآن يفشل التجهيز نفسه ويسمّي ما يجب فحصه.
تتحمّل الشهادة الاختبارية: سلطتها ليست في أي مخزن ثقة، فالتحقّق من الشهادة يُعطَّل حين يكون LETSENCRYPT_STAGING مفعَّلًا — وإلّا فشل مستأجر سليم.
الاستئناف بدل الإعادة
كل خطوة تسجّل نتيجتها في عمود state كـ JSON. تشغيل ثانٍ لمهمة توقّفت عند الخطوة الرابعة يتخطّى الأولى إلى الثالثة ولا ينشئ stack ثانيًا.
وrollback() يحذف الـ stack ليبدأ إعادة المحاولة نظيفة، لكنه يترك النطاق والشهادة عمدًا: كلاهما غير ضارّ وحده، وإعادة إصدار الشهادة مرارًا تصطدم بحدّ الإصدار.
العنوان حين يكون اسم الشركة عربيًّا
اسم الشركة العربي لا صورة ASCII له، فاشتقاق اسم المضيف منه ينتج تسمية فارغة — ومعظم المستأجرين هنا مسمّون بالعربية. لذلك:
- العميل يختار عنوانه بنفسه في نموذج التسجيل.
- إن تركه فارغًا، يُشتقّ من اسم الشركة.
- إن كان الاشتقاق فارغًا كذلك، يصير
client-<id>. - عند التصادم مع عنوان قائم، يُضاف رقم العميل لاحقةً.
نفس المنطق لاسم الـ stack، لأنه يجب أن يكون فريدًا في Portainer ويصير بادئة أسماء الحاويات ووحدات التخزين.
المنافذ في مسار بلِسك وحده
غير مستخدم في مسار Traefik إطلاقًا.
المخصِّص يجمع من ثلاثة مصادر قبل أن يحجز:
- ما خصّصه هو من قبل (قاعدة البيانات).
- ما يستمع فعلًا على الخادم عبر
ss -Hltnعلى اتصال SSH نفسه. - قائمة
reserved_portsاليدوية، لمنفذ محجوز لكنه غير مستمع الآن.
المصدر الثاني ليس ترفًا: المستأجرون المبنيّون يدويًّا (jwahr_web على 127.0.0.1:5098) وكل خدمة أخرى على الخادم ليست في أي قاعدة بيانات. المخصِّص الذي يقرأ من قاعدته وحدها أعمى عنها.
وإن سقط SSH لحظتها، يُسجَّل ذلك ويُكمَل بالقاعدة وحدها — القيد الفريد على web_port يمنع التصادم مع النفس، والمدى يبدأ من 10000 بعيدًا عمّا يُستخدم اليوم.
حين تفشل مهمة
SELECT id, company, status, last_step, last_error
FROM provisioning_jobs
WHERE status = 'failed';
last_step يقول أين توقّفت، وlast_error يحمل الرسالة. أعد تشغيل العامل بعد إصلاح السبب؛ سيستأنف من حيث وقف.
| الخطوة | السبب الشائع |
|---|---|
stack | الصورة غير موجودة على المضيف، أو مفتاح Portainer مرفوض |
wait | الحاوية تقلع ثم تسقط — راجع سجلّها في Portainer |
verify | سجل wildcard لا يصل، أو المنفذان مغلقان، أو الحاوية ليست على شبكة tenants |
ssl (بلِسك) | حدّ إصدار الشهادات — انتقل إلى الاختبارية وانتظر |