7–10 марта: Надёжность, multi-bridge и HA OAuth
7 марта 2026 — Надёжность и деплой (v2.10.7 → v2.12.0)
Заголовок раздела «7 марта 2026 — Надёжность и деплой (v2.10.7 → v2.12.0)»Перестройка архитектуры громкости (v2.10.7 – v2.10.13)
Заголовок раздела «Перестройка архитектуры громкости (v2.10.7 – v2.10.13)»Гибридный путь громкости — маршрутизация команд через MA WebSocket API для синхронизации UI MA — создал тройной feedback loop: API, эхо протокола sendspin и событие MA-монитора одновременно пытались установить громкость PulseAudio sink. Результат — скачки громкости (установлено 40 → прыгает на 47 → устаканивается на 55) и неожиданные изменения при смене трека.
Исправление было архитектурным: bridge_daemon стал единственным писателем громкости PulseAudio sink. API больше не обновляет оптимистично локальный статус при MA-пути — он ждёт реального эха от MA через протокол sendspin. Синхронизация громкости _handle_player_updated в MA-мониторе была удалена как избыточный третий путь. Новая опция конфигурации VOLUME_VIA_MA (по умолчанию: true) позволяет полностью отключить MA-прокси, направляя все изменения громкости/mute через прямой pactl.
Наблюдаемость и тестовая инфраструктура (v2.10.8)
Заголовок раздела «Наблюдаемость и тестовая инфраструктура (v2.10.8)»Все 27 молчаливых блоков except: pass были заменены на DEBUG-уровневое логирование — проблемы теперь видны с LOG_LEVEL=DEBUG без изменения runtime-поведения. Потокобезопасность была укреплена: вызовы run_coroutine_threadsafe получили 5-секундные таймауты, а fire-and-forget asyncio-задачи получили done_callback для логирования исключений. Проект обзавёлся первыми автоматическими тестами: pytest с 9 юнит-тестами, покрывающими загрузку конфига, сохранение громкости, маппинг MAC→player-ID и хеширование паролей (позже расширено до 15 тестов).
Модернизация LXC-инсталлятора (v2.10.16)
Заголовок раздела «Модернизация LXC-инсталлятора (v2.10.16)»LXC-инсталлятор был обновлён для скачивания всех модулей приложения (config, state, routes, services, templates, static) вместо изначальных 2 файлов. Конфигурация PulseAudio была исправлена для PA 17+ на Ubuntu 24.04: устаревший enable-lfe-remixing заменён на remixing-produce-lfe/remixing-consume-lfe, systemd-юнит больше не устанавливает User=pulse/Group=pulse (PA в режиме --system требует root), а tmpfiles.d-запись гарантирует, что /var/run/pulse переживёт перезагрузки.
Деплой в OpenWrt LXC (v2.11.0)
Заголовок раздела «Деплой в OpenWrt LXC (v2.11.0)»Новый инсталлятор lxc/install-openwrt.sh добавил поддержку роутеров на OpenWrt (Turris Omnia и т.д.) с управлением сервисом через procd — расширив варианты деплоя с 3 (Docker, HA addon, Proxmox LXC) до 4.
Watchdog зомби-воспроизведения и изоляция churn (v2.12.0)
Заголовок раздела «Watchdog зомби-воспроизведения и изоляция churn (v2.12.0)»Были добавлены две функции надёжности:
- Watchdog зомби-воспроизведения: автоматически перезапускает подпроцесс после 15 секунд состояния
playing=Trueбез аудиоданных (streaming=False), до 3 попыток. Ловит ситуации, когда sendspin-соединение живо, но аудиопайплайн сломан. - Изоляция BT churn (opt-in): автоматически отключает BT-управление для устройств, которые переподключаются слишком часто в скользящем окне, настраивается через
BT_CHURN_THRESHOLD(0 = отключено, по умолчанию) иBT_CHURN_WINDOW(по умолчанию 300 с). Предотвращает ситуацию, когда нестабильное Bluetooth-устройство занимает время адаптера и дестабилизирует другие колонки.
Новый индикатор застывшего эквалайзера показывает замороженные красные полосы, когда MA сообщает о воспроизведении, но аудио не стримится, с текстом «▶ No Audio».
8 марта 2026 — Multi-bridge и сообщество (v2.12.1 → v2.13.1)
Заголовок раздела «8 марта 2026 — Multi-bridge и сообщество (v2.12.1 → v2.13.1)»Кэширование, надёжность SSE и фиксы HA Ingress (v2.12.1 → v2.12.6)
Заголовок раздела «Кэширование, надёжность SSE и фиксы HA Ingress (v2.12.1 → v2.12.6)»Серия быстрых релизов решала поступательно обнаруженные проблемы поведения HA Ingress proxy: cache-busting статических ресурсов через query string (?v=) не работал, потому что Ingress обрезает query-параметры — переключились на path-based версионирование (/static/v2.12.5/app.js). HTML-ответы получили заголовки Cache-Control: no-cache. SSE-поток получил 2 КБ начального padding для сброса proxy-буферов, а клиентская логика переподключения SSE была обновлена с «один сбой → polling навсегда» на экспоненциальный backoff с 5 попытками.
Ленивая регистрация плеера (v2.12.2)
Заголовок раздела «Ленивая регистрация плеера (v2.12.2)»Sendspin daemon теперь запускается только после реального подключения Bluetooth, устраняя фантомные плееры в Music Assistant при старте контейнера.
Анализ и улучшения архитектуры multi-bridge (v2.13.0)
Заголовок раздела «Анализ и улучшения архитектуры multi-bridge (v2.13.0)»Глубокий анализ сценария multi-bridge (несколько мостов → один MA, cross-bridge sync-группы) выявил 6 потенциальных проблем и привёл к двум ключевым улучшениям:
-
Автозаполнение BRIDGE_NAME: при первом запуске hostname машины записывается в
config.json["BRIDGE_NAME"], чтобы пользователи видели предзаполненное значение в веб-интерфейсе до добавления устройств. СтарыйBRIDGE_NAME_SUFFIXboolean был удалён — больше не нужен, когда имя заполняется автоматически. Это предотвращает дублирование имён плееров (например, два «JBL Flip 6» с разных хостов), которое путало список плееров MA. -
Видимость cross-bridge sync-групп: когда плееры из нескольких мостов входят в одну MA sync-группу, бейдж группы теперь показывает
🔗 Kitchen Music +2(где +2 = плееры с других мостов). При наведении на бейдж раскрывается полный список участников с ✓ для локальных и 🌐 для внешних плееров. Данные берутся из кэша MA API (/api/players→ списки участников sync-групп), который мост уже поддерживает.
Фиксы продакшн-деплоя (v2.13.1)
Заголовок раздела «Фиксы продакшн-деплоя (v2.13.1)»Деплой v2.13.0 на два живых LXC-моста (Proxmox + Turris OpenWrt) выявил цепочку проблем:
- Waitress 3.x сломал SSE: обновление
waitressподтянуло v3.x, который строго соблюдает PEP 3333 и отклоняет hop-by-hop заголовки.Connection: keep-aliveв SSE-ответе вызывал крашAssertionError— заголовок полностью удалён. - Несовпадение имени JS-переменной: и polling-, и SSE-обработчики в
app.jsобращались кdata.groups, но распарсенная переменная называласьstatus— устройства никогда не рендерились. Исправлено наstatus.groups. - Несовпадение ID при обогащении групп:
_build_groups_summary()сравнивалgroup_idSendspin (UUID) с syncgroup ID MA (syncgroup_XXX) — разные системы ID, которые никогда не совпадали. Исправлено через резолвинг MA syncgroup по маппингу имён плееров. - Группы отсутствовали в ответе polling:
/api/statusдля мостов с одним устройством не включал полеgroups(оно было только в SSE), поэтому бейдж никогда не появлялся через polling. - Инцидент с bluetooth.service в LXC: случайный перезапуск
bluetooth.serviceвнутри контейнера Turris (где bluetoothd не может работать) сломал A2DP-состояние PulseAudio, потребовав повторное сопряжение устройств с хоста. Защита:bluetooth.serviceтеперь замаскирован (не просто disabled), аsendspin-client.serviceполучилTimeoutStopSec=15для предотвращения зависших остановок.
Инфраструктура GitHub Issues и Discussions (v2.13.0)
Заголовок раздела «Инфраструктура GitHub Issues и Discussions (v2.13.0)»Проект получил структурированное управление задачами: 3 YAML-шаблона issue (Bug Report с dropdown-ами deployment/audio, специализированная форма Bluetooth/Audio, Feature Request), 16 меток проекта (type:bug, area:bluetooth, deploy:ha-addon и т.д.) и приветственный пост в Discussions с руководством по маршрутизации (Issues для багов/фич, Discussions для помощи/идей).
Комплексное укрепление безопасности и аудит качества кода (v2.16.0)
Заголовок раздела «Комплексное укрепление безопасности и аудит качества кода (v2.16.0)»Полный code review всей кодовой базы выявил 42 проблемы в области безопасности, потокобезопасности, обработки ошибок, надёжности и покрытия тестами. Все были решены в одном координированном релизе:
Безопасность (5 фиксов): SSRF через path traversal flow_id в потоке HA auth; SSE endpoint мог исчерпать все Waitress-потоки (ограничено до 4); нелимитированная громкость с сервера могла перегрузить колонки на 200%+; инъекция MAC-адреса в stdin bluetoothctl; /api/status раскрывал MAC, IP и метаданные плееров без аутентификации.
Потокобезопасность (6 фиксов): итерация по списку _clients без блокировки в ~15 API endpoint-ах; stop_sendspin() обходил SSE-уведомление; race condition счётчика перезапусков зомби; чтение конфиг-файла без config_lock; несинхронизированная запись учётных данных MA API; пул BT executor слишком мал (2→4) для переподключения нескольких устройств.
Обработка ошибок и валидация входных данных (7 фиксов): краш request.get_json() на не-JSON POST; утечка внутренних строк исключений в 15 ответах об ошибках; краш IPC-команды громкости на нечисловом вводе; path traversal через подставной client_id; путаница типов player_names (string vs list); set_log_level принимал произвольные цели getattr; force=True ослаблял CSRF-защиту на endpoint пароля.
Покрытие тестами (65 новых тестов): с 42 до 107 тестов. Новые тестовые файлы для services/bluetooth.py, services/pulse.py, bluetooth_manager.py, services/daemon_process.py, scripts/translate_ha_config.py и routes/api.py. Добавлен общий conftest.py. datetime.UTC заменён на timezone.utc в 4 файлах для совместимости тестов с Python 3.9.
Совместимость с armv7l (hotfix после релиза): PyAV 12.3.0 (единственная версия, компилируемая на armv7l) не имеет AudioLayout.nb_channels, из-за чего FLAC-декодер sendspin падает с AttributeError — полная тишина. Monkey-patch в services/daemon_process.py заменяет FlacDecoder._append_frame_to_pcm на версию, использующую len(frame.layout.channels). Патч автоматически определяет версию PyAV при запуске и является no-op на PyAV 13+.
Raspberry Pi и Docker UX (v2.16.2): После того как первый пользователь из сообщества попробовал Docker на Raspberry Pi и столкнулся с проблемами конфигурации, мы добавили: диагностический скрипт pre-flight (scripts/rpi-check.sh), проверяющий Docker, Bluetooth, аудио, UID и архитектуру перед docker compose up; endpoint /api/preflight без аутентификации для программной проверки настройки; структурированную таблицу диагностики запуска в entrypoint.sh (видна в docker logs); специальное руководство по установке на Raspberry Pi (en/ru); и исправленную устаревшую Docker-документацию, которая всё ещё упоминала удалённую capability SYS_ADMIN и не содержала переменных окружения PULSE_SERVER/XDG_RUNTIME_DIR.
10 марта 2026 — HA OAuth и аутентификация MA API (v2.17.0–v2.20.0, ~45 коммитов)
Заголовок раздела «10 марта 2026 — HA OAuth и аутентификация MA API (v2.17.0–v2.20.0, ~45 коммитов)»Поток HA OAuth popup для MA addon (v2.17.3)
Заголовок раздела «Поток HA OAuth popup для MA addon (v2.17.3)»В режиме addon MA находится в приватной Docker-сети — недоступен из браузера пользователя. Мост добавил поток HA OAuth popup: веб-интерфейс открывает popup на endpoint авторизации HA OAuth, HA аутентифицирует пользователя (включая 2FA/TOTP), а мост обменивает полученный код на сессионный токен MA через серверные HTTP-вызовы через HA Ingress. Это устраняет необходимость ручной настройки MA_API_TOKEN.
Тихая MA-аутентификация через Ingress (v2.17.4)
Заголовок раздела «Тихая MA-аутентификация через Ingress (v2.17.4)»Поток popup требовал взаимодействия с пользователем. В режиме Ingress сессионный токен HA уже доступен в localStorage (hassTokens). Мост теперь считывает его автоматически при загрузке страницы, вызывает /api/ma/ha-silent-auth, который выполняет полный OAuth-обмен на стороне сервера — ноль кликов. Auto-discover тоже запускается при загрузке страницы, так что соединение с MA устанавливается прозрачно.
Долгоживущий MA API токен (v2.17.7)
Заголовок раздела «Долгоживущий MA API токен (v2.17.7)»Расследование persistent-ошибок «authentication failed» в MA-мониторе выявило фундаментальную проблему: OAuth callback возвращает сессионный JWT с коротким сроком жизни (30-дневный скользящий срок, is_long_lived=False), а не API-токен. Кроме того, баг в регулярном выражении захватывал #/ (Vue Router hash fragment) как часть JWT, повреждая его.
Исправление: после получения сессионного JWT через OAuth мост подключается к WebSocket API MA, аутентифицируется сессионным токеном и вызывает auth/token/create для получения полноценного долгоживущего JWT (10-летний срок). Сессионный токен никогда не сохраняется.
Идемпотентность: перед началом OAuth _validate_ma_token() проверяет, действителен ли существующий токен для целевого MA URL — предотвращая создание дублирующих долгоживущих токенов при перезагрузке страницы или перезапуске addon-а.
Обнаружение MA-сервера из sendspin-соединения (v2.17.9)
Заголовок раздела «Обнаружение MA-сервера из sendspin-соединения (v2.17.9)»В режиме addon с SENDSPIN_SERVER=auto обнаружение MA-сервера в крайнем случае опиралось на mDNS — но изменение API zeroconf (kwargs вместо позиционных аргументов) сломало коллбэк. Исправление: перед откатом к mDNS мост теперь извлекает хост MA-сервера из резолвенного WebSocket-соединения sendspin (connected_server_url). Поскольку sendspin уже обнаружил MA-сервер через собственный mDNS, мост переиспользует этот резолвенный адрес для endpoint MA API (тот же хост, порт 8095). Это устраняет необходимость отдельного mDNS-сканирования в большинстве случаев.
Упрощённый addon discovery и полуавтоматическая аутентификация (v2.17.10)
Заголовок раздела «Упрощённый addon discovery и полуавтоматическая аутентификация (v2.17.10)»Предыдущий подход имел фундаментальную проблему: определение addon-режима зависело от поля homeassistant_addon MA-сервера из его endpoint /info — но когда discovery использовал mDNS-путь (через _enrich_with_server_info вместо validate_ma_url), это поле отсутствовало, поэтому addon-режим никогда не определялся и тихая аутентификация никогда не срабатывала.
Исправление упростило весь поток. Мост теперь сообщает собственный флаг is_addon (из _detect_runtime()) в ответе discover — без зависимости от метаданных MA-сервера. В addon-режиме discovery сначала пробует http://homeassistant.local:8095 (внутренний DNS Supervisor — практически мгновенно), минуя эвристики SENDSPIN_SERVER и mDNS. Полностью автоматическая тихая аутентификация при загрузке страницы была заменена полуавтоматическим подходом: кнопка «Sign in with Home Assistant» показывается после того, как discover обнаружит addon-режим, и пользователь кликает на неё явно. В Ingress-режиме это выполняет аутентификацию в один клик (без popup); вне Ingress — открывает OAuth popup.
Беспарольная MA-аутентификация через Ingress JSONRPC (v2.18.0)
Заголовок раздела «Беспарольная MA-аутентификация через Ingress JSONRPC (v2.18.0)»Тихая аутентификация в v2.17.4–v2.17.12 пыталась выполнить POST на /auth/authorize HA с Bearer-токеном для получения OAuth-кода — но endpoint авторизации HA работает только на GET (он отдаёт HTML-страницу согласия) и возвращает HTTP 405. Fallback через popup работал, но требовал ввода учётных данных.
Подход v2.18.0 полностью обходит HA OAuth. Ingress-сервер MA (порт 8094) автоматически аутентифицирует запросы через заголовки X-Remote-User-ID / X-Remote-User-Name — тот же механизм, который HA использует внутренне для Ingress-трафика. Поскольку оба addon-а используют host_network: true, мост может достучаться до Ingress-порта MA на localhost:8094. Поток: (1) фронтенд отправляет HA access token из hassTokens в localStorage; (2) бэкенд подключается к WebSocket API HA и вызывает auth/current_user для получения ID и username пользователя; (3) бэкенд отправляет JSONRPC-запрос на Ingress endpoint MA (http://localhost:8094/api) с заголовками пользователя, вызывая auth/token/create; (4) MA автоматически аутентифицирует Ingress-запрос и создаёт долгоживущий 10-летний JWT. Весь поток невидим для пользователя — один клик кнопки, без учётных данных, без popup.
Укрепление и фиксы сети HAOS (v2.18.1–v2.18.3)
Заголовок раздела «Укрепление и фиксы сети HAOS (v2.18.1–v2.18.3)»Три быстрых патча решили реальные проблемы деплоя, обнаруженные при верификации на HAOS:
v2.18.1 — совместимость websockets. Docker-образ HAOS addon поставляется со старой библиотекой websockets (<14), которая не принимает именованный аргумент proxy=None. Был добавлен compatibility-враппер _ws_connect(), который сначала пробует с proxy=None, перехватывает TypeError и повторяет без него.
v2.18.2 — сеть HAOS addon. В HAOS каждый addon работает в собственном Docker-контейнере со своим сетевым namespace — localhost:8094 из addon моста не достигает Ingress-порта MA. Исправление: _find_ma_ingress_url() запрашивает Supervisor API HA (http://supervisor/addons/{slug}/info) для обнаружения Docker-hostname и Ingress-порта addon MA, затем подключается через Docker DNS (например, http://d5369777-music-assistant:8094). Известные slug-и addon MA (d5369777_music_assistant, _beta, _dev) перебираются по порядку. Конфигурация addon получила разрешения hassio_api: true и homeassistant_api: true.
v2.18.3 — формат ответа JSONRPC. auth/token/create MA возвращает токен как сырую JSON-строку при вызове через Ingress-порт, а не обёрнутым в {"result": "..."}. Парсер ответа теперь обрабатывает оба формата и логирует сырой ответ для диагностики.
Перестройка UI конфигурации (v2.19.0)
Заголовок раздела «Перестройка UI конфигурации (v2.19.0)»Раздел Configuration разросся органически и требовал реструктуризации. Кнопки сохранения были посередине формы, Music Assistant Integration была спрятана внутри Advanced settings (два клика вглубь), таблица BT Devices имела 9 столбцов с горизонтальным скроллом 700px на мобильных, а подписи были многословными абзацами.
Перестройка реорганизовала форму в чётко обозначенные секции — General, Bluetooth, Music Assistant (вынесен на верхний уровень), Advanced и Authentication — каждая с иконкой в заголовке и визуальным разделением. Sticky save bar теперь появляется внизу, когда в конфиге есть несохранённые изменения. Таблица BT Devices была разделена на основную строку (Name, MAC, Adapter, Format) и раскрывающуюся дополнительную строку для расширенных полей (Listen Address, Port, Delay, Keep-alive), которая автоматически открывается при нестандартных значениях.
Полировка UX конфигурации и обратная связь от сообщества (v2.20.0)
Заголовок раздела «Полировка UX конфигурации и обратная связь от сообщества (v2.20.0)»Обратная связь сообщества по релизу v2.19.0 привела ко второму раунду полировки. Пользователи отметили, что кнопка Add в списке отсканированных/сопряжённых устройств была слишком далеко от имени устройства, что затрудняло нацеливание. Панель Advanced settings (в которой к этому моменту осталось всего 4 поля) была полностью ликвидирована — поля перенесены в соответствующие секции, лишняя панель удалена.
Ключевые изменения: MA-форма теперь автоматически сворачивается в сводку при подключении (ссылка «Reconfigure» раскрывает её); поля auth скрываются, когда отключены; шеврон раскрытия BT-устройства перенесён на левую сторону строки для привычного tree-style взаимодействия; устройства по умолчанию свёрнуты; строки scan/paired устройств стали полностью кликабельными с hover-подсветкой; кнопка Scan перенесена перед +Add Device для discovery-first workflow. Добавлен guard _configLoading для предотвращения срабатывания индикатора несохранённых изменений при программном заполнении полей при загрузке страницы.
Аудит кода и внутренний рефакторинг (v2.20.3)
Заголовок раздела «Аудит кода и внутренний рефакторинг (v2.20.3)»Комплексный code review всей кодовой базы (~10 700 строк в 35 Python-файлах) выявил две критические проблемы: мёртвый endpoint /api/bt/reconnect (функция существовала, но не имела декоратора @route — ни один HTTP-запрос не мог до неё добраться) и wildcard postMessage('*') в коллбэке OAuth popup HA, нарушавший принцип same-origin. Обе были исправлены немедленно.
Более масштабным результатом стало разделение монолита routes/api.py в 3 178 строк — самого большого файла проекта — на пять целевых модулей: базовые маршруты volume/mute/pause остались в api.py (581 строка); Bluetooth scan/pair/reconnect переехали в api_bt.py (396); интеграция с Music Assistant и OAuth-поток — в api_ma.py (1 216); config и настройки — в api_config.py (502); статус, SSE-стриминг и диагностика — в api_status.py (647). Каждый модуль регистрирует собственный Flask Blueprint; web_interface.py связывает все пять. Были добавлены обратно-совместимые реэкспорты, чтобы существующие тесты и внешние вызовы продолжали работать без изменений.
Потокобезопасность получила точечные фиксы: шесть мест, итерировавших глобальный список _clients без _clients_lock, были исправлены — три в ma_monitor.py через новый хелпер state.get_clients_snapshot(), два в config и MA routes. Счётчик MaMonitor._msg_id, ранее обычный int, инкрементируемый из разных потоков, был заменён на itertools.count(1) — атомарный в CPython. Дублирующее регулярное выражение MAC-адреса было консолидировано в services/bluetooth.py как каноническая функция is_valid_mac().
Все 138 тестов прошли после рефакторинга; ruff check оставался чистым на протяжении всей работы.