HeadHunter إدارة النظام

إدارة النظام والدعم

هذا هو دليل من ينشر النظام، ويشغّله، ويدعمه — مشغّل منصّة Waves Technology. خلافًا لكل دليل آخر في هذه المجموعة، لا يقتصر هذا الدليل على دور داخل منظمة غير حكومية: أنت تقع خارج أي منظمة، على مضيفك الخاص، ومهمّتك أن تُبقي المنصّة سليمة للجميع عليها دون أن تصل إلى بيانات أي مستأجر إلا حين تُدعى إلى ذلك صراحةً ومؤقّتًا.

يأتي هذا الدليل في نصفين:

المبادئ الأربعة — اعرف في أي عالَم أنت

لدى HeadHunter أربعة أنواع من المستخدمين، وجلساتهم مفصولة عمدًا حتى لا يعبر ضعف في أحدها إلى غيره:

المبدأ يسجّل الدخول على ملفّ تعريف الجلسة سرّ التوقيع
فريق المنظمة (org_admin، hr_manager، recruiter، compliance_officer) app.<domain> hh_session (٨ ساعات) SESSION_SECRET
المراجِع الخارجي (hiring_manager) app.<domain> hh_session (يركب عالَم المنظمة، مقيَّد بالوظيفة) SESSION_SECRET
مشغّل المنصّة (أنت) ops.<domain> hh_ops_session (ساعتان) OPS_SESSION_SECRET
المرشّح (الباحث عن عمل) نطاق التسويق الجذر <domain> hh_candidate_session CANDIDATE_SESSION_SECRET

يخدم مونوليث واحد كل هذه المضيفات ويوجّه بحسب ترويسة Host. جلسة التشغيل منفصلة عمدًا — عمرها الأقصر البالغ ساعتين وسرّها الخاص يعزلان دائرة تأثير وحدة تحكّم المشغّل عن عالَمَي المنظمة والمرشّح. حسابك يعيش في platform_admins، لا في جدول users الخاص بأي منظمة.


الجزء أ — وحدة تحكّم مشغّل المنصّة

تسجيل الدخول

اذهب إلى /ops/login على مضيف التشغيل (ops.localhost في بيئة التطوير، وops.<domain> في الإنتاج). تستوثق مقابل عالَم مشرفي المنصّة، وهو منفصل تمامًا عن تسجيل دخول أي منظمة — يعيد بريد خاطئ وكلمة مرور خاطئة الخطأ العام نفسه، فلا تكشف الصفحة أبدًا ما إذا كان الحساب موجودًا. عند النجاح تحصل على hh_ops_session مدّتها ساعتان؛ يُعيد كل طلب قراءة سجلّك ويؤكّد من جديد أن حسابك لا يزال نشطًا. لا يوجد تسجيل ذاتي — يُزوَّد المشغّلون من سطر الأوامر (انظر التزويد في الجزء ب).

ملاحظة: لا يملك تسجيل دخول التشغيل بعد التحقّق بخطوتين ولا الحظر بعد محاولات كثيرة — وهذا متابعة سريعة مخطّطة. أبقِ الوصول إلى مضيف التشغيل محكمًا على مستوى الشبكة في هذه الأثناء.

شاشة تسجيل الدخول إلى وحدة تحكّم التشغيل
شاشة تسجيل الدخول إلى وحدة تحكّم التشغيل

أدوار المشغّل الأربعة

تأتي صلاحياتك من دورك على المنصّة (platform_admins.role)، وهو خريطة تُبقى منفصلة تمامًا عن نظام صلاحيات المنظمات (RBAC) حتى لا يرث المشغّل أبدًا صلاحية مقصورة على مستأجر:

الدور ما يمكنه فعله
superadmin كل شيء — بما في ذلك إدارة مشرفي المنصّة الآخرين، وتعليق المنظمات، وطلب كسر الزجاج.
support الطبقتان ١ و٢، ويمكنه طلب كسر الزجاج (لا تُفتح الطبقة ٣ إلا تحت منحة حيّة من المنظمة).
readonly الطبقة ١ والطبقة ٢ للقراءة فقط. لا كسر زجاج أبدًا.
ai_agent الطبقتان ١ و٢ برمجيًّا. غير مؤهّل أبدًا لكسر الزجاج، ولو بنيويًّا.

القاعدة التي عليك استيعابها: لا يستطيع الوصول إلى بيانات المرشّحين الحقيقية سوى superadmin وsupport، وذلك فقط تحت منحة نشطة. أما readonly وai_agent فمحظوران من كسر الزجاج لا بخريطة الصلاحيات وحدها، بل بفحص بنيوي ثانٍ — فتخفيف الخريطة لن يُدخلهما.

طبقات الوصول الثلاث

بُنيت وحدة التحكّم على شكل ثلاث طبقات متراكزة. تقضي معظم وقتك في الطبقتين الخارجيّتين اللتين لا تكشفان بيانات شخصية أبدًا؛ أما أعمقها فاستثناء نادر مشروط بالموافقة.

الطبقة ١ — لوحة التجميع (/ops/dashboard)

نبض عرضك اليومي، خالية من البيانات الشخصية بحكم بنيتها — مصدر بياناتها الوحيد يحسب أعدادًا وتجميعات ولا يختار أي أعمدة شخصية (يفرض اختبار ألّا يظهر فيها حتى اسم عمود يحمل بيانات شخصية). تعرض:

قراءة الطبقة ١ مراقبة لا إجراء، لذا لا تكتب أي سطر في سجلّ التدقيق.

ما لا تعرضه الطبقة ١: لا توجد هنا أي مقاييس للمرشّحين — لا عدد لحسابات المرشّحين، ولا أعداد للتأكيد. الرقم الوحيد المتعلّق بـ«التسجيلات» هو المنظمات الجديدة شهريًّا. حسابات المرشّحين ذاتية الخدمة على موقع التسويق ولا تظهر في لوحة المشغّل.

الطبقة ١ — لوحة التجميع
الطبقة ١ — لوحة التجميع

الطبقة ٢ — تشخيصات المنظمة الواحدة (/ops/orgs، /ops/orgs/[id])

حين تحتاج منظمة إلى دعم، ابدأ هنا. يسرد الدليل (/ops/orgs) صفًّا واحدًا لكل مستأجر — الحالة، وفئة الاشتراك، وهل هو معلَّق، وأعداد المستخدمين والوظائف المفتوحة والطلبات — مع بحث اختياري ?q= على اسم المنظمة. وهو خالٍ من البيانات الشخصية، وقراءة الدليل غير مدوَّنة في التدقيق.

افتح منظمة لتتعمّق فيها. كل تعمّق يُدوَّن (org.viewed)، وما تراه محجوب عمدًا:

من هنا يستطيع superadmin أن يعلّق أو يعيد تنشيط مستأجر (مشروط بـplatform.org.suspend)؛ وكلا الإجراءين مدوَّن (org.suspended / org.reactivated).

الطبقة ٢ — تشخيصات المنظمة المحجوبة
الطبقة ٢ — تشخيصات المنظمة المحجوبة

الطبقة ٣ — كسر الزجاج (/ops/orgs/[id]/breakglass)

هذا هو المسار الوحيد إلى بيانات المرشّحين الحقيقية غير المحجوبة (الاسم، والبريد، والهاتف، والموقع، ووجود سيرة ذاتية من عدمه)، وهو مسيَّج من كل جانب. عامله كالاستثناء النادر الذي هو عليه.

كيف تُنشأ المنحة (من جانب المنظمة). تفتح المنظمة الإعدادات ← وصول الدعم (/settings/support-access على مضيفها app. — إجراء لـ org_admin). تختار مشغّلًا مسمّى ومؤهّلًا، وتعطي سببًا ومرجع دعم اختياريًّا، وتضبط مدّة ضمن نافذة ١–٧٢ ساعة. يكتب ذلك منحةً بتاريخ انتهاء ويدوّنها على الجانبين — سجلّ المنظمة نفسها (support_access.granted) ومرآة المشغّل (breakglass.granted). وتستطيع المنظمة الإلغاء فورًا في أي وقت، وهو ما يُنعكس بالمثل. تكون المنحة «حيّة» فقط ما دامت غير منتهية وغير ملغاة.

الطبقة ٣ — كسر الزجاج المشروط بالموافقة
الطبقة ٣ — كسر الزجاج المشروط بالموافقة

الصورة التي عليك تذكّرها: المنظمة تمسك المفتاح. يمكنك أن تطلب، لكنك لا ترى بيانات مستأجر الحقيقية إلا ما دام ذلك المستأجر يسمح لك — بفعل نشط وواعٍ ومؤقّت — وكل ثانية من ذلك مسجَّلة لكليكما.


الجزء ب — النشر والتشغيل والإدارة

الحزمة والطوبولوجيا

HeadHunter مونوليث ممل يُشغَّل بـ Docker Compose — أربع خدمات، وأمر واحد لإقامته:

مُصمَّم ليناسب خادمًا افتراضيًّا (VPS) متواضعًا على Hostinger في الاتحاد الأوروبي (الاستضافة في الاتحاد الأوروبي هي أيضًا إشارة الثقة بموجب GDPR في وعد الخصوصية للمنتَج) — تكفي KVM 1 ‏(1 vCPU / 4 GB) لبرنامج تجريبي؛ وتمنح KVM 2 ‏(2 vCPU / 8 GB) هامشًا أكبر. تعيش الحالة الدائمة في وحدتَي تخزين مسمّاتين: postgres_data (قاعدة البيانات) و**cv_storage** المركّبة على /app/uploads (ملفات السير الذاتية الخام — بيانات شخصية، تُبقى خارج الصورة (image) وخارج git). يعمل التطبيق والمحلِّل مُصلَّبين (no-new-privileges، ويُسقِط التطبيق كل قدرات Linux إلا القليل الذي يحتاجه).

دورة الحياة — ./headhunter.sh

كل شيء روتيني يمرّ عبر سكربت دورة الحياة في جذر المستودع:

./headhunter.sh setup      # أول تشغيل: إنشاء .env (بأسرار عشوائية قوية) + شهادة موقَّعة ذاتيًا
./headhunter.sh up         # بناء + تشغيل الحزمة (docker compose up -d --build)
./headhunter.sh status     # حالة الحاويات (docker compose ps)
./headhunter.sh logs       # متابعة سجلّات التطبيق (Ctrl-C للإيقاف)
./headhunter.sh down       # إيقاف كل شيء — تُحفَظ وحدة تخزين postgres_data
./headhunter.sh restart    # down + up

ينسخ setup الملف .env.example.env ويملأ DB_PASSWORD وSESSION_SECRET وOPS_SESSION_SECRET بقيَم من openssl rand. في الإنتاج، اضبط CANDIDATE_SESSION_SECRET صراحةً — انظر التهيئة والأسرار. مرِّر -y / --yes لتخطّي رسائل التأكيد في الاستخدام المُبرمَج.

التزويد

لا يوجد تسجيل ذاتي للمنظمات أو المشغّلين — كلاهما يُنشأ عمدًا من سطر الأوامر. شغّل هذه من /app مع ضبط DATABASE_URL (أو عبر أغلفة headhunter.sh، التي تشغّلها مقابل حاوية قاعدة البيانات الحيّة).

منظمة جديدة و org_admin الأول لها:

npm run provision -- \
  --org-name "Mercy NGO" --org-name-ar "منظمة الرحمة" \
  --email admin@mercy.org --password 'StrongPass123' --full-name "Aisha Ali" \
  [--role org_admin] [--locale ar] [--tier pilot] [--pool-entitlement]
# أو: ./headhunter.sh provision --org-name "Mercy NGO" --email admin@mercy.org ...

مشغّل منصّة (يُنشأ أول superadmin بهذه الطريقة؛ ثم يُدار المشغّلون التالون داخل وحدة التحكّم):

npm run provision-admin -- \
  --email ops@waves.tech --password 'StrongPass123' --full-name "Operator Name" \
  [--role superadmin|support|readonly|ai_agent]
# أو: ./headhunter.sh provision-admin --email ops@waves.tech ... [--role support]

الدور الافتراضي هو superadmin إن حُذف. تُرفَض إعادة التشغيل ببريد موجود (البُرُد فريدة عالميًّا). تُجزَّأ كلمات المرور بـ Argon2id؛ ويُفرَض حدّ أدنى للطول قدره ٨.

قاعدة البيانات والترحيلات

تغييرات المخطَّط هي ترحيلات SQL مرقَّمة0NNN_name.sql مع 0NNN_name_down.sql مطابق — تحت postgres/migrations/. أما init.sql فهو الأساس (baseline) فقط ولا يُعدَّل أبدًا للمخطَّط الحيّ.

npm run db:migrate         # تطبيق الترحيلات المعلَّقة (tsx scripts/migrate.ts up) — يحتاج DATABASE_URL
npm run db:rollback        # التراجع عن أحدث ترحيل (down)
# عبر الحزمة: ./headhunter.sh migrate | rollback | db-reset

على قاعدة بيانات جديدة، تشغّل حاوية db في compose السكربت apply-migrations.sh بعد init.sql، مطبِّقةً كل ترحيل بالترتيب تلقائيًّا. تُبلِّغ نقطة /health بعدد الترحيلات المطبَّقة، فتؤكّد أن النشر مُرحَّل بالكامل في لمحة. يمسح ./headhunter.sh db-reset وحدة تخزين قاعدة البيانات ويعيد بناء أساس نظيف — إجراء مدمِّر؛ يطلب تأكيدًا.

التهيئة والأسرار

التهيئة عبارة عن متغيّرات بيئة في .env (لا يُلتزَم بها في git أبدًا). أهمّها:

المتغيّر الغرض
DATABASE_URL سلسلة اتصال Postgres (يبنيها compose من DB_PASSWORD).
SESSION_SECRET يوقّع جلسة المنظمة hh_session. وهو السرّ الأساس الذي تعود إليه البقية.
OPS_SESSION_SECRET يوقّع جلسة المشغّل hh_ops_session. يعود إلى SESSION_SECRET إن كان غير مضبوط/فارغًا.
CANDIDATE_SESSION_SECRET يوقّع جلسة المرشّح hh_candidate_session. اضبطه صراحةً في الإنتاج.
APP_HOST / MARKETING_HOST / OPS_HOST المضيفات الثلاث التي يوجّه بها المونوليث (app. / الجذر / ops.).
PARSER_URL المحلِّل الجانبي (http://parser:8000 على شبكة compose).
STORAGE_DIR حيث تعيش ملفات السير الذاتية الخام على القرص (/app/uploads، وحدة تخزين cv_storage).
ANTHROPIC_API_KEY مفتاح LLM اختياري (لمسوّدة الوصف الوظيفي فقط؛ تتدهور الميزة بلطف إن كان غير مضبوط).

فخّان يستحقّان التنويه:

البريد الصادر / WhatsApp و LLM كلّها مستقلّة عن المزوِّد. للتطوير والبرنامج التجريبي دون مزوِّد حيّ، تُكتب الرسائل الصادرة كأسطر JSON في صندوق صادر ملفّيّ (COMMS_OUTBOX_FILE) بدل إرسالها فعلًا — مفيد، لكن انظر تحفّظ عالَم المرشّح أدناه.

عالَم المرشّح — تشغيل المبدأ الرابع

أضافت المرحلة ٥ حساب المرشّح: عالَمًا ذاتيّ الخدمة، مواجهًا للإنترنت، وعلى نطاق واسع، فيستحقّ عنايته التشغيلية الخاصة.

الصحّة والمراقبة

دليل إجراءات الدعم

وجِّه كل طلب عبر المسار الأقلّ امتيازًا الذي يجيبه. الطبقة ٢ المحجوبة أولًا؛ وكسر الزجاج كملاذ أخير مشروط بالموافقة وحده.

الطلب من أين تبدأ
«مستخدم منظمة محبوس خارج حسابه / يحتاج إعادة تعيين» org_admin الخاص بالمنظمة يدير مستخدميها. لا تصعّد إلى الطبقة ٢ إلا لتأكيد حالة المنظمة.
«لماذا لم تُحلَّل هذه السيرة الذاتية؟» الطبقة ٢ إخفاقات المحلِّل للمنظمة (المرشّح مقنَّع إلى أحرفه الأولى) + صحّة طابور التحليل في الطبقة ١.
«توقّف وصولنا إلى التجمّع» الطبقة ٢ — تحقّق من فئة اشتراك المنظمة / تخويلها؛ الفوترة يدوية (نقدًا/بنكك)، يسجّلها org_admin.
«نريدك أن تطّلع على بيانات مرشّح بعينه» تمنح المنظمة وصول الدعم (/settings/support-access)؛ وعندئذٍ فقط، كسر الزجاج بالطبقة ٣ تحت تلك المنحة الحيّة.
مرشّح: «لم تصلني رسالة التأكيد» إعادة الإرسال الذاتية على /me أولًا؛ تحقّق من قابلية توصيل بريدك إلى داخل السودان. تصرّف المشغّل ملاذ، لا الأصل.
مرشّح: «احذف حسابي» / «صدِّر بياناتي» تدفّقا /me/settings الذاتيّان للتصدير والحذف. لا تتصرّف إلا إن عجز حقًّا.
اشتباه إساءة / فيضان تسجيل حجم الطبقة ١ + حدّ المعدّل / المصيدة للتسجيل؛ يستطيع superadmin تعليق منظمة من الطبقة ٢ عند الحاجة.

النسخ الاحتياطية. الحالة الدائمة هي وحدة تخزين postgres_data بالإضافة إلى ملفات السير الذاتية في cv_storage (/app/uploads). انسخهما معًا احتياطيًّا على الخادم الافتراضي — فقاعدة البيانات وحدها، دون ملفات السير الذاتية التي تشير إليها، استعادة ناقصة. (يحفظ ./headhunter.sh down وحدات التخزين؛ وdb-reset وحده هو ما يدمّر قاعدة البيانات.)


مرجع سريع

أريد أن… افعل هذا
أسجّل الدخول إلى وحدة تحكّم المشغّل /ops/login على ops.<domain>
أرى صحّة المنصّة كلها لوحة الطبقة ١ (/ops/dashboard) — خالية من البيانات الشخصية
أحقّق في منظمة واحدة الطبقة ٢ (/ops/orgs/[id]) — محجوبة، مدوَّنة
أرى بيانات مرشّح الحقيقية فقط عبر كسر الزجاج بالطبقة ٣، تحت منحة حيّة من المنظمة
أشغّل الحزمة / أوقفها ./headhunter.sh up / down
أنشئ منظمة + مشرفها الأول npm run provision -- --org-name … --email … --password … --full-name …
أنشئ مشغّلًا npm run provision-admin -- --email … --password … --full-name … [--role …]
أطبّق المخطَّط / أتراجع عنه npm run db:migrate / db:rollback (يحتاج DATABASE_URL)
أمسح المرشّحين غير المؤكَّدين أبدًا npm run candidate-retention -- [--days 30] [--dry-run]
أفحص الحيويّة GET /health (قاعدة البيانات + الترحيلات + المحلِّل)
أنسخ احتياطيًّا وحدتا تخزين postgres_data و cv_storage (/app/uploads)، معًا

أنت تشغّل المنصّة التي تستند إليها مسارات مِهَن الآخرين وقراراتهم في التوظيف. التصميم كلّه هنا يشير إلى اتجاه واحد: أبقِها سليمة من العرض التجميعي، وساعد المستأجرين من مسافة محجوبة، ولا تصل إلى البيانات الحقيقية إلا حين يُبقي المستأجر الباب مفتوحًا لك — بفعل نشط ومؤقّت.