SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-25

Как превратить Jellyfin в видеомагазин 90-х через Halcyon

Установите Halcyon для визуализации библиотеки Jellyfin в виде интерактивного магазина. В статье приведены Docker-команда, настройка прокси и важные ограничения текущей версии.

Что Halcyon делает с вашей библиотекой Jellyfin

Halcyon Video перерисовывает вашу библиотеку Jellyfin в виде интерактивного видеомагазина 1990-х годов в браузере. Каждый фильм из вашей коллекции превращается в коробку на полке. Вы ходите по проходам под светом люминесцентных ламп, снимаете коробку с полки, переворачиваете её, чтобы прочитать описание на обороте, и несёте к стойке, чтобы начать воспроизведение. Информация о запуске, прогрессе и остановке воспроизведения передаётся обратно в Jellyfin, поэтому точки возобновления и история просмотров остаются актуальными.

Halcyon считывает данные с существующего сервера Jellyfin через Jellyfin API и не хранит собственную библиотеку. В этом руководстве предполагается, что Jellyfin уже запущен и корректно просканирован. Если это не так, сначала настройте Jellyfin как медиасервер на VPS и вернитесь, когда ваша библиотека будет правильно отображаться в обычном веб-клиенте. Это решение стоит устанавливать, когда библиотека уже готова, а не потому, что вам нужен ещё один сервис в вашем списке self-hosting.

Проект распространяется по лицензии GPL-3.0 и написан одним автором; в README прямо указано, что pull requests не принимаются. Разработка идёт быстро, и нет второго сопровождающего, который мог бы исправить регрессии, поэтому фиксируйте версию образа (pin the image version), прежде чем показывать магазин кому-либо ещё. В последнем разделе описано, как это сделать.

Где происходит рендеринг?

В браузере. Halcyon — это приложение на Vite и TypeScript, построенное на базе three.js, библиотеки JavaScript, которая отрисовывает 3D-графику через WebGL (web graphics library, интерфейс браузера к GPU). Геометрия магазина и обложки объединяются (композитятся) на устройстве, которое отображает экран.

Контейнер выполняет минимум действий. Он запускает npm run serve, который является vite preview --port 1420 --strictPort --host, и отдает собранные файлы вместе с несколькими небольшими маршрутами middleware. Halcyon не выполняет транскодирование и не запускает движки на стороне сервера.

Таким образом, вопрос нагрузки на GPU относится к клиенту. Небольшой VPS легко справляется с этой задачей, так как отдача статических файлов по HTTP не требует больших ресурсов. Ноутбук, планшет или телевизор, на котором запущен браузер, определяет, будет ли магазин работать плавно или с задержками.

Одна функция нарушает это правило. Remote Play запускает headless-экземпляры Chromium на сервере и транслирует отрисованный магазин на телефон или ТВ-приставку через WebRTC (web real time communication). В этом сценарии рендеринг происходит на сервере; по умолчанию ограничение установлено на два экземпляра, его можно изменить с помощью REMOTE_PLAY_MAX_INSTANCES. Без проброшенного устройства /dev/dri эти экземпляры используют для рендеринга CPU, поэтому двухъядерный VPS будет ощущать нагрузку от каждого дополнительного зрителя.

Что магазин считывает из вашей библиотеки

Стеллажи формируются на основе структуры Jellyfin. Halcyon распределяет разделы из ваших библиотек и жанров, а также группирует сиквелы из ваших BoxSets. Технические характеристики, указанные на обороте каждой коробки, берутся из метаданных MediaStreams, которые уже хранятся в Jellyfin. Это означает, что всё, чего нет в Jellyfin, будет отсутствовать и на полке.

Таким образом, магазин является точным отражением ваших метаданных. Библиотека, наполняемая через стек *arr в Docker Compose с уже заполненными обложками и жанрами, выглядит здесь гораздо лучше, чем папка с разрозненными файлами и общими названиями. Фотобиблиотеки имеют такую же зависимость от того, что именно их проиндексировало. Об этом стоит помнить, когда вы сравниваете PhotoPrism и Immich для хранения снимков на одном сервере.

Протестируйте демо-версию магазина перед установкой

Проект предоставляет доступ к полноценному магазину, работающему с синтетической библиотекой, по адресу размещенной демо-версии. Добавление ?demo=1 к любому URL Halcyon выполнит аналогичное действие в вашем собственном развертывании.

Используйте это для проверки аппаратного обеспечения. Демо-библиотека содержит около 2000 наименований и требует примерно 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, контейнер знает только свой адрес 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.

Одно жесткое ограничение на август 2026 года: опубликованный образ доступен только для linux/amd64. Сборка arm64-части для мультиархитектурного образа завершилась ошибкой при эмуляции, ожидается запуск на нативных arm-раннерах. На VPS с архитектурой arm64 команда pull завершится с ошибкой 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 не проксирует 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 (transport layer security) termination и не имеет встроенного контроля доступа, поэтому при публикации в сети его следует размещать за 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://. Браузер блокирует обычный вызов http:// API со страницы 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 как self-hosted SSO-шлюз или откажитесь от публичного имени хоста и обращайтесь к хранилищу через туннель 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 выполняется в настройках или меню питания без перезагрузки страницы, поэтому тестирование обоих режимов на одном устройстве занимает секунды. Будьте реалистичны в ожиданиях: автор описывает плоский режим как грубый и находящийся в разработке. Рассматривайте его как резервный вариант для слабых клиентов.

Если клиент слишком слаб для 3D-магазина, сбой будет очевиден. Вкладка перезагружается сама по себе или браузер сообщает о потере контекста WebGL, обычно в момент заполнения полок. Переведите такое устройство в режим 2.5D вместо того, чтобы сокращать свою библиотеку.

Закрепляйте образ и проверяйте его перед загрузкой

Отнеситесь к этому серьезно. Теги v0.1.0 и v0.3.1 появились с разницей в несколько дней, а v0.2.1 существует только потому, что отправка образа для v0.2.0 завершилась ошибкой. Отчеты об ошибках принимаются разработчиками, но патчи — нет, поэтому поток релизов отражает текущее рабочее состояние системы одного человека.

Запуск latest с привычкой использовать docker pull означает, что содержимое хранилища может измениться в любой обычный вторник. Используйте закрепление по дайджесту — это единственная ссылка, которая не может измениться.

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

Эта команда выводит дайджест, соответствующий тегу. Используйте его вместо тега.

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

Этот дайджест был 0.3.1 10 августа 2026 года. Самостоятельно проверяйте текущее значение, а не копируйте его, и читайте примечания к выпуску перед обновлением, так как патч-релиз может содержать как исправления, так и изменения в структуре хранилища.

FAQ

Нужен ли Halcyon графический процессор на моем VPS?

Для обычного использования — нет. Витрина отрисовывается через three.js в браузере, поэтому рендеринг выполняет клиентская машина, а контейнер лишь отдает статические файлы на порту 1420. Исключение составляет Remote Play: он запускает headless Chromium на сервере и транслирует результат. Этот процесс выполняется на CPU, если вы не пробросите /dev/dri в контейнер для аппаратного ускорения.

Можно ли выставлять Halcyon в публичный интернет?

Только при наличии аутентификации. Витрина запрашивает учетные данные Jellyfin, но включение Remote Play передает вашу сессию Jellyfin серверу. В результате любой, кто загрузит /remote.html, получит доступ к вашей реальной библиотеке без авторизации. Установите перед ним reverse proxy с единым входом (SSO) или не добавляйте имя хоста в публичный DNS, получая доступ к витрине через VPN.

Почему полки пустые после входа в систему?

Браузер обращается к API Jellyfin напрямую, поэтому 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.3.1, используйте его и обновляйтесь только после прочтения примечаний к релизу. По состоянию на август 2026 года опубликованный образ доступен только для linux/amd64, поэтому на хосте с архитектурой arm64 необходимо выполнить сборку из клона с помощью docker compose up -d.