العودة إلى التحديثات
UpdatedAug 8, 2026

foxcage — Updated!

شغّل Firefox في حاوية Podman rootless مع إسقاط الصلاحيات (capabilities)، وعزل الشبكة، وتخزين مؤقت (ephemeral) لاحتواء محاولات الهروب من بيئة العزل (sandbox) ومنع اختراق المضيف.

مشاركة

أيقونة foxcage foxcage

شغّل Firefox داخل حاوية Podman بدون جذر لعزل أمني. يعمل متصفحك مع عدم وجود أي صلاحيات Linux تقريبًا، في مساحة مستخدم وشبكة خاصة به، معزولًا عن المضيف — مع احتفاظه الكامل بتسريع GPU والصوت ودعم DRM.

لماذا foxcage؟

يحتوي Firefox أصلًا على صندوق رمل (sandbox) متعدد العمليات يعزل عارضات محتوى الويب باستخدام مساحات أسماء Linux و seccomp-bpf. بالنسبة لمعظم التهديدات، يكون هذا فعّالًا. يضيف foxcage جدارًا ثانيًا: إذا استغل مهاجم ثغرة تهرب من صندوق رمل Firefox (وهذا يحدث — توجد CVEs لهذا)، فسيقع داخل حاوية مقفلة بإحكام بدلًا من جلسة المستخدم الكاملة.

ما الذي يحمي منه foxcage

  • الوصول إلى الملفات بعد الاستغلال. إن الهروب من صندوق الرمل على Firefox غير المعزول يمنح الوصول إلى كل ما يمكن لمستخدمك قراءته: ~/.ssh، و~/.gnupg، وملفات تعريف المتصفح للمتصفحات الأخرى، وقواعد بيانات مدير كلمات المرور، والمستندات، والكود المصدري. في foxcage، لا يرى المهاجم سوى ما قمت بتثبيته (mount) صراحةً.
  • بقايا التتبع على القرص. لا يترك القفص المؤقت @tmp أي أثر على القرص بعد إغلاق النافذة — بما في ذلك الإضافات، وحالة HSTS، وذاكرة تخزين جلسات TLS المؤقتة، وذاكرة DNS المؤقتة التي ما يزال التصفح الخاص في Firefox يحتفظ بها. تعمل أقفاص @tmp المتعددة في وقت واحد دون أن تتداخل مع بعضها.
  • الاستمرارية. على Firefox غير المعزول، يمكن للبرمجيات الخبيثة الكتابة إلى ~/.config/autostart أو ~/.bashrc أو cron أو أي مكان آخر للبقاء بعد إعادة التشغيل. حاوية foxcage المؤقتة (--rm) تعني أن لا شيء يستمر إلا إذا قمت بتثبيته عبر bind-mount بنفسك.
  • الحركة الجانبية عبر الشبكة. افتراضيًا، لا يمكن للحاوية استكشاف الخدمات على localhost. أما على Firefox غير المعزول، فإن الهروب من صندوق الرمل يتمتع بوصول شبكي كامل. (استخدم [network] mode = "host" إذا كانت الحاوية بحاجة إلى الوصول إلى localhost، مثلًا للتطوير المحلي — ولكن انظر إلى التحذير تحت قسم «الشبكات»: وضع host يكشف أيضًا مقابس Unix المجردة الخاصة بالمضيف.)
  • تصعيد الامتيازات. تُسقط الحاوية جميع قدرات Linux باستثناء CAP_SYS_CHROOT وتمنع اكتساب امتيازات جديدة. تُقطع مسارات تصعيد الامتيازات مثل ثنائيات setuid واستغلالات النواة عبر استدعاءات النظام (syscalls) الغامضة وما شابه.

ما لا يحمي منه foxcage

  • الهجمات على مستوى المتصفح. التصيد، والإضافات الخبيثة، وأي شيء يعمل ضمن وظائف Firefox العادية لا يتأثر — foxcage يعزل الحاوية عن المضيف، وليس المستخدم عن المتصفح.
  • الأدلة المثبتة عبر bind-mount. أي شيء تقوم بتثبيته (profile، downloads_dir، تثبيتات bind إضافية) يكون متاحًا بالكامل لمتصفح مخترَق. إذا قمت بتثبيت دليل ملفات تعريف من المضيف، يمكن للمهاجم العبث به تمامًا كما في Firefox غير المعزول.
  • التقاط الصوت عبر PulseAudio. يتم تثبيت مقبس PulseAudio داخل الحاوية عبر bind-mount. على الرغم من تثبيته للقراءة فقط على مستوى نظام الملفات، فإن مقابس Unix domain ثنائية الاتجاه — لا يزال بإمكان عملية مخترَقة إرسال طلبات التسجيل عبر المقبس. قد يتمكن هروب من صندوق رمل المتصفح من تسجيل الصوت من ميكروفون المضيف.
  • استغلال مُؤلِّف نوافذ Wayland. يتم تمرير مقبس Wayland. يعزل مُؤلِّفُو نوافذ Wayland العملاء عن بعضهم البعض تصميميًا، لكن أي ثغرة في المُؤلِّف نفسه ستكون قابلة للوصول.

الإعدادات الأمنية

تعمل الحاوية مع:

  • إسقاط جميع قدرات Linux (إضافة CAP_SYS_CHROOT فقط مرة أخرى لصندوق رمل محتوى Firefox؛ وإضافة CAP_SETUID/CAP_SETGID مؤقتًا عند تكوين init.root)
  • no-new-privileges لمنع تصعيد الامتيازات
  • مساحة اسم مستخدم بدون جذر (--userns keep-id)
  • /dev/shm خاصة (غير مشتركة مع المضيف) — حجم قابل للضبط عبر shm_size
  • شبكة معزولة عبر pasta مع حظر حلقة الاسترجاع المحلية للمضيف افتراضيًا
  • يستخدم DNS نظام أسماء نطاقات المضيف افتراضيًا (قابل للضبط عبر network.dns)
  • يتم تثبيت مقابس محددة فقط من XDG_RUNTIME_DIR عبر bind-mount (Wayland وPulseAudio وPipeWire ووكيل D-Bus المفلتر) — لا يتم كشف دليل وقت التشغيل الكامل للمضيف أبدًا
  • يتم دائمًا توسط الوصول إلى ناقل جلسة D-Bus للمضيف عبر xdg-dbus-proxy مفلتر يعمل على المضيف. فقط org.freedesktop.Notifications وorg.freedesktop.portal.Desktop وorg.mozilla.* (وبالنسبة للفروع، نطاق الفرع الخاص، مثل org.librewolf.*) يمكن الوصول إليها — خدمات الجلسة مثل حلقة المفاتيح ووكيل SSH/GPG محجوبة
  • الوصول عبر البوابة واسع النطاق. يُسمح بـ org.freedesktop.portal.Desktop ككل، لأن هذه هي طريقة عمل منتقي الملفات، و«فتح الرابط في تطبيق آخر»، ومشاركة الشاشة. كما يكشف RemoteDesktop (لوحة مفاتيح/فأرة اصطناعية للجلسة بأكملها)، وCamera، وLocation. هذه محكومة بحوارات الموافقة الخاصة بسطح المكتب لديك وليس بواسطة foxcage — ويشبه موجه RemoteDesktop موجه مشاركة الشاشة، لذا اقرأ حوارات الموافقة قبل قبولها. لا يملك xdg-dbus-proxy قاعدة «رفض واجهة واحدة»، لذا فإن تضييق هذا النطاق يعني تعداد كل واجهة يحتاجها Firefox؛ انظر docs/DESIGN.md لمعرفة سبب عدم القيام بذلك افتراضيًا
  • تستخدم جميع تثبيتات bind (profile، downloads_dir، و[mounts] bind الإضافية) الخيارين nosuid,noexec
  • التحقق من تنزيل المتصفح مقابل توقيعات GPG: Firefox مقابل مجاميع SHA-512 الموقعة من Mozilla، وLibreWolf مقابل التوقيع المنفصل لصانعي LibreWolf بالإضافة إلى SHA-256 المجاور
  • حاوية مؤقتة (--rm) — تُفقد كتابات نظام الملفات عند الخروج
  • لا يتم تمرير أي أجهزة مضيفة (كاميرا ويب، مفاتيح أمان، طابعات) إلا إذا تم تمكينها صراحةً

كل خيار من خيارات [network] و[mounts] تقوم بتمكينه يستبدل بعض العزل بالراحة. الإعدادات الافتراضية هي أكثر تكوين تقييدًا مع بقائك تحصل على متصفح قابل للاستخدام.

المتطلبات

  • Python 3.11+
  • Podman (بدون جذر)
  • مُؤلِّف نوافذ Wayland (X11 غير مدعوم)
  • pasta (sudo apt install passt) — إلا عند استخدام network.mode = "host"
  • xdg-dbus-proxy (sudo apt install xdg-dbus-proxy)
  • PulseAudio أو PipeWire مع توافق PulseAudio (للصوت)
  • GPU مع دعم DRI — اختياري؛ بدون /dev/dri يصدر foxcage تحذيرًا ويعرض Firefox بالبرمجيات (software rendering)

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

البيئة المختبرة: Debian 13 (Trixie) مع GNOME 3. قد تعمل توزيعات Linux الأخرى ومُؤلِّفُو نوافذ Wayland الآخرون لكن لم يتم اختبارها.

التثبيت

foxcage هو سكربت Python واحد لا توجد له تبعيات خارج المكتبة المعيارية للغة Python. انسخه إلى دليل ضمن PATH لديك:```sh sudo cp foxcage /usr/local/bin/foxcage

أو للتثبيت محليًا للمستخدم:```sh
cp foxcage ~/.local/bin/foxcage

تأكد من أن السكربت قابل للتنفيذ (chmod +x foxcage).

تحقق من المراجعة التي لديك باستخدام foxcage --version — مفيد عند الإبلاغ عن مشكلة، حيث يتم تثبيت foxcage عن طريق نسخ ملف واحد.

الاستخدام```sh

./foxcage

في أول تشغيل، يبني البرنامج النصي صورة الحاوية (ينزّل Firefox من Mozilla، ويثبّت تبعيات Debian الأساسية) ثم يبدأ تشغيل Firefox. في عمليات التشغيل اللاحقة، يتحقق foxcage من تحديثات Firefox ويعيد بناء الصورة تلقائيًا عند توفر إصدار جديد. كما تُعاد بناء الصورة بشكل دوري (كل 7 أيام افتراضيًا) لالتقاط تحديثات حزم النظام. إذا فشل فحص التحديث (خطأ في الشبكة، مهلة)، يتم تسجيل تحذير ويتم استخدام الصورة الموجودة — لا يتم حظر بدء التشغيل أبدًا.

مرّر الوسائط إلى Firefox:```sh
./foxcage https://example.com

اجمع بين قفص مُسمّى وخيارات Firefox:```sh ./foxcage @work --kiosk https://example.com

إذا كان القفص يعمل بالفعل، فسيُفتح الرابط في تبويب جديد في المتصفح الحالي بدلاً من تشغيل حاوية ثانية. تشغيل `foxcage` (أو `foxcage @cage`) بدون رابط مع قفص يعمل يخرج بنظافة مع رسالة "القفص يعمل بالفعل" — لا يمكن لـ foxcage رفع نافذة Wayland موجودة من خارج الحاوية، لذا فهو لا يحاول.

لا تنطبق خيارات الإطلاق المحددة **عندما** يكون القفص يعمل بالفعل. `--dns` و`--ipv4-only` و`--lifetime` و`--color` و`--fork` تُستهلك عند بدء تشغيل الحاوية، ولا يمكن تغيير إعدادات حاوية قيد التشغيل من الخارج، لذا يتم تجاهلها مع تحذير. أغلق القفص وأعد تشغيله لتطبيقها.

> استخدم مفتاح الإعداد `private_browsing` لجلسات وضع التصفح الخاص — *وليس* علامة `--private-window` المباشرة في Firefox. يضبط مفتاح الإعداد وضع التصفح الخاص على مستوى الجلسة (`browser.privatebrowsing.autostart`)، لذا يمكن لاستدعاءات `foxcage @cage URL` اللاحقة إعادة الفتح في تبويبات. أما `--private-window` كتمريرة إلى Firefox فسيجعل النافذة الأولى فقط خاصة وسيكسر سلوك إعادة الفتح في التبويبات الموضح أعلاه.
>
> **تنبيه:** الجلسات المفعّلة عبر `private_browsing = true` لا تُظهر مؤشرات واجهة نافذة التصفح الخاص المعتادة في Firefox (الشريط البنفسجي، أيقونة القناع، "(التصفح الخاص)" في العنوان). يعود السبب إلى أن كل نافذة في الجلسة خاصة، لذا ليس لدى Firefox نافذة غير خاصة ليقارنها بصريًا بها — فهو يخفي المؤشر. الجلسة *هي* خاصة حقًا؛ تحقق إن أردت بزيارة `about:privatebrowsing` داخل القفص (تعرض صفحة معلومات التصفح الخاص القياسية) أو `about:config` والتأكد من أن `browser.privatebrowsing.autostart = true`.

### التصفح المؤقت باستخدام `@tmp`

للروابط لمرة واحدة التي يجب ألا تترك أي أثر، استخدم القفص المحجوز `tmp`:```sh
./foxcage @tmp https://somewhere-suspicious.example

كل إطلاق @tmp هو نسخة Firefox جديدة للاستخدام مرة واحدة بدون ملف تعريف دائم. عندما تُغلق النافذة، يختفي كل شيء — ملفات تعريف الارتباط، وذاكرة التخزين المؤقت، والسجل، والإضافات، وحالة HSTS، وذاكرة تخزين جلسة TLS المؤقتة، وذاكرة تخزين DNS المؤقتة، وحالة التبويبات المحفوظة. وهذا يتجاوز وضع Firefox Private Browsing، الذي ما يزال يحتفظ بالإضافات وقدرًا لا بأس به من الحالة المخزنة على القرص.

تعمل أقفاص @tmp المتعددة بشكل متزامن، كلٌّ منها معزول عن الآخرين. يعرض شريط القائمة FoxCage - tmp (<short id>) حتى يمكنك التمييز بين النوافذ المؤقتة المتزامنة.

تفتح الأقفاص المؤقتة صفحة فارغة عند بدء التشغيل وتبويبات جديدة فارغة — فالصفحة الرئيسية الافتراضية لـ Firefox ومحتوى التبويب الجديد (المواقع الأعلى، توصيات Pocket، تدفق النشاط) هي مجرد ضوضاء على ملف تعريف جديد يُوشك أن يُرمى، لذلك يتم كبتها. الأقفاص الدائمة تحتفظ بإعدادات Firefox الافتراضية.

أقفاص مؤقتة مسماة

إذا كنت تريد اسمًا ذا معنى لجلسة مؤقتة (لنقل، حفرة بحث قد ترغب في إعادة فتحها في تبويب جديد)، استخدم @tmp-<name>:```sh ./foxcage @tmp-research https://example.com # first call → new window ./foxcage @tmp-research https://another.example # second call → new tab in the existing window

`@tmp-<name>` لا يزال مؤقتًا — عند إغلاق النافذة، يختفي كل شيء. الفرق عن `@tmp` المجرد هو أن الإطلاقات الثانية بنفس الاسم **تعيد استخدام النافذة الموجودة** (مثل الأقفاص الدائمة)، لذا يمكنك إضافة المزيد من التبويبات لاحقًا دون بدء نسخة موازية. يحافظ `@tmp` المجرد على سلوكه "كل إطلاق هو نسخة جديدة مؤقتة".

يظهر اسم النافذة في شريط القوائم الاسم الذي اخترته (`FoxCage - tmp-research`) بحيث تكون النافذة معنونة بشكل واضح.

#### تخصيص الإعدادات الافتراضية للأقفاص المؤقتة

أنشئ `~/.config/foxcage/tmp.toml` لتعيين الإعدادات الافتراضية لجميع الأقفاص المؤقتة (سواء `@tmp` المجرد أو كل `@tmp-<name>`). على سبيل المثال:```toml
private_browsing = true
lifetime = "30m"

[network]
dns = "cloudflare"

كل إطلاق مؤقت يحصل الآن على نافذة خاصة، وDoH من Cloudflare، ويُغلق تلقائيًا بعد 30 دقيقة — مع بقاء الكلية المؤقتة سليمة تمامًا. الأقفاص المؤقتة المُسمّاة ترث tmp.toml افتراضيًا؛ إذا أردت تجاوز ذلك لكل اسم، أنشئ ~/.config/foxcage/tmp-<name>.toml. حينها يُطبَّق هذا الملف بدلًا من tmp.toml — بدون دمج، الملف الأكثر تحديدًا يربح بالكامل. انسخ الإعدادات الافتراضية المشتركة إليه إذا كنت تريدها.

أي شيء يمكنك ضبطه في إعدادات قفص عادي يعمل هنا، باستثناء المفتاح الوحيد الذي قد يُفشل الكلية المؤقتة نفسها:

  • profile — خطأ صريح.

فهو يشير إلى دليل ملفات تعريف دائم على الجهاز، وهو ما يتعارض مباشرة مع الغرض من @tmp. إذا أردت قفصًا معزولًا بملف تعريف دائم، استخدم قفصًا مُسمّى عاديًا (@work، @research، إلخ.) لا يبدأ بـ tmp-.

تجاوز DNS لكل إطلاق

يَقبل علم --dns (ومفتاح الإعداد المكافئ network.dns) ثلاثة أشكال:```sh ./foxcage @tmp --dns 1.1.1.1 https://example.com # IP ./foxcage @tmp --dns cloudflare https://example.com # alias ./foxcage @tmp --dns https://dns.nextdns.io/ # custom DoH URI

**عندما تتطابق القيمة مع مزوّد معروف (بواسطة الاسم المستعار أو العنوان IP)، يقوم foxcage تلقائيًا بتمكين DNS عبر HTTPS إجباريًا إلى ذلك المزوّد.** يتم ضبط TRR في Firefox على الوضع 3 (صارم، بدون ارتداد للنص الواضح) مع ملء عنوان التمهيد حتى لا يكون هناك تسريب استعلام غير مشفّر عند الإقلاع. سترى إشعارًا من سطر واحد على stderr مثل `Enabling DNS over HTTPS via Cloudflare`.

الأسماء المستعارة المدمجة:

| الاسم المستعار | IP | التصفية |
|-------|------|-----------|
| `cloudflare` | 1.1.1.1 | لا شيء |
| `cloudflare-security` | 1.1.1.2 | يحظر البرمجيات الخبيثة |
| `cloudflare-family` | 1.1.1.3 | يحظر البرمجيات الخبيثة ومحتوى البالغين |
| `google` | 8.8.8.8 | لا شيء |
| `quad9` | 9.9.9.9 | يحظر البرمجيات الخبيثة (Quad9 الافتراضي) |
| `quad9-unfiltered` | 9.9.9.10 | لا شيء |
| `adguard` | 94.140.14.14 | يحظر الإعلانات والمتتبعات |
| `adguard-family` | 94.140.14.15 | الإعلانات والمتتبعات ومحتوى البالغين |
| `opendns` | 208.67.222.222 | بعض |

أي عنوان IP غير موجود في الجدول (مثل Pi-hole على شبكتك المحلية) يبقى بنص واضح فقط — لا يتم تمكين DoH، لأن foxcage لا يعرف نقطة نهاية DoH المقابلة. استخدم صيغة URI لذلك: `--dns https://pi.hole/dns-query` (مع شهادة صالحة) تتيح DoH وتترك DNS الحاوية كما هو.

صيغة URI تتخطى ضبط DNS النص الواضح للحاوية، لذلك أي شيء داخل الحاوية ليس Firefox سيظل يستخدم DNS المضيف. هذا مقصود — `--dns URI` تعني "اجعل Firefox يستخدم محلل DoH هذا"، وهذا كل شيء.

`--dns` غير متوافق مع `network.mode = "host"`، الذي يمتلك بالفعل وصولاً كاملاً لشبكة المضيف.

### التعرّف المرئي على القفص

كل قفص مسمّى يحصل على لون مميز في شريط القائمة بحيث يمكنك التمييز بين النوافذ بنظرة واحدة. **لا تحتاج إلى ضبط أي شيء** — يتم اشتقاق اللون حتميًا من اسم القفص (يُحوَّل بترميز SHA256 إلى درجة لونية، مع تشبّع وإضاءة ثابتين). `@banking`، `@work`، `@personal`، `@tmp-research` تحصل جميعها على ألوان مميزة ومستقرة دون أن ترفع إصبعك.

القفص الافتراضي (المجهول) يحتفظ باللون البرتقالي المدمج.

إذا أردت تجاوز اللون المشتق تلقائيًا، فاضبطه صراحةً:```toml
# ~/.config/foxcage/banking.toml
color = "#dc2626"   # red — overrides the auto-derived colour

المحتوى المرسل فارغ — لم يتم تضمين أي نص في الجزء 21 لترجمته. يرجى توفير النص المطلوب ترجمته.```sh ./foxcage @experiment --color "#10b981" https://example.com # teal, one-off

يقبل ألوان CSS السداسية العشرية القياسية: `#rgb` أو `#rrggbb` أو `#rrggbbaa` (مع قناة ألفا). الألوان المشتقة تلقائيًا مضبوطة لتكون مرئية على أشرطة القوائم الفاتحة والداكنة معًا (الإضاءة مثبتة عند 55%، والتشبع عند 75%)، لذا لن تحتاج إلى تجاوزها لأسباب تتعلق بالثيم.

### الأقفاص المحددة زمنيًا

علم `--lifetime` (ومفتاح الإعداد `lifetime` المكافئ) يغلق القفص تلقائيًا بعد مدة محددة. الصيغة هي `<number><unit>` مع وحدة `s` أو `m` أو `h`:```sh
./foxcage @tmp --lifetime 10m https://example.com
./foxcage @work --lifetime 2h

يبدأ العد التنازلي عندما يتم تشغيل Firefox فعليًا داخل القفص — لا يستهلك وقت بدء تشغيل الحاوية ووقت بناء الصورة من ميزانيتك. تعرض تسمية شريط القوائم في القفص العد التنازلي بجانب هوية القفص — على سبيل المثال: FoxCage - tmp (a3f2b1) | 9m — ويتم تحديثها مرة واحدة كل دقيقة عندما تبقى أكثر من دقيقة، ومرة كل ثانية في الدقيقة الأخيرة. عندما يصل العد التنازلي إلى الصفر، يغلق Firefox نفسه وتخرج الحاوية. إذا أغلقت Firefox بنفسك قبل انتهاء العمر الافتراضي، فلن يحدث شيء غير عادي.

عيّن عمرًا افتراضيًا لكل قفص في إعداده:```toml

~/.config/foxcage/tmp.toml — every @tmp launch auto-closes after 15 minutes

lifetime = "15m" private_browsing = true

`--lifetime` على سطر الأوامر يغلب على أي قيمة إعداد.

فرض إعادة بناء كاملة للصورة (إعادة تنزيل Firefox وجميع حزم النظام):```sh
./foxcage --rebuild

الحاوية قيد التشغيل تحتفظ بالصورة التي بدأت منها، حتى بعد أن يعيد foxcage بناء وسم الصورة. إذا حاولت فتح تبويب في cage تم تحديث صورته منذ ذلك الحين (عبر --rebuild، أو تحديث Firefox، أو إعادة البناء المجدولة)، يرفض foxcage ذلك مع خطأ (يظهر أيضًا كإشعار سطح مكتب) ويطلب منك إنهاء Firefox وإعادة تشغيله — مما يبدأ حاوية جديدة على الصورة الحالية. تحت --rebuild مع cage نشط، يحذّر foxcage مسبقًا، وينفذ البناء، ثم يطبق نفس الفحص.

التحديثات

يتحقق foxcage من إصدارات المتصفح الجديدة عند كل تشغيل — واجهة برمجة تطبيقات إصدارات Mozilla لـ Firefox، ونقطة نهاية إصدارات GitLab لـ LibreWolf. إذا كان التحديث متاحًا، يُعاد بناء صورة الحاوية تلقائيًا. كما يُعاد بناء الصورة بشكل دوري (كل 7 أيام افتراضيًا) لالتقاط تحديثات أمان Debian. مُحدِّث المتصفح المدمج معطَّل لأن التحديثات تتم على مستوى الصورة.

إذا فشل فحص التحديث (لا توجد شبكة، انتهت مهلة API)، تتم طباعة تحذير ويتم استخدام الصورة الموجودة — يمكنك دائمًا التصفح.

إيقاع التحديث موجود في المستوى الأعلى من الإعدادات؛ بينما تثبيت الإصدار والقناة موجود في قسم كل فرع:```toml rebuild_days = 14 # rebuild for base-image updates every 14 days (0 to disable)

[firefox] channel = "beta" # track the beta channel instead of stable (firefox only) version = "149" # pin to Firefox 149.x (latest patch release)

**تثبيت نسخة ESR يتطلب تحديد القناة أيضًا.** يعرض فهرس إصدارات Mozilla إصدارات ESR دون لاحقة `esr` التي تحملها تنزيلاتها، لذا فإن `version = "140"` المجردة على القناة الافتراضية تُحل إلى إصدار غير موجود. اضبط كلاهما:```toml
[firefox]
channel = "esr"
version = "140"        # → 140.13.0esr

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

يجب أن تكون الـ pins ذات اللواحق مؤهلة بالكامل — "140.13.0esr" و "150.0b9" تعملان، بينما يتم رفض "140esr" و "150b9" عند تحميل الإعدادات لأنه لا يمكن لأي إصدار مطابقتها أبدًا. وينطبق الشيء نفسه على مراجعات LibreWolf: "146.0.1-1" يعمل، بينما "146-1" لا يعمل.

لفرض إعادة بناء كاملة فورية: ./foxcage --rebuild

تفرعات Firefox (LibreWolf)

يمكن لـ foxcage تشغيل تفرع من Firefox موجّه للخصوصية بدلاً من Firefox الأصلي:```toml fork = "librewolf" # default is "firefox"

[librewolf] version = "146.0.1-1" # optional pin; partial pins ("146", "146.0.1") also work

أو لكل إطلاق عبر CLI:```sh
foxcage @tmp --fork librewolf https://example.com

LibreWolf: نسخة مشتقة من Firefox معزّزة بالخصوصية — حماية صارمة من التتبع، وDoH، وRFP، والقياس عن بُعد (telemetry) مقفل افتراضيًا. حزمة Linux موقّعة من GitLab (librewolf-community/browser/bsys6)، تم التحقق منها عبر GPG مقابل مفتاح مطوّري LibreWolf 662E 3CDD 6FE3 2900 2D0C A5BB 4033 9DD8 2B12 EF16 مع فحص تقاطعي من ملف .sha256sum مجاور. يتم الحفاظ على librewolf.cfg المضمّن في LibreWolf؛ يضيف foxcage تفضيلاته الخاصة فوقه بدلاً من الكتابة فوقه.

القناة خاصة بـ Firefox فقط: يتم رفض firefox.channel = "beta" | "esr" عندما يكون fork أي شيء آخر غير "firefox". يمتلك LibreWolf مسار إصدار واحد.

تغيير fork (عبر الإعدادات أو --fork) يغيّر تجزئة Containerfile، مما يطلق إعادة بناء عند الإطلاق التالي — دون الحاجة إلى --rebuild يدويًا.

توافق الملفات الشخصية

استخدم ملفًا شخصيًا مخصصًا لكل fork. الخيار الافتراضي الأكثر أمانًا هو ترك foxcage يجهّز ملفه الشخصي الخاص (احذف profile من الإعدادات)، أو وجّه profile إلى مجلد لا تفتحه أيضًا من المضيف.

  • LibreWolf: عادةً ما يكون من الجيد مشاركته مع ملف Firefox الشخصي على مضيفك — يتبع LibreWolf إصدارات Firefox في غضون أيام قليلة، لذا تكون تعارضات مخطط compatibility.ini نادرة. المخاطر: (1) الاستخدام المتسلسل فقط هو الآمن (ملف قفل Firefox يمنع الفتح المتزامن)؛ (2) في النافذة القصيرة بعد إصدار Firefox المستقر، قد يؤدي تشغيل Firefox أولاً ثم LibreWolf إلى ظهور حوار ترحيل بعبارة "used by a newer version"؛ (3) الميزات التي يزيلها LibreWolf (Sync وPocket وحساب Mozilla) لا تعمل بصمت لكنها لا تُفسد البيانات.

أقفاص مسماة

شغّل مثيلات منفصلة معزولة (sandboxed) بإعداداتها الخاصة وملف Firefox الشخصي الخاص بها:```sh ./foxcage @work

هذا يقوم بتحميل `~/.config/foxcage/work.toml` ويستخدم صورة منفصلة (`foxcage-work`)، وحاوية (`foxcage-work`)، ووحدة تخزين (`foxcage-work-profile`). يجب أن يوجد ملف الإعداد للأقفاص المُسمّاة. قد تحتوي أسماء الأقفاص على الحروف والأرقام والواصلات وشرطات التسطير فقط.

## التكوين

توجد ملفات الإعداد في `$XDG_CONFIG_HOME/foxcage/` (الافتراضي هو `~/.config/foxcage/`).

- `config.toml` — القفص الافتراضي (اختياري، إعدادات افتراضية معقولة بدونه)
- `<name>.toml` — قفص مُسمّى، يُحمَّل مع `@<name>` (مطلوب)

يتم رفض مفاتيح الإعداد غير المعروفة مع ظهور خطأ. راجع `config.toml.example` للاطلاع على جميع الخيارات المتاحة مع الإعدادات الافتراضية.

### مثال على config.toml```toml
# Bind-mount a host Firefox profile directory into the cage
profile       = "~/.mozilla/firefox/xxxxxxxx.default-release"

# Allow downloading files to ~/Downloads
downloads_dir = "~/Downloads"

# Shared memory size for Firefox IPC (default: 256m)
# shm_size = "256m"

# Pass through webcam devices (/dev/video*)
# webcam = true

# Pass through host CUPS socket for locally-connected printers (e.g. USB)
# local_printers = true

# Pass through FIDO2/U2F security key devices (/dev/hidraw*)
# security_keys = true

# Always open Firefox in private browsing mode
# private_browsing = true

# Auto-close the cage after a duration (<int> with unit s, m, or h)
# lifetime = "30m"

# Accent colour for the menu-bar label.  Named cages get a colour derived
# from the name automatically; set this to override it.
# color = "#4a90e2"

# Browser fork: "firefox" (default) or "librewolf"
# fork = "librewolf"

# Full image rebuild interval in days for base-image updates (default: 7, 0 to disable)
# rebuild_days = 7

[firefox]
# Firefox release channel: "release" (default), "beta", "esr".
# Only valid when fork = "firefox".
# channel = "release"

# Pin to a specific Firefox version (overrides channel).
# Partial versions like "149" or "149.0" resolve to the latest patch release.
# Suffixed versions must be fully qualified ("140.13.0esr", "150.0b9"); to
# follow the ESR line by major version, pair a numeric pin with
# channel = "esr" above.
# version = "149.0.2"

[librewolf]
# Pin to a specific LibreWolf version. Tags are "<firefox-version>-<rev>",
# e.g. "146.0.1-1". Partial pins like "146" or "146.0.1" also work.
# version = "146.0.1-1"

[network]
# "host" for full host networking (needed if the cage has to reach services
# on the host's localhost), or omit for isolated pasta (default)
# mode = "host"

# DNS server (isolated mode only, default: host DNS)
# dns = "1.1.1.1"

# Disable IPv6 in the cage (isolated mode only)
# ipv4_only = true

[mounts]
# Additional bind mounts into the container. Supported forms:
#   "~/Documents"                     — same path in container
#   "~/Documents:~/Documents"         — ~ expanded on both sides
#   "~/Documents:/home/user/Documents" — explicit container path
# Append :ro for read-only, e.g. "~/Documents:ro"
# nosuid,noexec are always enforced on bind mounts; an explicit "exec" or
# "suid" is rejected rather than silently dropped.
# Host paths must be absolute or start with "~/".
bind = [
    "~/Documents:ro",
]

[init]
# Commands to run at image build time (as root). Changes trigger a rebuild.
# build = ["apt-get update && apt-get install -y --no-install-recommends vim"]

# Commands to run at container startup as root, before Firefox.
# root = ["chown user:user /some/path"]

# Commands to run at container startup as your user, before Firefox.
# user = ["mkdir -p ~/custom-dir"]

ملف تعريف Firefox الخاص بالمضيف

لمشاركة ملف تعريف Firefox الخاص بالمضيف مع القفص، اضبط profile على دليل الملف الشخصي. ابحث عن مسار ملفك الشخصي بزيارة about:profiles في Firefox على المضيف — أو أشر فقط إلى دليل فارغ جديد إذا كنت تريد أن يبدأ القفص بملف تعريف نظيف يبقى على المضيف.```toml profile = "~/.mozilla/firefox/xxxxxxxx.default-release"

فقط هذا الدليل الواحد يتم ربطه (bind-mounted) داخل القفص. ملفات التعريف المجاورة تحت `~/.mozilla/firefox/` وسجل `profiles.ini` ليست مكشوفة — لا يمكن لقفص مخترَق العبث بها.

إذا كان `profile` غير معيّن، يتم تخزين ملف تعريف Firefox في وحدة تخزين Podman مسماة بدلاً من ذلك (انظر "ما الذي يبقى" أدناه). إذا كان نفس الملف التعريفي مفتوحًا بالفعل في Firefox على المضيف، فسيؤدي ملف قفل الملف التعريفي الخاص بـ Firefox إلى تعارض — استخدم ملفًا تعريفيًا مخصصًا لكل قفص.

### الشبكات

بشكل افتراضي، تستخدم الحاوية pasta مع حظر حلقة الاسترجاع الخاصة بالمضيف وDNS الخاص بالمضيف. يتطلب pasta إصدار podman 4.4 أو أحدث (وهو الافتراضي بدون صلاحيات الجذر منذ podman 5.0).

**شبكات المضيف** تزيل العزل الشبكي تمامًا. استخدم هذا عندما يحتاج القفص إلى الوصول إلى الخدمات على `localhost` الخاص بالمضيف (مثل خادم تطوير محلي، أو قاعدة بيانات على `127.0.0.1`):```toml
[network]
mode = "host"

dns لا يمكن دمجه مع mode = "host" — فشبكة المضيف تستخدم بالفعل محلل أسماء المضيف.

وضع المضيف يتخلى عن أكثر من مجرد localhost. فهو يضع القفص في فضاء أسماء الشبكة الخاص بالمضيف، وتكون مقابس Unix المجردة مقيّدة بذلك الفضاء بدلاً من نظام الملفات. لذا يمكن لقفص في وضع المضيف الوصول مباشرة إلى مقابس العناوين المجردة على المضيف — بما في ذلك @/tmp/.X11-unix/X0 الخاص بـ Xwayland إذا كنت تشغّل X11 أو Xwayland (تسجيل الإدخال، على الرغم من أن foxcage مخصص لـ Wayland فقط)، وحافلة جلسة مهيأة بـ unix:abstract=…، مما قد يتجاوز وكيل D-Bus المفلتر. هذا أمر متأصل في مشاركة حزمة الشبكة، وليس شيئًا يمكن لـ foxcage تصفيته. استخدم وضع المضيف عندما تحتاجه، وفضّل قفصًا مسمّى لا تشغّله إلا لهذا الغرض.

أقفاص IPv4 فقط تعطّل IPv6 تمامًا:```toml [network] ipv4_only = true

أو عند كل إطلاق باستخدام علامة `--ipv4-only` (الصيغة المختصرة `-4`، كما في `ssh`/`curl`/pasta):```sh
./foxcage @tmp -4 https://example.com

هذا يشغّل pasta في وضع IPv4 فقط (-4)، لذا لا يحتوي الحاوية على مكدس IPv6 إطلاقًا، بالإضافة إلى تعيين network.dns.disableIPv6 في Firefox لمنعه من حلّ سجلات AAAA — وهو أمر مهم عند تمكين DoH، لأن إجابات DoH تتجاوز محلّل الحاوية. لا يمكن دمج ipv4_only مع mode = "host" — فشبكة المضيف تستخدم مكدس شبكة المضيف مباشرة، لذا عطّل IPv6 على المضيف بدلًا من ذلك.

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

شغّل أوامر مخصصة في وقت البناء أو عند بدء تشغيل الحاوية عبر [init]:

  • build — يُنفَّذ في وقت بناء الصورة بصلاحيات الجذر. استخدمه لتثبيت الحزم أو أي إعداد بطيء آخر. أي تغييرات في أوامر البناء تؤدي تلقائيًا إلى إعادة بناء الصورة.
  • root — يُنفَّذ عند بدء تشغيل الحاوية بصلاحيات الجذر، قبل Firefox. استخدمه للمهام الجذرية السريعة أثناء التشغيل (تعديل الصلاحيات، كتابة ملفات الإعدادات).
  • user — يُنفَّذ عند بدء تشغيل الحاوية بصلاحيات المستخدم الخاص بك، قبل Firefox. استخدمه لإنشاء الدلائل وإعداد الحالة على مستوى المستخدم.```toml [init] build = [ "apt-get update && apt-get install -y --no-install-recommends fonts-noto-cjk", "rm -rf /var/lib/apt/lists/*", ] root = ["chmod 777 /tmp/shared"] user = ["mkdir -p ~/workspace"]
جميع المفاتيح الثلاثة هي قوائم من سلاسل أوامر الصدفة. إذا فشل أي أمر، تنتهي الحاوية دون تشغيل Firefox.

**ملاحظة أمنية:** عند ضبط `init.root`، تبدأ الحاوية كجذر مع إضافة `CAP_SETUID` و`CAP_SETGID` (إضافةً إلى `CAP_SYS_CHROOT` الافتراضي) بحيث يمكنها العودة إلى المستخدم العادي. تُحتجز هذه القدرات فقط أثناء مرحلة تهيئة الجذر — بعد التراجع عن الامتيازات، لا تملك عملية المستخدم العادي أي قدرات إضافية. بدون `init.root`، تعمل الحاوية بمجموعة القدرات الافتراضية الدنيا.

## ما يبقى

بدون إعداد، تقوم وحدة تخزين Podman مسماة بتخزين ملف تعريف Firefox (العلامات المرجعية، الإعدادات، الإضافات، إضافة Widevine DRM). كل شيء آخر مؤقت.

- القفص الافتراضي: `foxcage-profile`
- القفص المسمّى: `foxcage-<name>-profile`

للبدء من جديد، أزل وحدة التخزين:```sh
podman volume rm foxcage-profile

إذا تم تعيين profile، يتم تثبيت دليل المضيف مباشرةً (bind-mount) ولا يتم إنشاء أي وحدة تخزين (volume).

استخدام القرص

حجم كل صورة cage حوالي 1 جيجابايت. إعادة البناء تعيد وسم الصورة وتترك الصورة السابقة كإدخال غير موسوم <none>، لذلك يقوم foxcage بإزالة الصورة التي استبدلها للتو بعد كل بناء ناجح. وهو يزيل فقط تلك الصورة المحددة، ولا يزيل أبدًا صورةً لا يزال cage قيد التشغيل يستخدمها.

الصور التي أصبحت يتيمة (orphaned) قبل وجود هذا السلوك لا يتم تنظيفها بأثر رجعي. لاستعادتها:```sh podman images --filter dangling=true # review first podman image prune # then remove

يتم اكتشاف تحديثات Firefox تلقائيًا عند كل إطلاق. لفرض إعادة بناء كاملة (على سبيل المثال، لالتقاط تحديثات أمان النظام فورًا):```sh
./foxcage --rebuild

السمات

يمرر foxcage تلقائيًا ما يلي من المضيف، بحيث يبدو Firefox في الحاوية ويعمل كأنه تطبيق أصلي:

  • الخطوط. يتم ربط خطوط النظام (/usr/share/fonts) وخطوط المستخدم (~/.local/share/fonts) للقراءة فقط. كما يتم تمرير إعدادات الخطوط من ~/.config/fontconfig.
  • سمة GTK والوضع الداكن. يتم اكتشافها عبر GTK_THEME أو gsettings وتمريرها إلى الحاوية. كما يتم ربط إعدادات GTK من ~/.config/gtk-3.0 و~/.config/gtk-4.0 للقراءة فقط.
  • المنطقة الزمنية. يتم تمرير اسم المنطقة الزمنية للمضيف (يُكتشف من TZ أو الرابط الرمزي /etc/localtime أو /etc/timezone) إلى الحاوية كـ TZ، ويتم ربط /etc/localtime للقراءة فقط. كلاهما ضروري: يستمد Firefox المنطقة الزمنية لـ JavaScript من اسم المنطقة وليس من محتويات الملف — بدون TZ، ستعرض مواقع الويب الأوقات بصيغة UTC.
  • الإعداد المحلي. يتم تمرير LANG. ويتم توليد الإعداد المحلي للمضيف في صورة الحاوية وقت البناء.

تسمية القفص. يعرض شريط قوائم Firefox "FoxCage" (أو "FoxCage - name" للأقفاص المسماة) حتى تعرف بلمحة أنك في جلسة داخل حاوية. ويكون شريط القوائم ظاهرًا دائمًا عبر سياسة المؤسسات.

تحتوي الحاوية على سمة GTK Adwaita فقط. على أسطح مكتب GNOME، يعمل هذا مباشرة دون أي إعدادات. على KDE أو أسطح مكتب أخرى، سيعود Firefox إلى Adwaita إذا لم تكن سمة GTK لديك (مثل Breeze) مثبتة في الحاوية. لا يزال اكتشاف الوضع الداكن يعمل طالما تم تعيين التفضيل عبر gsettings أو GTK_THEME.

DRM (Netflix وDisney+ وغيرها)

يعمل Widevine DRM مباشرة دون أي إعدادات. عند أول زيارة لموقع محمي بـ DRM، سيقوم Firefox بتنزيل Widevine CDM تلقائيًا. قد يستغرق ذلك لحظة.

تكامل المضيف (مفعّل دائمًا)

يستخدم foxcage وكيل D-Bus مفلترًا ليمنح Firefox الوصول إلى بوابة سطح المكتب XDG Desktop Portal وخدمة الإشعارات الخاصة بالمضيف. هذه الميزات آمنة لأن كل الوصول يتم بوساطة المستخدم — حيث يعرض المضيف حوارات أصلية يجب أن تتفاعل معها. لا يمكن لمتصفح مخترق الوصول إلى موارد المضيف بصمت.

  • رفع الملفات — منتقي الملفات الأصلي للمضيف (تختار الملفات التي تريد مشاركتها)
  • الروابط الخارجيةmailto: وروابط المغناطيس وغيرها تُفتح عبر منتقي التطبيقات في المضيف
  • إشعارات سطح المكتب — تُمرَّر إلى خدمة إشعارات المضيف
  • مشاركة الشاشة — منتقي الشاشة في البوابة + دفق فيديو PipeWire (يتطلب PipeWire على المضيف)

تمرير الأجهزة (اختياري)

تمرّر هذه الميزات أجهزة المضيف مباشرةً إلى الحاوية وهي معطّلة افتراضيًا — على عكس ميزات البوابة أعلاه، لا يوجد تأكيد من جانب المضيف. يمكن لمتصفح مخترق استخدام العتاد بصمت.```toml webcam = true # /dev/video* — webcam for video calls local_printers = true # CUPS socket — USB printers (network printers work by default) security_keys = true # /dev/hidraw* — FIDO2/U2F hardware keys

## غير مدعوم بعد

بعض ميزات منصات الويب لا تعمل داخل الحاوية بسبب نقص التكامل مع المضيف. وهي مدرجة هنا من أجل الشفافية.

**البلوتوث وUSB والمنفذ التسلسلي وNFC.** تتطلب واجهات برمجة تطبيقات Web Bluetooth وWebUSB وWeb Serial وWebNFC الوصول إلى الأجهزة وخدمات النظام (BlueZ وudev) غير المتوفرة في الحاوية.

**أذرع التحكم وMIDI.** تحتاج واجهة Gamepad API إلى الوصول إلى `/dev/input/`. وتحتاج Web MIDI إلى الوصول إلى مُسلسِل ALSA. ولا يتم تمرير أي منهما.

**تثبيت PWA.** لا يمكن تثبيت تطبيقات الويب التقدمية على سطح المكتب المضيف من داخل الحاوية.

**إمكانية الوصول.** دعم قارئ الشاشة عبر AT-SPI معطّل (`NO_AT_BRIDGE=1`) — لا توجد لدى الحاوية أي اتصال بحافلة إمكانية الوصول الخاصة بالمضيف. ويعمل تركيب Web Speech API: فقد تم تثبيت `speech-dispatcher` مع محرك `espeak-ng` في cage ويتم تشغيله تلقائيًا عند أول استخدام، مع توجيه الصوت عبر مقبس PulseAudio المشترك.

## إعداد المضيف

### الموصى به: تخزين overlay مع fuse-overlayfs

قد يعتمد Podman غير الجذري افتراضيًا على مشغل التخزين `vfs`، الذي ينسخ طبقات الصورة كاملة بدلاً من استخدام تركيبات overlay. وهذا يجعل بدء تشغيل الحاوية بعد البناء أبطأ بكثير. لإصلاح ذلك، ثبّت `fuse-overlayfs` وأضف ما يلي إلى `~/.config/containers/storage.conf`:```toml
[storage]
driver = "overlay"

[storage.options.overlay]
mount_program = "/usr/bin/fuse-overlayfs"

تعيين foxcage كمتصفحك الافتراضي

أولاً، تأكد من أن السكربت foxcage موجود في موقعه الدائم (مثل ~/bin/foxcage أو /usr/local/bin/foxcage). يقوم أمر التثبيت بتسجيل المسار الحالي للسكربت في ملف .desktop، لذا فإن نقله لاحقاً سيكسر المشغّل.

ثم شغّل:```sh foxcage --install

ينشئ هذا ملف `.desktop` يشير إلى الموقع الحالي للسكربت، ويُثبّت أيقونة foxcage، ويُحدّث قواعد بيانات سطح المكتب والأيقونات. يجب أن يظهر FoxCage بعد ذلك في قائمة التطبيقات لديك.

لتعيين foxcage كمتصفح ويب افتراضي بحيث تُفتح الروابط التي يتم النقر عليها في التطبيقات الأخرى في foxcage:```sh
xdg-settings set default-web-browser foxcage.desktop

إذا كان القفص يعمل بالفعل، تفتح عناوين URL كعلامة تبويب جديدة في المتصفح الحالي.

للتراجع:```sh foxcage --uninstall

تم ضبط `StartupNotify=true` في ملف `.desktop`، وهو ما يخبر compositor بعرض مؤشر تحميل (spinner) أثناء إقلاع foxcage. وعند الحاجة إلى بناء صورة (وهو ما قد يستغرق عدة دقائق)، يرسل foxcage إشعارًا سطح المكتب حتى تعرف أن Firefox في طريقه. كما يتم عرض أي خطأ خروج مبكر (خطأ في كتابة الإعدادات، اعتماد مفتقد، اسم cage تالف) كإشعار سطح المكتب حتى لا يبقى المستخدمون الذين شغّلوا التطبيق من سطح المكتب يحدقون في لا شيء عند فشل foxcage دون وجود طرفية مرفقة. كلاهما يتطلب `notify-send` (من حزمة `libnotify-bin` على Debian/Ubuntu) — إذا لم يكن مثبتًا، يتم تخطي الإشعارات بصمت ويظل الخطأ يُرسل إلى stderr.

<details>
<summary>الإعداد اليدوي</summary>

إذا كنت تفضّل إنشاء ملف `.desktop` يدويًا، فأنشئ `~/.local/share/applications/foxcage.desktop`:```ini
[Desktop Entry]
Type=Application
Name=FoxCage
Comment=Firefox in a rootless Podman container
Exec=/path/to/foxcage %u
Icon=foxcage
MimeType=text/html;x-scheme-handler/http;x-scheme-handler/https;
Terminal=false
Categories=Network;WebBrowser;
StartupNotify=true
StartupWMClass=foxcage

استبدل /path/to/foxcage بالمسار الفعلي للبرنامج النصي. سجّله:```sh update-desktop-database ~/.local/share/applications

</details>

## تشغيل الاختبارات

تستخدم مجموعة الاختبارات pytest + pytest-cov، المُعلنة كتبعيات للتطوير فقط في `requirements-dev.txt`.```
pip install -r requirements-dev.txt
pytest

الاختبارات معزولة تمامًا — لا podman، لا شبكة، لا نظام ملفات حقيقي خارج tmp_path الخاص بـ pytest. تعتمد المجموعة على تغطية أسطر وفروع بنسبة 100% (مُهيأة في pytest.ini و.coveragerc)؛ أي سطر غير مُغطى، أو جانب غير مُختبر من شرط، يفشل التشغيل. يشغّل CI المجموعة عند كل push عبر .gitlab-ci.yml.

Acknowledgements

تم تطوير هذا المشروع بواسطة Mike Cardwell، بمساعدة Claude Code، أداة البرمجة بالذكاء الاصطناعي من Anthropic.

Support/Appreciate my work

الفئات