Установка — Docker Compose
Требования
Заголовок раздела «Требования»- Docker Engine и Docker Compose
- Bluetooth-адаптер на хосте
- PulseAudio или PipeWire на хосте
- Music Assistant в вашей сети
- На AMD64-хостах: CPU уровня
x86-64-v2(SSE4.2 + POPCNT)
Публикуемый образ поддерживает linux/amd64, linux/arm64 и linux/arm/v7.
Базовый уровень CPU (x86_64)
Заголовок раздела «Базовый уровень CPU (x86_64)»Аудио-стек (PyAV / ffmpeg / NumPy) собран в виде wheel-ов под микроархитектуру x86-64-v2, что требует SSE4.2 и POPCNT. На более старых CPU дочерний процесс демона запускается и тут же падает с signal=4 (SIGILL — «Illegal Instruction»). Начиная с v2.71.0 карточка устройства и блок --- SENDSPIN CONNECTION --- в диагностическом отчёте сразу показывают код выхода и сигнал; до этого релиза такая ошибка выглядела как тихий ~10-секундный restart-loop без traceback’а.
Подтверждённый минимум:
| Семейство | Минимум |
|---|---|
| Intel x86 | Sandy Bridge / 2-е поколение Core (2011), Atom Silvermont, любые современные Celeron / Pentium |
| AMD x86 | Bulldozer / FX (2011), Jaguar, Ryzen — всё, что выпущено с 2011 года, кроме нетбучной линейки Bobcat |
| ARM | Все образы aarch64 и arm/v7 для Raspberry Pi 4+ (ограничения по SSE не применимы) |
| QEMU / KVM | CPU-модель host (рекомендуется) или qemu64,+sse4.2,+popcnt — дефолтный qemu64 ниже baseline’а |
Проверка на хосте:
grep -m1 -E 'sse4_2|popcnt' /proc/cpuinfoНепустой результат — мост запустится. Пустой — демон будет падать на каждом запуске; см. Troubleshooting › CPU-baseline crash для диагностики и путей решения.
Быстрый старт
Заголовок раздела «Быстрый старт»-
Сначала сопрягите колонку на хосте
Окно терминала bluetoothctlscan onpair AA:BB:CC:DD:EE:FFtrust AA:BB:CC:DD:EE:FFconnect AA:BB:CC:DD:EE:FFexit -
Создайте
.envAUDIO_UID=1000AUDIO_GID=1000TZ=Europe/MoscowWEB_PORT=8080BASE_LISTEN_PORT=8928 -
Создайте
docker-compose.ymlservices:sendspin-client:image: ghcr.io/trudenboy/sendspin-bt-bridge:latestcontainer_name: sendspin-clientrestart: unless-stoppednetwork_mode: hostvolumes:- /var/run/dbus:/var/run/dbus- /run/user/${AUDIO_UID:-1000}/pulse:/run/user/${AUDIO_UID:-1000}/pulse- /run/user/${AUDIO_UID:-1000}/pipewire-0:/run/user/${AUDIO_UID:-1000}/pipewire-0- /etc/docker/Sendspin:/configenvironment:- SENDSPIN_SERVER=auto- TZ=${TZ:-UTC}- WEB_PORT=${WEB_PORT:-8080}- BASE_LISTEN_PORT=${BASE_LISTEN_PORT:-8928}- CONFIG_DIR=/config- AUDIO_UID=${AUDIO_UID:-1000}- AUDIO_GID=${AUDIO_GID:-1000}- PULSE_SERVER=unix:/run/user/${AUDIO_UID:-1000}/pulse/native- XDG_RUNTIME_DIR=/run/user/${AUDIO_UID:-1000}devices:- /dev/bus/usb:/dev/bus/usbcap_add:- NET_ADMIN- NET_RAW -
Запустите контейнер
Окно терминала mkdir -p /etc/docker/Sendspindocker compose up -d -
Откройте веб-интерфейс
http://<ip-хоста>:<WEB_PORT>
Планирование портов
Заголовок раздела «Планирование портов»WEB_PORTуправляет прямым listener’ом веб-интерфейса/API в Docker-режиме.BASE_LISTEN_PORTзадаёт базовый Sendspin-порт для устройств без явногоlisten_port.- Каждое устройство без ручного порта получает
BASE_LISTEN_PORT + индекс_устройства. - В сложных схемах можно задать
listen_portиlisten_hostна уровне устройства через веб-интерфейс или/config/config.jsonпосле первого запуска.
Пример блока устройства в /config/config.json:
{ "mac": "11:22:33:44:55:66", "player_name": "Колонка на кухне", "listen_port": 8935, "listen_host": "192.168.1.50"}listen_host меняет только рекламируемый host/IP для плеера и не влияет на bind-адрес внутри контейнера.
Несколько bridge-контейнеров на одном хосте
Заголовок раздела «Несколько bridge-контейнеров на одном хосте»Если вы запускаете несколько bridge-контейнеров на одной машине:
- задайте каждому контейнеру уникальный
WEB_PORT - задайте каждому контейнеру уникальный
BASE_LISTEN_PORT - не настраивайте одну и ту же Bluetooth-колонку в двух работающих контейнерах
Сеть и capabilities
Заголовок раздела «Сеть и capabilities»network_mode: host обязателен для:
- mDNS-обнаружения при
SENDSPIN_SERVER=auto - доступа к Bluetooth-стеку хоста через D-Bus
Необходимые capabilities:
| Capability | Назначение |
|---|---|
NET_ADMIN | Управление Bluetooth-адаптером |
NET_RAW | Raw Bluetooth/HCI socket access |
Проверка контейнера
Заголовок раздела «Проверка контейнера»docker logs -f sendspin-clientcurl -s http://localhost:${WEB_PORT:-8080}/api/preflight | python3 -m json.toolВ новых образах startup diagnostics также показывают:
- init UID/GID внутри контейнера
- app UID/GID запущенного bridge-процесса
- выбранный путь к audio socket
- владельца/права сокета
- результат живого
pactl infoprobe - пришлось ли контейнеру подождать позднюю готовность D-Bus / Bluetooth / audio при холодном старте хоста
- отдельное предупреждение, если bridge-процесс работает под другим UID, чем user-scoped audio socket на хосте
Если у вас уже настроены Bluetooth-устройства, новые образы также недолго ждут позднего появления host-зависимостей перед запуском bridge-процесса. Это убирает частый race, когда после перезагрузки хоста контейнеру нужен ещё один ручной restart.
Если хост стартует особенно медленно, ожидание можно подстроить:
environment: - STARTUP_DEPENDENCY_WAIT_ATTEMPTS=60 - STARTUP_DEPENDENCY_WAIT_DELAY_SECONDS=1Troubleshooting для user-scoped PipeWire / PulseAudio
Заголовок раздела «Troubleshooting для user-scoped PipeWire / PulseAudio»Спикер коннектится, но bluez_* sinks не появляются
Заголовок раздела «Спикер коннектится, но bluez_* sinks не появляются»На свежей установке Ubuntu / Raspberry Pi OS PipeWire по умолчанию устанавливается без Bluetooth-бэкенда. bluetoothctl показывает спикер как Connected: yes, но pactl list sinks short выдаёт только sendspin_fallback — никаких bluez_*. Через ~10 секунд BlueZ сам разрывает A2DP-соединение, бридж попадает в цикл переподключения.
Проверка и фикс:
dpkg -l libspa-0.2-bluetooth 2>/dev/null | grep -qE '^ii' \ && echo "libspa-0.2-bluetooth установлен" \ || sudo apt install -y libspa-0.2-bluetooth
# Перезапуск audio-стека после установкиsystemctl --user restart wireplumber pipewire pipewire-pulseПосле переподключения BT-устройства pactl list sinks short должен показать bluez_output.* или bluez_sink.*.
Прочие проверки
Заголовок раздела «Прочие проверки»Если на хосте аудио работает нормально, но в контейнере всё ещё видно Connection refused или pactl не может подключиться, проверьте:
docker exec sendspin-client ls -la /run/user/${AUDIO_UID:-1000}/pulse/docker exec sendspin-client env | grep -E 'PULSE|XDG'docker exec sendspin-client ps -o user:20,pid,command -C python3docker inspect sendspin-client --format '{{json .Mounts}}'И на хосте:
idpactl infols -la /run/user/${AUDIO_UID:-1000}/pulse/Если аудио всё ещё не работает, сначала убедитесь по startup diagnostics, что App UID совпадает с UID вашего host audio user. Старый глобальный Compose user: override теперь нужен только как временный диагностический тест для старых образов:
services: sendspin-client: user: "${AUDIO_UID:-1000}:${AUDIO_UID:-1000}"После этого перезапустите контейнер и проверьте, начал ли работать pactl. Это именно шаг диагностики: он помогает быстро подтвердить проблему несовпадения UID/session при доступе к host audio socket.
Применение изменений конфигурации
Заголовок раздела «Применение изменений конфигурации»Изменения устройств, адаптеров, WEB_PORT, BASE_LISTEN_PORT и настроек подключения к Music Assistant требуют перезапуска контейнера.