SSD Nodes Learn Hosting plans →
Guide Matt ConnorDi Matt Connor · Aggiornato 2026-08-27

Halcyon: Jellyfin diventa un videonoleggio anni 90

Scopri Halcyon per trasformare Jellyfin in un videonoleggio anni 90 nel browser: comando Docker, reverse proxy, integrazione API e limiti reali del progetto.

Cosa fa Halcyon con la libreria Jellyfin

Halcyon Video ridisegna la libreria Jellyfin come un videonoleggio degli anni 1990 visitabile nel browser. Ogni film disponibile diventa una custodia su uno scaffale. Puoi percorrere i corridoi sotto le luci al neon, prendere una custodia, girarla per leggere sul retro le specifiche e portarla al bancone per avviare la riproduzione. Halcyon invia a Jellyfin gli eventi di avvio, avanzamento e arresto della riproduzione, quindi i punti di ripresa e la cronologia delle visualizzazioni restano corretti.

Halcyon legge un server Jellyfin esistente tramite le API di Jellyfin e non mantiene una propria libreria. Questa guida presuppone che Jellyfin sia già in esecuzione e che la scansione della libreria sia completata senza errori. In caso contrario, configura prima Jellyfin come server multimediale su un VPS e torna qui quando la libreria viene visualizzata correttamente nel normale client web. Questo progetto si installa perché la libreria esiste già, non perché serva un altro servizio nel tuo elenco di servizi self-hosted.

Il progetto è distribuito con licenza GPL-3.0 ed è scritto da una sola persona. Il README dichiara chiaramente che non accetta pull request. Lo sviluppo procede rapidamente e non c'è un secondo manutentore che possa individuare una regressione. Per questo, blocca la versione dell'immagine prima di mostrare il videonoleggio ad altre persone. L'ultima sezione spiega come farlo.

Dove avviene il rendering?

Nel browser. Halcyon è un'applicazione Vite e TypeScript basata su three.js, una libreria JavaScript che genera grafica 3D tramite WebGL (web graphics library, l'interfaccia del browser verso la GPU). La geometria del negozio e le immagini delle confezioni vengono composite dal dispositivo a cui è collegato lo schermo.

Il container svolge un ruolo minimo. Esegue npm run serve, che è vite preview --port 1420 --strictPort --host, e distribuisce i file compilati oltre ad alcune piccole route middleware. Halcyon non esegue alcuna transcodifica e non esegue alcun engine sul server.

La questione della GPU riguarda quindi il client. Un piccolo VPS gestisce senza problemi questo carico, perché deve soltanto distribuire file statici tramite HTTP. È il laptop, il tablet o il televisore su cui gira il browser a determinare se il negozio viene visualizzato fluidamente o procede a scatti.

Una funzionalità costituisce un'eccezione. Remote Play avvia istanze headless di Chromium sul server e trasmette il negozio renderizzato a un telefono o a un set-top box tramite WebRTC (web real time communication). In questo percorso il rendering avviene sul server; per impostazione predefinita sono consentite al massimo due istanze, un limite regolabile con REMOTE_PLAY_MAX_INSTANCES. Senza un dispositivo /dev/dri mappato, tali istanze eseguono il rendering sulla CPU; per questo un VPS con due core risente di ogni spettatore aggiuntivo.

Cosa legge il negozio dalla tua libreria

Le sezioni derivano dalla struttura interna di Jellyfin. Halcyon organizza le aree in base alle librerie e ai generi e raggruppa i sequel provenienti dai tuoi BoxSets. Le specifiche stampate sul retro di ogni custodia provengono dai metadati MediaStreams già disponibili in Jellyfin; di conseguenza, qualsiasi informazione mancante in Jellyfin manca anche sullo scaffale.

Il negozio riflette quindi fedelmente i tuoi metadati. Una libreria alimentata da uno stack arr in Docker Compose, con immagini e generi già compilati, offre qui un risultato molto migliore rispetto a una cartella di file non organizzati con nomi generici. Le librerie fotografiche dipendono allo stesso modo dal sistema che le ha indicizzate. È un aspetto da ricordare quando valuti PhotoPrism rispetto a Immich per le immagini archiviate sullo stesso server.

Prova la demo del negozio video prima di installare qualsiasi componente

Il progetto pubblica l'intero negozio collegato a una libreria sintetica nella demo ospitata. Aggiungendo ?demo=1 a qualsiasi URL Halcyon si ottiene lo stesso risultato nella propria installazione.

Usala come test dell'hardware. La libreria demo contiene circa 2,000 titoli e richiede circa 2 GB di memoria del browser, quindi è più pesante della maggior parte delle librerie personali. Se la demo procede a scatti sul dispositivo da cui prevedi di navigare, anche la tua libreria procederà a scatti. La soluzione è la modalità 2.5D descritta di seguito, non un VPS più potente.

Eseguirlo con Docker

Questo è il comando documentato dal progetto.

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

Controlla quindi che il servizio sia stato avviato.

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

Il log dovrebbe mostrare il server di anteprima in ascolto sulla porta 1420 e curl dovrebbe rispondere a HTTP/1.1 200 OK. Un container che termina dopo pochi secondi quasi sempre indica un problema con la porta. --strictPort significa che il server non passa alla porta 1421 quando la 1420 è già occupata, quindi si arresta.

--network host serve per Remote Play, non per lo store. WebRTC deve pubblicizzare l'indirizzo reale della macchina al dispositivo che richiede lo streaming. Con la rete bridge predefinita di Docker, il container conosce soltanto il proprio indirizzo 172.x, che nessun telefono della rete può raggiungere; di conseguenza, lo streaming non si connette mai. Se vuoi usare lo store soltanto in un browser, pubblica invece la porta.

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

Questa è l'impostazione predefinita più adatta su un VPS, perché la rete host collega il container a tutte le interfacce disponibili sulla macchina, inclusa quella pubblica. Eseguire Docker su un VPS illustra il resto di questo compromesso. --restart unless-stopped riavvia lo store dopo un reboot, secondo lo stesso principio descritto in Servizi Compose avviati al boot.

Clonare il repository ed eseguire docker compose up -d crea invece l'immagine localmente. Il file Compose incluso nel repository esegue per impostazione predefinita la build dal codice sorgente e contiene commentata la riga image: relativa all'immagine precompilata; rimuovi il commento da quella riga se vuoi usare l'immagine pubblicata con Compose.

Un limite importante, ad agosto 2026: l'immagine pubblicata è disponibile soltanto per linux/amd64. La variante arm64 della build multi-architettura non è riuscita durante l'emulazione ed è in attesa di runner arm nativi. Su un VPS arm64, il pull fallisce con no matching manifest for linux/arm64/v8 in the manifest list entries; in questo caso, la soluzione è eseguire la build dal clone.

Indicare il server Jellyfin

Aprire http://<host>:1420 ed effettuare l'accesso usando l'indirizzo del server Jellyfin, il nome utente e la password. Il file .env.local.example nel repository serve soltanto per lo sviluppo locale. Vite espone al codice lato client le variabili con prefisso VITE_, quindi una password Jellyfin scritta in quel file viene inclusa nel bundle JavaScript che ogni visitatore scarica. Su un server raggiungibile da altre persone, effettuare l'accesso tramite l'interfaccia.

Il browser comunica direttamente con Jellyfin. Il container Halcyon non fa da proxy per l'API Jellyfin e, prima di iniziare il troubleshooting, è importante conoscere due conseguenze.

Primo: Jellyfin deve essere raggiungibile dal browser, non soltanto dal VPS che fornisce Halcyon. Un'istanza Jellyfin in ascolto su 127.0.0.1:8096 è adatta per un test locale, ma lascia gli scaffali vuoti per tutti gli altri utenti.

Secondo: la richiesta è cross-origin, dall'indirizzo di Halcyon a quello di Jellyfin. Per impostazione predefinita, Jellyfin risponde alle richieste API con Access-Control-Allow-Origin: *, quindi funziona senza configurazioni aggiuntive. Se hai limitato questa impostazione oppure hai inserito un proxy di autenticazione davanti all'API Jellyfin, la console del browser segnala blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource e lo store viene caricato con gli scaffali vuoti.

Pubblicalo dietro un reverse proxy, con l'autenticazione davanti

vite preview è un server di anteprima. Non gestisce la terminazione TLS (transport layer security) e non dispone di controlli di accesso propri, quindi deve essere pubblicato dietro nginx o Caddy quando è accessibile da Internet.

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;
  }
}

Per usare un nome di dominio davanti al container serve un'impostazione aggiuntiva. Halcyon accetta richieste indirizzate a localhost, agli indirizzi IP effettivi e ai nomi della macchina su cui è in esecuzione, come protezione contro il DNS rebinding. In un container, la macchina su cui è in esecuzione è il container stesso, quindi il suo hostname non corrisponde al tuo. Una richiesta che arriva come halcyon.example.com viene rifiutata e la risposta indica l'host rifiutato. Aggiungi quel nome.

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

Il valore è separato da virgole. Un punto iniziale, come .example.com, corrisponde ai sottodomini. all disabilita il controllo. Usa all soltanto su una macchina non raggiungibile dall'esterno.

Quando lo store viene pubblicato tramite https://, anche l'indirizzo Jellyfin inserito nella schermata di accesso deve essere https://. Un browser blocca una chiamata API tramite http:// eseguita da una pagina HTTPS e la console visualizza Mixed Content: The page at 'https://halcyon.example.com/' was loaded over HTTPS, but requested an insecure resource. L'accesso non riesce, senza fornire spiegazioni in Halcyon. Pubblica entrambi i servizi tramite TLS oppure usa HTTP semplice per entrambi all'interno di una rete privata.

Poi configura l'autenticazione. Lo store richiede le credenziali Jellyfin, quindi chi trova l'URL visualizza una schermata di accesso. Una funzionalità modifica questo comportamento. L'attivazione di Remote Play, in Settings e poi Connection, trasferisce al server la sessione Jellyfin, permettendo ai visitatori di /remote.html di ottenere una propria istanza della tua libreria reale. Questo è lo scopo della funzionalità e significa che la riservatezza dell'URL è l'unica barriera tra Internet e i tuoi film. Se abiliti Remote Play, configura il single sign-on davanti all'intero sito con Authentik come gateway SSO self-hosted oppure rimuovi l'hostname pubblico e raggiungi lo store tramite un tunnel WireGuard gestito con wg-easy.

Sono necessari altri due accorgimenti. Il reverse proxy gestisce soltanto lo store: lo streaming Remote Play usa WebRTC su UDP e non passa attraverso un proxy HTTP, quindi richiede un percorso dedicato sulla porta 3478/udp e sulle porte da 49200 a 49260/udp quando è in uso il relay TURN incluso. Inoltre, il semplice docker run precedente non conserva alcun volume, quindi il seed di Remote Play non sopravvive a docker rm. Il file Compose monta un volume halcyon-data in /data e imposta REMOTE_PLAY_SEED su /data/remote-play-seed.json proprio per questo motivo.

Cosa fare quando lo store funziona male

Halcyon esegue il rendering su richiesta. Uno store inattivo non compone alcun fotogramma e la perdita del focus della finestra interrompe il ciclo di animazione. Per questo una scheda lasciata aperta non scarica la batteria di un laptop. Questo aiuta una macchina che è semplicemente al limite. Non risolve il problema di una macchina che non riesce a eseguire il rendering dello store.

Per questi client è disponibile una modalità 2.5D, realizzata con semplice HTML e CSS e senza WebGL, pensata anche per hardware poco potente come un Raspberry Pi. Puoi passare dalla modalità 3D alla modalità 2.5D dalle impostazioni o dal menu di alimentazione, senza ricaricare la pagina. In questo modo puoi testare entrambe le modalità sullo stesso dispositivo in pochi secondi. Le prestazioni restano comunque limitate: l'autore descrive la modalità piatta come ancora grezza e in fase di sviluppo. Considerala un fallback per i client meno potenti.

Quando un client è troppo poco potente per lo store 3D, il problema è evidente. La scheda si ricarica da sola oppure il browser segnala la perdita del contesto WebGL, in genere mentre gli scaffali sono ancora in fase di caricamento. Imposta la modalità 2.5D su quel dispositivo invece di ridurre la libreria.

Blocca l’immagine e verifica prima di eseguire il pull

Prendi sul serio questo passaggio. I tag v0.1.0 fino a v0.3.1 sono stati pubblicati tutti a pochi giorni di distanza l’uno dall’altro, e v0.2.1 esiste soltanto perché il push dell’immagine per v0.2.0 non è riuscito. Le segnalazioni di bug a monte sono benvenute, ma le patch no; quindi il flusso delle release riflette lo stato di lavoro di una sola persona.

Eseguire latest con l’abitudine di docker pull significa che lo store può cambiare senza preavviso in un qualunque martedì. Blocca l’immagine tramite digest, l’unico riferimento che non può cambiare.

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

Questo comando stampa il digest associato al tag. Usalo al posto del tag.

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

Quel digest era 0.3.1 il 10 agosto 2026. Leggi personalmente quello attuale invece di copiarlo e consulta le note di rilascio prima di procedere, perché in questo caso una patch release può includere modifiche al layout dello store oltre alle correzioni.

FAQ

Halcyon richiede una GPU sul mio VPS?

Non per l'uso normale. Il negozio viene renderizzato da three.js nel browser, quindi il rendering viene eseguito dal computer client e il container serve soltanto file statici sulla porta 1420. L'eccezione è Remote Play, che esegue Chromium in modalità headless sul server e trasmette il risultato. In questo caso il rendering viene eseguito dalla CPU, a meno che non si mappi /dev/dri nel container per abilitare l'accelerazione hardware.

Posso esporre Halcyon su Internet?

Solo dietro autenticazione. Il negozio richiede le credenziali di Jellyfin, ma l'attivazione di Remote Play trasferisce la sessione Jellyfin al server. Di conseguenza, chiunque carichi /remote.html ottiene un'istanza della libreria reale senza effettuare l'accesso. Anteponi un reverse proxy con single sign-on oppure non pubblicare l'hostname nel DNS pubblico e accedi al negozio tramite una VPN.

Perché gli scaffali sono vuoti dopo l'accesso?

Il browser chiama direttamente l'API di Jellyfin. Jellyfin deve quindi essere raggiungibile dal browser, non soltanto dal VPS. Apri la console del browser. blocked by CORS policy indica che Jellyfin non accetta la richiesta proveniente dall'indirizzo di Halcyon. Un messaggio Mixed Content indica che la pagina usa HTTPS mentre l'indirizzo di Jellyfin inserito usa HTTP semplice.

È necessario --network host?

Solo per Remote Play. WebRTC deve pubblicizzare l'indirizzo reale della macchina. Dietro il bridge Docker, il container può offrire soltanto un indirizzo 172.x, che nessun telefono della rete può raggiungere. Per navigare nel negozio da un browser, -p 1420:1420 è sufficiente ed espone una parte molto più ridotta dell'host.

Quale tag dell'immagine devo usare?

Blocca un digest invece di latest. Leggi il digest di una versione con docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.3.1, esegui quel digest e aggiorna solo dopo aver letto le note di rilascio. Ad agosto 2026 l'immagine pubblicata è soltanto linux/amd64, quindi un host arm64 deve eseguire la build dal clone con docker compose up -d.