Как превратить Jellyfin в видеомагазин 90-х через Halcyon
Установите Halcyon для визуализации библиотеки Jellyfin в стиле видеопроката 90-х годов. В статье приведены Docker-команды, настройки обратного прокси и нюансы работы API.
Что Halcyon делает с вашей библиотекой Jellyfin
Halcyon Video перерисовывает вашу библиотеку Jellyfin в виде виртуального видеомагазина 1990-х годов, доступного через браузер. Каждый фильм из вашей коллекции превращается в коробку на полке. Вы ходите по проходам под светом люминесцентных ламп, снимаете коробку с полки, переворачиваете её, чтобы прочитать описание на обороте, и несёте к стойке, чтобы начать воспроизведение. Информация о начале, прогрессе и завершении просмотра передаётся обратно в Jellyfin, поэтому точки возобновления и история просмотров остаются актуальными.
Halcyon считывает данные с существующего сервера Jellyfin через Jellyfin API и не хранит собственную библиотеку. В этом руководстве предполагается, что Jellyfin уже запущен и корректно просканировал медиафайлы. Если это не так, сначала настройте Jellyfin как медиасервер на VPS и возвращайтесь, когда ваша библиотека будет правильно отображаться в обычном веб-клиенте. Это решение устанавливают тогда, когда библиотека уже готова, а не когда вам просто потребовался ещё один сервис в вашем списке self-hosting проектов.
Проект распространяется по лицензии GPL-3.0 и написан одним автором; в README прямо указано, что pull requests не принимаются. Разработка идёт быстро, и нет второго сопровождающего, который мог бы исправить регрессии, поэтому зафиксируйте версию образа (pin), прежде чем показывать магазин кому-либо ещё. В последнем разделе описано, как это сделать.
Где происходит рендеринг?
В браузере. Halcyon — это приложение на Vite и TypeScript, построенное на базе three.js, библиотеки JavaScript, которая отрисовывает 3D-графику через WebGL (web graphics library, интерфейс браузера к GPU). Геометрия магазина и обложки коробок компонуются на устройстве, на котором открыт экран.
Контейнер выполняет минимум действий. Он запускает npm run serve, который является vite preview --port 1420 --strictPort --host, и отдает собранные файлы вместе с несколькими небольшими маршрутами промежуточного ПО. Halcyon не выполняет транскодирование и не запускает движки на сервере.
Таким образом, вопрос о GPU относится к клиенту. Небольшой VPS успешно справляется с этой задачей, так как обслуживание означает лишь передачу статических файлов и нескольких легких маршрутов по HTTP. Ноутбук, планшет или телевизор, на котором запущен браузер, определяет, будет ли магазин работать плавно или с задержками.
Одна функция нарушает это правило. Частные экземпляры Remote Play запускают headless Chromium на сервере, по одному на каждого посетителя, и транслируют отрисованный магазин на телефон или ТВ-приставку через WebRTC (web real time communication). Этот путь выполняет рендеринг на сервере, по умолчанию ограничен двумя экземплярами, что можно настроить с помощью REMOTE_PLAY_MAX_INSTANCES. Для этого также требуется GPU на сервере. В опубликованном образе нет программного рендерера, поэтому без подключенного узла рендеринга /dev/dri сервер отказывается запускать частный магазин и выдает This store server has no GPU available, so it can’t render a private store.
Что магазин считывает из вашей библиотеки
Стеллажи формируются на основе собственной структуры Jellyfin. Halcyon распределяет разделы из ваших библиотек и жанров, а также группирует сиквелы из ваших BoxSets. Технические характеристики, указанные на обороте каждой коробки, берутся из метаданных MediaStreams, которые уже хранятся в Jellyfin. Это означает, что всё, чего нет в Jellyfin, будет отсутствовать и на полке.
Таким образом, магазин является точным отражением ваших метаданных. Библиотека, наполняемая через стек arr в Docker Compose с уже заполненными обложками и жанрами, выглядит здесь гораздо лучше, чем папка с разрозненными файлами и общими названиями. Библиотеки фотографий имеют такую же зависимость от того, что их проиндексировало. Об этом стоит помнить, когда вы сравниваете PhotoPrism и Immich для хранения снимков на одном сервере.
Опробуйте демо-версию магазина видео перед установкой
Проект содержит демо-версию, которая работает без сервера и авторизации. В текущем исходном коде эта сборка заполняет полки потоковыми названиями, а не библиотекой, похожей на вашу, поэтому рассматривайте её как ознакомительный пример. Добавление ?demo=1 к URL вашего собственного развертывания загружает синтетическую библиотеку.
Эта синтетическая библиотека является аппаратным тестом, но с одной оговоркой: она подстраивается под размер устройства. Браузер с запасом мощности GPU получает полный магазин из примерно 2000 наименований. Средний 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, так как host networking помещает контейнер на все интерфейсы машины, включая публичный. В Запуск Docker на VPS описаны остальные нюансы этого подхода. --restart unless-stopped позволяет магазину запускаться после перезагрузки; это тот же принцип, что и в Сервисы Compose, запускающиеся при загрузке.
Клонирование репозитория и выполнение docker compose up -d собирает образ локально. Файл Compose по умолчанию собирает образ из исходного кода, а строка с готовым образом image: закомментирована. Раскомментируйте её, если хотите использовать опубликованный образ в Compose.
Опубликованный образ поддерживает как linux/amd64, так и linux/arm64. По состоянию на 11 октября 2026 года текущий релиз 0.25.0 содержит отдельный дайджест для каждой платформы, поэтому VPS на архитектуре arm64 использует ту же команду, что и любой другой хост. Ограничение только архитектурой amd64 было недолгим: в журнале изменений указано, что образ arm64 был удален в v0.2.1 8 августа 2026 года и возвращен, будучи собранным на нативных сборщиках, в v0.4.0 11 августа 2026 года. Только хостам, зафиксированным на теге из этого временного окна, требуется сборка из клона.
Настройка подключения к серверу Jellyfin
Откройте http://<host>:1420 и выполните вход, используя адрес сервера Jellyfin, имя пользователя и пароль. Файл .env.local.example в репозитории предназначен только для локальной разработки. Vite делает переменные с префиксом VITE_ доступными для клиентского кода, поэтому пароль от Jellyfin, записанный в этот файл, будет включен в JavaScript-бандл, который загружает каждый посетитель. На сервере, доступном извне, выполняйте вход через интерфейс.
Браузер взаимодействует с Jellyfin напрямую. Контейнер Halcyon не проксирует API Jellyfin, и перед началом отладки следует учесть два последствия этого факта.
Во-первых, Jellyfin должен быть доступен из браузера, а не только с VPS, на котором работает Halcyon. Если Jellyfin привязан к 127.0.0.1:8096, это подходит для локального тестирования, но у остальных пользователей библиотеки будут пустыми.
Во-вторых, запрос является кросс-доменным (CORS) — от адреса Halcyon к адресу Jellyfin. По умолчанию Jellyfin отвечает на API-запросы с помощью Access-Control-Allow-Origin: *, поэтому дополнительная настройка не требуется. Если вы ограничили этот параметр или установили прокси-сервер аутентификации перед API Jellyfin, в консоли браузера появится ошибка blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource, а магазин загрузится с пустыми полками.
Разместите его за обратным прокси-сервером с предварительной аутентификацией
vite preview — это сервер предварительного просмотра. Он не выполняет TLS termination (завершение 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. Внутри контейнера именем машины является сам контейнер, поэтому его hostname не совпадает с вашим. Запрос, приходящий как 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, настройте единый вход (SSO) перед всем сайтом с помощью Authentik как шлюз SSO для self-hosted сервисов или откажитесь от публичного имени хоста и обращайтесь к хранилищу через туннель WireGuard, управляемый с помощью wg-easy.
С этим связаны две детали. Обратный прокси-сервер передает только данные хранилища: поток Remote Play работает по протоколу WebRTC поверх UDP и не проходит через HTTP-прокси, поэтому ему требуется собственный путь на порту 3478/udp и в диапазоне от 49200 до 49260/udp при использовании встроенного реле TURN. Кроме того, обычный docker run, упомянутый выше, не сохраняет тома, поэтому данные Remote Play не переживут docker rm. Именно по этой причине файл Compose монтирует том halcyon-data в /data и устанавливает REMOTE_PLAY_SEED в значение /data/remote-play-seed.json.
Что делать, если магазин работает медленно
Halcyon выполняет рендеринг по запросу. Простаивающий магазин не формирует кадры, а потеря фокуса окна останавливает цикл анимации, поэтому открытая вкладка не разряжает аккумулятор ноутбука. Это помогает на устройствах с пограничной производительностью. Однако это не поможет, если устройство вообще не может отрисовать магазин.
Для таких клиентов предусмотрен режим 2.5D — обычный HTML и CSS без использования WebGL, предназначенный для оборудования уровня Raspberry Pi. Переключение между 3D и 2.5D выполняется в настройках или меню питания без перезагрузки страницы, поэтому тестирование обоих режимов на одном устройстве занимает секунды. Оценивайте возможности трезво: в августе 2026 года автор назвал плоский режим «сырым» и отметил, что проект находится на ранней стадии. Рассматривайте его как резервный вариант для слабых клиентов.
Если клиентское устройство слишком слабое для 3D-магазина, сбой будет очевиден. Вкладка перезагружается сама по себе или браузер сообщает о потере контекста WebGL, обычно в момент заполнения полок товарами. Переключите такое устройство в режим 2.5D вместо того, чтобы сокращать свою библиотеку.
Закрепляйте образ и проверяйте его перед загрузкой
Отнеситесь к этому серьезно. Теги v0.1.0 и v0.3.1 появились с разницей в несколько дней, а v0.2.1 существует только потому, что отправка образа для v0.2.0 завершилась ошибкой. С тех пор темп не снизился: v0.25.0 был опубликован 7 октября 2026 года, через три дня после v0.23.0. Отчеты об ошибках принимаются в upstream, но патчи — нет, поэтому поток релизов представляет собой текущее рабочее состояние одного разработчика.
Запуск latest с привычкой использовать docker pull означает, что хранилище может измениться в любой обычный вторник. Используйте закрепление по дайджесту — это единственная ссылка, которая не может быть изменена.
docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.25.0Эта команда выводит дайджест, соответствующий тегу. Используйте его вместо самого тега.
docker run -d --name halcyon -p 1420:1420 --restart unless-stopped \
ghcr.io/halcyon-video/halcyon-video@sha256:7fc00e619a30004d275faa79ad36d810e5a8530757132603065ba4e848dd67deПо состоянию на 11 октября 2026 года это был дайджест для тега 0.25.0. Узнавайте актуальное значение самостоятельно, а не копируйте его, и читайте примечания к релизу перед обновлением, так как патч-релиз здесь может содержать как исправления, так и изменения в структуре хранилища.
FAQ
Нужен ли Halcyon графический процессор на моем VPS?
Для обычного использования — нет. Витрина отрисовывается через three.js в браузере, поэтому рендеринг выполняет клиентская машина, а контейнер лишь отдает собранные файлы и несколько легких серверных маршрутов на порту 1420. Исключение составляют приватные экземпляры Remote Play, которые запускают headless Chromium на сервере и транслируют результат. В опубликованном образе для этого пути необходимо пробросить /dev/dri в контейнер. Без GPU сервер отказывается запускать приватную витрину.
Можно ли выставлять Halcyon в публичный интернет?
Только при наличии аутентификации. Витрина запрашивает учетные данные Jellyfin, но включение Remote Play передает вашу сессию Jellyfin серверу. В результате любой, кто загрузит /remote.html, получит доступ к вашей реальной библиотеке без авторизации. Установите перед ним reverse proxy с поддержкой single sign on или не публикуйте имя хоста в публичном DNS, получая доступ к витрине через VPN.
Почему полки пустые после входа в систему?
Браузер обращается к Jellyfin API напрямую, поэтому Jellyfin должен быть доступен из браузера, а не только с VPS. Откройте консоль браузера. Ошибка blocked by CORS policy означает, что Jellyfin не принимает запрос с адреса Halcyon. Сообщение Mixed Content означает, что страница открыта по HTTPS, а введенный адрес Jellyfin использует обычный HTTP.
Нужен ли мне --network host?
Только для Remote Play. WebRTC должен анонсировать реальный адрес машины. За Docker bridge контейнер может предложить только 172.x адрес, недоступный для телефонов в вашей сети. Для просмотра витрины в браузере достаточно -p 1420:1420, это раскрывает гораздо меньше данных хоста.
Какой тег образа использовать?
Фиксируйте digest, а не latest. Найдите digest для версии с docker buildx imagetools inspect ghcr.io/halcyon-video/halcyon-video:0.25.0, используйте его и обновляйтесь только после прочтения release notes. По состоянию на 11 октября 2026 года опубликованный образ включает как linux/amd64, так и linux/arm64, поэтому хост arm64 скачивает его как любой другой и не требует сборки из клона.