sbarbase
المحتويات

النشر على خادم

نقطة التحقق المنشورة الحالية e08ed3b2684bdf04f0288c7162747a2783177026: نجحت مهام CI 37600158231 الأربع: الفحوص العامة وتوافق Python والتثبيت عبر Docker واستلام المضيف الخالي. نجح 2102 اختبار Python 3.12، ونجحت فحوص الموقع 37600311118 للبناء والاختبارات. أصبح استلام المضيف الخالي يعمل تلقائيا عند الرفع إلى main، إضافة إلى الطلب اليدوي الصريح؛ ولم يتغير سلوك طلبات الدمج.

يربط ملخص نقطة التحقق الأصلية الحالية حزمة Docker الجديدة بـ13 تقريرا و254 فحصا ناجحا لإنشاء المشروع وميزات Supabase والنسخ والاستعادة والترقية والتراجع وإعادة التشغيل. هذه حزمة التقارير الجديدة الخاصة بهذا التشغيل، وليست أرشيف baafbad السابق أو تقرير التثبيت التاريخي. ترتبط أدلة التشغيل بإيداع الشيفرة e08ed3b2684bdf04f0288c7162747a2783177026؛ ويحفظ نشر الوثائق اللاحق هذه الهوية دون قبول تشغيل تغييرات شيفرة أحدث.

تحتوي أدلة المضيف الخالي الجديدة على ستة تقارير وبيان تشغيل، مع 58 فحصا فريدا في قوائم التحقق و21 فحصا منفصلا لتهيئة Auth الحية. لا تضيف نسخة تسليم تقرير التجربة المطابقة فحوصا جديدة. يثبت سجل التشغيل الأصلي وحارسا السكربت النهائيان أن وحدة النظام المرفقة أصبحت active وأجابت لوحة الإدارة بعد التجربة. تسجل لقطة JSON السابقة مرحلة إيقاف الوحدة المقصودة أثناء التجربة؛ ولا تصف حالة الخدمة النهائية بعد إعادتها. هذه نقطة تحقق للتثبيت على عامل Ubuntu جديد؛ ولا تثبت إعادة تشغيل الجهاز أو Kali أو الاستعادة الكاملة بعد فقدان المصدر أو استمرارية Cron الباردة والدافئة أو القبول الإنتاجي. تحتفظ فحوص الملفات الثابتة وبديل TLS بنطاقها المنفصل؛ ولم تثبت شهادة عامة.

حفظ فحص واصفات systemd المنفصل على Fedora 44 هويات خمسة واصفات عبر exec لعملية Python بالمعرف نفسه، ضمن نطاق systemd محدود الموارد وتحت ملكية معلومة. يضم الأرشيف العام ملفي الفحص والملخص. لم يشغل ضيفا أو حاوية Docker؛ ويقبل سلوك واصفات Python المحدد فقط، دون آلة Linux ضيفة أو إعادة تشغيل الجهاز أو نشر إنتاجي قابل للنقل.

نقطة دمج تاريخية قبل e08ed3b في 2026-10-07: نجح CI للتثبيت عبر Docker بعد baafbad، مع نجاح 2086 اختبار Python و254 فحص خدمات حديثا. فشل الاستلام اليدوي للخدمة المستقلة عند تمرير إعدادات المقبس؛ ونجحت إعادة التشغيل اللاحقة على e08ed3b. الاستعادة الكاملة والإنتاج لم يُقبلا بعد. راجع الحالة الحالية؛ تحفظ الفقرات التالية نتائج اللقطات التاريخية وحدودها.

نقطة تحقق تاريخية في 2026-10-06: نجح التحقق الكامل للقطة الشيفرة حينها بنتيجة 1449 اختبارا، دون فشل أو أخطاء أو تحذيرات أو تجاوز، وقبلته مراجعة مستقلة. تشمل الإصلاحات إغلاق اتصالات SQLite واستجابات HTTPError، وملكية الملفات المؤقتة والتقاط تشخيصات الاختبارات المتوقعة. نجحت أيضا حالات تجهيز التشغيل الـ52، لكن تشغيل PostgreSQL الأصلي واستمرارية Cron وVault والاستعادة الفيزيائية لم تُقبل بعد. راجع الحالة الحالية لنتائج CI وحدود الأدلة.

حصل نطاق public55 V3 المنفصل على قبول عملي: نجحت 25 حالة للمكوّن و30 للمضخة، وطابقت قراءة الملفات الأصلية الـ290 وقراءة الملفات الأربعة المضافة مع فحص صلاحياتها الأدلة، وحُذفت الحاويات الثلاث المعروفة دون موارد معلقة. وحصل نطاق بيانات المساعدة العامة BusyBox V8 على قبول عملي: أعاد Bash 5.3.3 وBusyBox 1.37.0 في المسارين المحددين بيانات الإصدار والمساعدة مع خروج بالرمز 0، وحُذفت حاويته الوحيدة. لا تقبل النتيجتان طلبا خاصا أو توقيت timeout وإشاراته أو مصدر الملف التنفيذي أو تشغيل المرشح أو استمرارية Vault وCron الباردة والدافئة أو الاستعادة الفيزيائية أو الإنتاج.

أعلنت تجربة نافذة التحذيرات V4 المحفوظة نجاح 1447 اختبارا عاديا، لكنها سجلت ستة ResourceWarnings وخرجت بالرمز 1، وكانت نافذة الملاحظة 839 لكل التحذيرات. تحمل ثلاثة سجلات النوع الدقيق sqlite3.Connection، وثلاثة اسم tempfile.py والسطر 484 مع مصدر NoneType. لا تحدد نافذة الملاحظة موضع التخصيص أو سببه؛ وما زالت أصول تحذيرات tempfile الثلاثة مجهولة. كان يلزم عند تلك النقطة التاريخية ربط المصدر والهوية والصلاحيات والمجموعة الكاملة من جديد، ثم CI للإصدار النهائي واستلام الخادم الخالي بتشغيل يدوي. تحتفظ نتائج CI والموقع للإيداع المنشور 0e745394 بنطاق ذلك الإيداع فقط.

دليل لمسار التثبيت الأصلي على Linux. مسار الحاويات الأساسي موثق في التثبيت عبر Docker. تحتفظ تجارب سبتمبر على جهاز التطوير والآلة الافتراضية المحلية بنطاقها؛ ولا تمثل قبولا إنتاجيا جديدا.

يعمل الفرع الرئيسي على خادم حقيقي على sbarbase.sbarah.com (Ubuntu 26.04، وsystemd، ووكيل TLS خلف Cloudflare)، رُقّي من 0.2.0 في 2026-10-16، ونجحت عليه استعادة محلية واستعادة من R2 لبيئة واحدة بلا بيانات تطبيق. تاريخيًا: نجحت مهام الفحوص وتوافق Python وتثبيت Docker في CI 37206229288 عند الإيداع المنشور 0e745394. كانت مهمة الخادم الخالي متخطاة وتحتاج آنذاك إلى تشغيل يدوي جديد. نجح بناء الموقع 37206294329 منفصلا، دون نشر خدمة عامة. تخص النتائج هذا الإيداع وحده؛ تعديل المصدر والوثائق يحتاج فحوصا جديدة.

يستخدم المالك حاسوبه خادما محليا معزولا للتجارب المعلنة. تعرّف الأداة حالة تسليم مواد للتجهيز فقط؛ ولا يثبت هذا الدليل نتيجة تجهيز فعلي أو تشغيل PostgreSQL.

تحتفظ المحاولة السابقة V9 initV2 بنتيجتها المرفوضة: نجحت الحالات المركزة 14 و9 و29، لكن المجموعة الكاملة نفذت 1447 اختبارا خلال 72.300 ثانية مع فشل واحد و15 خطأ. كانت الأخطاء سبعة في صلاحيات سكربت استلام الخادم وثمانية في مساحة .lab المقروءة فقط، مع فشل فحص إذن التنفيذ. طابقت قراءتا الملفات الـ290 وربط المصدر الـ875، وحُذفت الحاويات الست المعروفة دون موارد معلقة؛ ولم يتكرر عيب Git السابق. العدد 1356 حد قبول أدنى، وليس عدد الاختبارات الفعلي.

كانت محاولات التحقق السابقة للصلاحيات ومساحة الاختبارات والتحذيرات نتائج مرفوضة مرتبطة بمصدرها وتاريخها. أُصلحت ملكية الموارد والتشخيصات، وقُبل التحقق الكامل لتلك اللقطة السابقة بنتيجة 1449 اختبارا؛ ولا ينقل ذلك أدلة التشغيل الأصلي أو الاستعادة من تجربة أخرى. يحتفظ سجل الحالة بنتائج المحاولات السابقة وحدودها.

المتطلبات المسبقة

المتطلب السبب
خادم Linux بمعمارية x86-64 مع Docker (خدمة محلية، لا بعيدة) كل مكان تشغيل يشغّل حاويات مثبّتة الإصدار
Bun في PATH (لخدمة نظام، أضف مجلده إلى PATH في الوحدة، مثل /home/sbarbase/.bun/bin) مدير الحزم، وبناء لوحة الإدارة، وفحوص البوابة
المفسّر /usr/bin/python3 بإصدار 3.12 أو أحدث، ومعه cryptography بيئة تشغيل المختبر تستخدم صيغ f-string تحتاج 3.12، وcryptography يشفّر حزم الاستعادة والنسخ الاحتياطية خارج الخادم
نسخة Git من هذا المستودع الحالة وملفات القفل موجودة في نسخة المستودع افتراضيًا
هامش الموارد: على خادم فارغ 4352 MiB متاحة (1792 MiB لحاويات قاعدة البيانات وStorage وAuth الإدارة، واحتياطي 2560 MiB) ونواتان على الأقل من المعالج؛ وأكثر كلما أُضيفت بيئات. يذكر الفحص المسبق الرقم الدقيق ويرفض إذا كان المتاح أقل منه يُشتق المطلوب من مكان التشغيل الذي سيشغّله التشغيل التالي: على خادم فارغ، حاويات النظام الثلاث بحدود فئتها؛ وبعد ذلك كل حاوية محفوظة بحدودها (كل بيئة تضيف سقفًا قدره 512 MiB و0.5 من المعالج لـAuth وREST الخاصين بها)، ثم الاحتياطي، ثم، في تثبيت نُقلت منه بيئة، الكلفة المقيسة لمرحلة المصدر العاملة (docs/evidence/source-stage-footprint.json). يجوز أن يصل مجموع سقوف المعالج إلى ضعف عدد الأنوية بعد حجز نواة للخادم؛ وبوابة ضغط cgroup ترفض العمل الجديد عند التزاحم الفعلي. ما زال مكان التشغيل المقسوم المحفوظ على جهاز التطوير يحتاج إلى 5888 MiB من الحدود فوقها الاحتياطي. يطبع الفحص المسبق التركيب، فيذكر الرفض كل بند باسمه
حساب خدمة موجود يملك نسخة المستودع تعمل الوحدة بهذا الحساب (User=)، لذلك يرفض --apply حسابًا غير موجود بدل تثبيت وحدة لا تستطيع العمل. الحساب الافتراضي المرفق هو sbarbase؛ سمِّ حساب الخادم بالخيارات --service-user و--home و--bun-dir (يمررها deploy/server-acceptance.sh أيضًا)
وصول حساب الخدمة إلى مقبس Docker يثبّت القبول المقبس المحلي المعلن، وافتراضيًا /var/run/docker.sock. سياقات Docker والعناوين البعيدة وDesktop خارج هذا المرشح. يجب إعلان المقبس وجذر البيانات غير الافتراضيين بالقيم نفسها للخدمة وتشغيل القبول؛ راجع عقد المضيف

تُسحب الصور المثبّتة ببصمتها عند التثبيت، ولا تُستخدم أي وسوم عائمة. اقرأ سياسة تحديث الأصل قبل تغيير أي تثبيت.

التثبيت

أمر واحد على الخادم يغطي المتطلبات المسبقة والفحص المسبق والتجربة الكاملة وأدلة القبول:

deploy/server-acceptance.sh --rehearse --bootstrap-file /path/to/operator.json

من دون --rehearse يشغّل الأمر نفسه المتطلبات المسبقة والفحص المسبق فقط. يرفض قبل أن يلمس أي شيء إذا غاب متطلب (docker أو bun أو git أو /usr/bin/python3 بإصدار 3.12 أو أحدث أو خدمة Docker محلية على Linux)، أو إذا لم تكن صلاحية ملف الإعداد الأولي 600، أو إذا أبلغ الفحص المسبق عن عائق، ولا يطبع سرًّا أبدًا. التسلسل التالي خطوة بخطوة هو ما ينفّذه، لمن يريد من المشغّلين أن يقود كل مرحلة بيده.

/usr/bin/python3 lab/install_server.py check        # read-only preflight, non-zero on blockers
/usr/bin/python3 lab/install_server.py plan         # print the exact steps
/usr/bin/python3 lab/operator_file.py /root/sbarbase-operator.json   # prompts, no echo, mode 0600
/usr/bin/python3 lab/install_server.py install --bootstrap-file /root/sbarbase-operator.json

أنشئ الملف بالأداة lab/operator_file.py لا يدويًا: تسألك عن البريد والمنظمة وكلمة المرور مرتين دون إظهارها، وترفض كلمة مرور أقصر من 12 حرفًا، وبريدًا غير صالح، ومسارًا نسبيًا، ورابطًا رمزيًا، وملفًا موجودًا (إلا مع --force)، وتنشئه بالصلاحية 0600، ولا تطبع كلمة المرور أبدًا. وللأتمتة تقبل الأداة JSON نفسه على مدخل قياسي محدود الحجم مع --stdin، فلا تصل كلمة المرور إلى وسيط أو إلى سجل أوامر الطرفية.

ينفّذ install بالترتيب: الفحص المسبق، ثم مجلدات الحالة الخاصة والأسرار (0700)، ثم سحب الصور المثبّتة إن لم تكن محلية، ثم bun install عند الحاجة، ثم بناء لوحة الإدارة، ثم تشغيل بيئة التشغيل المملوكة، ثم الإعداد الأولي لهوية المشغّل. ملف الإعداد الأولي كائن JSON بالصلاحية 0600 فيه email وpassword وorganization فقط؛ يُمرَّر عبر المدخل القياسي، ولا يُمرَّر وسيطًا ولا يُطبع أبدًا.

التثبيت الجديد يهيّئ جيل HBA واحدًا بالضبط للقاعدة الجديدة. أما التثبيت المحفوظ (الحاويات موجودة من قبل) فيُرفض حتى يحمل مصدره وهدف الاستعادة الحالي تثبيتات أجيال:

/usr/bin/python3 lab/adopt-retained.py source
/usr/bin/python3 lab/verify_retained.py source
/usr/bin/python3 lab/adopt-retained.py target
/usr/bin/python3 lab/verify_retained.py target

التبنّي يشغّل ويوقف حاوية القاعدة الملتقطة وحدها، ويحافظ على قواعدها الموجودة بايتًا بايتًا. لا تحذف أبدًا تثبيتًا أو سجل عملية أو إيصالًا لتتجاوز هذه البوابة.

الإشراف

ثبّت الوحدة وشغّلها بأمر واحد. يولّد الأمر الوحدة المرفقة لهذا التثبيت (المسارات وحساب الخدمة ومجلد Bun)، ويتحقق من الناتج بالأمر systemd-analyze verify، ثم يثبّتها ويعيد التحميل ويفعّلها ويشغّلها:

sudo /usr/bin/python3 lab/install_server.py supervise --apply \
     --service-user ops-account --home /srv/ops-account --bun-dir /srv/ops-account/.bun/bin

يستبدل sudo قيمة PATH، فإذا كان Bun خارج مجلدات النظام وجب ذكره بالخيار --bun-dir، وإلا عملت الوحدة بدونه.

من دون --apply يكتفي الأمر بالتوليد والتحقق، ويطبع الأوامر التي كان سينفّذها بالضبط، ويكتب docs/evidence/supervisor-unit.json. يرفض تثبيت وحدة لا تجتاز التحقق، ويرفض --apply دون صلاحيات root، ويرفض التثبيت لحساب غير موجود على الخادم. لاستخدام تخطيط مختلف، مرّر --service-user و--home و--bun-dir؛ الوحدة المرفقة لا تُعدَّل يدويًا أبدًا. يمرر deploy/server-acceptance.sh الخيارات الثلاثة نفسها، فيستطيع مسار القبول ذو الأمر الواحد أن يسمّي حساب الخادم أيضًا.

تحمل الوحدة ما تحتاجه التحديثات (الترقيات):

أعد توليد الوحدة المثبّتة قبل إضافة فحص المضيف وتثبيتها بالأمر supervise --apply للحصول على حد القبول في كل إعادة تشغيل. ومن دون سطر تنظيف الحاويات المتبقية يبقى التشغيل بعد توقف غير نظيف مرفوضًا عند الفحص المسبق، فأوقف الحاويات المتبقية مرة واحدة بالأمر /usr/bin/python3 lab/leftover_runtime.py بحساب الخدمة. أعد تثبيت الوحدة بالأمر supervise --apply حين تنتقل إلى الإصدار الذي يحويها.

قبل أن تعمل الوحدة يجب أن تتحقق أربعة شروط، ويذكر الفحص المسبق كل شرط باسمه بدل فشل غامض:

  1. حساب الخدمة موجود. أنشئه وأعطه نسخة المستودع، أو ثبّت مع --service-user/--home/--bun-dir.
  2. الخدمة تصل إلى Docker المحلي المعلن. المقبس الافتراضي هو /var/run/docker.sock مع صلاحية وصول لحساب الخدمة. يُرفض DOCKER_CONTEXT. لمقبس محلي أو جذر بيانات غير افتراضي، عيّن SBARBASE_DOCKER_SOCKET و SBARBASE_DOCKER_DATA_ROOT في ملف الخدمة الإضافي وبيئة تشغيل القبول. يجب أن يطابق DOCKER_HOST ذلك المقبس. مثال الأسطر مع المسارات الموجودة فعلًا: Environment=SBARBASE_DOCKER_SOCKET=/run/docker-custom.sock و Environment=DOCKER_HOST=unix:///run/docker-custom.sock و Environment=SBARBASE_DOCKER_DATA_ROOT=/srv/docker. يرفض الفحص الأول المسارات الغائبة أو غير المتطابقة دون إنشائها. حين يتعذر الوصول إلى Docker يذكر الفحص العنوان ولا يخمّن حالة الصور أو الحاويات.
  3. الخادم يملك الذاكرة الكافية. يحتاج التشغيل التالي إلى حدود الحاويات التي يشغّلها فوقها احتياطي 2560 MiB: أي 4352 MiB على خادم فارغ، وأكثر مع كل بيئة، و8.8 GiB لمكان التشغيل المقسوم المحفوظ على جهاز التطوير. يشتق ExecStartPre في الوحدة والفحص المسبق وقبول بيئة التشغيل الرقم نفسه، وترفض البوابة بالرمز host_memory_headroom بدل تشغيل نصفي.
  4. صلاحية الكتابة تبقى داخل نسخة المستودع. يذكر ReadWritePaths جذر التثبيت وحده. أي مدخل في ReadWritePaths لمجلد غير موجود يجعل systemd يُفشل الوحدة بالرمز 226/NAMESPACE قبل أن تشغّل أي شيء، لذلك لا تذكر الوحدة أبدًا مسارًا لا ينشئه التثبيت؛ وكل الأسرار موجودة في <checkout>/.secrets/upstream. جرى التحقق من التوليد على جهاز التطوير، وأوامر التثبيت مسجّلة في ملف الأدلة ذاك؛ أما التثبيت نفسه فيحتاج إلى صلاحيات root على الخادم الهدف.

تشغّل الوحدة lab/dev.py، الذي يبني لوحة الإدارة، ويشغّل بيئة التشغيل المملوكة، ويشغّل الواجهة البرمجية وعامل التجهيز، ويوقف بيئة التشغيل عند استلام SIGTERM. يعيد ExecStartPre تشغيل الفحص المسبق، فيفشل الخادم غير المناسب قبل لمس أي حاوية. للتشغيل المباشر في طرفية بدلًا من ذلك، نفّذ /usr/bin/python3 lab/dev.py في طرفية وأوقفه بـCtrl+C.

الأمر الوحيد الذي ينتج أدلة القبول هو deploy/server-acceptance.sh (أدناه)؛ يشغّل الفحص المسبق والفحوص والتجربة بالترتيب، ويحتفظ بنسخة التسليم. لقيادة المرحلتين بنفسك:

/usr/bin/python3 lab/install_server.py check
/usr/bin/python3 lab/deployment_rehearsal.py --bootstrap-file /path/to/operator.json

يحتاج التثبيت نفسه إلى ذاكرة مكان التشغيل فوقها الاحتياطي (4352 MiB على خادم فارغ): ترفض بيئة التشغيل المملوكة أن تبدأ حين لا يستطيع الخادم تحمّل حاويات مكان التشغيل والاحتياطي، وتذكر السبب (host_memory_headroom) بدل تشغيل نصفي. يحرر المثبّت قفل عملية التثبيت بعد بناء لوحة الإدارة وقبل تشغيل بيئة التشغيل المملوكة، لأن بيئة التشغيل تأخذ ذلك القفل بنفسها وتمسكه طوال عمرها.

على خادم لم تُثبَّت فيه الوحدة بعد، شغّل مسار القبول بصلاحيات root مع --install-unit: يولّد الوحدة ويتحقق منها، ويثبّتها ويشغّلها، ويثبت عمل لوحة الإدارة وإنهاء TLS، ثم يحرر الوحدة لتملك التجربة الحاويات والحالة، ويشغّل التجربة مع اشتراط وجود الوحدة، ثم يشغّل الوحدة من جديد ويتأكد أنها نشطة، ويترك الأدلة في مكان واحد. يعدّ systemd الوحدة نشطة لحظة بدء عملية المشرف، أي قبل أن تستطيع لوحة الإدارة الرد بوقت طويل. لذلك ينتظر التشغيل بعد كل بدء للوحدة حتى ترد لوحة الإدارة عبر عنوان loopback، مدة أقصاها 300 ثانية (غيّرها بـ--console-timeout)، فإن لم ترد فشل وذكر آخر ما رآه. يحدث التحرير كلما وُجدت الوحدة نشطة، مع --install-unit أو بدونه، ويعيد فخ (trap) تشغيلها عند أي خروج، فلا يستطيع فشل في منتصف التشغيل أن يترك التثبيت متوقفًا. لا يملك مشرفان الحاويات نفسها، لذلك لا تعمل التجربة أبدًا على تثبيت حي.

sudo deploy/server-acceptance.sh --rehearse --install-unit \
     --service-user ops-account --home /srv/ops-account --bun-dir /srv/ops-account/.bun/bin \
     --bootstrap-file /path/to/operator.json

يستبدل sudo قيمة PATH بقيمة افتراضية آمنة، فلا يرى السكربت ولا الوحدة التي يثبّتها أي Bun مثبّت في مجلد المستخدم الذي استدعاه. مرّر --bun-dir (يضيفه السكربت إلى PATH ويستخدمه في الوحدة) كلما كان Bun خارج مجلدات النظام؛ وبدونه يتوقف السكربت عند خطوة المتطلبات المسبقة ويذكر الخيار باسمه.

يُستخدم sudo لخطوتي الوحدة فقط. كل ما يلمس التثبيت يعمل بالحساب الذي يملكه (--service-user، أو مالك نسخة المستودع عند غياب الخيار)، لأن الحالة تحمل هوية مالكها، وبيئة التشغيل ترفض أي عملية لا يطابق uid الخاص بها الملفات التي تمسكها (HBA ownership inode mismatch): تشغيل الفحص المسبق أو الفحوص أو التجربة بصلاحيات root على تثبيت يملكه حساب خدمة يفشل، والتثبيت بصلاحيات root يترك ملفات لا تستطيع الخدمة استخدامها. يجري السكربت هذا الاستبدال بنفسه، فيبقى استدعاء sudo الواحد أعلاه هو الأمر كله.

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

deploy/server-acceptance.sh --rehearse --docker-host unix:///var/run/docker.sock

للعنوان المحلي المخصص، أعلن المقبس الموجود وجذر Docker الفعلي ثم مرّر --docker-host المطابق. يرفض اختلاف القيم أو تجاوز السياق قبل تأثيرات التجربة. ما زال المسار الأصلي يتطلب Python وBun وsystemd على المضيف؛ يستخدم مسار Docker الأساسي المشغّل العام.

يكتب تشغيل القبول تجربته في docs/evidence/server-acceptance-rehearsal.json وينسخها إلى server-acceptance-latest.json، ولا يلمس docs/evidence/deployment-rehearsal.json العادي. يحمل ذلك السجل معلومات الخادم (النواة، وDocker، وBun، وPython، والهامش، والقرص الحر)، والأمر الذي نُفّذ بالضبط، ومجموعة التثبيتات كاملة ببصماتها، وحالة الوحدة sbarbase.service، ووقتي البداية والنهاية، وصفًا لكل فحص مع نتيجته. التجربة التي لا تستطيع العمل تسجّل الملاحظة المانعة بدل النجاح، وتخرج برمز غير صفري.

فحص: هل يعمل المسار تحت الإشراف، لا التشغيل المباشر فقط؟

تشغّل التجربة المشرف مباشرة. تسجّل حالة وحدة systemd نفسها، وتُفشل الفحص the shipped supervisor unit is installed for an acceptance run حين لا يكون /etc/systemd/system/sbarbase.service مثبّتًا، فيعني نجاح تشغيل القبول أن الوحدة كانت موجودة ومفعّلة. الوحدة التي تتحقق منها بالأمر systemd-analyze هي الملف المثبّت نفسه، وتذكر الأدلة أي ملف كان؛ ولا يُتحقق من قالب نسخة المستودع إلا حين لا توجد وحدة مثبّتة، وتسجّل الأدلة ذلك أيضًا.

التحقق بعد التثبيت

/usr/bin/python3 lab/install_server.py smoke         # management Auth, per-environment routes, console pid
bun lab/combined-gateway-check.ts                    # 14 simultaneous gateway checks (needs the console built)
/usr/bin/python3 lab/combined-supervisor-check.py   # full rehearsal: start, checks, supervised shutdown
/usr/bin/python3 lab/deployment_rehearsal.py         # one command: install, supervise, verify, shut down, evidence
/usr/bin/python3 lab/target_placement_rehearsal.py   # retained target placement: start, probes, stop

يكتب deployment_rehearsal.py الملف docs/evidence/deployment-rehearsal.json ويخرج برمز غير صفري عند أي فشل؛ وعلى خادم ينقصه الهامش المطلوب يسجّل رفض الفحص المسبق ويتوقف دون تشغيل أي شيء. أما target_placement_rehearsal.py فيختبر مكان تشغيل هدف الاستعادة المتبنّى من أوله إلى آخره، ثم يعيده إلى حالة الإيقاف المؤقت والتوقف.

يبلغ أمر smoke حالة كل عنوان ولا يعدّل الحالة. وتحتاج تجربة المشرف فوق ذلك إلى الهامش نفسه الذي يحتاجه التشغيل العادي.

HTTPS والتعرض للشبكة

تربط لوحة الإدارة والبوابة العنوان المحلي فقط، ولا يُوصل إليهما إلا عبر الخادم. أنهِ TLS في وكيل عكسي، وأبقِ شبكات Docker داخلية: المثبّت لا ينشر أي منفذ. لا تكشف عنوان Auth الإدارة ولا واجهة التجهيز البرمجية مباشرة. اضبط العنوان العام الذي يجب أن تعلنه لوحة الإدارة في الوكيل، لا في بناء اللوحة. أما روابط البريد وعناوين العودة من OAuth في Auth فتأخذ العنوان العام من SBARBASE_PUBLIC_URL في ملف إضافي للخدمة (إعدادات تسجيل الدخول).

يتوفر Studio اختياريا لكل بيئة في البنية المشتركة السابقة، مع أدلة CI محددة النطاق. لا يثبت ذلك قبول Studio في البنية الأصلية المستقلة؛ وتبقى شروط الوكيل والمصادقة ضرورية. لكل بيئة عنوان Studio خاص بها. يخدمها الوكيل المرفق بالخيار --studio-host studio-{id}.example.com (النمط نفسه المضبوط في SBARBASE_STUDIO_HOST)، فلا يمرّر إلا الأسماء المطابقة له باسمها، بعد وجود سجل DNS شامل وشهادة تغطيه (Studio)؛ ودون ذلك يُوصل إلى Studio عبر نفق SSH. ويقرأ الوكيل أيضًا SBARBASE_STUDIO_HOST إن لم يُعطَ الخيار --studio-host، ويكتب الأمر lab/studio_address.py apply DOMAIN هذا الإعداد للوحة الإدارة ولوحدة الوكيل في خطوة واحدة. لا يملك Studio شاشة دخول خاصة به، وصفحاته تحمل سلسلة اتصال بقاعدة البيانات، فيجب ألا يُوصل إليه أبدًا دون مصادقة، وألا يمر عبر نمط مسارات طبقة البيانات. يغطي القسمان 4 و5 من مواصفة التكامل البوابة وخيارات التوجيه.

يأتي مع المستودع إنهاء TLS مرجعي، ويختبره فحص خاص (/usr/bin/python3 lab/tls_termination_check.py، 27 فحصًا، docs/evidence/tls-termination.json). لا يحتاج إلا إلى Bun وشهادة:

ثبّت منفذ لوحة الإدارة المحلي أولًا، فتبقى وجهة الوكيل صالحة بعد إعادة تشغيل الخدمة أو الخادم. بدون ذلك تختار اللوحة منفذًا مؤقتًا جديدًا عند كل تشغيل:

sudo mkdir -p /etc/systemd/system/sbarbase.service.d
printf '[Service]\nEnvironment=SBARBASE_CONSOLE_PORT=8787\n' | sudo tee /etc/systemd/system/sbarbase.service.d/console-port.conf
sudo systemctl daemon-reload && sudo systemctl restart sbarbase
bun deploy/console-tls-proxy.ts \
    --cert /etc/letsencrypt/live/console.example.com/fullchain.pem \
    --key  /etc/letsencrypt/live/console.example.com/privkey.pem \
    --public-host console.example.com \
    --upstream http://127.0.0.1:8787 \
    --https-port 8443 --http-port 8080

ليبقى الوكيل عاملًا بعد إعادة تشغيل الخادم، أعطه وحدة systemd خاصة به تعمل بحساب الخدمة. سمّها sbarbase-proxy.service، فإليها يكتب lab/studio_address.py apply ملفه الإضافي ما لم يسمِّ الخيار --proxy-service وحدة أخرى. على Fedora يمنع SELinux وحدةً تشغّل Bun من داخل /home مباشرة (الحالة 203/EXEC، رفض الإذن)، فشغّله عبر صدفة: ExecStart=/bin/sh -c 'exec /home/sbarbase/.bun/bin/bun deploy/console-tls-proxy.ts ...'. هكذا شُغّل في الآلة الافتراضية التجريبية، مع جهة شهادات محلية، وبقي يعمل بعد إعادة التشغيل (docs/evidence/vm-https-first-project.json). يحوّل طلبات HTTP العادية إلى https://<المضيف العام>/ على المنفذ 443، فانشره على المنفذ 443 في الخادم الحقيقي.

يرفض الوكيل أن يبدأ ما لم تكن الشهادة والمفتاح ملفين عاديين، والمفتاح غير قابل للقراءة من المجموعة أو من الجميع، و--public-host مضبوطًا على اسم مضيف مجرد، والوجهة عنوانًا محليًا. يُطبَّق شرط العنوان المحلي على عنوان لوحة الإدارة أيًّا كانت طريقة تمريره، بما فيها العنوان المقروء من .lab/upstream/server.json عند غياب --upstream. يجيب طلبات HTTP العادية بتحويل 308 إلى HTTPS، ويضيف Strict-Transport-Security وX-Content-Type-Options وReferrer-Policy وX-Forwarded-Proto وX-Forwarded-Host، ولا يسجّل إلا الطريقة والمسار والحالة: لا أجسام الطلبات، ولا سلاسل الاستعلام، ولا ملفات تعريف الارتباط، ولا بيانات الاعتماد أبدًا. ترويسات التمرير تحمل قيمة --public-host، ولا تحمل أبدًا قيم X-Forwarded-* أو Host التي أرسلها العميل، فهذه تُحذف قبل أن يصل الطلب إلى لوحة الإدارة. ولا يخدم إلا --public-host وأسماء loopback وعناوين Studio المطابقة لـ --studio-host، ويجيب أي مضيف آخر بـ 421، فلا يُظهر سجل DNS الشامل لوحة الإدارة تحت اسم آخر أبدًا.

تقوية الوكيل نفسه جزء من الفحوص: التحويل والمضيف الممرَّر يأتيان من --public-host، لا من ترويسة Host التي أرسلها العميل (فلا يستطيع مهاجم أن يحوّل التحويل إلى تحويل مفتوح بمضيف من عنده)، وتُزال ترويسات hop-by-hop قبل التمرير، وأي جسم طلب يتجاوز --max-body (حد الرفع مع 1 MiB إضافي افتراضيًا) يُجاب بالرمز 413 بمجرد أن يعبر التدفق الحد، دون تخزينه كاملًا. قد يفضّل المشغّل nginx أو Caddy أو وكيل المنصة؛ والفحوص أعلاه تحدد السلوك الذي يجب أن يحافظ عليه أي بديل.

الجدار الناري: مساران من الحاويات إلى الخادم

خدمتان داخل الحاويات تتصلان بالخادم عبر عناوين جسور Docker: يصل Studio إلى Auth وStorage عبر مساره على المنفذ 25432، وتصل Edge Functions إلى Storage على المنفذ 25433. الجدار الناري الذي يرفض الاتصالات الواردة افتراضيًا (وهذا افتراضي ufw) يرفض هذين المسارين أيضًا، فتفشل عندها صفحات Storage والمستخدمين والواجهة البرمجية في Studio. اسمح بهذين المسارين فقط، وتبقى SSH و80 و443 وحدها مفتوحة من الخارج:

SUBNET=$(docker network inspect sbarbase-durable-net --format '{{(index .IPAM.Config 0).Subnet}}')
GATEWAY=$(docker network inspect sbarbase-durable-net --format '{{(index .IPAM.Config 0).Gateway}}')
sudo ufw allow from "$SUBNET" to "$GATEWAY" port 25432 proto tcp comment 'sbarbase Studio route'
sudo ufw allow in on br-+ to any port 25433 proto tcp comment 'sbarbase Functions to Storage'

القاعدة الأولى لا تقبل إلا شبكة التشغيل، فلا تصل حاوية من شبكة أخرى إلى مسار Studio. والثانية تقبل أي جسر Docker على هذا المنفذ وحده، ثم يرفض كل مستمع أي مصدر من خارج شبكة بيئته. عند فتح Studio يُفحص المسار الأول من داخل Studio نفسه، فإن كان محجوبًا توقف الفتح برسالة تذكر الجدار الناري.

إذا انقطعت الاستعادة

الاستعادة الفاشلة تترك .lab/upstream/recovery-target.json، وإعادة التشغيل العادية ترفض لأن الواصف موجود. لا تعدّل تلك الحالة يدويًا:

/usr/bin/python3 lab/recovery_reconcile.py        # stop the retained containers
/usr/bin/python3 lab/retire_recovery_target.py --reason "why it failed"
/usr/bin/python3 lab/recovery-restore-db.py       # now a fresh restore may run

يرفض الأمر إيقاف الهدف نهائيًا ما لم تكن حالة الواصف interrupted أو failed أو cleanup-failed، وكل حاوية بتلك البادئة متوقفة، ولا عملية HBA معلّقة خاصة بالهدف. يؤرشف الأمر الواصف في recovery-target-history/، ويسجّل المسار في سجل عملية التحويل، ويحذف الواصف النشط؛ ولا تُحذف الحاويات ولا وحدات التخزين أبدًا. والهدف الذي يحمل قاعدة بيانات صالحة يمكن تبنّيه في مكانه بدلًا من ذلك بالأمر lab/adopt-retained.py target.

النسخ الاحتياطي والترقية والتراجع

الخطوات التفصيلية في النسخ الاحتياطي والاستعادة والترقيات.

أدلة التجارب السابقة والحدود الحالية

صلاحيات مجلد الأسرار

يجهّز التثبيت وبدء الخدمة المجلدين .secrets و.secrets/upstream بالصلاحية 0700 وملكية الحساب الذي يشغّل الخدمة. تُرفض الروابط الرمزية والمجلدات الموجودة بملكية أو صلاحيات مختلفة. ويبقى مجلد حالة التشغيل .lab/upstream أيضًا بالصلاحية 0700: بخلاف الأسرار، يُغلق عند التشغيل إن تركه تثبيت بقناع 002 مفتوحًا، لأن الاستعادة ترفض سجلها في مجلد مفتوح؛ ويُرفض الرابط الرمزي أو المالك المختلف. إذا رفض إصدار جديد مجلدا أنشأه إصدار قديم بصلاحية 0755، أوقف التثبيت بالطريقة المعتادة وتحقق أن المجلدين فعليان ويملكهما حساب الخدمة نفسه قبل ضبط صلاحية هذين المجلدين فقط إلى 0700. في صورة Docker المرفقة الحساب هو root داخل الحاوية. تغيير الملكية أو صلاحيات الملفات الداخلية ليس جزءا من هذا الإصلاح؛ احتفظ ببيانات الأسرار والنسخ الاحتياطية. رسائل Runtime private directory ownership or mode refused وRuntime private directory identity changed تدل على وجوب تصحيح الصلاحيات أو مسار التثبيت قبل المحاولة مجددا.

عدّل على GitHub