sbarbase
المحتويات

الترقيات

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

لا شيء هنا جاهز للإنتاج. بُنيت قناة التحديث (فحص الإصدارات، وصفحة لوحة الإدارة، والتثبيت بنقرة، والتحديث التلقائي، وحارس التشغيل، والتصريف) في 2026-09-25، وتغطيها اختبارات الوحدات وثلاث حالات في CI نجحت في 2026-09-25 على جهاز CI نظيف، لا على خادم حقيقي (الدليل، 45 فحصًا من 45): عاد وحده كلٌّ من إصدار يرحّل فهرس التحكم ثم يتوقف، وإصدار لا يجيب فيه PostgREST أبدًا، وإصدار لا ينجح في فحوص السلامة أبدًا، مع استعادة لقطة الفهرس، وكانت حركة التطبيقات تُجاب بالرمز 503 بينما تنتظر فحوص السلامة، ورُفض وسم غير موقّع ووسم موقّع بمفتاح غير مسرود دون أن يتحرك شيء. نقلت تلك التشغيلات نسخة المستودع بالأمر lab/upgrade.py من سطر الأوامر. وفي 2026-09-26 شغّلت الآلة الافتراضية التجريبية، تحت systemd، دورة سطر الأوامر ثم القناة نفسها كما يستخدمها المشغّل: إصدارات موقّعة ظهرت وثُبّتت عبر واجهة الإدارة التي تستخدمها لوحة الإدارة (لم تُشغَّل صفحة التحديثات نفسها) وبالتحديث التلقائي، وإصدار معطوب عاد منه صباربيز وحده، وإصدار يغيّر Auth ثُبّت بعد تأكيد تحذيره. وعلى الخادم الحقيقي كان 0.3.1 أول إصدار موقّع ثُبّت من سطر الأوامر بالأمر lab/upgrade.py start --release؛ أما زر التثبيت في لوحة الإدارة والتحديث التلقائي والزر Roll back فلم تُشغَّل على خادم حقيقي بعد؛ انظر ما الذي شُغّل.

أين ترى التحديث

لا يرى شيئًا عن التحديثات إلا مشغّل التثبيت، أي مالك أو مدير في المنظمة التي أُنشئت عند الإعداد. بقية الأعضاء لا يرون شيئًا.

الخادم هو الذي يقرر هل يمكن تثبيت إصدار الآن، لا الصفحة: يحكم المشرف على ذلك وينشر حكمه، وتعرض لوحة الإدارة الحكم وسببه كما هما.

يفحص صباربيز كل 6 ساعات، وأول فحص بعد 5 دقائق من تشغيله. إذا فشل الفحص (خادم بلا اتصال مثلًا) أعاد المحاولة بعد 30 دقيقة، ثم بعد ساعة، ثم ساعتين، ثم 4 ساعات، ثم يعود إلى كل 6 ساعات، وتبقى آخر نتيجة سليمة في الصفحة. الفحص لا يثبّت شيئًا أبدًا.

ثبّت إصدارًا من لوحة الإدارة

  1. افتح Updates واقرأ ملاحظات الإصدار وقائمة ما يتغير.
  2. اضغط Install update. لا يتاح الزر إلا حين يقول الخادم إن الإصدار قابل للتثبيت الآن.
  3. في الإصدار الموسوم Needs your confirmation، اقرأ التحذير واختر المربع الذي يقول إن بيانات البيئات قد تحتاج استعادة من النسخ الاحتياطية إذا عاد التحديث إلى الإصدار الحالي. ثم اضغط Install now.
  4. ابقَ في الصفحة. تعرض كل مرحلة: انتظار الخادم، ثم إنهاء العمل الجاري، ثم النسخ الاحتياطي والانتقال، ثم إعادة التشغيل، ثم فحوص السلامة. ينقطع اتصال لوحة الإدارة بضع دقائق أثناء إعادة التشغيل، ثم يعود وحده.
  5. حين تقول الصفحة إن التحديث ثُبّت وإنه سليم، اضغط Reload console لتحمّل الصفحة الإصدار الجديد.

إذا لم يصبح الإصدار الجديد سليمًا، تخبرك الصفحة بذلك، ويكون صباربيز قد عاد إلى إصدارك السابق. وإذا لم تظهر نتيجة نهائية بعد 10 دقائق، فنفّذ python3 lab/upgrade.py status على الخادم.

ما الذي يجري خطوة بخطوة

  1. الطلب: لا تستطيع لوحة الإدارة ترقية نفسها، فهي تكتب طلبًا وينفّذه المشرف، وهو العملية التي تشغّل كل شيء، بعد أن يحكم عليه من جديد. لا يوجد إلا طلب واحد في كل وقت، والطلب الذي لا يلتقطه أحد خلال ساعة يسقط. وينتظر التحديث انتهاء النسخ الاحتياطي اليومي إن كان جاريًا.
  2. التصريف: يتوقف عامل التجهيز عن التقاط مهام جديدة، وينتظر المشرف حتى تنتهي المهمة التي بين يديه، وأي تغيير في Studio أو تسجيل الدخول أو Realtime أو Edge Functions أو الوصول إلى قاعدة البيانات أو مفتاح التوقيع، والنسخ الاحتياطي اليومي، وحتى لا يبقى سجل عملية ينتظر التسوية. لا يبدأ شيء جديد في هذه الأثناء. وإذا استغرق ذلك أكثر من 10 دقائق فشل الطلب، ولا ينتقل شيء، ويستمر التجهيز.
  3. الفحوص: يجلب الأمر lab/upgrade.py start --release <tag> وسم الإصدار من جديد، ويتحقق من توقيعه، ويحسب صنفه مرة أخرى من المصدر. ويرفض دون أن يغيّر شيئًا إذا كان في نسخة المستودع تعديلات محلية على ملفات متتبَّعة، أو إذا لم يحوِ الإصدار كل إيداع تشغّله نسخة المستودع، أو إذا غيّر الإصدار صورة PostgreSQL، أو إذا لم تكتمل بداية ترقية سابقة، أو إذا بقي سجل عملية ينتظر التسوية، أو إذا كان نسخ احتياطي أو استعادة قيد التنفيذ. ملفات الأدلة التي كتبها اختبار الاستلام والفحوص الحية داخل docs/evidence/ لا تُعد تعديلات محلية: تُنسخ إلى .lab/upgrades/evidence-<time>/ قبل أن تنتقل نسخة المستودع.
  4. الصور: يسحب كل صورة يثبّتها الإصدار الجديد، فلا يوقف تنزيلٌ ناقص تثبيتًا يعمل. كل ما سبق لا يغيّر شيئًا.
  5. النسخة الاحتياطية: يأخذ نسخة احتياطية من كل بيئة على هذا الخادم ومن قاعدة Storage المشتركة storage_metadata (النسخ الاحتياطي والاستعادة)، وكلها بوقت تشغيل واحد. بداية هذه الخطوة هي نقطة اللاعودة للمحاولة التلقائية. تُوسم النسخ المأخوذة لترقية بهذا السبب. وحين تنتقل نسخة المستودع، يُسجَّل التشغيل أيضًا في .lab/backups/upgrade-moved.json: تُحفظ نسخ آخر 3 ترقيات نقلت نسخة المستودع مهما كان عمرها، ولا تُحسب ضمن SBARBASE_BACKUP_KEEP. أما المحاولة التي توقفت قبل الانتقال فنسخها عادية.
  6. لقطة التحكم والحارس: ينسخ حالة التحكم (فهرس التحكم، وكل مخزن SQLite مباشرة تحت .lab/upstream/، ومخزن المفاتيح .secrets/upstream/managed-keys.sqlite) إلى .lab/upgrades/snapshots/، وينسخ حارس التشغيل الخاص بهذا الإصدار إلى .lab/upgrades/guard.py (انظر حارس التشغيل).
  7. الانتقال: ينقل نسخة المستودع إلى إيداع الإصدار ويثبّت اعتمادياته.
  8. إعادة التشغيل: يوقف المشرف لوحة الإدارة والحاويات المملوكة بترتيب، ثم يخرج برمز 42. يعيد systemd تشغيله (RestartForceExitStatus=42 في الوحدة)، وكذلك Docker (restart: unless-stopped). أما من سطر الأوامر فتعيد التشغيل بنفسك.
  9. الحارس ثم الإصدار الجديد: كل تشغيل يبدأ بالحارس. وإذا ترك الإصدار السابق سجل عملية دون تسوية، يعود أول تشغيل للإصدار الجديد فورًا قبل أن يمس أي شيء، ليسوّيه الإصدار السابق؛ ولا تُحسب هذه العودة على الإصدار في التحديث التلقائي.
  10. الحجز: قبل أن يفتح أي شيء فهرس التحكم، يأخذ الإصدار الجديد لقطة جديدة لحالة التحكم، وهي التي تستعيدها العودة. وحتى تنجح فحوص السلامة:
    • تتلقى الطلبات عبر البوابة 503 مع Retry-After: 5، وتُرفض مقابس Realtime والوصول المباشر إلى قاعدة البيانات؛
    • تتلقى تغييرات الإدارة في لوحة الإدارة 409 مع جملة تقول إن التغييرات متوقفة مؤقتًا، لأن العودة كانت ستُسقطها؛
    • تبقى لوحة الإدارة تجيب على القراءة، وتسجيل الدخول والخروج، وRoll back، وCheck now، وإعدادات التحديث؛
    • ينتظر عامل التجهيز، والنسخ الاحتياطي اليومي، وتشغيل Studio، وبقية التغييرات في الخلفية.
  11. فحوص السلامة: بعد أن تعمل عملية لوحة الإدارة، يجب أن تجيب جولة فحوص كاملة خلال 120 ثانية: /health الخاص بلوحة الإدارة (وهو يقرأ فهرس التحكم ومخزن المفاتيح)، وAuth الإدارة، ولكل بيئة في الخدمة Auth وREST وStorage مباشرة، ثم REST وAuth مرة أخرى عبر البوابة كما يصل إليهما التطبيق، برمز صالح لهذا التشغيل وحده. حين تنجح جولة يُعتمد التحديث: تمر الحركة، ويبدأ العامل، ويُرسل update.applied. وبعدها يعود للعمل وحده Studio الذي استُخدم في آخر ثماني ساعات، ويُعاد تحميل تبويبه المفتوح فيكمل (Studio).
  12. العودة: إذا فشلت مرحلة من مراحل التشغيل، أو لم تنجح أي جولة خلال 120 ثانية، يعيد المشرف نسخة المستودع إلى الخلف، ويستعيد اللقطة من الخطوة 10، ثم يخرج بخطأ. تشغّل سياسة إعادة التشغيل الإصدار السابق بصوره السابقة، فيمر بالحجز وفحوص السلامة نفسها قبل أن يُسجَّل rolled_back، ويُرسل update.rolled_back. وإذا لم يعمل الإصدار السابق أيضًا، تصبح الحالة rollback_failed ويُرسل update.rollback_failed، والطريق حينها استعادة النسخ الاحتياطية من الخطوة 5.

عند إعادة التشغيل لا تُستبدل إلا حاويات Auth وREST وStorage وRealtime وEdge Functions التي تغيّرت صورتها المثبّتة، وتعيد العودة الصور السابقة. أما حاوية قاعدة البيانات فلا تستبدلها الترقية أبدًا.

حارس التشغيل

الحارس سكربت صغير يعمل قبل أي شيفرة من الإصدار الذي تحمله نسخة المستودع: أول ExecStartPre في وحدة systemd، وأول خطوة في سكربت تشغيل الحاوية، ومن داخل lab/dev.py حين تشغّل صباربيز من الطرفية. تنسخ الترقية حارس الإصدار الذي تغادره إلى .lab/upgrades/guard.py، وتشغّل مرات التشغيل التالية تلك النسخة، فلا يستطيع إصدار معطوب أن يكسر طريق العودة إلى الإصدار الذي قبله.

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

ويكمل أيضًا عودة انقطعت. وإذا فشل الإصدار السابق بالطريقة نفسها، يسجّل الحارس rollback_failed ويترك الإصدار السابق يعمل دون فحوص السلامة.

كل عودة، سواء أجراها الحارس أو المشرف أو تراجع طلبه المشغّل، تنقل نسخة المستودع قسرًا ثم تتحقق من النتيجة:

وإذا فشلت العودة داخل start نفسه، تبقى الترقية في المرحلة applied، ويعيد التشغيل التالي نسخة المستودع إلى الخلف قبل أن تعمل أي شيفرة من الإصدارين.

والنقل الذي يستمر في الفشل تعيد مرات التشغيل التالية محاولته. وبعد 3 محاولات فاشلة يحدث واحد من ثلاثة:

وبعد أن تفشل عملية تحديث فرعية بطريقة قد تترك نسخة المستودع بين إصدارين، لا يستأنف المشرف التجهيز عليها: يُفشل الطلب، ويقول إن صباربيز سيعيد التشغيل، ثم يخرج ليسوّي الحارس نسخة المستودع أولًا. ويُعلن عن العودة التي يجريها الحارس في أول تشغيل تالٍ يستطيع إرسال الإشعارات. تضبط وحدة systemd القيمة StartLimitIntervalSec=0 حتى لا يتوقف systemd عن إعادة التشغيل بينما يحتاج الحارس عدة مرات، وTimeoutStartSec=600 لأن العودة تعيد تثبيت اعتماديات الإصدار السابق قبل الفحص المسبق.

المشرف الذي يُقتل بدل أن يتوقف (SIGKILL، أو قاتل نفاد الذاكرة)، أثناء فحوص السلامة أو في أي وقت آخر، يترك الحاويات المملوكة تعمل، لأن Docker هو من يملكها لا الخدمة. وبعد الحارس يوقفها التشغيل التالي كما يوقفها المشرف نفسه، فتبقى كل حاوية وكل وحدة تخزين (lab/leftover_runtime.py، وهو ثاني ExecStartPre في الوحدة، ويشغّله المشرف مرة أخرى بعد أن يأخذ أقفاله). ويرفض بدل ذلك، ويذكر السبب، حين يمسك قفلها مشرف أو عامل ما زال يعمل، أو حين ينتظر إيصال تجهيز أو سجل HBA تسويةً. والوحدة المثبّتة قبل إضافة هذا السطر تبقى ترفض عند الفحص المسبق حتى يُعاد تثبيتها (النشر على خادم).

الأصناف الأربعة

يُحسب الصنف من الفرق بين الإيداع الذي تشغّله وإيداع الإصدار، ولا يؤخذ مما يقوله الإصدار عن نفسه. كل ما يستطيعه الإصدار أن يجعل نفسه أشد، بأن يعلن ترحيل بيانات.

الصنف ما الذي يحدده ماذا تفعل
آمن (Safe) لا يتغير شيء مما في الصفوف التالية. قد تتغير الشيفرة والاعتماديات وتثبيتات PostgREST وEdge Functions وStudio، ولا يغيّر أي منها قاعدة بيانات بيئة ثبّته من لوحة الإدارة أو من سطر الأوامر، أو اتركه للتحديث التلقائي
يحتاج تأكيدك (Needs your confirmation، attended) يتغير تثبيت Auth أو Storage أو Realtime ثبّته من لوحة الإدارة بعد أن تقرّ بالتحذير، أو بالخيار --allow-class attended. لا يُثبَّت تلقائيًا أبدًا
يحتاج إعادة بناء (Needs a rebuild) يتغير compose.yaml أو deploy/sbarbase.service أو .dockerignore أو وكيل TLS (deploy/console-tls-proxy.ts) أو Dockerfile، أو أي ملف ينسخه Dockerfile الخاص بالإصدار إلى الصورة (أسطر COPY وADD فيه). إعادة التشغيل لا تقرأ هذه من جديد ثبّته على الخادم ثم أعد البناء (أدناه). لا يُثبَّت من لوحة الإدارة ولا تلقائيًا أبدًا
يحتاج ترحيلًا (Needs a migration) تتغير صورة PostgreSQL، أو يعلن الإصدار ترحيل بيانات ليس ترقية. لا يُثبَّت من لوحة الإدارة ولا تلقائيًا ولا بالأمر start --release أبدًا

إصدارات تحتاج تأكيدك

يشغّل كل من Auth وStorage وRealtime ترحيلات المخطط الخاصة به في كل قاعدة بيانات بيئة عند تشغيله، وقد لا تعمل الصورة السابقة على المخطط بعد ترحيله. والعودة لا تستعيد إلا حالة التحكم، لا قواعد بيانات البيئات. لذلك إذا عاد تحديث من هذا الصنف إلى الإصدار السابق، فقد تحتاج بيانات البيئات إلى استعادة من النسخ الاحتياطية المأخوذة قبل الترقية. ويرحّل Storage أيضًا قاعدة البيانات المشتركة storage_metadata، وفيها إعدادات Storage لكل البيئات، والنسخة الاحتياطية المأخوذة قبل الترقية تشملها أيضًا إلى جانب البيئات. لهذا لا يُثبَّت هذا الصنف إلا بعد إقرار صريح، ولا يُثبَّت تلقائيًا أبدًا.

ولإلغاء تحديث كهذا كليًا بعد العودة، استعد التشغيل المأخوذ قبله وصباربيز يعمل على الإصدار السابق. يَسِم الأمر python3 lab/backup.py list ذلك التشغيل بعبارة "before an upgrade". استعد كل بيئة أولًا، ثم storage_metadata، فاستعادتها تعيد تشغيل Storage في النهاية فيبدأ على تسجيلات تطابق البيئات المستعادة. وبعد تحديث Auth، استعد أيضًا قاعدة الإدارة من التشغيل نفسه: نطاق الإدارة يستخدم صورة Auth نفسها، وقد رحّلت حسابات المشغّلين:

python3 lab/backup.py restore <environment> <backup>     # once per environment
python3 lab/backup.py restore-storage <backup>           # Storage pauses for every environment meanwhile
python3 lab/backup.py restore-management <backup>        # after an Auth update; operators sign in again afterwards

إصدارات تحتاج إعادة بناء

مع Docker، يشغّل المشغّل start --release مع --allow-class rebuild، ثم يعيد البناء:

sudo deploy/compose.sh upgrade vX.Y.Z
sudo deploy/compose.sh up

ومع خدمة systemd، مرّر إلى supervise --apply الخيارات نفسها التي ثُبّتت بها الخدمة ليثبّت الوحدة الجديدة، ثم أعد التشغيل:

/usr/bin/python3 lab/upgrade.py start --release vX.Y.Z --allow-class rebuild
sudo /usr/bin/python3 lab/install_server.py supervise --apply
sudo systemctl restart sbarbase

الإصدار الذي يحتاج إعادة بناء ويغيّر أيضًا تثبيت Auth أو Storage أو Realtime يحتاج الخيارين معًا: --allow-class rebuild --allow-class attended. ولا يمرّر مشغّل Docker إلا الأول، فشغّل مثل هذا الإصدار مع Docker بالأمر docker compose exec sbarbase python3 lab/upgrade.py start --release vX.Y.Z --allow-class rebuild --allow-class attended، ثم sudo deploy/compose.sh up. وحين يتغير وكيل TLS، أعد تشغيل وحدته الخاصة بعد التحديث أيضًا. تمر إعادة التشغيل بعدها بالحارس واللقطة والحجز وفحوص السلامة والعودة نفسها كأي ترقية.

إصدارات تحتاج ترحيلًا

تغيير صورة PostgreSQL يستبدل حاوية قاعدة البيانات، وهذه مهمة lab/migrate-generation.py (حاويات قواعد البيانات حالة خاصة). أما ترحيل البيانات المعلن فاتبع فيه ما تذكره ملاحظات الإصدار. ليس في صباربيز أداة تطبّق أيًا منهما من صفحة التحديثات.

التحديث التلقائي

معطّل افتراضيًا. فعّله من الإعدادات أسفل صفحة التحديثات: اختر Install safe updates automatically، وحدد نافذة الصيانة، ثم احفظ. النافذة بالتوقيت المحلي للخادم، وتظهر بنظام 12 ساعة مع المنطقة الزمنية التي يستخدمها الخادم، وقيمتها الافتراضية من 3:00 AM إلى 5:00 AM. والنافذة التي تنتهي قبل وقت بدئها تعبر منتصف الليل. مع Docker يكون التوقيت المحلي للخادم هو توقيت الحاوية: UTC ما لم تضبط TZ (مثل sudo TZ=Asia/Dubai deploy/compose.sh up)، ويمرره المشغّل إلى الحاوية، ولا يقرأ أي ملف .env، فأعطه القيمة مع كل up لاحق، والصورة تحمل قاعدة بيانات المناطق الزمنية.

لا يُثبَّت تحديث وحده إلا إذا تحققت كل هذه الشروط:

تُستنفد المحاولة التلقائية حين تبلغ نقطة اللاعودة، أي بداية النسخ الاحتياطي. المحاولة التي توقفت قبلها (فشل في الشبكة أثناء جلب الإصدار أو سحب صوره، أو رفض، أو تصريف انتهت مهلته) تُعاد، 3 مرات على الأكثر لكل إصدار، بفاصل 10 دقائق ثم 20 دقيقة، داخل النافذة. أما المحاولة التي يفشل بعدها نسخها الاحتياطي أو لقطتها أو انتقالها فلا تتكرر. والإصدار الذي تُراجع عنه، وحده أو بطلب منك، لا يُثبَّت تلقائيًا أبدًا، إلا إذا كان سبب العودة الوحيد سجل عملية لم يسوّه الإصدار السابق. ويبقى بإمكانك تثبيت أي منها من لوحة الإدارة.

ساعة النسخ الاحتياطي اليومي (SBARBASE_BACKUP_HOUR) بتوقيت UTC، أما النافذة فبالتوقيت المحلي. على خادم ساعته بتوقيت UTC يتداخلان افتراضيًا، فينتظر التحديث حتى ينتهي النسخ الاحتياطي.

إيقاف الفحص

على خادم بلا اتصال أو خادم خاص، ألغِ اختيار Check for new releases واحفظ. عندها لا يتصل صباربيز بمصدر الإصدارات من تلقاء نفسه، ويتوقف التحديث التلقائي معه. يبقى الزر Check now يشغّل فحصًا واحدًا حين تضغطه.

يقرأ الفحص المستودع الرسمي عبر HTTPS، لا origin الخاص بك. لقراءة نسخة أخرى (مرآة يصل إليها خادمك)، اضبط SBARBASE_RELEASE_SOURCE على عنوان Git الخاص بها أو مسارها في بيئة الخدمة، ومع Docker أعطه للمشغّل مع كل up (sudo SBARBASE_RELEASE_SOURCE=... deploy/compose.sh up)، فيمرره إلى الحاوية. يبقى التحقق من التواقيع بالمفاتيح التي تسردها نسخة مستودعك.

التراجع

قبل الاعتماد تكون العودة تلقائية كما وُصفت أعلاه، ولا شيء عليك أن تضغطه.

بعد الاعتماد تعرض صفحة التحديثات الزر Roll back تحت آخر تحديث. يعيد نسخة المستودع إلى الإصدار الذي كنت تشغّله قبله ويعيد التشغيل، مرورًا بالتصريف والحارس والحجز وفحوص السلامة نفسها. وله حدود:

يخبرك python3 lab/upgrade.py rollback --check هل سيمضي التراجع، ولماذا لا، دون أن يغيّر شيئًا.

ما الذي يبقى وما الذي قد يضيع

من سطر الأوامر

مع Docker ضع docker compose exec sbarbase قبل كل أمر python3. ومع systemd شغّل كل أمر بحساب الخدمة وبقيم HOME وPATH نفسها التي في ملف الوحدة (sudo -u sbarbase env HOME=/home/sbarbase PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/sbarbase/.bun/bin /usr/bin/python3 lab/upgrade.py ...): فإن لم يجد bun في المسار، يتوقف start بعد النسخ الاحتياطي عند تثبيت اعتماديات الإصدار الجديد، وإعادة التشغيل عندها تثبّته على الإصدار السابق.

المهمة الأمر بالأمر sbarbase
أحدث إصدار موقّع، وصنفه، وما يغيّره python3 lab/upgrade.py channel (--json للنتيجة كاملة، و--preview لتضمين الإصدارات التجريبية) sbarbase upgrade channel (--json؛ ولا يوجد --preview)
تثبيت إصدار موقّع python3 lab/upgrade.py start --release vX.Y.Z sbarbase upgrade start --release vX.Y.Z
تثبيت إصدار يحتاج تأكيدك أضف --allow-class attended بالطريقة نفسها
تثبيت إصدار يحتاج إعادة بناء أضف --allow-class rebuild، ثم أعد البناء كما في الأعلى بالطريقة نفسها
الانتقال إلى أي إيداع أو وسم، دون تحقق من التوقيع ولا تصنيف python3 lab/upgrade.py start --to <tag or commit> (الافتراضي origin/main) sbarbase upgrade start --to <tag or commit>
معرفة ما سيغيّره --to، دون تغيير أي شيء python3 lab/upgrade.py check --to <tag or commit> sbarbase upgrade check --to <tag or commit>
إعادة التشغيل عليه sudo deploy/compose.sh up (أو sudo systemctl restart sbarbase)
معرفة النتيجة python3 lab/upgrade.py status sbarbase upgrade status
هل سيمضي التراجع؟ python3 lab/upgrade.py rollback --check sbarbase upgrade rollback --check
العودة إلى الإصدار السابق python3 lab/upgrade.py rollback، ثم إعادة التشغيل بالطريقة نفسها sbarbase upgrade rollback

الأمر start --to اختيار صريح من المشغّل: لا يتحقق من توقيع ولا يصنّف. لكنه يرفض تغيير صورة PostgreSQL، ويأخذ نسخة احتياطية أولًا، وله العودة نفسها.

البدء من سطر الأوامر لا يصرّف المشرف العامل، فيبقى يشغّل الإصدار السابق من نسخة المستودع المنقولة حتى إعادة التشغيل. وحين يكون صباربيز عاملًا يقول start ذلك ويطلب منك إعادة التشغيل فورًا، قبل أن يبدأ المشرف القديم أي شيء آخر. ويعرض status المرحلة، وبعد عودة تلقائية سطر why back بسببها.

التراجع من سطر الأوامر بينما ينتظر إصدار جديد فحوص السلامة يستعيد لقطة التحكم، فيحتاج إيقاف صباربيز أولًا (sudo systemctl stop sbarbase أو docker compose stop)، ويخبرك بذلك إن لم يكن متوقفًا.

إذا أردت الرجوع إلى تثبيتات أقدم بعد عودة تلقائية، وقال rollback إنه لا توجد ترقية للتراجع عنها، فرقِّ إلى الإصدار الأقدم نفسه: python3 lab/upgrade.py start --to <earlier commit>، ثم أعد التشغيل. يأخذ نسخة احتياطية أولًا مثل أي ترقية.

يمرّر الأمر sbarbase كل واحد من هذه إلى lab/upgrade.py في وسائط منفصلة (الأمر sbarbase). ويرفض قبل أن يشغّل أي شيء إذا لم يكن الإصدار وسمًا بسيطًا على شكل vX.Y.Z، أو إذا سمّى --allow-class شيئًا غير rebuild أو attended، أو إذا لم يناسب خيارٌ الأمرَ الفرعي؛ وأعطِ --allow-class مرتين لإصدار يحتاج الاثنين.

الانتقال إلى أول إصدار فيه قناة التحديث

الإصدار الذي لا يحوي قناة التحديث (لا يعرف lab/upgrade.py فيه الأمر channel) لا يستطيع رؤية إصدار ولا التحقق منه. والانتقال إلى أول إصدار فيه القناة يغيّر Dockerfile وcompose.yaml وسكربت تشغيل الحاوية وdeploy/sbarbase.service، فهو يحتاج إعادة بناء ولا يمكن بنقرة:

  1. نفّذ python3 lab/upgrade.py start --to <tag or commit> (هذا المسار لا يتحقق من التوقيع).
  2. مع Docker نفّذ docker compose up -d --build. من دون إعادة البناء لا يكون في الحاوية ssh-keygen ولا حارس في سكربت تشغيلها، فيُرفض كل إصدار بالرسالة "ssh-keygen is not installed, so no release signature can be checked; rebuild the container image".
  3. مع systemd نفّذ sudo /usr/bin/python3 lab/install_server.py supervise --apply بخيارات الخدمة، ثم sudo systemctl restart sbarbase. وإلى أن تُثبَّت الوحدة الجديدة يعمل الحارس من داخل lab/dev.py لا قبل الفحص المسبق.

أول تشغيل للإصدار الجديد يأخذ لقطة التحكم ويحجز الحركة ويشغّل فحوص السلامة، حتى لو بدأ الانتقالَ الأمرُ الأقدم. وإن فشل، تصل العودة إلى الإصدار الأقدم، وليس فيه حارس، ويعتمد تشغيله دون فحوص سلامة ولا حجز، ولا يستطيع إيصال إشعارات update.*.

ما الذي شُغّل

للمطورين: نشر إصدار موقّع

الإصدارات وسوم vX.Y.Z مشروحة (annotated) وموقّعة بمفتاح SSH، ويحمل كل منها release.json (الإصدار، وأقدم إصدار يمكن الترقية منه، وملاحظات بالإنجليزية والعربية، والترحيلات المعلنة).

  1. جهّز الملف وإصدار package.json:

    /usr/bin/python3 lab/release.py prepare --version X.Y.Z --notes-en TEXT --notes-ar TEXT

    أضف --minimum-from X.Y.Z حين يجب أن تمر التثبيتات الأقدم بإصدار سابق أولًا، و--migration TEXT لكل ترحيل بيانات (وهذا يجعل الإصدار يحتاج ترحيلًا). لا ينشئ الأمر وسمًا ولا يدفع شيئًا؛ يطبع الأوامر الدقيقة التي تودع التغيير، وتوقّع بالأمر git tag -s مع gpg.format=ssh، وتتحقق مقابل deploy/release-signers، وتدفع الوسم إلى المستودع الرسمي.

  2. لا تثق التثبيتات إلا بالمفاتيح المسرودة في deploy/release-signers في الإصدار الذي تشغّله. أضف النصف العام من مفتاح التوقيع هناك، سطرًا لكل مفتاح بصيغة allowed signers الخاصة بـ ssh-keygen، في إيداع مستقل. التثبيت الذي ما زال على إصدار بلا ذلك المفتاح يرفض كل إصدار على أنه غير موقّع، ويصل إلى الإصدار الذي فيه المفتاح عبر start --to.

إلى أن يُسرد مفتاح، يُرفض كل إصدار على أنه غير موقّع. هذا هو الوضع الافتراضي المقصود، لا عطل. وتعديل ذلك الملف محليًا تعديل محلي يوقف الترقيات، فيجب أن يأتي المفتاح في إيداع.

للمطورين: تغيير إصدار أصلي مثبّت

يشغّل صباربيز صور Supabase الأصلية بإصدارات مثبّتة. تغيير تثبيت واحد يكون عن قصد، بمراجعة مكتوبة وبعد اجتياز كل الاختبارات. مرّت سبعة إصدارات أصلية بهذا الإجراء في أكتوبر 2026 (Auth وPostgREST وStorage وRealtime وEdge Runtime وStudio وpostgres-meta)، لكل منها مراجعة مكتوبة؛ وما زال فحص قبولها الحي معلقًا، مع أن صور Auth وREST وStorage الجديدة تعمل على الخادم الحقيقي منذ 2026-10-16. القواعد الملزمة موجودة في سياسة تحديث الأصل.

ما المثبّت

كل صورة أصلية مثبّتة بالوسم والبصمة في ستة ملفات قفل: lab/distro-image.lock.json (صورة PostgreSQL من Supabase)، وlab/images.lock.json (Auth وPostgREST وPostgreSQL القياسي الذي تستخدمه البيئات الثابتة)، وlab/storage-image.lock.json (Storage)، وlab/realtime-image.lock.json (Realtime)، وlab/functions-image.lock.json (Edge Runtime)، وlab/studio-image.lock.json (Studio وpostgres-meta). اطبع المجموعة كاملة وتحقق أن لا شيء منها عائم:

/usr/bin/python3 lab/pin_update.py show
/usr/bin/python3 lab/pin_update.py verify

تغيير مكوّن واحد

  1. اقرأ ملاحظات الإصدار الأصلية للنسخة المرشحة.

  2. حضّر التغيير. يكتب هذا الأمر مدخل مراجعة مؤرخًا تحت docs/upstream/، ويسجّل البصمة السابقة تثبيتًا للتراجع، ويغيّر مكوّنًا واحدًا بالضبط:

    /usr/bin/python3 lab/pin_update.py stage --file lab/images.lock.json --component rest \
        --tag public.ecr.aws/supabase/postgrest:vX.Y --digest sha256:<64 hex> --note "why"
  3. أكمل مدخل المراجعة: ما الذي تغيّر، وأي أجزاء من صباربيز يمسّها، والتغييرات الكاسرة والترحيلات، وقرار الاعتماد أو التأجيل.

  4. اجتز بوابة الاعتماد: مجموعتا اختبارات Python وBun كاملتين، والفحوص الحية للتكامل مع النسخة الجديدة. لا اعتماد بلا اختبارات.

  5. اعتمد مكوّنًا واحدًا في كل مرة، فيدل أي فشل على تغيير إصدار واحد.

التراجع عن تثبيت

أعد التثبيت السابق المسجّل في مدخل المراجعة وانشره في إصدار جديد، فتصل إليه التثبيتات عبر lab/upgrade.py. إذا رحّلت النسخة الأحدث البيانات، فقد يحتاج التراجع إلى ترحيل بيانات خاص به؛ سجّله في المدخل نفسه.

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

لا يعيد التشغيل إنشاء حاوية قاعدة بيانات أبدًا كترقية ضمنية. حاوية القاعدة المُدارة مثبّتة بهويتها الدقيقة، واستبدالها (لصورة جديدة مثلًا) يمر عبر lab/migrate-generation.py، وهو إجراء بسجل عملية ومختبر ضد الانهيار على بيئات ثابتة مؤقتة. جرى أول تشغيل له تحت إشراف على بيانات محفوظة في 2026-09-25، وبقيت أعداد الصفوف كلها كما هي؛ انظر الحالة.

الانتقال إلى صورة PostgreSQL أخرى

يرفض start أي إصدار يختلف فيه lab/distro-image.lock.json. والوصول إليه يكون بنقل واحد تحت إشراف، على أمرين، وفقط إذا كانت تثبيتات الصور في الإصدار الهدف لا تختلف عن الإصدار المثبّت إلا في صورة PostgreSQL (انقل الصور الأخرى أولًا بالأمر start). والمقارنة بين تثبيتات الصور وحدها: أي تغيير آخر في ذلك الإصدار يبدأ معه دون فحوص السلامة ودون العودة التلقائية اللتين يمنحهما start للترقية، ولا شيء في هذا النقل يستعيد لقطة حالة التحكم التي تأخذها الخطوة 1، وقد تحذفها الترقيات اللاحقة. لذلك انتقل إلى إصدار لا يغيّر إلا القليل غير صورة PostgreSQL:

  1. أوقف الكتابة في كل البيئات (الإيقاف المؤقت للكتابة)، ثم شغّل وصباربيز يعمل: /usr/bin/python3 lab/upgrade.py database --to REF. يرفض ما لم يكن الإيقاف قائمًا في كل بيئة منشورة، ثم يسحب صور الإصدار الهدف، ويأخذ نسخة احتياطية من كل البيئات ومن قاعدة الإدارة، ويلتقط حالة التحكم، ويسجّل النقل على أنه مُجهَّز. لم يتحرك شيء بعد: database --abandon يلغيه.
  2. أوقف صباربيز، ثم شغّل: /usr/bin/python3 lab/upgrade.py database --continue. يرفض ما دام صباربيز يعمل أو كانت حاوية قاعدة البيانات القديمة تعمل، ويُبقي صباربيز متوقفًا حتى ينتهي. ينقل نسخة المستودع إلى الإصدار الهدف (النقل المتحقَّق منه نفسه الذي يستعمله طريق العودة)، ثم يستبدل حاوية قاعدة البيانات بصورة القفل الجديد عبر lab/migrate-generation.py --image-from-lock، مع الإبقاء على اسمها ومالكها ووحدة تخزين بياناتها. وحين يطلب منك ذلك، شغّل صباربيز، ونفّذ الفحوص التي تلي التغيير، ثم استأنف الكتابة. وفي Docker، والحاوية متوقفة، شغّل هذه الخطوة بالصيغة docker compose run --rm --entrypoint /usr/bin/python3 sbarbase lab/upgrade.py database --continue بدل docker compose exec؛ ولم تُجرَّب هذه الصيغة بعد.

إذا توقف أي من الأمرين في منتصفه، فشغّله مرة أخرى (عدا الحالات المذكورة في آخر هذه الفقرة): يعيد --continue نقل نسخة المستودع إن لم يكتمل، ويكمل تغيير الحاوية الذي انقطع بالصورة التي سجّلها (ومنه تغيير توقف بعد حذف الحاوية القديمة وقبل أن تجهز الجديدة: يتولى التشغيل التالي الحاوية الجديدة ويعيد تشغيلها إن كانت متوقفة)، ولا يعيد التغيير إن كان قد اكتمل. وبعد أن يبدأ --continue يرفض صباربيز أن يبدأ حتى يكتمل النقل (أما النقل المُجهَّز فلا يمسّ الإصدار العامل)، ويرفض start وrollback ونقل ثانٍ ما دام نقل مسجّلًا. والأمر database وحده يعرض أين وصل. وهناك انقطاع واحد لا يغطيه هذا: يكتب Git ملفات نسخة المستودع في أماكنها مباشرة، فقد يترك القتل أو إعادة تشغيل المضيف في أثناء نقل --continue لنسخة المستودع بعضها مكتوبًا إلى نصفه. عندها قد تفشل الأوامر قبل أن تقرأ سجل النقل، وقد لا يرفض صباربيز البدء أيضًا، لأن ذلك الفحص يعمل من الملفات نفسها. فإن فشل --continue بخطأ من Python بعد انقطاع كهذا، فأبقِ صباربيز متوقفًا، واقرأ الإيداع الهدف (to) من .lab/upgrades/database-move.json، ونفّذ git checkout -f <to> بحساب الخدمة، ثم شغّل --continue مرة أخرى. ولم تُجرَّب هذه الطريقة بعد. والأمر نفسه git checkout -f <to> هو ما يطلبه --continue حين يجد نسخة المستودع قد تغيّرت بعد أن نقلها.

طريق العودة إلى الصورة السابقة هو النقل نفسه نحو الإصدار السابق، ولا يمكن إلا نحو إصدار فيه هذا النقل (ترفض الخطوة 1 إصدارًا ليس في lab/migrate-generation.py الخاص به الخيار --image-from-lock). وهو آمن فقط إذا لم تغيّر الصورة الأحدث إصدارات الإضافات ولم تُعِد كتابة فهارس النظام على وحدة البيانات؛ وإلا فطريق العودة استعادة من النسخ الاحتياطية المأخوذة في الخطوة 1. وبعد أن تنتقل نسخة المستودع تُحفظ هذه النسخ من الحذف التلقائي كما تُحفظ نسخ الترقية (نسخ آخر 3 ترقيات نقلت نسخة المستودع)؛ وتسمح الترقيات اللاحقة بحذفها في النهاية. ولأن صباربيز يبقى يعمل بين الخطوتين، يعيد --continue الفحص قبل أن يسجّل بدء النقل، ويرفض دون أن يغيّر شيئًا في حالتين: ما دام سجل عملية ينتظر التسوية (شغّل صباربيز ليسوّي السجل، ثم أوقفه مرة أخرى وتابع)، وإذا نُشرت بيئة أو استؤنفت الكتابة في بيئة منذ النسخ الاحتياطية في الخطوة 1، فتلك النسخ تفوتها كتابات عندها (ألغِ النقل بالأمر --abandon وأعد الخطوة 1). وهذا الفحص يخص البيئات وحدها: تبقى لوحة الإدارة تعمل في أثناء ذلك، وما يغيّره المشغّلون عندها (الحسابات، وعوامل تسجيل الدخول، والجلسات، والدعوات) لا تحمله نسخة قاعدة الإدارة المأخوذة في الخطوة 1، فلا تغيّر شيئًا منها حتى ينتهي النقل. ويقبل كذلك صور الإصدار الهدف من جديد، فينزّل ما حُذف منها. وبعد أن يسجّل --continue بدء النقل، لا طريق إلى الخلف: يرفض --abandon من تلك اللحظة. وبعد ذلك لا مخرج هنا للنقل الذي يبقى عالقًا: تغيير جيل يظل يرفض، أو صورة جديدة لا تجهز أبدًا على وحدة البيانات بعد حذف الحاوية القديمة، يجعل كل تشغيل جديد يفشل، بينما يرفض --abandon والنقل إلى الخلف وrollback وlab/backup.py start-database، ولا يبدأ صباربيز. وإذا لم تجهز الصورة الجديدة أبدًا بعد حذف الحاوية القديمة، فالمخرج أن تعيد بناء الكتلة من النسخ الاحتياطية المأخوذة في الخطوة 1 ثم تعتمدها، وصباربيز متوقف: /usr/bin/python3 lab/cluster_rebuild.py <دفعة النسخ من الخطوة 1> --image-at <الإصدار الذي بدأ منه النقل>، ثم /usr/bin/python3 lab/cluster_takeover.py. يعيد أمر الاعتماد نسخة المستودع إلى ذلك الإصدار، بشرط أن يكون فيه هذا المخرج أصلًا (بروتوكول الاستبدال نفسه في lab/hba_migration.py الخاص به؛ فالنقل الذي بدأ من إصدار أقدم لا يستطيع استخدامه)، ويؤرشف تغيير الجيل العالق والنقل على أنهما استُبدلا (ولا يُحذف شيء قبل أرشفته)، ويحذف الحاوية البديلة التي لم تجهز، ويشغّل قاعدة البيانات على وحدة التخزين المعاد بناؤها (استعادة الكتلة كاملة). وإذا انقطع أمر الاعتماد، يدلّك بدء التشغيل وdatabase --continue عليه من جديد؛ فشغّله مرة أخرى. وإذا تعذّر عليه المتابعة قبل أن يبدأ تغيير الجيل (كأن تتبيّن أن وحدة التخزين المعاد بناؤها غير صالحة)، فالأمر lab/cluster_takeover.py --release يعيد النقل إلى database --continue. تبقى وحدة البيانات القديمة، مع أن الحاوية الفاشلة عملت عليها، وتضيع الكتابات التي جاءت بعد النسخ الاحتياطية في الخطوة 1، ولهذا تبقى الكتابة متوقفة. أما تغيير الجيل الذي يظل يرفض والحاوية القديمة ما زالت موجودة فلا مخرج له بعد، ولا مخرج كذلك لاعتماد لا تجهز فيه قاعدة البيانات الجديدة نفسها على وحدة التخزين المعاد بناؤها. للأمرين اختبارات وحدات فقط، لذلك جرّب النقل على نسخة من التثبيت أولًا. لهذا النقل اختبارات وحدات فقط: لم يُشغَّل على قاعدة بيانات حقيقية، ولا في تجربة كاملة، ولا على خادم.

الحدود

عدّل على GitHub