У меня дома стоит роутер Keenetic с USB-диском на 2 ТБ. Долгое время он работал просто как сетевое хранилище — файлы лежат, Transmission на роутере крутится, всё вроде бы работает. Но дефолтный интерфейс Transmission выглядит как привет из 2009 года, управлять им с телефона неудобно, а посмотреть скачанное на телевизоре — вообще отдельный квест.
Однажды вечером решил это исправить. Итог — три Docker-контейнера, которые поднимаются одной командой, и теперь выглядит это так:
-
Открываю Telegram, кидаю боту
.torrentфайл -
Бот подтверждает: “✅ Добавлен!”
-
Через время пишет: “✅ Скачано! 📁 Название фильма · 💾 15 ГБ”
-
Открываю Jellyfin на телевизоре — фильм уже там, с постером и описанием на русском
Расскажу как это всё устроено.
Что получится в итоге
-
Transmission + Flood UI — торрент-клиент с современным интерфейсом вместо дефолтного
-
Jellyfin — медиасервер с постерами, описаниями, субтитрами. Работает на телевизоре, телефоне, в браузере
-
Telegram-бот — добавляй торренты и получай уведомления прямо в мессенджере
-
Watch-папка — кинул
.torrentфайл в папку на NAS, качается само -
Всё хранится на сетевом диске NAS (SMB/CIFS) и переживёт переустановку ОС
Стек и архитектура
┌─────────────────────────────────────┐│ Docker на Windows ││ ││ ┌──────────────┐ ┌──────────────┐ ││ │ Transmission │ │ Jellyfin │ ││ │ + Flood UI │ │ :8096 │ ││ │ :9091 │ └──────┬───────┘ ││ └──────┬───────┘ │ ││ │ ┌──────────┘ ││ ┌──────┴─────┴──┐ ││ │ Docker Volume │ ││ │ (CIFS/SMB) │ ││ └──────┬────────┘ ││ ┌──────┴───────┐ ││ │ Telegram Bot │ ││ └──────────────┘ │└─────────────┬───────────────────────┘ │ SMB┌─────────────▼───────────────────────┐│ NAS / Keenetic ││ \\192.168.1.хх\Transmission ││ ├── Downloads/ ← фильмы ││ ├── .incomplete/ ← в процессе ││ └── watch/ ← auto-add │└─────────────────────────────────────┘
Ключевая идея — Docker volume типа CIFS монтирует сетевую шару напрямую внутрь контейнеров. Оба контейнера (Transmission и Jellyfin) работают с одними и теми же файлами на NAS: первый пишет, второй читает.
Требования
-
Docker Desktop (Windows / macOS) или Docker Engine (Linux)
-
NAS или роутер с USB-диском и SMB-шарой (Keenetic, Synology, QNAP и др.)
-
Telegram-аккаунт для бота
Нет NAS? В конце есть FAQ с вариантом на локальной папке.
Шаг 1. Клонируем репозиторий
git clone https://github.com/vervs3/mediabox.gitcd mediabox
Структура проекта:
mediabox/├── docker-compose.yml├── .env.example├── bot/│ ├── bot.py│ ├── Dockerfile│ └── requirements.txt└── transmission/ ├── setup-flood.sh └── custom-cont-init.d/ └── 01-fix-settings.sh
Шаг 2. Настраиваем конфигурацию
cp .env.example .env
Редактируем .env:
# Часовой поясTZ=Europe/Moscow# Сетевой диск (NAS, роутер Keenetic и т.д.)SMB_HOST=//192.168.1.45/TransmissionSMB_USER=adminSMB_PASSWORD=your_password# Telegram-бот (получить у @BotFather)BOT_TOKEN=123456789:AAxxxxx...# Ваш Telegram ID (получить у @userinfobot)ALLOWED_USER_ID=123456789
Шаг 3. Устанавливаем Flood UI
Здесь первые грабли. Я ожидал что образ linuxserver/transmission включает Flood прямо из коробки — раньше так и было. Но в свежих версиях сторонние UI убрали, и при запуске контейнер пишет:
Changes Required!This image no longer bundles 3rd party Transmission UI packages.We would advise you to use subfolders under /config to store your UI packages
Поэтому скачиваем Flood вручную:
# Linux / macOSbash transmission/setup-flood.sh
Скрипт скачивает последний релиз flood-for-transmission и распаковывает в transmission/config/flood-ui/.
На Windows — скачайте архив вручную с GitHub releases и распакуйте в папку transmission/config/flood-ui/.
Шаг 4. Запускаем
docker compose up -d
При первом запуске Docker скачает образы (~700 МБ суммарно) и запустит все три сервиса.
Проверяем:
docker compose ps
NAME STATUStransmission Upjellyfin Uptransmission-bot Up
Открываем:
-
Transmission (Flood UI): http://localhost:9091
-
Jellyfin: http://localhost:8096
Как это работает внутри — три нетривиальных момента
1. Docker volume с CIFS вместо bind mount
Первое что я попробовал — пробросить сетевой диск Z:\ как обычный volume:
volumes: - "Z:/:/downloads" # Не работает!
Не работает. Docker Desktop на Windows работает поверх WSL2, и смонтированные в Windows сетевые диски (Z:\, \\server\share) недоступны изнутри контейнера. WSL2 просто их не видит.
Решение — Docker volume с драйвером CIFS. Docker сам монтирует шару напрямую, минуя Windows:
volumes: downloads: driver: local driver_opts: type: cifs device: ${SMB_HOST} o: "username=${SMB_USER},password=${SMB_PASSWORD},vers=3.0,uid=1000,gid=1000,file_mode=0777,dir_mode=0777"
Оба контейнера — Transmission и Jellyfin — подключают этот volume и работают с одними файлами. Transmission пишет в /downloads/Downloads, Jellyfin читает из /media/Downloads (это один и тот же volume, просто под разными именами внутри контейнеров).
2. Патч настроек Transmission при каждом старте
Второй сюрприз — образ linuxserver/transmission при каждом запуске перезаписывает часть настроек дефолтными значениями. Пытаешься задать watch-dir через переменные окружения — не применяется. Правишь settings.json — при следующем старте снова сбрасывается.
Помогает механизм custom-cont-init.d: скрипты из этой папки выполняются после инициализации образа, но до старта transmission-daemon. Подкладываем туда патч:
#!/bin/sh# Создаём нужные папки на шареmkdir -p /downloads/Downloadsmkdir -p /downloads/.incompletemkdir -p /downloads/watch# Патчим settings.jsonSETTINGS=/config/settings.jsonif [ -f "$SETTINGS" ]; then sed -i 's|"watch-dir": ".*"|"watch-dir": "/downloads/watch"|' $SETTINGS sed -i 's|"watch-dir-enabled": false|"watch-dir-enabled": true|' $SETTINGS sed -i 's|"download-dir": ".*"|"download-dir": "/downloads/Downloads"|' $SETTINGS sed -i 's|"incomplete-dir": ".*"|"incomplete-dir": "/downloads/.incomplete"|' $SETTINGS sed -i 's|"incomplete-dir-enabled": false|"incomplete-dir-enabled": true|' $SETTINGSfi
В логах видно что скрипт выполнился:
[custom-init] Files found, executing[custom-init] 01-fix-settings.sh: executing...[custom-init] 01-fix-settings.sh: exited 0Connection to localhost (127.0.0.1) 9091 port [tcp/*] succeeded!
3. Git Bash ломает пути в переменных окружения
Третий подводный камень специфичен для Windows + Git Bash. Когда передаёшь путь вида /flood-for-transmission/ через -e в docker run, Git Bash радостно превращает его в C:/Program Files/Git/flood-for-transmission/.
В логах это выглядит так:
ERR utils.cc:144 Couldn't read 'C:/Program Files/Git/flood-for-transmission//index.html'
Лечится переменной окружения перед командой:
MSYS_NO_PATHCONV=1 docker run ...
В docker-compose.yml эта проблема не возникает — там пути не проходят через интерпретатор Git Bash.
Telegram-бот
Бот работает в контейнере с network_mode: "service:transmission" — разделяет сетевой стек с Transmission и подключается к нему через localhost:9091. Не нужно открывать лишние порты и нет сетевых задержек между контейнерами.
Команды
|
Команда |
Что делает |
|---|---|
|
|
Список всех торрентов с прогресс-барами и кнопками управления |
|
|
Только активные загрузки |
|
|
Скорости, статистика за всё время |
|
|
Справка |
Добавление торрентов
Три способа:
-
Отправить
.torrentфайл боту прямо в чат -
Отправить
magnet:ссылку текстом -
Кинуть
.torrentв папкуwatchна NAS — Transmission подхватит сам
Прогресс-бар из юникода
Мелочь, но приятная — прогресс отображается прямо в тексте сообщения:
def progress_bar(pct, width=12): filled = round(pct * width) return "█" * filled + "░" * (width - filled)# [████████░░░░] 67%
Уведомления о завершении без дублей
Чтобы бот не спамил уже завершёнными торрентами при рестарте, при старте инициализируем множество уже скачанных ID:
async def check_completed(app): # При старте — молча запоминаем уже завершённые torrents = await client.get_torrents(["id", "percentDone"]) completed_ids = {t["id"] for t in torrents if t["percentDone"] == 1.0} while True: await asyncio.sleep(CHECK_INTERVAL) torrents = await client.get_torrents(["id", "name", "percentDone", "totalSize"]) for t in torrents: if t["percentDone"] == 1.0 and t["id"] not in completed_ids: completed_ids.add(t["id"]) # Только новые завершения — уведомляем await notify_completed(app, t)
Когда торрент скачается, бот пришлёт:
✅ Скачано!📁 Название.Фильма.2025.WEB-DL.1080p💾 15.2 ГБ[📋 Подробнее] [🗑 Удалить]
Jellyfin — первый запуск
-
Открыть http://localhost:8096
-
Пройти мастер настройки (язык, создать пользователя)
-
Добавить медиатеку → тип “Фильмы” → путь
/media/Downloads -
Jellyfin сам найдёт файлы, скачает постеры и описания с TMDB
Подключение с телевизора или телефона
Установите приложение Jellyfin (Android TV, Apple TV, Roku, Fire TV, iOS, Android) и введите:
http://192.168.x.x:8096
Где 192.168.x.x — локальный IP вашего компьютера (ipconfig на Windows, ip a на Linux).
FAQ
Q: Работает ли без NAS, просто на локальной папке?
Да. Замените volume в docker-compose.yml:
volumes: downloads: driver: local driver_opts: type: none device: /absolute/path/to/folder o: bind
Q: Как добавить VPN чтобы провайдер не видел торренты?
Добавьте сервис gluetun и переведите Transmission на его сеть:
transmission: network_mode: "service:gluetun" depends_on: - gluetun
Q: Как обновить до новых версий образов?
docker compose pull && docker compose up -d
Q: Можно ли открыть доступ извне, не только в локальной сети?
Да, проще всего через Tailscale или Cloudflare Tunnel — без проброса портов на роутере.
Итог
Стек получился компактным — три контейнера, один docker-compose.yml, один .env файл с паролями. Поднимается с нуля за 5 минут, файлы живут на NAS и никуда не денутся при переустановке ОС.
Если у вас дома лежит роутер с USB-диском и вы до сих пор смотрите кино через проводник — попробуйте. Разница ощутимая.
Репозиторий: https://github.com/vervs3/mediabox
Буду рад звёздочке ⭐ и вопросам в комментариях — особенно если столкнётесь с другими граблями на своём железе.
ссылка на оригинал статьи https://habr.com/ru/articles/1025302/