Мой сетап домашнего медиа сервера

· · обновлено

С чего всё началось

С момента как появился домашний сервер было желание сделать нормальный медиа сервер - чтобы по красоте смотреть фильмы и сериалы в 4K, без костылей.

Начиналось всё просто: сам искал фильм на rutracker, закидывал в qBittorrent, а папка загрузок была расшарена с клиентом. Клиентом сначала был Kodi - не устроил ни вид, ни функционал. Поставил Plex - уже приятнее, но часть функций платная, что крайне не устраивало.

В итоге перешёл на Jellyfin и он мне понравился. Но проблема осталась та же: часть метаданных и обложек не подгружалась, а самому всё равно приходилось искать фильм и вручную кидать его в qBittorrent, выбирая категорию указывающую куда положить файл.

Хотелось, чтобы в одном приложении искал фильм, нажимал «скачать» - и дальше само: разложилось на фильмы и сериалы, подтянулись метаданные, обложки, всё как положено.

Цель

Свести получение любого фильма или сериала к одному действию: нашёл → нажал «скачать» → посмотрел. Всё остальное - поиск релиза, скачивание, проверка качества и языка дорожки, переименование файла, раскладка по нужным папкам библиотеки - должно происходить где‑то фоново, без меня. Отдельно не хотелось терять качество выбора. Раньше при ручном поиске я сам сравнивал раздачи по качеству, сидам, дорожкам.

Что получилось на выходе

Ключевой результат - из Jellyfin теперь вообще не нужно выходить. Плагин Jellyfin‑Enhanced встраивает поиск и запрос через Seerr прямо в интерфейс Jellyfin: ищу фильм, жму «скачать» - не переключаясь на отдельную вкладку или приложение. А дальше система сама находит релиз на трекере, проверяет его по метаданным, скачивает, переименовывает и кладёт в правильную папку. Через некоторое время фильм уже находится в библиотеке Jellyfin с обложкой и описанием, готовый к просмотру. Ну просто песня, то, что и хотел.

Пример карточки

Отступление

Готового решения «поставил - и работает» под такой результат я не нашёл. Когда собирал всю эту связку, мягко говоря не был в восторге от того, сколько отдельных инструментов пришлось ставить на каждый чих. Radarr отвечает только за фильмы, Sonarr - только за сериалы, Prowlarr - только за подключение к торрент‑трекерам, Seerr - за поиск поверх всего перечисленного. Чтож так сложно жить‑то, почему до сих пор никто не собрал это всё в один готовый контейнер - вопрос открытый.

Архитектура

Схема архитектуры: Jellyfin, Seerr, Radarr, Sonarr, Prowlarr и qBittorrent

  • Jellyfin - единственная точка входа глазами пользователя. Через плагин Jellyfin‑Enhanced - поиск и запрос (проксируется в Seerr), плюс просмотр готовых файлов из /movies и /tv.
  • Seerr - поиск (TMDB) и маршрутизации запроса - кому передать, Radarr или Sonarr - и проверки, что уже есть в библиотеке. Интерфейс к нему теперь встроен в Jellyfin через плагин, отдельно как вкладку не открываю.
  • Radarr - весь жизненный цикл фильма: запрос → поиск релиза через индексер → скачивание → сверка по метаданным → переименование → hardlink в /movies. Следит за апгрейдом качества.
  • Sonarr - то же самое для сериалов, плюс отслеживание выхода новых серий по сезонам (нумерация S01E05, докачка пропущенного).
  • Prowlarr - держит подключение к индексеру (например к rutracker) и раздаёт его в Radarr и Sonarr одной настройкой.
  • FlareSolverr - прокси для обхода защиты индексеров (Cloudflare, DDoS‑Guard), к которому Prowlarr обращается при необходимости.
  • qBittorrent - просто качает файл в папку своей категории.
docker‑compose.yaml
# Перед раскаткой обязательно создаем сеть:
# docker network create --driver bridge home-network
networks:
  home-network:
    driver: bridge
    external: true

x-logging: &default-logging
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

services:
  qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    restart: unless-stopped
    mem_limit: 1g
    cpus: "2.0"
    ports:
      - "8080:8080"
    environment:
      - PUID=1000
      - PGID=1000
      - WEBUI_PORT=8080
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./qbittorrent/config:/config
      - /mnt/media:/data
    networks:
      - home-network
    logging: *default-logging
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  radarr:
    image: lscr.io/linuxserver/radarr:latest
    container_name: radarr
    restart: unless-stopped
    mem_limit: 512m
    cpus: "1.0"
    ports:
      - "7878:7878"
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./radarr/config:/config
      - /mnt/media:/data
    networks:
      - home-network
    logging: *default-logging
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:7878/ping"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  sonarr:
    image: lscr.io/linuxserver/sonarr:latest
    container_name: sonarr
    restart: unless-stopped
    mem_limit: 512m
    cpus: "1.0"
    ports:
      - "8989:8989"
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./sonarr/config:/config
      - /mnt/media:/data
    networks:
      - home-network
    logging: *default-logging
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8989/ping"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  prowlarr:
    image: lscr.io/linuxserver/prowlarr:latest
    container_name: prowlarr
    restart: unless-stopped
    mem_limit: 256m
    cpus: "0.5"
    ports:
      - "9696:9696"
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./prowlarr/config:/config
    networks:
      - home-network
    logging: *default-logging
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9696/ping"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  flaresolverr:
    image: ghcr.io/flaresolverr/flaresolverr:latest
    container_name: flaresolverr
    restart: unless-stopped
    mem_limit: 1g
    cpus: "1.0"
    ports:
      - "8191:8191"
    environment:
      - LOG_LEVEL=info
    volumes:
      - /etc/localtime:/etc/localtime:ro
    networks:
      - home-network
    logging: *default-logging
    healthcheck:
      test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8191/health', timeout=5)"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  seerr:
    image: ghcr.io/seerr-team/seerr:latest
    init: true
    container_name: seerr
    restart: unless-stopped
    mem_limit: 512m
    cpus: "1.0"
    ports:
      - "5055:5055"
    environment:
      - LOG_LEVEL=info
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./jellyseerr/config:/app/config
    networks:
      - home-network
    logging: *default-logging
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:5055/api/v1/status', r => process.exit(r.statusCode === 200 ? 0 : 1)).on('error', () => process.exit(1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

  jellyfin:
    image: jellyfin/jellyfin:latest
    container_name: jellyfin
    mem_limit: 2g
    cpus: "2.0"
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ./jellyfin/config:/config
      - ./jellyfin/cache:/cache
      - /mnt/media/movies:/movies
      - /mnt/media/tv:/tv
    restart: unless-stopped
    network_mode: host
    environment:
      - JELLYFIN_PublishedServerUrl=http://192.168.0.159:8096
    devices:
      - /dev/dri:/dev/dri
    logging: *default-logging

Из ключевого там это единый путь /data во всех контейнерах. qbittorrent, radarr и sonarr монтируют /mnt/media под одним и тем же путём. Это обязательное условие для того, чтобы Radarr/Sonarr переносили готовый файл через hardlink, а не через полное копирование - мгновенно и без расхода места на диске дважды.

Получение API ключа

Для дальнейшего подключения сервисов необходимо получить API-ключ Jellyfin. Для этого под аккаунтом администратора переходим в Панель управления → API-ключи, нажимаем «Новый API-ключ», вводим любое название, например «Медиа-сервер». Создание API-ключа в панели управления Jellyfin

Настройка Radarr

  1. Settings → Media Management → Root Folders → /data/movies
  2. Settings → Media Management → Importing → Use Hardlinks instead of Copy
  3. Settings → Download Clients → Add → qBittorrent: Host: qbittorrent (имя контейнера - оба в одной сети home-network, резолвится по имени), Port: 8080, Username/Password: логин‑пароль от WebUI, Category: radarr
  4. Settings → Connect → Jellyfin (URL http://jellyfin:8096 + API‑ключ) - чтобы библиотека обновлялась сразу после импорта.

Настройка Sonarr

То же самое: Root Folder /data/tv, категория sonarr, Use Hardlinks, Connect → Jellyfin.

FlareSolverr

FlareSolverr - прокси‑сервер для обхода защиты Cloudflare и DDoS‑Guard. Многие торрент‑индексеры - это обычный сайт без какого‑либо API, и если Prowlarr попробует обратиться к нему напрямую, IP довольно быстро попадёт под проверку на робота, и интеграция перестанет работать.

Обходит защиту FlareSolverr с помощью Selenium и chromedriver - по сути, эмулирует поведение обычного пользователя в браузере: загружает весь JS, а затем отдаёт запросу уже валидные cookie. Сам по себе веб‑интерфейса не имеет, работает как обычный API‑сервис на порту 8191, с которым по сети home-network общается только Prowlarr.

Нужен он не для всех индексеров подряд - конкретному трекеру он либо требуется (если тот стоит за Cloudflare/DDoS‑Guard), либо нет.

Настройка Prowlarr

Prowlarr - агрегатор индексеров, который раздаёт подключение к трекеру сразу в Radarr и Sonarr одной настройкой.

Прежде чем добавлять сами индексеры, подключаем FlareSolverr как прокси:

  1. Settings → Indexers → вкладка Indexer Proxies → Add (+) → FlareSolverr.
  2. Host: http://flaresolverr:8191 - контейнеры в одной Docker-сети home-network, резолвится по имени.
  3. В поле Tags задаём тег - привязка идёт именно по нему: прокси применяется к любому индексеру с совпадающим тегом. Я назвал тег по имени индексера - rutracker.

Подключение FlareSolverr к Prowlarr

Дальше сами индексеры и приложения:

  1. Indexers → Add Indexer → RuTracker.org → логин/пароль от аккаунта. Если индексеру нужен обход защиты - в его настройках в поле Tags добавляем тот же тег, что и на прокси (rutracker) - без совпадения тегов FlareSolverr использоваться не будет.
  2. Settings → Apps → Add Application:

После этого rutracker сам появляется в индексерах у Radarr и Sonarr - прописывать его там второй раз не нужно.

Настройка Seerr

  1. Settings → Services → Jellyfin (URL, логин), Radarr (URL + API Key), Sonarr (URL + API Key)
  2. Settings → General → регион/язык ru-RU - тайтлы и описания на русском (данные из TMDB)
  3. Раз сервер личный - включил автоодобрение заявок: Request сразу уходит в работу, без второго подтверждения

Настройка Jellyfin

Чтобы вся связка заработала как нужно, необходимо поставить плагин Jellyfin‑Enhanced. Инструкция по установке простая, просто в Jellyfin добавляем репозиторий, устанавливаем плагин и перезагружаем сервис. После этого в настройках в меню у нас появится пункт «Jellyfin Enhanced.»

Пункт Jellyfin Enhanced в настройках Jellyfin

Переходим в данный раздел, открываем Seerr и настраиваем по инструкции.

На этом настройка закончена. Перезагружаем Jellyfin, обновляем страницу, пишем поиске название и нажимаем «Запрос» (скачать) и все.

Поиск и запрос фильма через интеграцию Seerr прямо в Jellyfin

Загруженный фильм в библиотеке Jellyfin с обложкой и описанием

Грабли по пути

В Seerr ничего не загружалось, страницы фильмов и сериалов, в Jellyfin не подтягивались метаданные. Спустя время была найдена причина - не было доступа до themoviedb. У меня стоит AdGuard поэтому я там в настройках DNS, в Upstream DNS servers переопределил днс добавив пару строк которые и решили проблему:

[/themoviedb.org/]9.9.9.9
[/tmdb.org/]9.9.9.9

Плагины которые поставил

jellyfin‑editors‑choice‑plugin - Полноразмерный слайдер на главной странице с подборкой контента, как на Netflix. Источник подборки - избранное конкретного пользователя или случайная выборка с фильтром по рейтингу.

Слайдер с подборкой контента на главной странице Jellyfin от плагина jellyfin-editors-choice-plugin

Jellyfin‑Enhanced - помимо интеграции с Seerr это также популярный UX‑плагин: горячие клавиши, кастомизация субтитров, кастомизация карточки фильма, отзывы с TMDB.

Настройки плагина Jellyfin-Enhanced

intro‑skipper - по аудио‑отпечатку определяет заставку/титры и даёт кнопку «Пропустить» как в Netflix. Работает по всей библиотеке автоматически, разового сканирования достаточно.

jellyfin‑icon‑metadata - заменяет текстовые ссылки на источники метаданных (IMDb, TMDb, TheTVDB и так далее) на иконки на странице фильма/сериала.

Будущие цели

Вроде бы получил все что хотел, но порой хочется просто выбрать какой‑то рандомный фильм, а не конкретный. В Seerr для этого есть раздел фильмы/сериалы/найти что‑то новое с подборками по рекомендациям, студиям и тд. Вот этого не хватаетв в Jellyfin для полной автономности. Есть плагин SeerrFin но там пока что только просто лента фильмов/сериалов, без возможности искать рекомендации, по жанрам, студиям.