حوّل Jellyfin إلى متجر فيديو من التسعينيات مع Halcyon
اكتشف كيف يعرض Halcyon مكتبة Jellyfin كمتجر فيديو قابل للتجول، مع أمر Docker، وإعداد reverse proxy، وتحذيرات صريحة حول مشروع يطوره شخص واحد.
ما الذي يفعله Halcyon بمكتبة Jellyfin لديك
يعيد Halcyon Video عرض مكتبة Jellyfin لديك على شكل متجر فيديو من تسعينيات القرن العشرين يمكن التجول فيه عبر المتصفح. يتحول كل فيلم تملكه إلى علبة على رف. تتجول بين الممرات تحت مصابيح الشرائط، وتسحب علبة، وتقلبها لقراءة المواصفات على ظهرها، ثم تحملها إلى المنضدة لبدء التشغيل. يرسل التشغيل تقارير البدء والتقدم والإيقاف إلى Jellyfin، لذلك تظل نقاط الاستئناف وسجل المشاهدة صحيحين.
يقرأ Halcyon خادم Jellyfin موجوداً عبر Jellyfin API، ولا يحتفظ بمكتبة خاصة به. يفترض هذا الدليل أن Jellyfin يعمل بالفعل ويفحص الملفات دون أخطاء. إذا لم يكن كذلك، فأعد إعداد Jellyfin كخادم وسائط على VPS أولاً، ثم عد بعد أن تبدو مكتبتك صحيحة في عميل الويب العادي. هذا النوع من الأدوات تثبّته لأن المكتبة موجودة بالفعل، لا لأنك تحتاج إلى خدمة أخرى في قائمة خدمات self hosting لديك.
المشروع مرخّص بموجب GPL-3.0، ويطوّره شخص واحد، وينص README بوضوح على أنه لا يقبل pull requests. يتقدم التطوير بسرعة، ولا يوجد maintainer ثانٍ لاكتشاف حالات التراجع، لذلك ثبّت إصدار image قبل أن تعرض المتجر على أي شخص آخر. يوضح القسم الأخير كيفية تنفيذ ذلك.
أين يحدث التصيير؟
في المتصفح. Halcyon هو تطبيق Vite وTypeScript مبني على three.js، وهي مكتبة JavaScript ترسم الرسومات ثلاثية الأبعاد عبر WebGL (مكتبة الرسومات على الويب، وهي واجهة المتصفح إلى GPU). تُركِّب الآلة التي تشغّل الشاشة هندسة المتجر وصور الأغلفة.
لا تنفّذ الحاوية إلا القليل. فهي تشغّل npm run serve، وهو vite preview --port 1420 --strictPort --host، وتقدّم الملفات المبنية وبعض مسارات middleware الصغيرة. لا يضيف Halcyon أي transcoding، ولا يشغّل أي engine على الخادم.
لذلك يتعلق سؤال GPU بالعميل. يستطيع VPS صغير تقديم هذا التطبيق بسهولة، لأن تقديمه يعني إرسال ملفات ثابتة وبعض المسارات الخفيفة عبر HTTP. أما الحاسوب المحمول أو الجهاز اللوحي أو التلفزيون الذي يشغّل المتصفح، فهو الذي يحدد ما إذا كان المتجر سيعمل بسلاسة أو ببطء شديد.
تخرق إحدى الميزات هذه القاعدة. تنشئ مثيلات Remote Play الخاصة Chromium بلا واجهة على الخادم، مثيلاً واحداً لكل زائر، وتبث المتجر المُصيَّر إلى هاتف أو جهاز استقبال عبر WebRTC (اتصالات الويب الآنية). يحدث التصيير في الخادم ضمن هذا المسار، ويُحدَّد عدد المثيلات باثنين افتراضياً، ويمكن ضبطه باستخدام REMOTE_PLAY_MAX_INSTANCES. يحتاج الخادم هناك أيضاً إلى GPU. لا تتضمن الصورة المنشورة software renderer، لذلك يرفض الخادم بدء متجر خاص من دون render node معيّن عبر /dev/dri، ويردّ بـThis store server has no GPU available, so it can’t render a private store.
ما يقرأه المتجر من مكتبتك
تأتي الممرات من بنية Jellyfin نفسها. يعرض Halcyon الأقسام من مكتباتك وأنواع المحتوى، ويجمع الأجزاء المتتابعة من BoxSets. وتأتي المواصفات المطبوعة على ظهر كل غلاف من بيانات MediaStreams الوصفية التي يحتفظ بها Jellyfin مسبقاً، ما يعني أن أي بيانات مفقودة في Jellyfin ستكون مفقودة على الرف.
وهذا يجعل المتجر انعكاساً دقيقاً لبياناتك الوصفية. تبدو المكتبة التي يزوّدها مكدس arr في Docker Compose، مع اكتمال الأعمال الفنية والأنواع فيها، أفضل بكثير هنا من مجلد يحتوي على ملفات مبعثرة ذات أسماء عامة. وتعتمد مكتبات الصور أيضاً على الأداة التي فهرستها، ومن المفيد تذكّر ذلك عند المفاضلة بين PhotoPrism وImmich للصور الثابتة الموجودة على الخادم نفسه.
جرّب العرض التجريبي لمتجر الفيديو قبل تثبيت أي شيء
يستضيف المشروع عرضاً تجريبياً يعمل من دون خادم ومن دون تسجيل دخول. في المصدر الحالي، يملأ الإصدار المستضاف رفوفه بعناوين بث، وليس بمكتبة مماثلة لمكتبتك، لذلك استخدمه للتعرّف على المتجر فقط. تؤدي إضافة ?demo=1 إلى عنوان URL الخاص بعملية النشر لديك إلى تحميل مكتبة اصطناعية بدلاً من ذلك.
تُستخدم هذه المكتبة الاصطناعية لاختبار الأجهزة، مع وجود قيد واحد: فهي تضبط حجمها وفقاً للجهاز. يحصل المتصفح الذي تتوفر لديه سعة GPU زائدة على المتجر الكامل، الذي يضم نحو 2,000 عنوان. ويحصل جهاز GPU متوسط الأداء على نحو 800 عنوان. أما الهاتف، أو أي متصفح يبلّغ عن ذاكرة جهاز بسعة 4 GB أو أقل، فيحصل على نحو 300 عنوان. إذا عمل العرض التجريبي بسلاسة على جهاز ضعيف، فهذا يعني أنه صغّر نفسه بالفعل، ولذلك لا يوضح كثيراً كيف ستعمل مكتبتك الكاملة عليه. الاختبار المفيد هو استخدام جهاز يحصل على المتجر الكامل. إذا ظهرت فيه تقطعات، فستظهر التقطعات نفسها في مكتبتك ذات الحجم المماثل، والحل هو وضع 2.5D الموضح أدناه، وليس استخدام VPS أكبر.
شغّله باستخدام Docker
هذا هو الأمر الذي توثّقه الجهة المطوِّرة.
docker run -d --name halcyon --network host --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-videoثم تحقّق من أنه بدأ.
docker logs halcyon
curl -I http://127.0.0.1:1420يجب أن يُظهر السجل أن خادم المعاينة يستمع على المنفذ 1420، وأن curl يستجيب بـ HTTP/1.1 200 OK. إذا توقفت الحاوية خلال بضع ثوانٍ، فغالباً ما يكون السبب هو المنفذ. يعني --strictPort أن الخادم يرفض الانتقال إلى 1421 عندما يكون 1420 مستخدماً، ولذلك يتوقف بدلاً من ذلك.
يُستخدم --network host مع Remote Play، وليس مع المتجر. يجب أن يعلن WebRTC عن العنوان الحقيقي للجهاز إلى الجهاز الذي يريد استقبال البث. خلف جسر Docker الافتراضي، لا تعرف الحاوية سوى عنوان 172.x الخاص بها، ولا يستطيع أي هاتف على شبكتك الوصول إليه، ولذلك لا يتصل البث أبداً. إذا كنت تريد المتجر في متصفح فقط، فانشر المنفذ بدلاً من ذلك.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-videoهذا هو الإعداد الافتراضي الأفضل على VPS، لأن استخدام شبكة المضيف يضع الحاوية على كل واجهة لدى الجهاز، بما في ذلك الواجهة العامة. يشرح تشغيل Docker على VPS بقية جوانب هذا الاختيار. يعيد --restart unless-stopped تشغيل المتجر بعد إعادة التشغيل، وفق الفكرة نفسها الموضحة في خدمات Compose التي تبدأ عند الإقلاع.
يؤدي استنساخ المستودع وتشغيل docker compose up -d إلى إنشاء الصورة محلياً بدلاً من ذلك. ينشئ ملف Compose المضمَّن الصورة من المصدر افتراضياً، ويحتوي على سطر image: للصورة المنشأة مسبقاً وهو معلّق بالتعليق. أزل التعليق عن ذلك السطر إذا أردت استخدام الصورة المنشورة مع Compose.
تدعم الصورة المنشورة كلاً من linux/amd64 وlinux/arm64. وفق التحقق الذي أُجري في 11 October 2026، يسرد إصدار 0.25.0 الحالي digest منفصلاً لكل منصة، ولذلك يسحب VPS يعمل على arm64 الصورة باستخدام الأمر نفسه المستخدم على أي مضيف آخر. كانت فترة دعم amd64 فقط قصيرة: أزال سجل التغييرات صورة arm64 في v0.2.1 بتاريخ 8 August 2026، ثم أعادها، بعد إنشائها على runners أصلية، في v0.4.0 بتاريخ 11 August 2026. لا يحتاج إلى الإنشاء من النسخة المستنسخة إلا مضيف ثبّت tag من تلك الفترة.
وجِّهه إلى خادم Jellyfin لديك
افتح http://<host>:1420 وسجّل الدخول باستخدام عنوان خادم Jellyfin واسم المستخدم وكلمة المرور. ملف .env.local.example في المستودع مخصّص للتطوير المحلي فقط. يعرّض Vite المتغيرات التي تبدأ بالبادئة VITE_ إلى الشيفرة من جهة العميل، لذلك تُضمَّن كلمة مرور Jellyfin المكتوبة فيه داخل حزمة JavaScript التي ينزّلها كل زائر. على خادم يمكن لأشخاص آخرين الوصول إليه، سجّل الدخول من خلال الواجهة.
يتصل المتصفح بـJellyfin مباشرة. لا تعمل حاوية Halcyon كوسيط لواجهة Jellyfin البرمجية، ولهذا نتيجتان يجب معرفتهما قبل بدء تصحيح الأخطاء.
أولاً، يجب أن يكون Jellyfin قابلاً للوصول من المتصفح، وليس من VPS الذي يقدّم Halcyon فقط. يكون ربط Jellyfin بالعنوان 127.0.0.1:8096 مناسباً للاختبار المحلي، لكنه يترك الرفوف فارغة لجميع المستخدمين الآخرين.
ثانياً، الطلب عابر للأصل، من عنوان Halcyon إلى عنوان Jellyfin. يجيب Jellyfin عن طلبات API باستخدام Access-Control-Allow-Origin: * افتراضياً، ولذلك يعمل دون إعداد إضافي. إذا قلّصت هذا الإعداد، أو وضعت authentication proxy أمام API الخاص بـJellyfin، فستعرض وحدة تحكم المتصفح blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource، وتُحمّل الواجهة برفوف فارغة.
ضعه خلف Reverse Proxy مع مصادقة أمامية
vite preview هو خادم معاينة. لا ينهي TLS (أمان طبقة النقل)، ولا يوفّر آلية للتحكم في الوصول من تلقاء نفسه. لذلك يجب وضعه خلف nginx أو Caddy عند إتاحته للعامة.
server {
listen 443 ssl;
server_name halcyon.example.com;
location / {
proxy_pass http://127.0.0.1:1420;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}يتطلب استخدام اسم نطاق أمام الحاوية إعداداً إضافياً. يستجيب Halcyon لاسم localhost وعناوين IP الخام وأسماء الجهاز الذي يعمل عليه، وذلك للحماية من إعادة ربط DNS. داخل الحاوية، يكون الجهاز الذي يعمل عليه هو الحاوية نفسها، ولذلك لا يكون اسم المضيف هو اسمك. يُرفض الطلب الذي يصل باعتباره halcyon.example.com، وتذكر الاستجابة اسم المضيف الذي رُفض. أضف ذلك الاسم.
docker run -d --name halcyon -p 127.0.0.1:1420:1420 --restart unless-stopped \
-e HALCYON_ALLOWED_HOSTS=halcyon.example.com \
ghcr.io/halcyon-video/halcyon-videoتُفصل القيم بفواصل. تطابق النقطة في البداية، مثل .example.com، النطاقات الفرعية. وتعطّل all هذا الفحص. استخدم all فقط على جهاز لا يمكن لأي جهة خارجية الوصول إليه.
بعد تقديم المتجر عبر https://، يجب أن يكون عنوان Jellyfin الذي تدخله عند تسجيل الدخول هو https:// أيضاً. يحظر المتصفح طلب API عاديًا من نوع http:// صادراً من صفحة HTTPS، وتعرض وحدة التحكم Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource. يفشل تسجيل الدخول ببساطة، من دون توضيح داخل Halcyon. قدّم كليهما عبر TLS، أو استخدم HTTP العادي لكليهما داخل شبكة خاصة.
ثم تأتي المصادقة. يطلب المتجر بيانات اعتماد Jellyfin، ولذلك سيواجه أي شخص يعثر على عنوان URL شاشة تسجيل الدخول. تغيّر إحدى الميزات هذا الوضع. يؤدي تفعيل Remote Play، من Settings ثم Connection، إلى منح خادم المتجر جلسة Jellyfin الخاصة بك، بحيث يحصل زوار /remote.html على نسخة خاصة بهم من مكتبتك الفعلية. هذه هي وظيفة الميزة، وتعني أن سرية عنوان URL هي الحاجز الفاصل بين الإنترنت وأفلامك. إذا فعّلت Remote Play، فضع single sign-on أمام الموقع بالكامل باستخدام Authentik كبوابة SSO مستضافة ذاتياً، أو أزل اسم المضيف العام واصل إلى المتجر عبر نفق WireGuard تديره wg-easy.
يرتبط بذلك تفصيلان. يمرر Reverse Proxy المتجر فقط. أما بث Remote Play فهو WebRTC عبر UDP، ولا يمر عبر HTTP proxy، لذلك يحتاج إلى مساره الخاص على 3478/udp وعلى 49200 إلى 49260/udp عند استخدام TURN relay المضمّن. كما أن docker run العادي المذكور أعلاه لا يحتفظ بأي volume، ولذلك لا يبقى seed الخاص بـRemote Play بعد docker rm. يربط ملف Compose volume من نوع halcyon-data بالمسار /data، ويضبط REMOTE_PLAY_SEED على /data/remote-play-seed.json لهذا السبب تحديداً.
ما الذي تفعله عندما يعمل المتجر بشكل سيئ
يعتمد Halcyon على التصيير عند الطلب. لا ينشئ المتجر الخامل أي إطارات، ويؤدي فقدان تركيز النافذة إلى إيقاف حلقة الحركة. لذلك لا يؤدي ترك علامة تبويب مفتوحة إلى استنزاف بطارية الحاسوب المحمول. يساعد ذلك الجهاز الذي يعمل عند الحد الأدنى المقبول. لكنه لا يفيد الجهاز الذي لا يستطيع تصيير المتجر على الإطلاق.
تتوفر لهؤلاء العملاء وضعية 2.5D، وهي تعتمد على HTML وCSS فقط من دون WebGL، وصُممت لتعمل حتى على أجهزة صغيرة مثل Raspberry Pi. يمكنك التبديل بين 3D و2.5D من الإعدادات أو قائمة الطاقة من دون إعادة تحميل الصفحة. لذلك يستغرق اختبار الوضعين على الجهاز نفسه بضع ثوانٍ. كن واقعياً بشأن النتيجة: في August 2026 وصف المؤلف الوضع المسطح بأنه غير مصقول، وقال إن المشروع لا يزال في بدايته تقريباً. تعامل معه كخيار احتياطي للعملاء ضعيفي الإمكانات.
عندما يكون العميل أضعف من أن يشغّل المتجر ثلاثي الأبعاد، يكون الفشل واضحاً. تعيد علامة التبويب تحميل نفسها، أو يبلغ المتصفح عن فقدان سياق WebGL، وعادةً يحدث ذلك بينما لا تزال الأرفف تمتلئ. انقل ذلك الجهاز إلى 2.5D بدلاً من تقليص مكتبتك.
ثبّت صورة الحاوية، وتحقّق قبل السحب
تعامل مع هذا الجزء بجدية. وصلت الوسوم من v0.1.0 إلى v0.3.1 خلال أيام قليلة من بعضها، وظهر v0.2.1 فقط لأن دفع الصورة الخاصة بـv0.2.0 فشل. ولم تتباطأ الوتيرة منذ ذلك الحين: نُشر v0.25.0 في 7 October 2026، أي بعد ثلاثة أيام من v0.23.0. نرحّب بتقارير الأخطاء في المشروع الأساسي، لكن لا تُقبل الرقع، لذلك يعكس مسار الإصدارات حالة عمل شخص واحد.
يعني تشغيل latest مع الاعتماد على docker pull أن يتغير المستودع دون علمك في أي يوم عادي. ثبّت الصورة باستخدام digest، فهو المرجع الوحيد الذي لا يمكن أن يتغير.
docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.25.0يطبع ذلك digest المرتبط بالوسم. استخدمه بدلاً من الوسم.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video@sha256:7fc00e619a30004d275faa79ad36d810e5a8530757132603065ba4e848dd67deفي 11 October 2026، كان ذلك هو digest المرتبط بالوسم 0.25.0. اقرأ القيمة الحالية بنفسك بدلاً من نسخها، واقرأ ملاحظات الإصدار قبل التحديث، لأن إصدار التصحيح هنا قد يتضمن تغييرات في تخطيط المستودع إلى جانب الإصلاحات.
FAQ
هل يحتاج Halcyon إلى GPU على VPS الخاص بي؟
ليس للاستخدام العادي. يرسم المتصفح المتجر باستخدام three.js، لذلك ينفّذ جهاز العميل عملية التصيير، بينما يقدّم الـcontainer الملفات المبنية وبعض مسارات الخادم الخفيفة على المنفذ 1420. الاستثناء هو الحالات الخاصة في Remote Play، التي تشغّل Chromium بلا واجهة على الخادم وتبث النتيجة. مع الصورة المنشورة، يحتاج هذا المسار إلى ربط /dev/dri بالـcontainer. من دون GPU، يرفض الخادم بدء تشغيل متجر خاص.
هل يمكنني وضع Halcyon على الإنترنت العام؟
فقط خلف المصادقة. يطلب المتجر بيانات اعتماد Jellyfin، لكن تفعيل Remote Play يرسل جلسة Jellyfin الخاصة بك إلى الخادم، لذلك يحصل أي شخص يحمّل /remote.html على نسخة من مكتبتك الفعلية من دون تسجيل الدخول. ضع Reverse Proxy مع تسجيل دخول موحّد أمامه، أو أبقِ اسم المضيف خارج DNS العام، وصِل إلى المتجر عبر VPN.
لماذا تكون الرفوف فارغة بعد تسجيل الدخول؟
يستدعي المتصفح واجهة Jellyfin البرمجية مباشرة، لذلك يجب أن يكون Jellyfin قابلاً للوصول من المتصفح، لا من VPS فقط. افتح وحدة تحكم المتصفح. يعني blocked by CORS policy أن Jellyfin لا يقبل الطلب من عنوان Halcyon. وتعني رسالة Mixed Content أن الصفحة تستخدم HTTPS، بينما عنوان Jellyfin الذي أدخلته يستخدم HTTP عادي.
هل أحتاج إلى --network host؟
فقط من أجل Remote Play. يجب أن يعلن WebRTC عن العنوان الفعلي للجهاز، لكن الـcontainer لا يستطيع، خلف جسر Docker، تقديم سوى عنوان 172.x لا يمكن لأي هاتف على شبكتك الوصول إليه. لتصفح المتجر في المتصفح، يعمل -p 1420:1420 ويكشف جزءاً أقل بكثير من المضيف.
ما وسم الصورة الذي ينبغي أن أستخدمه؟
ثبّت digest بدلاً من latest. اقرأ digest لإصدار يتضمن docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.25.0، وشغّل ذلك الـdigest، ولا تنتقل إلى إصدار آخر إلا بعد قراءة ملاحظات الإصدار. اعتباراً من 11 October 2026، تتضمن الصورة المنشورة كلاً من linux/amd64 وlinux/arm64، لذلك يسحبها مضيف arm64 مثل أي صورة أخرى، ولا يحتاج إلى البناء من النسخة المستنسخة.