تجاوز إلى المحتوى
أُنز.
الفصول

٣ — التجهيز التلقائي

ما يحدث بين ضغط العميل على «تسجيل» ووجود نظامه جاهزًا.

الكود في مشروع لوحة 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 له، فاشتقاق اسم المضيف منه ينتج تسمية فارغة — ومعظم المستأجرين هنا مسمّون بالعربية. لذلك:

  1. العميل يختار عنوانه بنفسه في نموذج التسجيل.
  2. إن تركه فارغًا، يُشتقّ من اسم الشركة.
  3. إن كان الاشتقاق فارغًا كذلك، يصير client-<id>.
  4. عند التصادم مع عنوان قائم، يُضاف رقم العميل لاحقةً.

نفس المنطق لاسم الـ stack، لأنه يجب أن يكون فريدًا في Portainer ويصير بادئة أسماء الحاويات ووحدات التخزين.


المنافذ في مسار بلِسك وحده

غير مستخدم في مسار Traefik إطلاقًا.

المخصِّص يجمع من ثلاثة مصادر قبل أن يحجز:

  1. ما خصّصه هو من قبل (قاعدة البيانات).
  2. ما يستمع فعلًا على الخادم عبر ss -Hltn على اتصال SSH نفسه.
  3. قائمة 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 (بلِسك)حدّ إصدار الشهادات — انتقل إلى الاختبارية وانتظر