Halcyon: חנות וידאו משנות ה־90 לספריית Jellyfin
הפכו את ספריית Jellyfin לחנות וידאו תלת־ממדית בדפדפן. המדריך כולל פקודת Docker, הגדרת reverse proxy והסברים על קיבוע גרסה ומגבלות הפרויקט.
מה Halcyon עושה לספריית Jellyfin שלך
Halcyon Video מציג מחדש את ספריית Jellyfin שלך כחנות וידאו משנות ה־1990 שאפשר להסתובב בה דרך הדפדפן. כל סרט שבבעלותך הופך לקופסה על מדף. אפשר ללכת בין המעברים תחת תאורת הפלורסנט, להוריד קופסה מהמדף, להפוך אותה כדי לקרוא את המפרט בגב, ולשאת אותה לדלפק כדי להתחיל את ההפעלה. דיווחי ההפעלה על התחלה, התקדמות ועצירה נשלחים חזרה אל Jellyfin, ולכן נקודות ההמשך והיסטוריית הצפייה נשארות תקינות.
Halcyon קורא שרת Jellyfin קיים דרך ה־Jellyfin API ואינו מחזיק ספרייה משלו. המדריך הזה מניח ש־Jellyfin כבר פועל וסורק את המדיה ללא שגיאות. אם לא, הגדירו תחילה את Jellyfin כשרת מדיה ב־VPS, וחזרו לאחר שהספרייה מוצגת כראוי בלקוח האינטרנט הרגיל. זהו שירות שמתקינים משום שהספרייה כבר קיימת, ולא משום שנדרש לכם שירות נוסף ב־רשימת שירותי האירוח העצמי שלכם.
הפרויקט מופץ ברישיון GPL-3.0 ונכתב בידי אדם אחד, וב־README מצוין במפורש שהוא אינו מקבל pull requests. הפיתוח מתקדם במהירות, ואין מתחזק נוסף שיזהה נסיגה, לכן יש לקבע את גרסת ה־image לפני שמציגים את החנות למישהו אחר. הסעיף האחרון מסביר כיצד לעשות זאת.
היכן מתבצע הרינדור?
בדפדפן. Halcyon הוא יישום Vite ו־TypeScript המבוסס על three.js, ספריית JavaScript שמציירת גרפיקה תלת־ממדית באמצעות WebGL (ספריית גרפיקת אינטרנט, הממשק של הדפדפן ל־GPU). הגאומטריה של החנות ועטיפות המשחקים מורכבות במחשב שמחובר למסך.
המכולה עושה מעט מאוד. היא מריצה את npm run serve, שהוא vite preview --port 1420 --strictPort --host, ומגישה את הקבצים שנבנו וכן כמה נתיבי middleware קטנים. Halcyon אינו מבצע transcoding ואינו מריץ מנוע בשרת.
לכן שאלת ה־GPU שייכת ללקוח. VPS קטן מטפל בכך ללא קושי, משום שהשירות מסתכם בהגשת קבצים סטטיים באמצעות HTTP. המחשב הנייד, הטאבלט או הטלוויזיה שמריצים את הדפדפן הם שקובעים אם החנות תפעל בצורה חלקה או תגיב באיטיות.
תכונה אחת חורגת מכלל זה. Remote Play מפעיל מופעי Chromium ללא ממשק גרפי בשרת ומזרים את החנות שעברה רינדור לטלפון או לממיר באמצעות WebRTC (תקשורת בזמן אמת באינטרנט). במסלול זה הרינדור מתבצע בשרת. כברירת מחדל מוגבלים המופעים לשניים, וניתן לשנות את המגבלה באמצעות REMOTE_PLAY_MAX_INSTANCES. ללא התקן /dev/dri שמופה למכולה, המופעים מבצעים את הרינדור באמצעות ה־CPU, ולכן VPS בעל 2 ליבות מושפע מכל צופה נוסף.
מה החנות קוראת מהספרייה שלך
המעברים מגיעים מהמבנה של Jellyfin עצמו. Halcyon מציג את המדורים מתוך הספריות והז'אנרים שלך, ומקבץ סרטי המשך לפי ה־BoxSets שלך. המפרטים המודפסים בגב כל מארז מגיעים ממטא־הנתונים של MediaStreams ש־Jellyfin כבר מחזיק, ולכן כל מידע שחסר ב־Jellyfin חסר גם על המדף.
כך החנות משקפת נאמנה את המטא־נתונים שלך. ספרייה שנבנתה באמצעות מחסנית arr ב־Docker Compose, עם תמונות ויצירות אמנות וז'אנרים שכבר הוזנו, נראית כאן טוב בהרבה מתיקייה של קבצים נפרדים עם שמות כלליים. גם ספריות תמונות תלויות באותו כלי שאינדקס אותן, וכדאי לזכור זאת כאשר משווים בין PhotoPrism לבין Immich עבור התמונות המאוחסנות באותו שרת.
נסו את הדגמת חנות הווידאו לפני התקנת רכיב כלשהו
הפרויקט מפרסם את החנות כולה כשהיא פועלת מול ספרייה סינתטית, ב־הדגמה המתארחת. הוספת ?demo=1 לכל כתובת URL של Halcyon מפעילה את אותה הדגמה בפריסה שלכם.
השתמשו בה לבדיקת החומרה. ספריית ההדגמה כוללת כ־2,000 כותרים ודורשת כ־2 GB מזיכרון הדפדפן, ולכן היא כבדה יותר מרוב הספריות האישיות. אם ההדגמה מקרטעת במכשיר שממנו אתם מתכננים לגלוש, גם הספרייה שלכם תקרטע. במקרה כזה, הפתרון הוא מצב 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, משום ש־host networking מציב את המכולה על כל הממשקים של המכונה, כולל הממשק הציבורי. הפעלת Docker ב־VPS מסבירה את שאר השיקולים. --restart unless-stopped הוא שמפעיל מחדש את החנות לאחר אתחול, בהתאם לאותו עיקרון של שירותי Compose שעולים עם האתחול.
שכפול המאגר והפעלת docker compose up -d בונים את התמונה באופן מקומי. קובץ ה־Compose שבמאגר נבנה מקוד המקור כברירת מחדל, והוא כולל את השורה של image: כשהיא בהערה. הסירו את ההערה מהשורה אם ברצונכם להשתמש בתמונה שפורסמה באמצעות Compose.
מגבלה חשובה אחת, נכון ל־August 2026: התמונה שפורסמה זמינה עבור linux/amd64 בלבד. בניית גרסת arm64 של ההפצה מרובת הארכיטקטורות נכשלה תחת אמולציה, והיא ממתינה ל־runners מקומיים מסוג arm. ב־VPS מסוג arm64, פעולת המשיכה נכשלת עם no matching manifest for linux/arm64/v8 in the manifest list entries, ובנייה מתוך השכפול היא הדרך לעקוף זאת.
הפנו אותו לשרת Jellyfin שלכם
פתחו את http://<host>:1420 והתחברו באמצעות כתובת שרת Jellyfin, שם המשתמש והסיסמה שלכם. הקובץ .env.local.example במאגר מיועד לפיתוח מקומי בלבד. Vite חושף לקוד בצד הלקוח משתנים בעלי קידומת VITE_, ולכן סיסמת Jellyfin שתיכתב שם תיכלל בחבילת JavaScript שכל מבקר מוריד. בשרת שאנשים אחרים יכולים לגשת אליו, התחברו דרך הממשק.
הדפדפן מתקשר ישירות עם Jellyfin. המכולה של Halcyon אינה משמשת כ־proxy ל־API של Jellyfin, ולכך יש שתי השלכות שכדאי להכיר לפני שמתחילים לנפות תקלות.
ראשית, Jellyfin חייב להיות נגיש מהדפדפן, ולא רק מה־VPS שמגיש את Halcyon. קישור של Jellyfin ל־127.0.0.1:8096 מתאים לבדיקה מקומית, אך יגרום לכך שהמדפים יישארו ריקים עבור כל השאר.
שנית, מדובר בקריאה בין מקורות, מהכתובת של Halcyon אל הכתובת של Jellyfin. כברירת מחדל, Jellyfin משיב לבקשות API באמצעות Access-Control-Allow-Origin: *, ולכן אין צורך בתצורה נוספת. אם צמצמתם את ההגדרה הזו, או הצבתם 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 rebinding. בתוך מכולה, המחשב שעליו הוא פועל הוא המכולה עצמה, ולכן שם המארח שלה אינו שם המארח שלכם. בקשה שמגיעה בתור 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, ולכן אדם זר שמוצא את הכתובת מגיע למסך התחברות. תכונה אחת משנה זאת. הפעלת Remote Play, תחת Settings ולאחר מכן Connection, מעבירה לשרת את סשן ה־Jellyfin שלכם. כתוצאה מכך, מבקרים ב־/remote.html מקבלים מופע משלהם של הספרייה האמיתית שלכם. זו מטרת התכונה, ומשמעות הדבר היא שסודיות הכתובת היא המחסום בין האינטרנט לבין הסרטים שלכם. אם תפעילו Remote Play, הציבו single sign on לפני האתר כולו באמצעות Authentik כשער SSO לאירוח עצמי, או הסירו את שם המארח הציבורי והגיעו אל החנות דרך מנהרת WireGuard המנוהלת באמצעות wg-easy.
יש לכך שני היבטים נוספים. ה־reverse proxy מעביר רק את החנות. זרם Remote Play הוא WebRTC מעל UDP ואינו עובר דרך HTTP proxy, ולכן הוא זקוק לנתיב משלו על 3478/udp ועל 49200 עד 49260/udp כאשר ממסר TURN הכלול נמצא בשימוש. בנוסף, ה־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 במקום לצמצם את הספרייה.
נעלו את image, ובדקו לפני שאתם מושכים
התייחסו לחלק הזה ברצינות. Tags v0.1.0 עד v0.3.1 פורסמו בהפרש של ימים זה מזה, ו־v0.2.1 קיים רק משום שה־image push עבור v0.2.0 נכשל. דיווחי באגים יתקבלו בברכה upstream, אך patches אינם מתקבלים, ולכן רצף הגרסאות משקף מצב עבודה של אדם אחד.
הפעלת latest מתוך הרגל של docker pull פירושה שהתוכן המאוחסן יכול להשתנות בכל יום שלישי שגרתי. נעלו לפי digest, שהוא הייחוס היחיד שאינו יכול להשתנות.
docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1פקודה זו מדפיסה את ה־digest שמאחורי ה־tag. השתמשו בו במקום ב־tag.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video@sha256:747dcc821a3d2fa318b50e76024783c1835609047e84f502e23d021bc1898b20ה־digest הזה היה 0.3.1 ב־10 August 2026. קראו בעצמכם את ה־digest הנוכחי במקום להעתיק אותו, וקראו את הערות הגרסה לפני המעבר, משום שגרסת תיקון כאן עלולה לכלול גם שינויים במבנה האחסון, נוסף על תיקונים.
FAQ
האם Halcyon זקוק ל־GPU ב־VPS שלי?
לא בשימוש רגיל. החנות מוצגת באמצעות three.js בדפדפן, ולכן מחשב הלקוח מבצע את הרינדור, ואילו המכולה מגישה רק קבצים סטטיים בפורט 1420. החריג הוא Remote Play, שמפעיל Chromium ללא ממשק גרפי בשרת ומזרים את התוצאה. במסלול זה הרינדור מתבצע ב־CPU, אלא אם ממפים את /dev/dri לתוך המכולה לצורך האצת חומרה.
האם אפשר להפעיל את 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 לפרסם את הכתובת האמיתית של המחשב. מאחורי גשר Docker, המכולה יכולה להציע רק כתובת 172.x שאליה שום טלפון ברשת שלכם אינו יכול להגיע. לצורך גלישה בחנות בדפדפן, -p 1420:1420 פועל וחושף חלק קטן בהרבה מהמארח.
באיזו תגית image עליי להשתמש?
הצמידו digest במקום latest. קראו את ה־digest של גרסה הכוללת את docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1, הפעילו את אותו digest, ועדכנו רק לאחר קריאת הערות הגרסה. נכון ל־August 2026, ה־image שפורסם הוא linux/amd64 בלבד, ולכן מארח arm64 חייב לבנות אותו מה־clone באמצעות docker compose up -d.