Домашний медиахаб на Orange Pi RV2

ЧАСТЬ 1. От «чистой» платы до базово защищённой системы

(Orange Pi RV2 + Ubuntu Server)


0. Введение — что мы строим и зачем

В этой серии статей мы шаг за шагом превращаем Orange Pi RV2 в полноценный домашний медиахаб и сервисный узел, который:

  • хранит фильмы и сериалы на внешнем SSD;
  • раздаёт медиаконтент по DLNA на телевизоры и приставки;
  • скачивает торренты без GUI через qBittorrent-nox;
  • имеет собственный Web-интерфейс на Rust (Axum);
  • готов к будущему WireGuard-доступу из интернета;
  • работает стабильно и безопасно.

Цель проекта — не «чтобы как-то работало», а построить правильную архитектуру, которую не стыдно оставить работать годами без постоянного обслуживания.


🔁 Шаг 0. Запись образа Ubuntu Server (microSD)

0.1 Скачивание образа

Используем официальный Ubuntu Server для Orange Pi RV2
(на момент написания — Ubuntu 24.04 Noble, riscv64).

📌 Важно:

  • именно Server, не Desktop;
  • именно образ под RV2 (RISC-V).

0.2 Запись через dd (осознанно)

Мы используем dd, потому что:

  • он ничего не «улучшает» и не портит;
  • показывает реальное устройство;
  • исключает «магические» проблемы, которые любят графические утилиты.

Определяем microSD

lsblk

Пример:

sda      465G
mmcblk0   32G   ← microSD

НЕ перепутай диск.
Ошибка здесь = потеря данных.

Запись образа

sudo dd if=ubuntu-opi-rv2.img of=/dev/mmcblk0 bs=4M status=progress conv=fsync

После завершения:

sync
  • вынимаем карту;
  • вставляем в Orange Pi RV2;
  • включаем питание.

1. Подготовка системы

1.1 Первый вход и проверка образа

Подключаемся по SSH (IP смотрим в роутере):

ssh orangepi@192.168.1.xxx

Пароль по умолчанию — orangepi.

Проверяем систему:

lsb_release -a
uname -a

Убеждаемся, что:

  • Ubuntu Server;
  • архитектура riscv64;
  • система загружается стабильно.

Перенос системы с microSD на eMMC (штатный способ Orange Pi)

После первой загрузки Orange Pi RV2 с microSD логично перенести систему на встроенную eMMC:

  • eMMC быстрее microSD
  • стабильнее при долгой работе
  • освобождает слот microSD
  • официально поддерживается образом Orange Pi

❗ ВАЖНО
Мы НЕ используем dd, НЕ копируем вручную разделы, НЕ трогаем bootloader.
Всё делается через встроенную утилиту, которая уже есть в образе Orange Pi Ubuntu Server.


🔹 Что нам нужно перед началом

  • Orange Pi RV2 загружен с microSD
  • Вход под пользователем (root или sudo)
  • Никаких важных данных на eMMC (она будет полностью очищена)

🔹 Проверяем, что eMMC определяется системой

lsblk

Обычно:

  • mmcblk0 — microSD
  • mmcblk1 — eMMC

Пример:

mmcblk0    14.9G
└─mmcblk0p1
mmcblk1    29.1G

Если mmcblk1 присутствует — можно продолжать.


🔹 Запуск штатной утилиты Orange Pi

В образе уже есть фирменный скрипт:

sudo orangepi-config

Откроется текстовое меню (ncurses).


🔹 Путь в меню

System  →  Install system to eMMC

(формулировка может незначительно отличаться, но смысл именно такой)


🔹 Что делает утилита

Утилита автоматически:

  • полностью очищает eMMC
  • копирует систему с microSD
  • переносит загрузчик
  • корректно настраивает boot-раздел
  • сохраняет совместимость с обновлениями

⚠️ Все данные на eMMC будут удалены — это подтверждается отдельным предупреждением.

Подтверждаем перенос.


🔹 Ожидание завершения

Процесс занимает обычно ~ 5 минут, зависит от скорости microSD.

Во время процесса:

  • ничего не отключаем
  • не перезагружаем плату
  • не вынимаем питание

По завершении появится сообщение об успешной установке.


🔹 Перезагрузка и проверка

Выключаем плату:

sudo poweroff
  1. Вынимаем microSD
  2. Включаем питание обратно

Если всё сделано правильно — система загрузится уже с eMMC.

Проверяем:

lsblk

Теперь корневая система (/) должна быть на mmcblk1.


1.2 ❗ Исправление APT (Ubuntu Ports) — КРИТИЧЕСКИ ВАЖНО

На ARM/RISC-V платах часто используются сломанные зеркала
(Huawei Cloud и т.п.).
Если не исправить это сейчас — дальше всё будет ломаться.

Открываем файл:

sudo nano /etc/apt/sources.list

Полностью заменяем содержимое:

deb http://ports.ubuntu.com/ubuntu-ports noble main restricted universe multiverse
deb http://ports.ubuntu.com/ubuntu-ports noble-updates main restricted universe multiverse
deb http://ports.ubuntu.com/ubuntu-ports noble-security main restricted universe multiverse
deb http://ports.ubuntu.com/ubuntu-ports noble-backports main restricted universe multiverse

Сохраняем (Ctrl+O → Enter → Ctrl+X) и обновляем систему пакетов:

sudo apt clean
sudo apt update

❗ Если здесь есть ошибки — дальше идти нельзя.


1.3 Полное обновление системы

sudo apt upgrade -y
sudo reboot

После перезагрузки снова подключаемся по SSH


2. Базовая безопасность (обязательно)

2.1 Настройка hostname

Hostname используется:

  • в логах;
  • в systemd;
  • в VPN;
  • в Web UI.

Проверяем текущее имя:

hostname

Задаём корректное имя:

sudo hostnamectl set-hostname rv2-hub

Правим /etc/hosts:

sudo nano /etc/hosts

Приводим к виду:

127.0.0.1   localhost
127.0.1.1   rv2-hub

Перезагружаемся:

sudo reboot

2.2 Проверка имени и сети

hostname
hostname -I

Ожидаемо:

  • hostname: rv2-hub
  • IP: 192.168.1.xxx

2.3 Создание отдельного пользователя

Никогда не работаем постоянно под orangepi или root.

Создаём пользователя:

sudo adduser hub

Даём права sudo:

sudo usermod -aG sudo hub

Проверяем:

su - hub
sudo whoami

Если вывод — root, всё в порядке.

👉 Дальше работаем только под пользователем hub.


2.4 SSH hardening (аккуратно)

Открываем конфиг:

sudo nano /etc/ssh/sshd_config

Проверяем / приводим строки:

PermitRootLogin no
PasswordAuthentication yes
PubkeyAuthentication yes
X11Forwarding no
UsePAM yes

📌 Если строки закомментированы — раскомментируй.

Перезапуск SSH:

sudo systemctl restart ssh

ОБЯЗАТЕЛЬНО:
Открой новое SSH-подключение перед тем, как закрывать старое.

Проверка защиты:

ssh root@rv2-hub

Ожидаемо:

Permission denied

Проверяем открытые порты:

ss -tulpen

На этом этапе должен быть открыт только порт 22 (SSH).


⏭ Что дальше

На этом этапе у нас есть:

  • корректный Ubuntu Server;
  • исправленные репозитории;
  • актуальная система;
  • нормальный hostname;
  • отдельный пользователь;
  • базово защищённый SSH.

👉 В ЧАСТИ 2 мы займёмся сетевым периметром:

  • UFW по-настоящему подробно;
  • multicast и SSDP (без которых DLNA не работает);
  • Fail2Ban с реальным конфигом;
  • подготовка системы к WireGuard.

🧱 ЧАСТЬ 2. Firewall и защита

(UFW + multicast + Fail2Ban + подготовка к WireGuard)

В Части 1 мы сделали главное:

  • установили Ubuntu Server;
  • привели APT в порядок;
  • обновили систему;
  • настроили hostname rv2-hub;
  • создали отдельного пользователя hub;
  • аккуратно ужесточили SSH.

Теперь у нас чистая и управляемая система.
Но без сетевой защиты это всё — иллюзия безопасности.

👉 Часть 2 — это сетевой периметр.
Здесь чаще всего допускают критические ошибки:

  • либо открывают всё подряд;
  • либо боятся открыть даже нужное;
  • либо «включают UFW, а потом DLNA не работает».

Мы пойдём правильным путём и сделаем всё сразу и навсегда.


Что мы делаем в этой части

В Части 2 мы:

  • настраиваем UFW;
  • разбираем multicast и SSDP, без которых DLNA не живёт;
  • включаем Fail2Ban с реальным конфигом;
  • готовим систему к будущему WireGuard, не устанавливая его.

1️⃣ UFW — firewall

На Ubuntu Server UFW — оптимальный выбор, потому что он:

  • простой;
  • предсказуемый;
  • отлично дружит с systemd;
  • не ломает multicast (если настроить правильно).

1.1 Проверяем и устанавливаем UFW

Работаем под пользователем hub.

sudo apt install ufw -y

Если UFW уже установлен — это нормально.


1.2 Политики по умолчанию (ОБЯЗАТЕЛЬНО)

Это основа безопасности, без этого UFW не имеет смысла.

sudo ufw default deny incoming
sudo ufw default allow outgoing

Что это означает:

  • все входящие соединения запрещены;
  • все исходящие разрешены.

📌 Сервер может сам ходить в интернет
📌 Но к нему нельзя подключиться, пока мы явно не разрешим порт


1.3 Разрешаем SSH (иначе отрежем себе доступ)

sudo ufw allow ssh

или явно:

sudo ufw allow 22/tcp

Проверяем:

sudo ufw status verbose

Ожидаемо:

22/tcp  ALLOW  Anywhere

2️⃣ Почему мы открываем порты СРАЗУ, а не «потом»

Это ключевой архитектурный момент.

❌ Плохая практика

  • включить firewall;
  • поставить сервис;
  • «почему не работает?»;
  • открыть порт;
  • забыть;
  • через месяц всё сломалось.

✅ Правильная практика

  • заранее понимать архитектуру;
  • открыть все нужные порты один раз;
  • больше не возвращаться к firewall без причины.

Мы уже знаем, что на этом сервере будет:

  • qBittorrent-nox;
  • MiniDLNA;
  • Web UI (RV2 Hub);
  • WireGuard в будущем.

👉 Значит, закладываем всё сейчас.


3️⃣ Открываем порты для сервисов

🔹 Web UI (RV2 Hub)

sudo ufw allow 8081/tcp

🔹 qBittorrent-nox (Web UI)

По умолчанию qBittorrent использует 8080/tcp:

sudo ufw allow 8080/tcp

(порт можно изменить позже, логика останется той же)


🔹 MiniDLNA (HTTP)

MiniDLNA использует 8200/tcp:

sudo ufw allow 8200/tcp

4️⃣ Multicast и SSDP — главный затык DLNA

Без multicast DLNA не работает, даже если сервис запущен.

Что такое SSDP

DLNA-клиенты:

  • не ищут сервер по IP;
  • ✅ используют multicast.

Параметры SSDP:

  • адрес: 239.255.255.250
  • порт: 1900/udp

Разрешаем SSDP

sudo ufw allow 1900/udp

📌 Это пункт, который:

  • пропускают 90% инструкций;
  • потом пишут «телевизор не видит DLNA».

Проверяем итоговые правила

sudo ufw status verbose

Ожидаемо:

22/tcp    ALLOW
8080/tcp  ALLOW
8081/tcp  ALLOW
8200/tcp  ALLOW
1900/udp  ALLOW

5️⃣ Включаем firewall

⚠️ Перед этим ЕЩЁ РАЗ убедись, что SSH разрешён.

sudo ufw enable

Ответ:

Firewall is active and enabled on system startup

6️⃣ Проверка multicast на Orange Pi RV2

На Orange Pi интерфейс НЕ eth0.

Чаще всего это:

  • end0
  • enx<mac>
  • enP...

Проверяем:

ip a

Пример:

2: end0: <BROADCAST,MULTICAST,UP,LOWER_UP>
    inet 192.168.1.108/24

📌 Ключевое — наличие флага MULTICAST
Если его нет — DLNA не заработает вообще


7️⃣ Fail2Ban — защита от перебора паролей

Fail2Ban:

  • следит за логами;
  • банит IP при подборе пароля;
  • критически важен перед WireGuard.

7.1 Установка

sudo apt install fail2ban -y

Проверка:

systemctl status fail2ban

Ожидаемо:

Active: active (running)

7.2 Почему дефолт «почти норм», но мы делаем лучше

По умолчанию Fail2Ban:

  • уже защищает SSH;
  • использует sshd jail.

👉 Это нормально, но мы делаем явный и читаемый конфиг.


7.3 Реальный рабочий конфиг

Создаём локальный файл:

sudo nano /etc/fail2ban/jail.local

Вставляем:

[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
findtime = 10m
bantime = 1h

Что это значит:

  • 5 попыток;
  • за 10 минут;
  • бан на 1 час.

Перезапуск:

sudo systemctl restart fail2ban

Проверка:

sudo fail2ban-client status sshd

8️⃣ Подготовка к WireGuard (без установки)

Мы НЕ ставим WireGuard сейчас, но готовим систему.

Почему это важно:

  • WireGuard = внешний доступ;
  • внешний доступ = атаки;
  • Fail2Ban + UFW = защита.

📌 Благодаря текущей настройке:

  • SSH защищён;
  • порты под контролем;
  • multicast работает;
  • система готова к VPN.

9️⃣ Финальная проверка Части 2

sudo ufw status verbose
systemctl status fail2ban
ss -tulpen

Ожидаемо:

  • открыт только нужный минимум;
  • лишних портов нет;
  • Fail2Ban активен;
  • multicast разрешён.

🔚 Итог Части 2

На этом этапе у нас:

  • ✅ грамотно настроенный firewall;
  • ✅ рабочий multicast для DLNA;
  • ✅ защита от перебора паролей;
  • ✅ готовность к WireGuard;
  • ✅ и главное — к UFW больше не возвращаемся.

🧱 ЧАСТЬ 3. Подключение и подготовка SSD

Основа стабильности: данные, права, структура

В предыдущих частях мы:

  • установили и обновили Ubuntu Server;
  • привели APT в порядок;
  • настроили базовую безопасность;
  • выстроили правильный firewall, multicast и защиту.

Теперь переходим к фундаменту всего проекта — внешнему SSD.
Именно здесь чаще всего закладываются ошибки, которые потом «вылезают» в виде:

  • MiniDLNA «не видит файлы»;
  • qBittorrent не может писать;
  • сервисы работают только от root;
  • «после перезагрузки всё пропало».

⚠️ Если этот этап сделать правильно — дальше всё будет работать годами без вмешательства.


🔹 Зачем вообще SSD, если есть microSD / eMMC

microSD / eMMC:

  • ограниченный ресурс записи;
  • низкая скорость;
  • не предназначены для постоянной нагрузки.

SSD:

  • высокая скорость;
  • надёжность;
  • идеально подходит для:
    • фильмов и сериалов (DLNA);
    • загрузок qBittorrent;
    • приложений (RV2 Hub);
    • будущих сервисов и бэкапов.

👉 Правильная архитектура:

  • microSD / eMMC → только система
  • SSD → все данные и сервисы

4️⃣ Подключение и проверка SSD

4.1 Проверка — видит ли система диск

Подключаем SSD к Orange Pi RV2 (желательно USB 3.0).

Проверяем:

lsblk -o NAME,SIZE,TYPE,FSTYPE,MOUNTPOINT

Пример корректного вывода:

sda      465G disk
└─sda1   465G part exfat
mmcblk0   32G disk
├─mmcblk0p1 512M part /boot
└─mmcblk0p2 31G  part /

Что здесь важно:

  • mmcblk0 — системный носитель (microSD / eMMC)
  • sda — наш SSD
  • exfatплохо для сервера

❌ Почему exFAT / NTFS — плохой выбор

Для серверной Linux-системы:

  • ❌ нет нормальных Unix-прав;
  • ❌ проблемы с systemd-сервисами;
  • ❌ MiniDLNA часто не индексирует файлы;
  • ❌ «магические» ошибки доступа.

👉 Выбор один: ext4.


🔥 4.2 Полная переразметка SSD (exFAT → ext4)

⚠️ ВСЕ ДАННЫЕ НА SSD БУДУТ УДАЛЕНЫ
Если на диске есть что-то важное — остановись.

Шаг 1. Проверяем, что диск не смонтирован

mount | grep sda

Если смонтирован:

sudo umount /dev/sda1

Шаг 2. Удаляем старую разметку

sudo fdisk /dev/sda

Вводим строго по шагам:

d        ← удалить раздел
1        ← номер

n        ← новый
p        ← primary
1        ← номер
Enter    ← начало
Enter    ← конец (весь диск)

w        ← записать и выйти

Шаг 3. Создаём файловую систему ext4

sudo mkfs.ext4 -L STORAGE /dev/sda1

Проверяем:

blkid /dev/sda1

Пример:

/dev/sda1: UUID="abcd-1234-efgh-5678" TYPE="ext4" LABEL="STORAGE"

👉 UUID обязательно сохраняем.


4.3 Автомонтирование через UUID (fstab)

Шаг 1. Создаём точку монтирования

sudo mkdir -p /srv/storage

📌 Почему /srv:

  • стандарт Linux для сервисных данных;
  • логично и читаемо;
  • удобно для бэкапов и миграций.

Шаг 2. Правим /etc/fstab

Добавляем строку (UUID свой!):

UUID=abcd-1234-efgh-5678  /srv/storage  ext4  defaults,noatime  0  2

💡 noatime — меньше лишних записей → SSD живёт дольше.

Шаг 3. Проверка без перезагрузки

sudo mount -a

Ошибок быть не должно
Если есть ошибка — НЕ перезагружайся, исправляй сразу.

Проверка:

df -h | grep storage

Ожидаемо:

/dev/sda1   477G   1G  450G   1%  /srv/storage

4.4 Права доступа — самый важный момент

80% проблем с DLNA и торрентами — это права.

Мы работаем под пользователем hub.

Шаг 1. Назначаем владельца

sudo chown -R hub:hub /srv/storage

Шаг 2. Базовые права

sudo chmod 755 /srv/storage

Что это даёт:

  • hub — полный доступ;
  • сервисы могут читать;
  • MiniDLNA индексирует без проблем.

Шаг 3. Проверка

touch /srv/storage/test.txt
ls -l /srv/storage

Ожидаемо:

-rw-r--r-- 1 hub hub 0 test.txt

Если файл создаётся — всё сделано правильно.


4.5 Финальная структура каталогов

Создаём осмысленную структуру, а не свалку:

mkdir -p /srv/storage/{apps,downloads,media}
mkdir -p /srv/storage/media/{movies,tv,music}
mkdir -p /srv/storage/downloads/{watch,incomplete,complete}

📌 Почему watch оставляем:

  • qBittorrent умеет «следить» за папкой;
  • закинул .torrent → загрузка началась автоматически;
  • удобно и без Web UI.
tree /srv/storage -L 3

Ожидаемо:

/srv/storage
├── apps
├── downloads
│   ├── watch
│   ├── incomplete
│   └── complete
└── media
    ├── movies
    ├── music
    └── tv

📌 Эта структура базовая и финальная:

  • media → MiniDLNA
  • downloads → qBittorrent
  • apps → RV2 Hub (Rust) и будущие сервисы

✅ Итог Части 3

На этом этапе у нас:

  • SSD в ext4;
  • стабильное автомонтирование по UUID;
  • корректные права под hub;
  • продуманная структура каталогов;
  • нулевые предпосылки к будущим проблемам.

🧱 ЧАСТЬ 4. MiniDLNA — полноценный медиасервер

(Orange Pi RV2 · Ubuntu Server · SSD · DLNA)

В предыдущих частях мы сделали самое важное:

  • подготовили систему и безопасность;
  • настроили firewall и multicast (SSDP);
  • корректно подключили SSD;
  • создали правильную структуру каталогов.

👉 Теперь пришло время запустить MiniDLNA — сервис, ради которого вся эта инфраструктура и строилась.

❗ MiniDLNA — простой, стабильный и предсказуемый DLNA-сервер.
Он не требует GUI, не тянет лишних зависимостей и отлично работает на слабых ARM / RISC-V платах.


1️⃣ Что такое MiniDLNA и почему именно он

MiniDLNA (ReadyMedia):

  • раздаёт видео, музыку и фото по DLNA;
  • автоматически обнаруживается ТВ, приставками, консолями;
  • работает без браузера, аккаунтов и облаков;
  • не индексирует «магически» — всё прозрачно и под контролем.

Почему НЕ Plex / Jellyfin:

  • требуют больше ресурсов;
  • сложнее в обслуживании;
  • зависят от web-стека и БД;
  • часто избыточны для «просто смотреть фильмы».

👉 Наша цель — надёжный медиасервер, который просто работает годами.


2️⃣ Установка MiniDLNA

Устанавливаем пакет из официальных репозиториев Ubuntu:

sudo apt install minidlna -y

Проверяем, что сервис появился:

systemctl status minidlna

На этом этапе он может быть запущен, но:

  • медиакаталог не задан;
  • права не настроены;
  • индекс пустой.

👉 Всё это исправляем дальше.


3️⃣ Основной конфиг MiniDLNA — строка за строкой

3.1 Открываем конфигурацию

sudo nano /etc/minidlna.conf

⚠️ Важно: файл большой, но нас интересует конкретный набор параметров.
Не нужно «улучшать» всё подряд.


3.2 media_dir — где лежат медиафайлы

Мы уже создали правильную структуру в Части 3:

/srv/storage/media
├── movies
├── tv
└── music

Прописываем их явно:

media_dir=V,/srv/storage/media/movies
media_dir=V,/srv/storage/media/tv
media_dir=A,/srv/storage/media/music

Обозначения:

  • V — Video
  • A — Audio

📌 Не используем общий media_dir без типа — так индексирование стабильнее.


3.3 friendly_name — имя сервера в ТВ

friendly_name=RV2 Media Server

👉 Именно это имя ты увидишь в меню телевизора / приставки.


3.4 db_dir и log_dir — база и логи

По умолчанию MiniDLNA хранит базу в /var/cache/minidlna,
но мы оставляем это так, потому что:

  • база небольшая;
  • не критична к скорости;
  • не требует SSD.

Проверяем, что строки НЕ закомментированы:

db_dir=/var/cache/minidlna
log_dir=/var/log

3.5 user — под кем работает MiniDLNA

По умолчанию:

user=minidlna

Мы НЕ меняем пользователя.

Почему:

  • у нас уже корректные права (755);
  • DLNA нужен только READ-доступ;
  • меньше риска сломать сервис.

3.6 inotify — автообновление библиотеки

inotify=yes

Что это даёт:

  • добавил фильм → он появился без рескана;
  • не нужно перезапускать сервис каждый раз.

📌 Работает отлично на ext4 (поэтому exFAT был плохой идеей).


3.7 notify_interval — как часто анонсировать себя в сети

notify_interval=60

Оптимальное значение:

  • ТВ быстрее «видит» сервер;
  • нет лишнего сетевого шума.

3.8 Итоговый минимальный конфиг (фрагмент)

В результате у тебя в minidlna.conf обязательно должны быть:

media_dir=V,/srv/storage/media/movies
media_dir=V,/srv/storage/media/tv
media_dir=A,/srv/storage/media/music

friendly_name=RV2 Media Server

db_dir=/var/cache/minidlna
log_dir=/var/log

user=minidlna
inotify=yes
notify_interval=60

Остальное можно оставить по умолчанию.

💾 Сохраняем файл:

  • Ctrl + O
  • Enter
  • Ctrl + X

4️⃣ Права доступа — почему у нас всё работает

Мы уже сделали всё правильно в Части 3, но важно зафиксировать логику.

Каталог:

ls -ld /srv/storage/media

Ожидаемо:

drwxr-xr-x  hub hub  /srv/storage/media

Что это значит:

  • hub — владелец, полный доступ;
  • minidlna (другой пользователь) — может читать;
  • DLNA индексирует без ошибок.

❌ Типовая ошибка:

root:root 700

👉 В этом случае DLNA не увидит файлы, даже если сервис «работает».


5️⃣ Первый запуск и сканирование

5.1 Перезапуск сервиса

sudo systemctl restart minidlna

Проверяем статус:

systemctl status minidlna

Ожидаемо:

Active: active (running)

5.2 Принудительный рескан (один раз)

При первом запуске рекомендуется пересобрать базу:

sudo systemctl stop minidlna
sudo minidlnad -R
sudo systemctl start minidlna

Что делает -R:

  • удаляет старую базу;
  • пересканирует все каталоги;
  • создаёт чистый индекс.

5.3 Проверка логов (если что-то не так)

journalctl -u minidlna -n 50

Или:

cat /var/log/minidlna.log

Ищем:

  • ошибки доступа;
  • проблемы с каталогами;
  • сообщения о добавленных файлах.

6️⃣ Проверка с ТВ или приставки

На телевизоре / Android TV / приставке:

  1. Заходим в Источники / DLNA / Медиа-серверы
  2. Ищем:
  1. RV2 Media Server
  2. Заходим → Movies / TV / Music
  3. Запускаем любой файл

👉 Если:

  • сервер виден;
  • файлы открываются;
  • перемотка работает —

🎉 MiniDLNA настроен правильно.


7️⃣ Типовые проблемы и решения

❌ Сервер не виден

Почти всегда причина НЕ в MiniDLNA:

  • multicast был не разрешён;
  • интерфейс без флага MULTICAST;
  • firewall настроен неправильно.

👉 Мы это уже решили в Части 2.


❌ Сервер виден, но папки пустые

Проверь:

ls /srv/storage/media/movies

Если файлы есть — значит проблема в правах:

sudo chown -R hub:hub /srv/storage
sudo chmod 755 /srv/storage

Пересканируй базу (minidlnad -R).


❌ Новые фильмы не появляются

Убедись, что включено:

inotify=yes

И файловая система — ext4, не exFAT.


✅ Итог Части 4

Теперь у нас есть:

  • полностью рабочий DLNA-сервер;
  • корректная индексация;
  • автоматическое обновление медиатеки;
  • стабильная работа без Docker и GUI.

MiniDLNA использует всё, что мы сделали ранее, и именно поэтому он работает правильно, а не «через раз».

🧱 ЧАСТЬ 5. qBittorrent-nox

В предыдущих частях мы:

  • подготовили систему и безопасность;
  • настроили firewall и защиту;
  • корректно подготовили SSD;
  • развернули и проверили MiniDLNA.

Теперь добавляем источник контентаqBittorrent-nox.

⚠️ Важно:
Мы НЕ делаем «торрент-помойку».
Наша цель:

  • качать аккуратно;
  • сразу класть файлы туда, где их увидит MiniDLNA;
  • не городить лишние костыли и скрипты.

5️⃣ Почему именно qBittorrent-nox

qBittorrent-nox — это:

  • тот же qBittorrent, но без GUI;
  • Web-интерфейс через браузер;
  • минимальная нагрузка на систему;
  • идеален для ARM/RISC-V и headless-серверов.

👉 Это лучший вариант для Orange Pi RV2.


5.1 Установка qBittorrent-nox

Устанавливаем из репозиториев Ubuntu:

sudo apt install qbittorrent-nox -y

Проверяем, что бинарник есть:

qbittorrent-nox --version

5.2 Первый запуск и ВРЕМЕННЫЙ пароль (ОЧЕНЬ ВАЖНО)

❗ Это пункт, который пропускают почти все, а потом «не могут войти».

Запускаем вручную один раз:

qbittorrent-nox

В терминале появится сообщение вида:

Web UI: http://localhost:8080
Username: admin
Password: <RANDOM_PASSWORD>

📌 Скопируй этот пароль — он одноразовый.

Останавливаем процесс:

Ctrl + C

5.3 Первый вход в Web UI

В браузере открываем:

http://IP_ORANGE_PI:8080

Логин:

admin

Пароль:

тот самый временный

👉 После входа сразу меняем пароль.


5.4 Базовая и безопасная настройка

5.4.1 Меняем пароль

Settings → Web UI

  • Username: admin (оставляем)
  • Password: задай нормальный
  • ☑️ Bypass authentication for clients on localhost — ❌ выключено

Сохраняем.


5.4.2 Привязываем Web UI к LAN

Settings → Web UI

  • Web UI address: 0.0.0.0
  • Web UI port: 8080

📌 Порт уже открыт в UFW — мы это сделали заранее.


5.5 Каталоги загрузки — КЛЮЧЕВАЯ ЧАСТЬ

❗ Важное архитектурное решение

Мы НЕ будем:

  • качать в /downloads, а потом переносить;
  • писать скрипты-переносчики;
  • городить automation, который ломается.

👉 Самый надёжный вариант:

qBittorrent качает СРАЗУ в нужный медиакаталог

MiniDLNA увидит файлы автоматически.


5.6 Каталог загрузки по умолчанию

Settings → Downloads

Default Save Path

/srv/storage/media/movies

📌 Почему именно так:

  • фильмы — самый частый тип контента;
  • MiniDLNA уже смотрит в /srv/storage/media;
  • файл появляется → DLNA его индексирует.

5.7 Как быть с сериалами и музыкой

Когда добавляешь торрент через Web UI:

👉 Просто выбираешь нужный каталог:

  • 🎬 Фильмы
/srv/storage/media/movies

📺 Сериалы

/srv/storage/media/tv

🎵 Музыка

  • /srv/storage/media/music

📌 Это нормальный, ручной, контролируемый процесс
и он на практике удобнее любых автоматических скриптов.


5.8 А что делать с /downloads?

Каталог /srv/storage/downloads оставляем, но используем осмысленно.

Предлагаемая роль:

/srv/storage/downloads
├── complete
├── incomplete
└── watch
  • completeНЕ медиаконтент
    • образы
    • архивы
    • софт
    • ISO
  • incomplete — временные файлы (если понадобится)
  • watchопционально, не основной сценарий

5.9 Про папку watch — честно и без мифов

❗ ВАЖНОЕ УТОЧНЕНИЕ

Watch НЕ спрашивает путь сохранения.

Если включить:

Automatically add torrents from:
 /srv/storage/downloads/watch

➡ торрент автоматически стартует
в каталог по умолчанию


5.10 Права доступа (обязательно проверяем)

qBittorrent работает от пользователя hub.

Проверяем владельца:

ls -ld /srv/storage/media

Если вдруг не hub:hub:

sudo chown -R hub:hub /srv/storage

5.11 systemd-сервис для qBittorrent-nox

Создаём сервис:

sudo nano /etc/systemd/system/qbittorrent-nox.service

Содержимое:

[Unit]
Description=qBittorrent-nox
After=network.target

[Service]
User=hub
ExecStart=/usr/bin/qbittorrent-nox
Restart=on-failure
LimitNOFILE=4096

[Install]
WantedBy=multi-user.target

Активируем:

sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable qbittorrent-nox
sudo systemctl start qbittorrent-nox

Проверка:

systemctl status qbittorrent-nox

5.12 Проверка всей цепочки

1️⃣ Добавь торрент фильма
2️⃣ Выбери /srv/storage/media/movies
3️⃣ Дождись окончания
4️⃣ Проверь на ТВ / приставке

👉 Фильм должен появиться без перезапуска MiniDLNA


✅ Итог Части 5

Теперь у нас:

  • qBittorrent-nox без GUI, но с Web UI;
  • безопасный доступ;
  • никаких скриптов-переносчиков;
  • файлы сразу попадают туда, где их ждёт MiniDLNA;
  • watch используется осознанно.

🧱 ЧАСТЬ 6. RV2 Hub — собственный Web UI на Rust (Axum)

(без Docker, без Node.js, с понятной архитектурой)

К этому моменту у нас уже есть полностью рабочий медиахаб:

  • SSD смонтирован в /srv/storage
  • MiniDLNA раздаёт контент на ТВ
  • qBittorrent-nox качает файлы сразу в нужные каталоги
  • firewall, multicast и безопасность настроены

👉 Но управлять всем этим по SSH неудобно.
Нам нужен локальный Web-интерфейс, который:

  • показывает состояние системы;
  • показывает, что лежит в медиакаталогах;
  • в будущем может расширяться (WireGuard, статистика, сервисы).

Так появляется RV2 Hub.


6.1 Зачем вообще нужен RV2 Hub

Важно сразу понять философию.

❌ Мы НЕ делаем:

  • замену Plex / Jellyfin;
  • торрент-клиент в браузере;
  • сложную панель управления.

✅ Мы делаем:

  • лёгкий Web UI для владельца сервера;
  • обзор состояния узла;
  • точку расширения проекта.

Почему Rust + Axum:

  • минимальное потребление ресурсов (важно для RV2);
  • бинарник без зависимостей;
  • быстрый старт и стабильность;
  • идеально для systemd.

6.2 Где живёт RV2 Hub в нашей структуре

Мы уже заложили каталог /srv/storage/apps.

Именно там будут жить все наши сервисы.

Финальный путь проекта:

/srv/storage/apps/rv2-hub

Это важно:

  • не в /home;
  • не в /opt;
  • не вперемешку с медиа.

6.3 Установка Rust (официально и правильно)

Работаем под пользователем hub.

6.3.1 Устанавливаем зависимости

sudo apt install -y build-essential pkg-config libssl-dev

6.3.2 Установка Rust через rustup

curl https://sh.rustup.rs -sSf | sh

Выбираем:

1) Proceed with installation (default)

Подгружаем окружение:

source ~/.cargo/env

Проверка:

rustc --version
cargo --version

6.4 Создание проекта RV2 Hub

cd /srv/storage/apps
cargo new rv2-hub
cd rv2-hub

Удаляем лишнее и сразу приводим к финальной структуре.


6.5 Финальная структура проекта (ОКОНЧАТЕЛЬНАЯ)

rv2-hub/
├── Cargo.toml
├── src/
│   ├── main.rs
│   ├── state.rs
│   └── routes/
│       ├── mod.rs
│       └── index.rs
├── templates/
│   └── index.html
└── static/
    └── style.css

📌 Это не «пример», а финальная, рабочая структура.


6.6 Cargo.toml (проверенный и минимальный)

Открываем:

nano Cargo.toml

Заменяем полностью:

[package]
name = "rv2-hub"
version = "0.1.0"
edition = "2021"

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
tower-http = { version = "0.5", features = ["fs"] }
askama = "0.12"

Почему так:

  • axum 0.7 — актуальная версия;
  • tokio full — без сюрпризов;
  • tower-http fs — раздача CSS;
  • askama — шаблоны без JS.

6.7 main.rs — запуск без axum::Server

nano src/main.rs
use axum::{Router, routing::get};
use tower_http::services::ServeDir;

mod routes;
mod state;

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(routes::index::index))
        .nest_service("/static", ServeDir::new("static"));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8081")
        .await
        .unwrap();

    println!("🚀 RV2 Hub running on http://0.0.0.0:8081");

    axum::serve(listener, app).await.unwrap();
}

📌 Важно:

  • никакого axum::Server — он устарел;
  • слушаем 0.0.0.0, чтобы открыть доступ по сети;
  • порт 8081 мы уже открыли в UFW.

6.8 routes и state — разделяем логику

6.8.1 routes/mod.rs

nano src/routes/mod.rs
pub mod index;

6.8.2 routes/index.rs

nano src/routes/index.rs
use axum::response::Html;
use askama::Template;

use crate::state;

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    system: String,
    movies: Vec<String>,
    dlna: String,
}

pub async fn index() -> Html<String> {
    let tpl = IndexTemplate {
        system: state::system_status(),
        movies: state::movies(),
        dlna: state::dlna_status(),
    };

    Html(tpl.render().unwrap())
}

6.8.3 state.rs — логика без Web-кода

nano src/state.rs
use std::fs;

pub fn system_status() -> String {
    "OK".to_string()
}

pub fn dlna_status() -> String {
    "Running".to_string()
}

pub fn movies() -> Vec<String> {
    let path = "/srv/storage/media/movies";
    fs::read_dir(path)
        .map(|entries| {
            entries
                .filter_map(|e| e.ok())
                .filter(|e| e.path().is_file())
                .map(|e| e.file_name().to_string_lossy().to_string())
                .collect()
        })
        .unwrap_or_default()
}

📌 Здесь мы:

  • читаем реальный каталог с фильмами;
  • не используем shell-команды;
  • не ломаем безопасность.

6.9 Шаблон index.html (красивый, но простой)

nano templates/index.html
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>RV2 Hub</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>

<header>
    <h1>RV2 Hub</h1>
    <p>Orange Pi RV2 • Media & Services Hub</p>
</header>

<main>
    <section class="card">
        <h2>📊 System</h2>
        <p>Status: <b>{{ system }}</b></p>
    </section>

    <section class="card">
        <h2>🎬 Movies</h2>
        <ul>
        {% for movie in movies %}
            <li>{{ movie }}</li>
        {% endfor %}
        </ul>
    </section>

    <section class="card">
        <h2>📡 DLNA</h2>
        <p>Status: <b>{{ dlna }}</b></p>
    </section>
</main>

<footer>
    RV2 Hub • Rust + Axum
</footer>

</body>
</html>

6.10 CSS — НЕ лаконичный, а «приятный»

nano static/style.css
body {
    margin: 0;
    font-family: system-ui, sans-serif;
    background: #0f172a;
    color: #e5e7eb;
}

header {
    background: linear-gradient(90deg, #2563eb, #1e40af);
    padding: 20px;
    text-align: center;
}

header h1 {
    margin: 0;
    font-size: 32px;
}

main {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 20px;
    padding: 30px;
}

.card {
    background: #020617;
    border-radius: 12px;
    padding: 20px;
    box-shadow: 0 10px 30px rgba(0,0,0,0.4);
}

.card h2 {
    margin-top: 0;
    color: #60a5fa;
}

ul {
    padding-left: 20px;
}

li {
    margin-bottom: 6px;
}

footer {
    text-align: center;
    padding: 15px;
    color: #94a3b8;
}

6.11 Сборка и запуск

cargo clean
cargo build --release
./target/release/rv2-hub

Открываем в браузере:

http://192.168.1.xxx:8081

6.12 systemd-сервис для RV2 Hub

Создаём сервис:

sudo nano /etc/systemd/system/rv2-hub.service
[Unit]
Description=RV2 Hub Web UI
After=network.target

[Service]
User=hub
WorkingDirectory=/srv/storage/apps/rv2-hub
ExecStart=/srv/storage/apps/rv2-hub/target/release/rv2-hub
Restart=always

[Install]
WantedBy=multi-user.target

Активируем:

sudo systemctl daemon-reexec
sudo systemctl enable rv2-hub
sudo systemctl start rv2-hub

Проверка:

systemctl status rv2-hub

🔚 Итог Части 6

Теперь у нас есть:

✅ собственный Web UI
✅ без Docker и JS-зоопарка
✅ быстрый и стабильный
✅ логично встроенный в архитектуру
✅ идеальная база для расширения

🧱 ЧАСТЬ 7. Ошибки, тупики и финальная проверка системы

(что было сделано правильно, что неправильно и почему итоговая схема — лучшая)

К этому моменту у нас полностью готовый медиахаб на Orange Pi RV2:

  • Ubuntu Server настроен правильно
  • SSD подключён, размечен и смонтирован корректно
  • MiniDLNA стабильно раздаёт медиатеку
  • qBittorrent-nox качает без GUI и без бардака
  • Firewall и защита настроены один раз и навсегда
  • Архитектура готова к WireGuard и дальнейшему развитию

Теперь самое важное — разобрать ошибки и тупики, которые обычно убивают такие проекты через 1–2 недели эксплуатации.


1️⃣ Docker — почему он здесь был лишним

Очень популярный, но неправильный путь для этого сценария.

Почему Docker кажется хорошей идеей

  • «изолировано»
  • «красиво»
  • «все так делают»
  • «одной командой поднял»

Почему Docker ЗДЕСЬ — плохое решение

❌ Проблемы с DLNA

  • multicast + Docker = боль
  • SSDP внутри контейнера работает нестабильно
  • проброс UDP 1900 — нестандартный и ненадёжный

DLNA любит работать напрямую с сетью хоста.

❌ Проблемы с правами на SSD

  • host → container → volume
  • UID/GID не совпадают
  • MiniDLNA и qBittorrent начинают «не видеть» файлы

В итоге:

«вроде всё работает, но иногда нет»

❌ Избыточность

У нас:

  • один сервер
  • фиксированный набор сервисов
  • systemd уже есть

Docker не даёт преимуществ, но добавляет сложность.

✅ Почему systemd + нативные сервисы лучше

  • прямой доступ к сети
  • прямые пути к файлам
  • нормальные логи
  • предсказуемый запуск
  • проще отлаживать

👉 Вывод: Docker здесь был лишним, и мы правильно от него отказались.


2️⃣ Типовые ошибки с MiniDLNA (и почему они ломают всё)

❌ Ошибка №1: неправильные права

Самая частая причина:

/srv/storage принадлежит root:root

В итоге:

  • MiniDLNA запускается
  • DLNA-сервер «есть»
  • медиатеки нет

✔️ Правильно:

chown -R hub:hub /srv/storage
chmod 755 /srv/storage

❌ Ошибка №2: exFAT / NTFS на SSD

Почему не работает нормально:

  • нет Unix-прав
  • проблемы с inotify
  • нестабильная индексация

✔️ Правильно:

  • ext4
  • UUID в fstab
  • noatime

❌ Ошибка №3: multicast не работает

Симптомы:

  • MiniDLNA запущен
  • порт 8200 открыт
  • ТВ не видит сервер

Причина:

  • SSDP (239.255.255.250:1900/udp) блокируется

✔️ Мы сделали правильно:

  • разрешили 1900/udp в UFW
  • проверили флаг MULTICAST на интерфейсе

❌ Ошибка №4: ожидание «мгновенного» DLNA

DLNA не индексирует мгновенно.

Правильный цикл:

sudo systemctl restart minidlna
sudo journalctl -u minidlna -f

И только потом проверка на ТВ.


3️⃣ Типовые ошибки с qBittorrent-nox

❌ Ошибка №1: качать всё в одну папку

Так делать нельзя:

/srv/storage/downloads

Почему:

  • DLNA не понимает структуру
  • мусор в медиатеке
  • сложно управлять

✅ Мы сделали правильно

  • default download:
/srv/storage/media/movies

если это сериал:

/srv/storage/media/tv

если музыка:

  • /srv/storage/media/music
  • downloads/complete:
    • для «прочих» загрузок
    • ISO, образы, архивы

❌ Ошибка №2: переоценка watch-каталога

Важно зафиксировать:

watch-каталог НЕ спрашивает путь загрузки

Он просто стартует загрузку в default directory.

✔️ Поэтому мы:

  • оставили watch как удобную опцию
  • основной контроль — через Web UI

4️⃣ Финальная проверка всей системы

Перед тем как считать систему готовой, проверяем всё по чеклисту.

🔹 Сервисы

systemctl status minidlna
systemctl status qbittorrent-nox
systemctl status fail2ban

Все должны быть active (running).


🔹 Порты

ss -tulpen

Ожидаемо:

  • 22/tcp — SSH
  • 8080/tcp — qBittorrent Web UI
  • 8200/tcp — MiniDLNA
  • 1900/udp — SSDP

❌ Никаких лишних портов.


🔹 DLNA

  • ТВ / приставка видит сервер
  • медиатека отображается
  • файлы воспроизводятся

🔹 SSD

df -h | grep storage
mount | grep storage
  • ext4
  • UUID
  • смонтирован автоматически

5️⃣ Куда развивать медиахаб дальше

Теперь у тебя чистая и правильная база.

Вот логичные направления развития:

🔜 WireGuard

  • безопасный доступ из интернета
  • без проброса сервисов наружу
  • готовность системы уже есть

🔜 RV2 Hub (Rust + Axum)

  • Web UI для:
    • статуса системы
    • списка фильмов
    • состояния DLNA
    • управления сервисами

Мы сознательно оставили это отдельной частью, потому что это уже приложение, а не администрирование.


🔜 Бэкапы

  • rsync
  • borg
  • внешний диск или NAS

🏁 Итог всей серии

Ты не просто “поднял DLNA”.

Ты построил:

  • правильную файловую архитектуру
  • предсказуемую сетевую модель
  • безопасную систему
  • медиахаб, который можно:
    • расширять
    • поддерживать
    • не бояться обновлять

👉 Это именно тот случай, когда “один раз сделать правильно” экономит десятки часов в будущем.