SSD Nodes Learn Hosting plans →
নির্দেশিকা Matt Connorদ্বারা Matt Connor

Halcyon দিয়ে Jellyfin-কে 90-এর দশকের ভিডিও স্টোর বানান

Halcyon ব্রাউজারে আপনার Jellyfin লাইব্রেরিকে হাঁটা যায় এমন 1990-এর দশকের rental store বানায়। Docker command, reverse proxy ও বাস্তব সীমাবদ্ধতা জানুন।

Halcyon আপনার Jellyfin লাইব্রেরিতে কী করে

Halcyon Video ব্রাউজারে আপনার Jellyfin লাইব্রেরিকে হাঁটা যায় এমন 1990-এর দশকের একটি ভিডিও স্টোর হিসেবে দেখায়। আপনার মালিকানাধীন প্রতিটি চলচ্চিত্র shelf-এর একটি case হয়ে যায়। আপনি strip light-এর নিচে aisle ধরে হাঁটেন, shelf থেকে একটি box নামান, পেছনের অংশে লেখা specification পড়তে সেটি উল্টে দেখেন, তারপর playback শুরু করতে counter-এ নিয়ে যান। Playback-এর start, progress এবং stop তথ্য Jellyfin-এ ফেরত পাঠানো হয়। ফলে resume point এবং watch history সঠিক থাকে।

Halcyon Jellyfin API ব্যবহার করে একটি বিদ্যমান Jellyfin server থেকে তথ্য পড়ে এবং নিজস্ব কোনো library সংরক্ষণ করে না। এই নির্দেশিকায় ধরে নেওয়া হয়েছে যে Jellyfin ইতিমধ্যে চলছে এবং library সঠিকভাবে scan করছে। তা না হলে আগে VPS-এ Jellyfin-কে media server হিসেবে সেট আপ করুন এবং সাধারণ web client-এ library সঠিক দেখানোর পর এখানে ফিরে আসুন। এটি এমন একটি service, যা library আগে থেকেই থাকার কারণে install করবেন; আপনার self-hosting তালিকায় আরেকটি service দরকার বলে নয়।

প্রকল্পটি GPL-3.0-এর অধীনে প্রকাশিত এবং একজন ব্যক্তি এটি লিখেছেন। README-তে স্পষ্টভাবে বলা আছে যে এটি pull request গ্রহণ করে না। Development দ্রুত এগোয় এবং regression শনাক্ত করার মতো দ্বিতীয় কোনো maintainer নেই। তাই অন্য কাউকে store দেখানোর আগে image version pin করুন। শেষ section-এ এর পদ্ধতি দেখানো হয়েছে।

রেন্ডারিং কোথায় হয়?

ব্রাউজারে। Halcyon হলো three.js-এর ওপর তৈরি একটি Vite ও TypeScript অ্যাপ। three.js একটি JavaScript library, যা WebGL-এর মাধ্যমে 3D graphics আঁকে। WebGL-এর পূর্ণরূপ web graphics library; এটি ব্রাউজারের GPU-তে ব্যবহারের interface। Store-এর geometry এবং box art যে মেশিনে screen রয়েছে, সেই মেশিনেই composited হয়।

Container খুব সামান্য কাজ করে। এটি npm run serve চালায়, যা vite preview --port 1420 --strictPort --host, এবং built files-এর সঙ্গে কয়েকটি ছোট middleware route পরিবেশন করে। Halcyon কোনো transcoding যোগ করে না এবং server-এ কোনো engine চালায় না।

তাই GPU-সংক্রান্ত প্রশ্নটি client-এর ক্ষেত্রে প্রযোজ্য। একটি ছোট VPS এটি সহজেই পরিবেশন করতে পারে, কারণ এখানে static files এবং HTTP-এর মাধ্যমে কয়েকটি হালকা route পরিবেশন করাই কাজ। Browser চালানো laptop, tablet বা television-ই নির্ধারণ করে store মসৃণভাবে চলবে, নাকি ধীরে চলবে।

একটি feature এই নিয়মের ব্যতিক্রম। Remote Play-এর private instance server-এ প্রতি visitor-এর জন্য একটি করে headless Chromium চালায় এবং rendered store-টি WebRTC-এর মাধ্যমে phone বা set top box-এ stream করে। WebRTC-এর পূর্ণরূপ web real time communication। এই পথে rendering server-এ হয়। ডিফল্টভাবে সর্বোচ্চ দুইটি instance চলে এবং REMOTE_PLAY_MAX_INSTANCES দিয়ে এই সংখ্যা পরিবর্তন করা যায়। Server-এ GPU-ও প্রয়োজন। প্রকাশিত image-এ কোনো software renderer নেই। তাই mapped /dev/dri render node না থাকলে server private store চালু করতে অস্বীকার করে এবং This store server has no GPU available, so it can’t render a private store. উত্তর দেয়.

আপনার লাইব্রেরি থেকে স্টোর কী তথ্য পড়ে

Aisle-গুলো Jellyfin-এর নিজস্ব structure থেকে আসে। Halcyon আপনার library ও genre অনুযায়ী section সাজায় এবং আপনার BoxSets থেকে sequel-গুলো group করে। প্রতিটি case-এর পেছনে মুদ্রিত spec-গুলো Jellyfin-এ আগে থেকেই থাকা MediaStreams metadata থেকে আসে। তাই Jellyfin-এ যা missing, shelf-এও তা missing।

এতে store আপনার metadata-এর একটি নির্ভরযোগ্য প্রতিচ্ছবি হিসেবে কাজ করে। artwork ও genre আগে থেকেই পূরণ করা Docker Compose-এ একটি arr stack দিয়ে তৈরি library এখানে generic নামের loose file-এর folder-এর তুলনায় অনেক ভালো দেখায়। Photo library-র ক্ষেত্রেও একইভাবে সেটি index করা software-এর ওপর নির্ভরতা থাকে। একই server-এ থাকা still image-এর জন্য PhotoPrism এবং Immich-এর তুলনা করার সময় এই বিষয়টি মনে রাখা দরকার।

ইনস্টল করার আগে video store demo পরীক্ষা করুন

প্রকল্পটিতে একটি demo আছে, যা কোনো server বা login ছাড়াই চলে। বর্তমান source-এ hosted build-টি আপনার মতো library দিয়ে নয়, streaming title দিয়ে তার shelf পূরণ করে। তাই এটিকে store-এর একটি নমুনা হিসেবে দেখুন। নিজের deployment-এর URL-এর শেষে ?demo=1 যোগ করলে এর বদলে একটি synthetic library লোড হবে।

এই synthetic library-ই hardware test। তবে একটি সীমাবদ্ধতা আছে: এটি নিজে থেকেই device অনুযায়ী আকার নির্ধারণ করে। অতিরিক্ত GPU headroom থাকা browser প্রায় 2,000টি title-সহ সম্পূর্ণ store পায়। মাঝারি GPU প্রায় 800টি title পায়। কোনো phone, অথবা যে browser device memory হিসেবে 4 GB বা তার কম জানায়, প্রায় 300টি title পায়। দুর্বল device-এ মসৃণভাবে চলা demo নিজেই ছোট হয়ে গেছে। তাই সেখানে আপনার সম্পূর্ণ library কীভাবে চলবে, সে বিষয়ে এটি খুব কম তথ্য দেয়। কার্যকর test হলো এমন device ব্যবহার করা, যা সম্পূর্ণ store পায়। সেটিতে যদি stutter দেখা যায়, একই আকারের আপনার library-তেও stutter হবে। সে ক্ষেত্রে সমাধান হলো নিচে বর্ণিত 2.5D mode ব্যবহার করা, বড় VPS নেওয়া নয়।

Docker দিয়ে চালান

এটি upstream-এ দেওয়া command।

docker run -d --name halcyon --network host --restart unless-stopped \
  ghcr.io/halcyon-video/halcyon-video

এরপর service চালু হয়েছে কি না পরীক্ষা করুন।

docker logs halcyon
curl -I http://127.0.0.1:1420

Log-এ preview server-কে port 1420-এ listening অবস্থায় দেখা উচিত, এবং curl-এর জন্য HTTP/1.1 200 OK উত্তর পাওয়া উচিত। কোনো container কয়েক সেকেন্ডের মধ্যে exit করলে প্রায় সব ক্ষেত্রেই port-ই কারণ হয়। --strictPort server-কে 1420 দখল হয়ে থাকলে 1421-এ সরে যেতে দেয় না, তাই এটি বন্ধ হয়ে যায়।

Remote Play-এর জন্য --network host প্রয়োজন; store-এর জন্য নয়। Stream পেতে চাওয়া device-কে WebRTC-কে মেশিনের প্রকৃত address জানাতে হয়। Default Docker bridge-এর পেছনে container শুধু নিজের 172.x address-ই জানে, যেটিতে আপনার network-এর কোনো phone পৌঁছাতে পারে না। তাই stream কখনো connect হয় না। আপনি যদি শুধু browser-এ store ব্যবহার করতে চান, তাহলে port publish করুন।

docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
  ghcr.io/halcyon-video/halcyon-video

VPS-এ এটি বেশি উপযোগী default, কারণ host networking ব্যবহার করলে container মেশিনের প্রতিটি interface-এ, public interface-সহ, যুক্ত হয়। এই trade-off-এর বাকি বিষয়গুলো VPS-এ Docker চালানো অংশে দেখানো হয়েছে। reboot-এর পরে store আবার চালু করে --restart unless-stopped; এটি boot-এর সময় চালু হওয়া Compose service-এর মতো একই ধারণা।

Repository clone করে docker compose up -d চালালে image স্থানীয়ভাবে build হয়। Committed Compose file default হিসেবে source থেকে build করে এবং prebuilt image: line-টি comment করা থাকে। Compose-এর অধীনে published image ব্যবহার করতে চাইলে ওই line-এর comment সরিয়ে দিন।

Published image-টি linux/amd64 এবং linux/arm64 উভয়কেই সমর্থন করে। 11 October 2026-এ পরীক্ষা করে দেখা গেছে, বর্তমান 0.25.0 release-এ প্রতিটি platform-এর জন্য আলাদা digest তালিকাভুক্ত আছে। তাই arm64 VPS-এ অন্য host-এর মতো একই command-এ image pull হয়। amd64-only সময়কালটি সংক্ষিপ্ত ছিল: 8 August 2026-এ changelog v0.2.1-এ arm64 image বাদ দিয়েছিল এবং 11 August 2026-এ native runner-এ build করা image হিসেবে v0.4.0-এ তা ফিরিয়ে আনে। ওই সময়ের tag-এ pinned কোনো host-কেই শুধু clone থেকে build করতে হবে।

Jellyfin সার্ভারের ঠিকানা নির্ধারণ করুন

http://<host>:1420 খুলে Jellyfin সার্ভারের address, username এবং password দিয়ে লগ ইন করুন। Repository-র .env.local.example file শুধু local development-এর জন্য। Vite client-side code-এ VITE_ দিয়ে শুরু হওয়া variable প্রকাশ করে। তাই সেখানে লেখা Jellyfin password প্রতিটি visitor যে JavaScript bundle download করে, তার মধ্যে compile হয়ে যায়। অন্যরা reach করতে পারে এমন server-এ interface ব্যবহার করে লগ ইন করুন।

Browser সরাসরি Jellyfin-এর সঙ্গে যোগাযোগ করে। Halcyon-এর container Jellyfin API-র জন্য proxy হিসেবে কাজ করে না। Debugging শুরু করার আগে এর দুটি গুরুত্বপূর্ণ ফলাফল জানা দরকার।

প্রথমত, Jellyfin-কে শুধু Halcyon serve করা VPS থেকে নয়, browser থেকেও reach করতে হবে। 127.0.0.1:8096-এ bound থাকা Jellyfin local test-এর জন্য ঠিক আছে, কিন্তু অন্য সবার জন্য shelves খালি থাকবে।

দ্বিতীয়ত, এই call cross-origin। এটি Halcyon-এর address থেকে Jellyfin-এর address-এ যায়। Jellyfin defaultভাবে API request-এর উত্তর Access-Control-Allow-Origin: * দিয়ে দেয়, তাই অতিরিক্ত configuration ছাড়াই এটি কাজ করে। আপনি যদি ওই setting সীমিত করে থাকেন, অথবা Jellyfin API-র সামনে authentication proxy বসিয়ে থাকেন, browser console-এ blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource দেখাবে এবং store-এ খালি shelves লোড হবে।

Reverse proxy-এর পেছনে রাখুন এবং সামনে authentication দিন

vite preview একটি preview server। এটি TLS (transport layer security) termination করে না এবং নিজস্ব access control নেই। তাই public ব্যবহারের ক্ষেত্রে এটি 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;
  }
}

Container-এর সামনে domain name ব্যবহার করতে আরও একটি setting প্রয়োজন। DNS rebinding প্রতিরোধ করতে Halcyon localhost, raw IP address এবং যে machine-এ এটি চলে তার নামের অনুরোধ গ্রহণ করে। Container-এর ভিতরে যে machine-এ এটি চলে সেটি হলো container নিজেই। তাই সেই hostname আপনার hostname নয়। halcyon.example.com হিসেবে আসা অনুরোধ প্রত্যাখ্যাত হয় এবং response-এ প্রত্যাখ্যাত host-এর নাম দেখানো হয়। সেই নামটি যোগ করুন।

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

Value-টি comma-separated। .example.com-এর মতো শুরুতে থাকা dot subdomain-গুলোর সঙ্গেও match করে। all check-টি বন্ধ করে। all কেবল এমন machine-এ ব্যবহার করুন, যেটিতে বাইরের কোনো উৎস থেকে পৌঁছানো যায় না।

Store-টি https://-এর মাধ্যমে serve করার পর login-এর সময় দেওয়া Jellyfin address-ও https:// হতে হবে। HTTPS page থেকে করা plain http:// API call browser block করে এবং console-এ Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource দেখা যায়। Halcyon-এর ভিতরে কোনো ব্যাখ্যা ছাড়াই login ব্যর্থ হয়। উভয়টিই TLS-এর মাধ্যমে serve করুন, অথবা private network-এর ভিতরে উভয়টিই plain HTTP-তে রাখুন।

এরপর authentication। Store Jellyfin credential চায়। তাই URL খুঁজে পাওয়া কোনো অপরিচিত ব্যক্তি login screen দেখতে পাবে। একটি feature এই আচরণ বদলে দেয়। Settings এবং তারপর Connection-এর অধীনে Remote Play চালু করলে আপনার Jellyfin session server-কে দেওয়া হয়। ফলে /remote.html-এ আসা visitor আপনার আসল library-এর নিজস্ব instance পায়। Feature-টির উদ্দেশ্যই এটি। এর অর্থ হলো URL-এর গোপনীয়তাই internet এবং আপনার film library-এর মাঝের একমাত্র বাধা। Remote Play চালু করলে পুরো site-এর সামনে single sign-on রাখুন; এ জন্য Authentik-কে self-hosted SSO gateway হিসেবে ব্যবহার করুন, অথবা public hostname বাদ দিয়ে wg-easy দিয়ে পরিচালিত WireGuard tunnel-এর মাধ্যমে store-এ পৌঁছান।

এটির সঙ্গে আরও দুটি বিষয় মনে রাখতে হবে। Reverse proxy কেবল store বহন করে। Remote Play stream WebRTC over UDP এবং HTTP proxy-এর মধ্য দিয়ে যায় না। তাই 3478/udp-এ এবং bundled TURN relay ব্যবহৃত হলে 49200 থেকে 49260/udp-এ এর নিজস্ব path প্রয়োজন। এছাড়া উপরের plain docker run কোনো volume সংরক্ষণ করে না। তাই Remote Play seed docker rm-এর পর টিকে থাকে না। এই কারণেই Compose file একটি halcyon-data volume-কে /data-এ mount করে এবং REMOTE_PLAY_SEED-এর মান /data/remote-play-seed.json নির্ধারণ করে।

স্টোর ধীরগতিতে চললে কী করবেন

Halcyon প্রয়োজন অনুযায়ী রেন্ডার করে। নিষ্ক্রিয় স্টোর কোনো frame composite করে না। Window focus হারালে animation loop বন্ধ হয়ে যায়। তাই কোনো tab খোলা থাকলেও laptop battery দ্রুত শেষ হয় না। শুধু সীমার কাছাকাছি সক্ষমতার মেশিনের ক্ষেত্রে এটি সহায়ক। কিন্তু কোনো মেশিন যদি স্টোর একেবারেই render করতে না পারে, তাহলে এতে কোনো উপকার হয় না।

এই client-গুলোর জন্য 2.5D mode রয়েছে। এতে WebGL ছাড়া সাধারণ HTML এবং CSS ব্যবহার করা হয়। Raspberry Pi-এর মতো সীমিত hardware-এর জন্য এটি তৈরি। Page reload ছাড়াই settings অথবা power menu থেকে 3D এবং 2.5D-এর মধ্যে পরিবর্তন করা যায়। তাই একই device-এ উভয় mode পরীক্ষা করতে কয়েক সেকেন্ডই লাগে। তবে প্রত্যাশা বাস্তবসম্মত রাখুন। August 2026-এ লেখক flat mode-কে অপরিণত বলেছেন এবং জানিয়েছেন যে project-এর কাজ তখনও প্রাথমিক পর্যায়ে ছিল। দুর্বল client-এর জন্য এটিকে fallback হিসেবে ব্যবহার করুন।

কোনো client 3D store চালানোর জন্য খুব সীমিত হলে ব্যর্থতা স্পষ্টভাবে দেখা যায়। Tab নিজে থেকে reload হয়। অথবা browser lost WebGL context-এর বার্তা দেখায়। সাধারণত shelves পূরণ হওয়ার সময় এটি ঘটে। Library ছোট করার পরিবর্তে ওই device-কে 2.5D mode-এ স্থানান্তর করুন।

ইমেজ pin করুন এবং pull করার আগে যাচাই করুন

এই অংশটি গুরুত্বসহকারে নিন। v0.1.0 থেকে v0.3.1 পর্যন্ত tag-গুলো কয়েক দিনের ব্যবধানে প্রকাশিত হয়েছে, আর v0.2.1-এর অস্তিত্বের কারণ শুধু এই যে v0.2.0-এর image push ব্যর্থ হয়েছিল। এরপরও গতি কমেনি: v0.25.0 7 October 2026-এ প্রকাশিত হয়েছে, v0.23.0-এর মাত্র তিন দিন পরে। Upstream-এ bug report গ্রহণ করা হয়, কিন্তু patch নয়। তাই release stream-টি একজনের বর্তমান working state-এর ওপর নির্ভরশীল।

docker pull করার অভ্যাস নিয়ে latest চালালে যেকোনো সাধারণ মঙ্গলবারে store আপনার অজান্তে পরিবর্তিত হতে পারে। Digest দিয়ে pin করুন। এটিই একমাত্র reference, যা পরিবর্তন করা যায় না।

docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.25.0

এটি tag-এর পেছনে থাকা digest দেখায়। Tag-এর পরিবর্তে এটি ব্যবহার করুন।

docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
  ghcr.io/halcyon-video/halcyon-video@sha256:7fc00e619a30004d275faa79ad36d810e5a8530757132603065ba4e848dd67de

11 October 2026-এ এটিই 0.25.0 tag-এর পেছনে থাকা digest ছিল। এটি কপি না করে নিজে বর্তমান digest পড়ুন। পরিবর্তন করার আগে release notes-ও পড়ুন, কারণ এখানে একটি patch release-এ fix-এর পাশাপাশি store layout-এর পরিবর্তনও থাকতে পারে।

FAQ

Halcyon চালাতে কি আমার VPS-এ GPU প্রয়োজন?

সাধারণ ব্যবহারের জন্য নয়। ব্রাউজারে three.js দিয়ে store আঁকা হয়, তাই rendering client machine-এ হয়। Container built files এবং port 1420-এ কয়েকটি হালকা server route পরিবেশন করে। ব্যতিক্রম হলো Remote Play-এর private instances। এগুলো server-এ headless Chromium চালিয়ে ফলাফল stream করে। প্রকাশিত image ব্যবহার করলে এই পথে /dev/dri container-এ map করতে হয়। GPU না থাকলে server private store start করতে অস্বীকার করে।

Halcyon কি public internet-এ রাখা যাবে?

শুধু authentication-এর পেছনে রাখলে। Store Jellyfin credentials চায়। কিন্তু Remote Play চালু করলে আপনার Jellyfin session server-কে দেওয়া হয়। ফলে /remote.html load করা যে কেউ login ছাড়াই আপনার আসল library-এর একটি instance পেয়ে যায়। সামনে single sign on-সহ একটি reverse proxy বসান। অথবা hostname-কে public DNS-এর বাইরে রেখে VPN-এর মাধ্যমে store-এ প্রবেশ করুন।

Login করার পরে shelves খালি কেন?

Browser সরাসরি Jellyfin API-তে call করে। তাই Jellyfin-কে শুধু VPS থেকে নয়, browser থেকেও reachable হতে হবে। Browser console খুলুন। blocked by CORS policy-এর অর্থ হলো Jellyfin Halcyon-এর address থেকে request গ্রহণ করছে না। Mixed Content message-এর অর্থ হলো page-টি HTTPS-এ চলছে, কিন্তু আপনার দেওয়া Jellyfin address plain HTTP।

আমার কি --network host প্রয়োজন?

শুধু Remote Play-এর জন্য। WebRTC-কে machine-এর আসল address advertise করতে হয়। Docker bridge-এর পেছনে container কেবল এমন একটি 172.x address দিতে পারে, যেটিতে আপনার network-এর কোনো phone পৌঁছাতে পারে না। Browser-এ store browse করার জন্য -p 1420:1420 যথেষ্ট এবং এতে host-এর অনেক কম অংশ exposed হয়।

কোন image tag ব্যবহার করা উচিত?

latest-এর পরিবর্তে একটি digest pin করুন। docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.25.0-সহ কোনো version-এর digest পড়ে দেখুন। সেই digest চালান এবং release notes পড়ার পরেই পরিবর্তন করুন। 11 October 2026 অনুযায়ী প্রকাশিত image-এ linux/amd64 এবং linux/arm64 উভয়ই রয়েছে। তাই arm64 host অন্য যেকোনো image-এর মতো এটিও pull করতে পারে এবং clone থেকে build করার প্রয়োজন হয় না।