# Supabase Studio لكل بيئة

تستطيع كل بيئة فتح Supabase Studio الأصلي: محرر الجداول، ومحرر SQL، والمستخدمين، وحاويات Storage، وكلها على تلك البيئة وحدها. يعمل Studio فقط حين يحتاجه أحد، ومن خلف تسجيل الدخول إلى الواجهة، فلا يكلّف شيئًا وهو مغلق.

## افتحه

1. سجّل الدخول وأنشئ مشروعاً. ينشئ النظام **production** ويفتح تبويب انتظار نتيجة ضغطك على Create.
2. يجري تجهيز البيئة ثم تشغيل Studio وفتحه تلقائياً في ذلك التبويب. تبقى صفحة النظام مفتوحة. يلغي **Stay in console** أو إغلاق تبويب الانتظار فتح اللوحة تلقائياً.
3. للمشروع الموجود اضغط **Open Supabase dashboard** مرة واحدة. يشغّل النظام Studio عند الحاجة ويفتح صفحته الأصلية كاملة على عنوان Studio الخاص بالبيئة، باستخدام جلستك الحالية. لا يضاف شريط خاص أو iframe داخل Studio.
4. تفتح **Connection details** إعدادات البيئة. يعرض **Database connection** إيقاف اللوحة وإعدادات الاتصال المباشر. يضبط **Sign-in settings** خدمة Auth المستضافة ذاتياً، بينما تبقى إدارة المستخدمين في Studio الأصلي.

إذا حظر المتصفح تبويب الانتظار، يجهز النظام اللوحة ويتيح زر الفتح المعتاد. عند فشل التشغيل يغلق التبويب المؤقت وتبقى رسالة قابلة لإعادة المحاولة في النظام. يعالج **Retry dashboard status** فشل جلب الحالة. عند فشل تجهيز البيئة استخدم **Retry** في صفها. لا تنشئ إعادة المحاولة مشروعاً آخر. اسم production لا يعني أن التثبيت اجتاز قبول الإنتاج.

وحدهم مالكو العميل ومديروه يستطيعون تشغيل Studio أو فتحه أو إيقافه. المشاهدون لا يرونه.

## فتحه من أي مكان

عنوان Studio الافتراضي هو `<id>.studio.localhost`، وكل متصفح يرسل هذا الاسم إلى جهازه نفسه. لذلك يعمل على حاسوبك، أو على خادم عبر نفق SSH (`ssh -N -L 8790:127.0.0.1:8790 you@your-server` ثم افتح `http://localhost:8790`)، كما يشرح [دليل Docker](https://base.sbarah.com/docs/guides/docker/). وإن فتحت لوحة الإدارة من عنوانها العام، فإنها تشرح هذا بدل أن تفتح تبويبًا لا يستطيع المتصفح الوصول إليه.

ولتفتح Studio من العنوان العام، جهّز نطاقًا واحدًا مرة واحدة. بعدها يحصل كل مشروع وكل بيئة تنشئها على عنوان Studio خاص تحته، دون أي عمل في DNS أو الشهادات لكل مشروع.

1. **اختر نطاقًا لا يستضيف موقعًا آخر.** السجل الشامل في الخطوة التالية يجيب عن كل اسم تحت النطاق ليس له سجل خاص، فاستعمل نطاقًا مخصصًا لهذا، لا نطاقًا يخدم مواقع أخرى. تبقى السجلات المسمّاة عندك تعمل كما هي في الحالتين، وأي اسم آخر يصل إلى صباربيز فيرفضه.
2. **اعرف ما عليك تجهيزه.** على الخادم شغّل `/usr/bin/python3 lab/studio_address.py plan example.com`. يطبع النمط (`studio-{id}.example.com`) وما تجهّزه أنت: سجل DNS شاملًا (`*.example.com`، عبر وكيل Cloudflare إن كنت تستعمله) وشهادة تغطي `*.example.com`. مع Cloudflare أنشئ شهادة أصل (Origin CA) لـ `*.example.com` و`example.com`، وولّد المفتاح على الخادم والصق الطلب فقط في Cloudflare، ثم ركّب الشهادة على وكيل TLS.
3. **طبّقه.** شغّل `sudo /usr/bin/python3 lab/studio_address.py apply example.com`. يكتب `SBARBASE_STUDIO_HOST` ملفًا إضافيًا لوحدة systemd للوحة الإدارة (`sbarbase`) ولوكيل TLS (`sbarbase-proxy`، أو الوحدة التي يسمّيها `--proxy-service`)، وكلاهما يقرؤه، ثم يعيد تشغيلهما. ويرفض إن كان الوكيل يمرّر أصلًا `--studio-host` مختلفًا.
4. **افحصه.** شغّل `/usr/bin/python3 lab/studio_address.py verify example.com`. يخبرك أي خطوة ناقصة: DNS لا يحلّ الاسم، أو شهادة لا تغطيه، أو خادم يرفض العنوان.

بعد ذلك تفتح كل بيئة Studio على عنوانها الخاص، مثل `studio-<24 hex>.example.com`. ولا يمرّر الوكيل إلا الأسماء المطابقة للنمط، ويرفض كل اسم آخر بـ 421، فلا يُظهر السجل الشامل لوحة الإدارة تحت اسم آخر أبدًا. وللتراجع، احذف الملفين الإضافيين المسميين `studio-host.conf` (تحت `sbarbase.service.d` و`sbarbase-proxy.service.d`)، وأعد تشغيل الخدمتين، ثم احذف سجل DNS.

يحسن أن تعرف:

- إن كان السجل الشامل سيقع على نطاق يخدم مواقع أخرى، فسمِّ سجل كل بيئة بنفسك (سجل `studio-<24 hex>` لكل بيئة) أو استعمل نطاقًا خاصًا به.
- في خطة Cloudflare المجانية تغطي شهادة الواجهة مستوى واحدًا تحت النطاق، فأبقِ النمط `studio-{id}.example.com` لا `{id}.studio.example.com`.
- مع Docker ابدأ بالأمر `sudo SBARBASE_STUDIO_HOST=studio-{id}.example.com deploy/compose.sh up` (يمرّره المشغّل إلى لوحة الإدارة، ولا يقرأ أي ملف `.env`، فأعطه القيمة مع كل `up` لاحق أيضًا)، وشغّل وكيل TLS بالمتغير نفسه أو بالخيار. أما `apply` فيكتب ملفات systemd الإضافية، فهو لتثبيت systemd.
- تقرأ لوحة الإدارة والوكيل قيمة `SBARBASE_STUDIO_HOST` نفسها. ويبقى الخيار `--studio-host` للوكيل يعمل ويغلب إن وُجد الاثنان.
- يُوصل إلى Studio على المنفذ 443، ولا يُدعم العنوان العام على منفذ آخر.
- إن كانت أمامه شبكة توزيع تعمل وكيلًا، مثل Cloudflare، فإنها تنهي TLS وترى حركة Studio كما ترى حركة لوحة الإدارة.
- يُرفض الطلب الذي يبدؤه موقع آخر، إلا زيارة صفحة عادية، فتصل إلى الصفحة الرئيسية للمشروع دون ما في رابطها من معاملات، فلا يستطيع رابط أن يفتح محرر SQL وفيه استعلام جاهز.

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


## ماذا يستطيع

| الصفحة | تعمل على |
|---|---|
| محرر الجداول ومحرر SQL | قاعدة بيانات البيئة نفسها، بصلاحيات حساب المطوّر الخاص بها |
| مستخدمو المصادقة | خدمة Auth الخاصة بالبيئة |
| حاويات Storage وملفاتها | ملفات البيئة نفسها |
| استعلامات SQL المحفوظة | مجلد داخل جلسة Studio هذه، يُحذف عند إغلاقها، فاحفظ ما يدوم في ملفات الترحيل |
| قائمة Edge Functions وشيفرتها | نسخة من الشيفرة المنشورة لكل دالة، تُؤخذ عند فتح Studio. النشر من لوحة الإدارة أو من سطر الأوامر |

يعمل Studio بحساب المطوّر الخاص بالبيئة، كما يعمل Studio على Supabase بحساب `postgres` الخاص بالمشروع: يرى كل الصفوف، ومنها الصفوف التي يخفيها أمان الصفوف، ويستطيع تعديل الجداول والسياسات والمخططات التي أنشأتها ملفات الترحيل. وما ينشئه يملكه الحساب نفسه، فتستطيع ملفات الترحيل اللاحقة تعديله. لا يستطيع إنشاء أدوار، ولا العمل بصلاحيات المستخدم الأعلى، ولا الوصول إلى قاعدة بيانات بيئة أخرى.

صفحتا السجلات وRealtime في Studio تنتظران خدمات منصة Supabase نفسها، فتبقيان فارغتين أو مخفيتين. استخدم لوحة الإدارة لهما: [السجلات والمقاييس](https://base.sbarah.com/docs/guides/logs-and-metrics/) و[Realtime](https://base.sbarah.com/docs/guides/realtime/). على خادم عليه جدار ناري، تحتاج صفحتا Storage والمستخدمين في Studio إلى المسار المشروح في [نشر الخادم](https://base.sbarah.com/en/docs/guides/server-deployment/#firewall-two-routes-from-containers-to-the-host).

## كيف يبقى آمنًا

- يؤدي تغيير سياسة MFA أو تسجيل الخروج إلى إبطال جلسات اللوحة.
- لكل بيئة عنوان Studio خاص بها، وجلسة بيئة لا تفتح بيئة أخرى أبدًا.
- تعطي الواجهة تذكرة صالحة لدقيقة واحدة ولا تعمل إلا مرة واحدة، وتتحول إلى ملف تعريف جلسة لذلك العنوان وحده، من نوع HttpOnly، ومع `Secure` والبادئة `__Host-` على العنوان العام، فلا يستطيع موقع آخر من النطاق نفسه ضبطه ولا قراءته. دونه لا يصل شيء إلى Studio.
- ما دمت تستخدم صفحة Studio تتجدد الجلسة وحدها، فلا تُغلق وأنت تعمل. وتنتهي بعد ثماني ساعات دون أي طلب، أو بعد سبعة أيام من فتحها من لوحة الإدارة، أيهما أسبق، وعندها افتح Studio من لوحة الإدارة مرة أخرى. وتنتهي فورًا عند تسجيل الخروج من لوحة الإدارة، أو تغيير كلمة المرور، أو فقدان دور المالك أو المدير.
- الطلب الذي يغيّر شيئًا يجب أن يأتي من صفحة Studio نفسها (ترويسة `Origin`)، وتُرفض الصفحة الآتية من موقع آخر في النطاق نفسه.
- فقدان دور المالك أو المدير يغلق الوصول من الطلب التالي.
- يدخل Studio إلى قاعدة البيانات بحساب خاص به، لا يوجد إلا أثناء عمل Studio، ويأخذ كلمة مرور جديدة مع كل تشغيل. وعند الاتصال يعمل بصفة حساب المطوّر الخاص بالبيئة كما ورد أعلاه، أما حساب Studio نفسه فلا يملك صلاحيات المستخدم الأعلى ولا يملك أي شيء.
- لا يطلب Studio ولا مساعده (postgres-meta) تسجيل دخول، لذلك لا يستمع أيّ منهما حيث تعمل البيئات الأخرى. لكل جلسة شبكة خاصة لا يصل إليها إلا الخادم، فلا تستطيع Edge Functions ولا قواعد بيانات البيئات الأخرى الاتصال بها.
- إيقاف Studio، أو إعادة تشغيل صباربيز، يغلق ذلك الحساب ويزيل الحاويتين وشبكة الجلسة. وبعد إعادة التشغيل، أو بعد تأكيد الترقية، يعود Studio الذي استُخدم في آخر ثماني ساعات للعمل وحده بحساب دخول جديد، وتعرض الصفحة المفتوحة "Studio is starting" بضع ثوانٍ ثم تكمل، دون أن تفتحه من جديد.

## الذاكرة

يستهلك Studio أثناء عمله حتى 768 MiB (‏Studio ‏512 MiB وpostgres-meta ‏256 MiB). إذا لم تكفِ ذاكرة الخادم يفشل التشغيل برسالة واضحة ولا يُمس أي شيء آخر. أوقف Studio عندما تنتهي.

يشغّل CI ‏Studio على جهاز نظيف مع كل تغيير، ويستخدم عبره قائمة الجداول وSQL والمستخدمين والحاويات، ويتحقق من كل حالة رفض مذكورة أعلاه، ثم يوقفه ([الدليل](https://github.com/M7MMAD-OMAR/sbarbase/blob/main/docs/evidence/docker-studio-checks.json)).
