HomeLab

📷 День 48 - Две USB-камеры в Proxmox: отдельный LXC с Motion и motionEye

14 августа 2026, 13:47
📷 День 48 - Две USB-камеры в Proxmox: отдельный LXC с Motion и motionEye

Для удалённого визуального контроля одной из площадок HomeLab я подключил к компактному Lenovo ThinkCentre M710q две обычные USB-камеры:

  • Logitech C270;
  • Logitech C525.

Первый тест работал прямо на гипервизоре. Затем видеосервис переехал в отдельный unprivileged LXC. В итоге Proxmox остался чистым, а камеры получили удобный веб-интерфейс motionEye.

🧩 Архитектура

+------------------+       +--------------------------+
| USB camera C270  | ----> |                          |
+------------------+       | Proxmox VE host          |
                           |  /dev/video0             |
+------------------+       |  /dev/video2             |
| USB camera C525  | ----> |            |             |
+------------------+       +------------+-------------+
                                        | dev passthrough
                                        v
                           +------------+-------------+
                           | Unprivileged Debian LXC  |
                           |  Motion + motionEye      |
                           |  MJPEG streams           |
                           +------------+-------------+
                                        | local network / VPN
                                        v
                           +--------------------------+
                           | Browser live-view        |
                           +--------------------------+

Схема передачи двух USB-камер в LXC и защищённого просмотра

🔎 Как Linux видит камеры

Обе камеры определились стандартным драйвером Linux uvcvideo.

lsusb
046d:0825 Logitech Webcam C270
046d:0826 Logitech HD Webcam C525

В системе появились четыре V4L2-устройства:

/dev/video0
/dev/video1
/dev/video2
/dev/video3

Это нормально: одна USB-камера может создавать несколько /dev/videoN. Один интерфейс обычно отдаёт основной поток, другой используется для дополнительных функций или метаданных.

Основными видеоустройствами в тестовой конфигурации оказались:

C270: /dev/video0
C525: /dev/video2

Соответствие лучше проверять не по номеру, а через V4L2 и стабильные ссылки:

v4l2-ctl --list-devices
ls -l /dev/v4l/by-id/

🧹 Почему Motion не остался на гипервизоре

Для быстрого теста Motion можно запустить прямо на Proxmox. Для постоянной эксплуатации это плохо масштабируется:

  • на гипервизоре появляются лишние пакеты и зависимости;
  • видеосервис конкурирует с виртуализацией за ресурсы;
  • обновления Motion смешиваются с обновлениями Proxmox;
  • сложнее резервировать и переносить конфигурацию;
  • ошибка в видеостеке затрагивает сам гипервизор.

Отдельный LXC даёт чистую границу: хост передаёт только нужные символьные устройства, а весь прикладной стек живёт внутри контейнера.

📦 Параметры LXC

Для двух USB-камер мощный контейнер не нужен:

ОС: Debian 12
CPU: 2 vCPU
RAM: 2 ГБ
Swap: 512 МБ
Диск: 16 ГБ
Сеть: статический адрес
Автозапуск: включён
Тип: unprivileged LXC

Если нужен только live-view, 16 ГБ более чем достаточно. Постоянная запись требует отдельного хранилища, квоты и политики очистки архива.

pct create <CTID> local:vztmpl/debian-12-standard_amd64.tar.zst \
  --hostname cam01 \
  --unprivileged 1 \
  --cores 2 \
  --memory 2048 \
  --swap 512 \
  --rootfs <STORAGE>:16 \
  --net0 name=eth0,bridge=vmbr0,ip=<STATIC-IP>/24,gw=<GATEWAY> \
  --onboot 1

🔌 Передача камер в LXC

В Proxmox VE 9 символьные устройства можно передать контейнеру штатными параметрами devN. Для V4L2 используется major number 81.

pct set <CTID> \
  --dev0 path=/dev/video0,gid=44,mode=0660 \
  --dev1 path=/dev/video2,gid=44,mode=0660

После перезапуска проверяем права и major/minor:

pct reboot <CTID>
pct exec <CTID> -- ls -l /dev/video0 /dev/video2
crw-rw---- root video 81,0 /dev/video0
crw-rw---- root video 81,2 /dev/video2

Группа video в Debian обычно имеет GID 44, но перед настройкой это нужно проверить внутри контейнера:

getent group video

Благодаря точечной передаче устройств Motion получает доступ к камерам без перевода всего LXC в privileged-режим.

Важный нюанс: /dev/videoN может измениться

Символические ссылки из /dev/v4l/by-id/ не всегда корректно передаются через devN, поэтому надёжнее указывать реальные символьные устройства.

При перезагрузке или повторном подключении USB номера могут поменяться. Для постоянной эксплуатации на хосте нужны udev-правила, которые формируют предсказуемые имена по VID, PID и, если он доступен, серийному номеру.

⚙️ Установка Motion и motionEye

Внутри LXC устанавливаем базовые зависимости:

apt update
apt install -y \
  ca-certificates \
  curl \
  python3 \
  python3-venv \
  python3-pip \
  motion \
  v4l-utils \
  ffmpeg

motionEye изолируем в отдельном Python-окружении:

python3 -m venv /opt/motioneye
/opt/motioneye/bin/pip install --upgrade pip
/opt/motioneye/bin/pip install motioneye
/opt/motioneye/bin/motioneye_init

В этой конфигурации systemd unit ожидал meyectl в /usr/local/bin, поэтому потребовалась ссылка:

ln -s /opt/motioneye/bin/meyectl /usr/local/bin/meyectl
systemctl daemon-reload
systemctl enable --now motioneye

Официальный проект описывает motionEye как веб-интерфейс для Motion. Инструкции и исходники: motioneye-project/motioneye.

По умолчанию веб-интерфейс слушает TCP-порт 8765. Сразу после первого запуска нужно установить пароль администратора и отдельный пароль для просмотра.

Порт 8765 не публикуется в интернет. Доступ разрешён только из доверенной локальной сети и через VPN.

🎥 Настройка двух потоков

Обе камеры добавлены как Local V4L2 Camera.

Logitech C270: /dev/video0
Logitech C525: /dev/video2

Разрешение: 1280x720
Формат: MJPEG
Частота: 30 FPS
Streaming Quality: 75%
Запись: отключена
Снимки: отключены
Детекция движения: отключена

Обе камеры умеют отдавать MJPEG. Камера сама сжимает кадры, поэтому USB-шина и CPU нагружаются меньше, чем при несжатом YUYV.

v4l2-ctl -d /dev/video0 --list-formats-ext
v4l2-ctl -d /dev/video2 --list-formats-ext

🧪 Проверка после запуска

systemctl status motioneye
pgrep -af motion
ss -lntp

Motion создаёт отдельные MJPEG-потоки. Текущие кадры можно проверить локально из контейнера:

curl -o camera1.jpg http://127.0.0.1:9081/current
curl -o camera2.jpg http://127.0.0.1:9082/current
file camera1.jpg camera2.jpg

Оба файла должны определяться как JPEG нужного разрешения. После этого проверяем USB, UVC и сам сервис:

journalctl -k | grep -Ei 'usb|uvc|video'
journalctl -u motioneye

📊 Нагрузка и границы решения

В ходе теста оба потока 1280x720 / MJPEG / 30 FPS работали одновременно. Ошибок USB, повреждённых кадров и отключений камер не наблюдалось.

Для обычного live-view ресурсов LXC хватает с большим запасом. Основная нагрузка появится, если включить:

  • постоянную запись;
  • перекодирование в H.264/H.265;
  • детекцию движения;
  • распознавание объектов;
  • длительное хранение архива.

Для такого сценария логичным следующим шагом будет Frigate с аппаратным ускорением Intel Quick Sync или OpenVINO. Но для текущей задачи Motion и motionEye проще и достаточно.

🛠️ Что ещё стоит улучшить

  1. Создать udev-правила для стабильной привязки камер.
  2. Ограничить motionEye локальной сетью и VPN.
  3. Добавить HTTPS через reverse proxy.
  4. Разделить учётные записи администратора и просмотра.
  5. Включить резервное копирование конфигурации LXC.
  6. Добавить мониторинг доступности обеих камер.
  7. При включении записи вынести архив на отдельное хранилище.
  8. Проверить автовосстановление после физического отключения USB.

🏁 Итог

Получилась простая и изолированная система просмотра двух USB-камер:

USB cameras
     |
     v
Proxmox VE
     | device passthrough
     v
Unprivileged LXC
     |
     +-- Motion
     +-- motionEye
             |
             v
     Web UI through VPN

Такой вариант хорошо подходит для HomeLab, удалённой площадки, серверной или технического помещения. Он почти не нагружает сервер, не требует отдельных IP-камер и оставляет понятный путь к записи, событиям и детекции объектов.