SSD Nodes Learn Hosting plans →
الأدلة Matt Connorبقلم Matt Connor

حوّل 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. إذا لم يكن هناك جهاز /dev/dri معيَّن، فستصيّر هذه المثيلات على CPU، ولذلك يواجه VPS ذو نواتين أثر كل مشاهد إضافي.

ما يقرأه المتجر من مكتبتك

تأتي الممرات من بنية Jellyfin نفسها. يعرض Halcyon الأقسام من مكتباتك وأنواع المحتوى، ويجمع الأجزاء المتتابعة من BoxSets. وتأتي المواصفات المطبوعة على ظهر كل غلاف من بيانات MediaStreams الوصفية التي يحتفظ بها Jellyfin مسبقاً، ما يعني أن أي بيانات مفقودة في Jellyfin ستكون مفقودة على الرف.

وهذا يجعل المتجر انعكاساً دقيقاً لبياناتك الوصفية. تبدو المكتبة التي يزوّدها مكدس arr في Docker Compose، مع اكتمال الأعمال الفنية والأنواع فيها، أفضل بكثير هنا من مجلد يحتوي على ملفات مبعثرة ذات أسماء عامة. وتعتمد مكتبات الصور أيضاً على الأداة التي فهرستها، ومن المفيد تذكّر ذلك عند المفاضلة بين PhotoPrism وImmich للصور الثابتة الموجودة على الخادم نفسه.

جرّب العرض التوضيحي لمتجر الفيديو قبل تثبيت أي شيء

ينشر المشروع المتجر كاملاً وهو يعمل باستخدام مكتبة اصطناعية على العرض التوضيحي المستضاف. تؤدي إضافة ?demo=1 إلى أي عنوان URL في Halcyon الوظيفة نفسها في عملية النشر الخاصة بك.

استخدمه لاختبار العتاد، مع ملاحظة مهمة: يضبط العرض التوضيحي حجمه وفقاً للجهاز. يحصل المتصفح الذي يملك سعة كافية في 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 bridge الافتراضي، لا تعرف الحاوية سوى عنوان 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. ووفق التحقق الذي أُجري في 9 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 من الإعدادات أو قائمة الطاقة من دون إعادة تحميل الصفحة، ولذلك يستغرق اختبار الوضعيتين على الجهاز نفسه بضع ثوانٍ. كن واقعياً بشأن النتيجة: يصف المؤلف الوضعية المسطحة بأنها بدائية ولا تزال قيد التطوير. تعامل معها كخيار احتياطي للأجهزة الضعيفة.

عندما تكون إمكانات الجهاز غير كافية لتشغيل متجر 3D، يكون الفشل واضحاً. تعيد علامة التبويب تحميل نفسها، أو يعرض المتصفح رسالة تفيد بفقدان سياق 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

كان ذلك الـdigest هو 0.25.0 في 9 October 2026. اقرأ القيمة الحالية بنفسك بدلاً من نسخها، واقرأ ملاحظات الإصدار قبل الترقية، لأن إصدار التصحيح هنا قد يتضمن تغييرات في تخطيط المخزن إلى جانب الإصلاحات.

FAQ

هل يحتاج Halcyon إلى GPU على VPS الخاص بي؟

ليس للاستخدام العادي. يرسم three.js المتجر في المتصفح، لذلك ينفّذ جهاز العميل عملية التصيير، بينما يقدّم الـcontainer الملفات الثابتة فقط على المنفذ 1420. الاستثناء هو Remote Play، الذي يشغّل Chromium بوضع headless على الخادم ويبث النتيجة. ينفّذ هذا المسار التصيير باستخدام CPU، ما لم تربط /dev/dri بالـcontainer لتفعيل تسريع العتاد.

هل يمكنني وضع Halcyon على الإنترنت العام؟

فقط خلف المصادقة. يطلب المتجر بيانات اعتماد Jellyfin، لكن تفعيل Remote Play يسلّم جلسة Jellyfin الخاصة بك إلى الخادم، لذلك يحصل أي شخص يحمّل /remote.html على نسخة من مكتبتك الفعلية دون تسجيل الدخول. ضع reverse proxy مع single sign-on أمامه، أو أبقِ اسم المضيف خارج DNS العام، والوصول إلى المتجر عبر VPN.

لماذا تكون الرفوف فارغة بعد تسجيل الدخول؟

يستدعي المتصفح API الخاص بـJellyfin مباشرة، لذلك يجب أن يكون Jellyfin قابلاً للوصول من المتصفح، لا من VPS فقط. افتح وحدة تحكم المتصفح. يعني blocked by CORS policy أن Jellyfin لا يقبل الطلب من عنوان Halcyon. وتعني رسالة Mixed Content أن الصفحة تستخدم HTTPS، بينما عنوان Jellyfin الذي أدخلته يستخدم HTTP عادي.

هل أحتاج إلى --network host؟

فقط من أجل Remote Play. يجب أن يعلن WebRTC عن العنوان الفعلي للجهاز، لكن الـcontainer خلف Docker bridge لا يستطيع تقديم سوى عنوان 172.x لا يمكن لأي هاتف على شبكتك الوصول إليه. أما لتصفح المتجر في المتصفح، فيعمل -p 1420:1420 ويكشف جزءاً أقل بكثير من المضيف.

ما وسم الصورة الذي ينبغي أن أستخدمه؟

ثبّت digest بدلاً من latest. اقرأ digest لإصدار يتضمن docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.25.0، وشغّل ذلك digest، ولا تنتقل إلى إصدار آخر إلا بعد قراءة ملاحظات الإصدار. اعتباراً من 9 October 2026، تتضمن الصورة المنشورة كلاً من linux/amd64 وlinux/arm64، لذلك يسحبها مضيف arm64 مثل أي صورة أخرى، ولا يحتاج إلى بنائها من clone.