SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-08-28

Halcyon: jak zmienić Jellyfin w wypożyczalnię wideo

Przekształć bibliotekę Jellyfin w interaktywną wypożyczalnię kaset z lat 90. Poradnik instalacji przez Docker, konfiguracja reverse proxy oraz ograniczenia synchronizacji API.

Jak Halcyon przetwarza bibliotekę Jellyfin

Halcyon Video przekształca bibliotekę Jellyfin w interaktywną wypożyczalnię kaset wideo z lat 90. dostępną w przeglądarce. Każdy posiadany film staje się pudełkiem na półce. Użytkownik może przechadzać się między alejkami w świetle jarzeniówek, zdjąć pudełko z półki, odwrócić je, aby przeczytać specyfikację na odwrocie, a następnie zanieść do lady, aby rozpocząć odtwarzanie. Informacje o rozpoczęciu, postępie i zakończeniu odtwarzania są przesyłane z powrotem do Jellyfin, dzięki czemu punkty wznowienia oraz historia oglądania pozostają zsynchronizowane.

Halcyon odczytuje istniejący serwer Jellyfin za pośrednictwem Jellyfin API i nie przechowuje własnej biblioteki. Niniejszy przewodnik zakłada, że Jellyfin jest już uruchomiony i poprawnie skanuje zasoby. Jeśli tak nie jest, należy najpierw skonfigurować Jellyfin jako serwer multimediów na VPS i powrócić, gdy biblioteka będzie poprawnie wyświetlana w standardowym kliencie webowym. Jest to rozwiązanie instalowane w sytuacji, gdy biblioteka jest już gotowa, a nie jako kolejna usługa na liście własnych usług.

Projekt jest udostępniony na licencji GPL-3.0 i tworzony przez jedną osobę, a plik README wyraźnie wskazuje, że autor nie przyjmuje pull requestów. Rozwój projektu przebiega szybko, a brak drugiego opiekuna utrudnia wyłapywanie regresji, dlatego przed udostępnieniem wypożyczalni innym osobom należy przypiąć wersję obrazu. Ostatnia sekcja zawiera instrukcję, jak to wykonać.

Gdzie odbywa się renderowanie?

W przeglądarce. Halcyon to aplikacja typu Vite i TypeScript oparta na three.js, bibliotece JavaScript rysującej grafikę 3D za pomocą WebGL (web graphics library, interfejs przeglądarki do GPU). Geometria sklepu oraz grafiki pudełek są składane przez urządzenie, na którym wyświetlany jest obraz.

Kontener wykonuje minimalną pracę. Uruchamia npm run serve, który jest vite preview --port 1420 --strictPort --host, i serwuje zbudowane pliki oraz kilka niewielkich tras middleware. Halcyon nie dodaje transkodowania i nie uruchamia żadnego silnika po stronie serwera.

Zatem kwestia GPU dotyczy klienta. Niewielki VPS obsłuży to bez problemu, ponieważ serwowanie oznacza przesyłanie plików statycznych przez HTTP. Laptop, tablet lub telewizor z uruchomioną przeglądarką decyduje o tym, czy sklep działa płynnie, czy zacina się.

Jedna funkcja łamie tę zasadę. Remote Play uruchamia bezgłowe instancje Chromium na serwerze i przesyła wyrenderowany sklep do telefonu lub przystawki STB przez WebRTC (web real time communication). Ta ścieżka renderuje się na serwerze, domyślnie ograniczona do dwóch instancji, z możliwością regulacji za pomocą REMOTE_PLAY_MAX_INSTANCES. Bez zmapowanego urządzenia /dev/dri instancje te renderują się na CPU, więc dwurdzeniowy VPS odczuje każdego dodatkowego widza.

Co sklep odczytuje z biblioteki

Układ alejek wynika ze struktury Jellyfin. Halcyon rozmieszcza sekcje na podstawie bibliotek i gatunków, a także grupuje sequele z BoxSets. Specyfikacje nadrukowane na odwrocie każdego pudełka pochodzą z metadanych MediaStreams przechowywanych przez Jellyfin, co oznacza, że wszystko, czego brakuje w Jellyfin, nie pojawi się na półce.

Dzięki temu sklep stanowi wierne odzwierciedlenie metadanych. Biblioteka zasilana przez stos arr w Docker Compose z uzupełnionymi grafikami i gatunkami prezentuje się tutaj znacznie lepiej niż folder z luźnymi plikami o ogólnych nazwach. Biblioteki zdjęć wykazują taką samą zależność od narzędzia, które je zaindeksowało, o czym warto pamiętać, porównując PhotoPrism z Immich w kontekście zdjęć przechowywanych na tym samym serwerze.

Przetestuj demo sklepu wideo przed instalacją

Projekt udostępnia pełną wersję sklepu działającą w oparciu o syntetyczną bibliotekę pod adresem hostowanego demo. Dodanie ?demo=1 do dowolnego adresu URL Halcyon wywoła ten sam efekt we własnym wdrożeniu.

Należy potraktować to jako test sprzętowy. Biblioteka demonstracyjna zawiera około 2000 tytułów i wymaga około 2 GB pamięci przeglądarki, co stanowi większe obciążenie niż w przypadku większości bibliotek osobistych. Jeśli demo zacina się na urządzeniu, z którego planujesz korzystać, Twoja własna biblioteka również będzie działać z opóźnieniami. Rozwiązaniem jest wówczas tryb 2.5D opisany poniżej, a nie zwiększanie zasobów VPS.

Uruchomienie w Docker

Jest to polecenie zalecane w dokumentacji producenta.

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

Następnie należy sprawdzić, czy usługa została uruchomiona.

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

Dziennik powinien wskazywać, że serwer podglądu nasłuchuje na porcie 1420, a curl powinno odpowiadać na HTTP/1.1 200 OK. Kontener, który kończy działanie w ciągu kilku sekund, niemal zawsze sygnalizuje problem z portem. --strictPort oznacza, że serwer odmawia przejścia na port 1421, gdy port 1420 jest zajęty, więc zamiast tego zatrzymuje się.

--network host służy do Remote Play, a nie do sklepu. WebRTC musi rozgłosić rzeczywisty adres maszyny do urządzenia, które chce odebrać strumień. Za domyślnym mostem Docker kontener zna tylko swój adres 172.x, którego żaden telefon w sieci lokalnej nie może osiągnąć, dlatego połączenie strumieniowe nie zostaje nawiązane. Jeśli dostęp do sklepu jest wymagany tylko w przeglądarce, należy opublikować port.

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

Jest to lepsze ustawienie domyślne na VPS, ponieważ tryb host networking umieszcza kontener na każdym interfejsie maszyny, w tym na interfejsie publicznym. Uruchamianie Docker na VPS omawia pozostałe aspekty tego rozwiązania. --restart unless-stopped przywraca działanie sklepu po restarcie, zgodnie z zasadami opisanymi w Usługi Compose uruchamiane przy starcie systemu.

Sklonowanie repozytorium i uruchomienie docker compose up -d powoduje zbudowanie obrazu lokalnie. Dostarczony plik Compose domyślnie buduje obraz ze źródeł i zawiera zakomentowaną linię image:; należy ją odkomentować, aby użyć opublikowanego obrazu w ramach Compose.

Jedno twarde ograniczenie na sierpień 2026: opublikowany obraz jest dostępny wyłącznie dla linux/amd64. Część multi-arch dla arm64 nie przeszła procesu budowania w emulacji i oczekuje na natywne środowiska uruchomieniowe arm. Na VPS z architekturą arm64 pobieranie kończy się błędem no matching manifest for linux/arm64/v8 in the manifest list entries, dlatego jedynym rozwiązaniem jest budowanie z klonu repozytorium.

Wskaż serwer Jellyfin

Otwórz http://<host>:1420 i zaloguj się, używając adresu serwera Jellyfin, nazwy użytkownika oraz hasła. Plik .env.local.example w repozytorium służy wyłącznie do lokalnego programowania. Vite udostępnia zmienne z prefiksem VITE_ w kodzie po stronie klienta, więc hasło do Jellyfin zapisane w tym pliku zostałoby skompilowane do pakietu JavaScript pobieranego przez każdego użytkownika. Na serwerze dostępnym publicznie należy zalogować się przez interfejs.

Przeglądarka komunikuje się z Jellyfin bezpośrednio. Kontener Halcyon nie pośredniczy w ruchu do API Jellyfin, co rodzi dwie konsekwencje istotne przed rozpoczęciem debugowania.

Po pierwsze, Jellyfin musi być osiągalny z poziomu przeglądarki, a nie tylko z VPS, na którym działa Halcyon. Jellyfin powiązany z 127.0.0.1:8096 jest odpowiedni do testów lokalnych, lecz sprawi, że biblioteki będą puste dla wszystkich innych użytkowników.

Po drugie, wywołanie jest typu cross-origin, z adresu Halcyon do adresu Jellyfin. Jellyfin domyślnie odpowiada na żądania API za pomocą Access-Control-Allow-Origin: *, więc całość działa bez dodatkowej konfiguracji. Jeśli to ustawienie zostało ograniczone lub przed API Jellyfin umieszczono proxy uwierzytelniające, konsola przeglądarki zgłosi blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource, a sklep załaduje się z pustymi półkami.

Umieszczenie za reverse proxy z uwierzytelnianiem

vite preview to serwer podglądu. Nie kończy on połączeń TLS (transport layer security) i nie posiada własnej kontroli dostępu, dlatego w przypadku jakiejkolwiek ekspozycji publicznej powinien znajdować się za nginx lub 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;
  }
}

Nazwa domeny przed kontenerem wymaga dodatkowego ustawienia. Halcyon odpowiada na localhost, surowe adresy IP oraz nazwy maszyny, na której działa, co stanowi zabezpieczenie przed DNS rebinding. Wewnątrz kontenera maszyną jest sam kontener, więc jego nazwa hosta nie jest Twoją nazwą. Żądanie przychodzące jako halcyon.example.com jest odrzucane, a odpowiedź zawiera nazwę hosta, która została odrzucona. Dodaj tę nazwę.

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

Wartość jest rozdzielana przecinkami, kropka na początku, taka jak .example.com, dopasowuje subdomeny, a all wyłącza sprawdzanie. Używaj all tylko na maszynie, do której nikt z zewnątrz nie ma dostępu.

Gdy sklep jest serwowany przez https://, adres Jellyfin wpisywany przy logowaniu również musi być https://. Przeglądarka blokuje zwykłe wywołanie API http:// wykonane ze strony HTTPS, a w konsoli pojawia się Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource. Logowanie po prostu kończy się niepowodzeniem bez wyjaśnienia wewnątrz Halcyon. Serwuj oba przez TLS lub pozostaw oba na zwykłym HTTP wewnątrz sieci prywatnej.

Następnie uwierzytelnianie. Sklep prosi o dane logowania Jellyfin, więc osoba postronna, która znajdzie adres URL, zobaczy ekran logowania. Jedna funkcja to zmienia. Włączenie Remote Play w ustawieniach połączenia przekazuje sesję Jellyfin do serwera, dzięki czemu odwiedzający /remote.html otrzymują własną instancję Twojej rzeczywistej biblioteki. Na tym polega ta funkcja i oznacza to, że poufność adresu URL jest jedyną barierą między Internetem a Twoimi filmami. Jeśli włączysz Remote Play, umieść single sign on przed całą witryną za pomocą Authentik jako brama SSO hostowana samodzielnie lub zrezygnuj z publicznej nazwy hosta i uzyskuj dostęp do sklepu przez tunel WireGuard zarządzany za pomocą wg-easy.

Z tym wiążą się dwa szczegóły. Reverse proxy obsługuje tylko sklep: strumień Remote Play to WebRTC przez UDP i nie przechodzi przez proxy HTTP, więc wymaga własnej ścieżki na porcie 3478/udp oraz 49200 do 49260/udp, gdy używany jest dołączony przekaźnik TURN. Ponadto zwykły docker run powyżej nie przechowuje wolumenu, więc ziarno Remote Play nie przetrwa docker rm. Plik Compose montuje wolumen halcyon-data w /data i ustawia REMOTE_PLAY_SEED na /data/remote-play-seed.json dokładnie z tego powodu.

Co zrobić, gdy sklep działa nieprawidłowo

Halcyon renderuje obraz na żądanie. Bezczynny sklep nie generuje klatek, a utrata fokusu okna zatrzymuje pętlę animacji, dzięki czemu karta pozostawiona w tle nie obciąża baterii laptopa. Pomaga to w przypadku maszyn o ograniczonej wydajności. Nie rozwiązuje to jednak problemu urządzeń, które w ogóle nie są w stanie wyrenderować sklepu.

Dla takich klientów dostępny jest tryb 2.5D, oparty na czystym HTML i CSS bez WebGL, przeznaczony dla sprzętu o niskiej mocy, takiego jak Raspberry Pi. Przełączanie między trybem 3D a 2.5D odbywa się w ustawieniach lub menu zasilania bez konieczności przeładowania strony, więc przetestowanie obu wariantów na tym samym urządzeniu zajmuje kilka sekund. Należy zachować realizm: autor opisuje tryb płaski jako surowy i wciąż rozwijany. Należy go traktować jako rozwiązanie awaryjne dla słabych klientów.

Gdy klient jest zbyt słaby dla sklepu 3D, błąd jest wyraźny. Karta przeładowuje się samoczynnie lub przeglądarka zgłasza utratę kontekstu WebGL, zazwyczaj w trakcie ładowania półek. W takim przypadku należy przełączyć urządzenie na tryb 2.5D, zamiast ograniczać rozmiar biblioteki.

Przypinanie obrazu i weryfikacja przed pobraniem

Należy podejść do tego punktu poważnie. Tagi v0.1.0 oraz v0.3.1 pojawiły się w odstępie zaledwie kilku dni, a v0.2.1 istnieje wyłącznie dlatego, że wypchnięcie obrazu v0.2.0 zakończyło się niepowodzeniem. Zgłoszenia błędów są mile widziane przez twórców, jednak poprawki kodu nie są przyjmowane, dlatego strumień wydań odzwierciedla bieżący stan prac jednej osoby.

Uruchamianie latest z nawykiem stosowania docker pull oznacza, że zawartość repozytorium może zmienić się w dowolny wtorek. Należy przypinać obrazy za pomocą skrótu (digest), ponieważ jest to jedyne odniesienie, którego nie można zmienić.

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

To polecenie wyświetla skrót przypisany do danego tagu. Należy go użyć zamiast nazwy tagu.

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

Ten skrót miał wartość 0.3.1 w dniu 10 sierpnia 2026. Należy samodzielnie odczytać aktualną wartość zamiast kopiować ją z przykładu oraz zapoznać się z informacjami o wydaniu przed aktualizacją, ponieważ wydanie poprawkowe może zawierać zarówno poprawki błędów, jak i zmiany w strukturze repozytorium.

FAQ

Czy Halcyon wymaga GPU na moim VPS?

Nie w standardowym zastosowaniu. Sklep jest renderowany przez three.js w przeglądarce, więc to maszyna kliencka wykonuje obliczenia graficzne, a kontener jedynie serwuje pliki statyczne na porcie 1420. Wyjątkiem jest Remote Play, który uruchamia bezgłowy (headless) proces Chromium na serwerze i przesyła strumieniowo wynik. Ta ścieżka renderuje obraz na CPU, chyba że zmapujesz /dev/dri do kontenera w celu uzyskania akceleracji sprzętowej.

Czy mogę udostępnić Halcyon w publicznym Internecie?

Tylko za warstwą uwierzytelniania. Sklep wymaga poświadczeń Jellyfin, jednak włączenie Remote Play przekazuje sesję Jellyfin do serwera. W rezultacie każdy, kto załaduje /remote.html, uzyska dostęp do instancji Twojej biblioteki bez konieczności logowania. Umieść przed usługą reverse proxy z mechanizmem single sign on lub ukryj nazwę hosta przed publicznym DNS i łącz się ze sklepem przez VPN.

Dlaczego po zalogowaniu półki są puste?

Przeglądarka wywołuje API Jellyfin bezpośrednio, więc Jellyfin musi być osiągalny z poziomu przeglądarki, a nie tylko z poziomu VPS. Otwórz konsolę przeglądarki. blocked by CORS policy oznacza, że Jellyfin nie akceptuje żądania pochodzącego z adresu Halcyon. Komunikat Mixed Content oznacza, że strona działa przez HTTPS, podczas gdy wprowadzony adres Jellyfin korzysta z nieszyfrowanego HTTP.

Czy potrzebuję --network host?

Tylko w przypadku Remote Play. WebRTC musi ogłosić rzeczywisty adres maszyny, a za mostkiem Docker kontener może zaoferować jedynie adres 172.x, którego telefon w Twojej sieci nie będzie w stanie osiągnąć. Do przeglądania sklepu w przeglądarce wystarczy -p 1420:1420, co ogranicza ekspozycję hosta.

Którego tagu obrazu powinienem użyć?

Przypnij konkretny digest zamiast latest. Odczytaj digest dla wersji z docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1, uruchom ten konkretny digest i aktualizuj dopiero po zapoznaniu się z informacjami o wydaniu (release notes). Według stanu na sierpień 2026 opublikowany obraz jest dostępny wyłącznie dla linux/amd64, więc host arm64 musi przeprowadzić budowę z klonu przy użyciu docker compose up -d.