discover · compare · collections · requirements · contribute · feed · watching · stats · help with your tokens
derangedabbot/yandex-station-2-airplay · source
Snapshot 0 · 2026-11-04T12:47:28Z · Shell · MIT
Content fingerprint
877373afa7db83ff9ccc8679c567f0fc6d0db62f055847084ce8ac1721864909GitHub description at capture
Root на Яндекс.Станции 2 (платформа yandexmidi) и добавление нативного AirPlay 2: метод, закрепление доступа, приёмник shairport-sync, панель, подсветка, голос
README at capture
# Root + AirPlay на Яндекс.Станции 2 (платформа `yandexmidi`) Практический отчёт: как получить root на «Яндекс Станции 2» через подмену облачного конфига, закрепить доступ и **добавить AirPlay**, которого в этой колонке нет. Проверено на реальном устройстве: модель `YNDX-00051` («Станция 2»), фактическая платформа **`yandexmidi`**, прошивка `0.369.1.50.*` (сборка от 21.10.2026). > **Про названия — важная путаница.** Это «Станция 2» (`YNDX-00051`, второе поколение), а не > «Станция Миди»: Миди — отдельная модель `YNDX-00054` с маленьким дисплеем, у неё другой софт. > Слово `midi` в наших текстах — это **внутреннее имя платформы** Яндекса (`yandexmidi`: папка > прошивки, пресет активации, файл диагностики ALSA), а не название продукта. Сама колонка о себе > в `quasar.cfg` пишет `deviceName: "Яндекс.Станция (второе поколение)"` — это и есть «Станция 2». > Проверить на своей: `grep -i deviceName /system/vendor/quasar/quasar.cfg`. > **English summary.** This repo documents rooting a Yandex Station 2 (`yandexmidi` platform) > via a cloud-config interception (leaked CA + the tooling from > [hu553in/yandex-station-root](https://github.com/hu553in/yandex-station-root)), and — the part > that was missing everywhere — turning the speaker into a working **AirPlay receiver** using > `shairport-sync` from Alpine, with automatic handover of the sound device between the stock > media daemon and the AirPlay receiver. --- ## ✨ Что здесь есть | | Что | Куда смотреть | |---|---|---| | 🔓 | root на Станции 2 через подмену облачного конфига — и почему на нашей прошивке это сработало | [`docs/01-root.md`](docs/01-root.md) | | 🔁 | закрепление доступа: `adbd` по Wi-Fi, постоянный SSH на `2222`, автозапуск | [`docs/02-persistence.md`](docs/02-persistence.md) | | 📡 | **AirPlay 2**, которого в колонке не было: `shairport-sync`, мультирум, управление с колонки | [`docs/03-airplay.md`](docs/03-airplay.md) | | 🎛 | обратное управление AirPlay: пауза, дальше, назад — с панели, голосом и из MPRIS | [`docs/03-airplay.md`](docs/03-airplay.md) | | 🌐 | веб-панель на `socat`: статус, логи, громкость, «сказать Алисе», плеер «Звука» | [`docs/06-panel.md`](docs/06-panel.md) | | 🟢 | подсветка кольца своим зелёным дыханием (кадровый буфер `/dev/fb0`) | [`docs/08-leds.md`](docs/08-leds.md) | | 🎤 | свои голосовые команды без облака — через локальный канал Алисы | [`docs/09-voice.md`](docs/09-voice.md) | | 🎵 | свой трек её же плеером: локальный раздатчик + `audio_play` | [`docs/11-playback.md`](docs/11-playback.md) | | 💾 | USB-host: флешки, SD, Ethernet через Type-C (OTG) | [`docs/12-usb-host.md`](docs/12-usb-host.md) | | 🧯 | восстановление после сброса и проверка, что всё на месте | [`docs/04-recovery.md`](docs/04-recovery.md), [`docs/10-verify.md`](docs/10-verify.md) | **Готовый плеер «Звука» для колонки** живёт отдельно: [zvuk-yandex-station-player](https://github.com/derangedabbot/zvuk-yandex-station-player) — ставится одной командой `./scripts/install.sh <IP>`. --- ## 🔓 Откуда взялся способ 1. В октябре 2026 публично утёк приватный ключ CA (EM OV TLS CA – G3C, разбор — [Fluepke/emudrahahahahaha](https://github.com/Fluepke/emudrahahahahaha)). Колонки доверяют сертификатам этого CA, когда ходят за своим облачным конфигом. 2. Колонка при старте забирает конфиг с `quasar.yandex.net` и **слушается поля `system_config.adbd`** — то есть облако может включить ей сетевой ADB. 3. Инструменты для этого собраны в [hu553in/yandex-station-root](https://github.com/hu553in/yandex-station-root): DNS-подмена трёх имён, reverse-прокси на mitmproxy с сертификатом от утёкшего CA и подмена ответа `get_sync_info` (включает `adbd.remoteActivation` и `adbd.network`). Наша работа — не изобрести это заново, а **проверить на Станции 2** и довести до состояния, в котором колонкой можно пользоваться каждый день. ## 🧪 Что выяснилось на нашей прошивке Автор метода пишет, что сборка `.50` отвергает цепочку сертификатов. **На `yandexmidi` это не так**: подмена конфига на `0.369.1.50.*` проходит, `adbd` поднимается, `adb shell` отдаёт `uid=0(root)`. Похоже, закрытие дыры разъехалось по платформам волной, и наша платформа в неё не попала (или попала позже). Поэтому откат прошивки на старую сборку **не понадобился** — а именно этого мы ждали в начале. ## 🔌 Питание вилкой — часть нашего метода, а не случайность > **Главное, что обычно не пишут: окно отладки мы ловили не «подождать и поймать», а выдёргиванием > кабеля питания.** Сразу после сброса колонка уходит в обновление из `/bkupgrade` и может > 10–15 минут показывать анимацию, не отвечая по сети. Мы ни разу этого не дожидались: питание > выдёргивалось, колонка перезагружалась, и **на загрузке заново шла за конфигом** — вот в этот > момент прокси и подменял ответ, а `adbd` поднимался на `5555`. Как это выглядело на практике: 1. сброс → колонка «догорает» обновление; 2. **выдернуть питание** (обычная вилка, без выключения кнопкой); 3. включить обратно → загрузка → запрос конфига → окно ADB на 30–60 секунд. Побочные следствия, которые надо знать: * **Обновление ни разу не завершилось** — мы каждый раз обрывали его питанием. Поэтому «прошивка осталась `.50`» — факт, а «сброс перепрошивает колонку» — недоказанное утверждение. * Резкие обрывы питания колонка переживала спокойно: поднималась с синим миганием, прошивка цела, `/data` на месте. Но это лотерея, а не безопасная процедура. * Если дать обновлению доиграть и перезагрузиться штатно, окно закрывается — и подмена конфига уже не даёт эффекта. Похоже, именно поэтому у разных людей на одной и той же `.50` получается разный результат. Дальше в [`docs/01-root.md`](docs/01-root.md) шаг с питанием описан в общем виде: там же сказано, что ждать обновление не нужно, но и обрывать его — решение, за которое вы отвечаете сами. ## 🧰 Что понадобилось сверх авторского набора Штатные `make status` / `make install` на Станции 2 **падают**: они рассчитаны на раскладку Mini 2 (отдельный `/system`, другой скрипт активации). Всё, что ниже, сделано руками: | Отличие Станции 2 | Следствие | |---|---| | Корень `ubi0:rootfs / ubifs ro`, но **перемонтируется в rw** (`mount -o remount,rw /`) | можно писать в `/etc` и `/system` | | Отдельного маунта `/system` нет | `activate_adb.sh` живёт в `/system/vendor/quasar`, но это каталог на корне | | `/data` → симлинк на `/bkupgrade/data` (ubi1_0, rw) | метки и утилиты переживают перезагрузку | | Активация ADB — через `onboot.sh` → `activate_adb.sh check` | достаточно создать метки в `/data/quasar` | | Прикладной слой Яндекса — **32-бит ARM**, система 64-бит | сторонние бинарники — только aarch64-musl | | Busybox `tar` без `-z` | распаковка: `gzip -dc f.tar.gz \| tar -xf -` | | Облако при своём конфиге **удаляет метки и гасит adbd** | ADB — короткое окно, постоянный канал — SSH | Подробный разбор: [`docs/01-root.md`](docs/01-root.md), закрепление — [`docs/02-persistence.md`](docs/02-persistence.md). ## 📡 AirPlay — то, чего в колонке не было Приёмник — **`shairport-sync 5.5.1` с AirPlay 2**, собранный под aarch64/musl в контейнере (статический мини-ffmpeg + `nqptp`), плюс `avahi` для объявления в сети. Колонка появляется на iPhone/Mac как **«Яндекс Станция»** и играет в свои динамики. Сборка — `docs/07-airplay2-build.md`. Пришлось решить две вещи, на которых всё обычно и останавливается: 1. **mDNS.** У колонки уже есть свой mDNS — демон `maind` держит UDP 5353 и публикует `yandex-station-gen2-<id>.local`. Avahi с другим именем получает конфликт PTR-записей, отзывает адреса и переименовывается по кругу. Решение: дать avahi **точно то же имя хоста**, что у колонки. 2. **Эксклюзивный звук.** `maind --mediad` держит `pcmC0D0p` и не пускает никого (`EBUSY`). Решение — распорядитель: при подключении к AirPlay он останавливает медиа-демон, а после сессии перезапускает приёмник (тот отпускает устройство, но остаётся в сети) и поднимает демон обратно. В итоге Алиса жива, AirPlay доступен всегда, а звук переходит по очереди. **Управление с колонки.** AirPlay 2 не даёт обратного управления через DACP, но механизм есть: plist `sendMediaRemoteCommand` на канал событий аудио-сессии. Мы его включили — пауза, «дальше», «назад» работают и с Mac, и с iPhone, причём командовать можно с самой колонки: кнопками панели, голосом или `airplay-ctl.sh`. Детали и грабли: [`docs/03-airplay.md`](docs/03-airplay.md), шаг 6. Рецепт целиком: [`docs/03-airplay.md`](docs/03-airplay.md). **Чем это отличается от рецепта для Станции Мини 3** ([hackalice.me](https://hackalice.me/recipes/airplay/)). Там берут готовый пакет `shairport-sync 4.3.7`, патчат в нём 4 байта `airplayd` и вешают хуки на паузу. Отсюда четыре отличия, которые стоит знать до выбора пути: * **патча нет** — приёмник у нас свой, поэтому после переустановки пакета AirPlay ничего не отваливается (у них патч и хуки исчезают, скрипт надо запускать заново), и время на дисплее не при чём (у них это побочный эффект патча, лечится флагом `--no-patch`); * **управление с колонки** — пауза, «дальше», «назад» уходят источнику через `sendMediaRemoteCommand`; у них обратного управления нет, «Play надёжнее нажимать с телефона»; * **владелец звука определяется по TCP-сессии**, а не по событиям старт/пауза: у них на стыке кнопку могут поймать оба демона, у нас распорядитель отдаёт устройство и возвращает Алису сам; * **mDNS и ALSA решены явно** — avahi получает имя хоста колонки (иначе устройство «то появляется, то нет»), приёмник берёт свой `ALSA_CONFIG_PATH`, потому что системный тянет 32-битные плагины. В минус нам: у них путь короче — сборки нет вовсе. Подробный разбор по пунктам, включая «что общего» и почему модели сравнивать напрямую нельзя: [`docs/13-vs-airplay-recipe.md`](docs/13-vs-airplay-recipe.md). ## 📂 Что лежит в репозитории ``` docs/ 01-root.md — получение root на Станции 2: пошагово, с отличиями от Mini 2 02-persistence.md — SSH-автозапуск, заморозка обновлений, сторож 03-airplay.md — AirPlay: mDNS, ALSA, распорядитель звука 04-recovery.md — как откатить всё назад 06-panel.md — веб-панель: статус, кнопки, логи 07-airplay2-build.md — сборка AirPlay 2: статический ffmpeg, shairport-sync, nqptp 08-leds.md — подсветка кольца: framebuffer, паттерны, привязка к AirPlay 09-voice.md — голос: текст распознавания из локального IPC Алисы (127.0.0.1:9875) 10-verify.md — проверка «всё ли живо» одной командой (15 пунктов, ничего не меняет) 13-vs-airplay-recipe.md — чем наш AirPlay отличается от рецепта для Станции Мини 3 scripts/ device/ — скрипты, которые живут на колонке mac/verify-station.sh — проверка состояния колонки по SSH: 15 пунктов, только чтение build/airplay2/ — сборка AirPlay 2 (nqptp, shairport-sync, мини-ffmpeg) в контейнере build/led-patterns.py — генератор кадров подсветки build/ — сборка musl-набора утилит из пакетов Alpine mac/ — то, что запускается с компьютера ``` Инструменты (собранные из подписанных пакетов Alpine: `jq`, `strace`, `lsof`, `tmux`, `rsync`, `dig`, `shairport-sync`, `mpg123`, `alsa-utils`, `dropbear`, `avahi`, `dbus`) в репозиторий не включены — соберите сами, см. [`docs/03-airplay.md`](docs/03-airplay.md) и [`docs/05-tools.md`](docs/05-tools.md). ## 💾 USB-host: флешки, SD-карты, Ethernet Порт Type-C на Станции 2 умеет быть хостом — производитель просто не коммутирует его: по умолчанию он отдан gadget-контроллеру, а xHCI при загрузке умирает (`xHCI host not responding`). Переключение — четыре регистра USB2-PHY, всё в рантайме. Проверено: хаб, флешка FAT32, SD-карта в картридере, USB-Ethernet (`eth0`). Есть защита от бутлупа и откат без перезагрузки. Рецепт у нас: [`docs/12-usb-host.md`](docs/12-usb-host.md), скрипты — [`scripts/device/otg/`](scripts/device/otg/). Основа — рецепт для Станции Лайт с [hackalice.me](https://hackalice.me/recipes/usb-host/) ([Marker284/yandex-station-extended](https://github.com/Marker284/yandex-station-extended)); наша часть — проверка на Midi, автозапуск и грабли. ## 🌐 Веб-панель Чтобы не лезть в SSH из-за каждой мелочи — страница на `socat` (в busybox этой прошивки нет `httpd`), без единого нового пакета: статус приёмника и демона, кнопки «перезапустить AirPlay», «поднять Алису», «перезагрузить», хвосты логов, громкость ползунком, карточка «Сказать Алисе» и управление плеером Звука: поиск с «+» (трек / альбом / исполнитель), очередь с переходом, удалением и кнопками «Альбом»/«Исполнитель», «Очистить очередь». Слушает только домашнюю подсеть (`socat …,range=`), токена нет — в своей сети он только мешает. Логи показываются по Москве, хотя на диске время UTC. ``` http://<STATION_IP>:8081 ``` Подробно: [`docs/06-panel.md`](docs/06-panel.md). ## 🟢 Подсветка: кольцо дышит, когда играет AirPlay Кольцо станции висит на `/dev/fb0`: **89 пикселей в строку по 4 байта (R, G, B, X)**. Штатные анимации в `/system/vendor/quasar/ledpatterns/panel/*.led` — ровно те же кадры по 356 байт. То есть светом можно управлять напрямую, без облака и директив. Сделано: пока идёт AirPlay — плавное «дыхание» триколором (белое → синее → красное, смена цвета в полной темноте), после остановки — гаснет. Живёт в распорядителе звука. Подробности, включая грабли (busybox-awk портит байты >127, AirPlay 2 держит TCP-соединение после остановки и ломает детект): [`docs/08-leds.md`](docs/08-leds.md). ## 🎤 Голос: свои команды без облака Станция отдаёт **готовый текст распознавания** в локальный сокет `127.0.0.1:9875` (4 байта длины + protobuf, поле `252` → статус `3` и текст). Свой слушатель ловит фразы из списка и запускает что угодно — модели и микрофон трогать не нужно. Сделано: слушатель живёт на колонке, автозапуск, дедупликация срабатываний, пять рабочих фраз — «включи звук», «пауза», «продолжи», «дальше», «стоп». Главный вывод: её облако отвечает позже локального слушателя, поэтому `next`/`resume` ставятся с задержкой 1.5 с — иначе играет её поток. Подробности и грабли: [`docs/09-voice.md`](docs/09-voice.md). ## 🎵 Свой трек её плеером Самое полезное, что мы нашли: файл можно отдать **штатному плееру станции** командой `audio_play` (protobuf через `externalCommandBypass`) — играет `maind --mediad`, Алиса жива, громкость и пауза штатные. Обязательное условие — `Range` на нашем раздатчике, пауза ставится только вместе с потоком (`set_pause: true`), а продолжение — повторным `audio_play` с `offset_ms`. Схема целиком: [`docs/11-playback.md`](docs/11-playback.md). ## ⚠️ Риски, честно - Сетевой ADB (`5555`) работает **без ключевой аутентификации**. Не держите его открытым в недоверенной сети; в нашей схеме основной канал — SSH по ключу. - Прошивка заморожена: обновления отключены. Это осознанная плата — в новых сборках дыру закроют. - Полный сброс колонки возвращает заводскую прошивку и **стирает `/data`** вместе с метками, утилитами и ключами SSH. - Надёжность AirPlay в этой схеме — «работает и восстанавливается само», но это надстройка: после каждого отключения приёмник на несколько секунд исчезает из списка. - Всё делается на свой страх и риск. Автор не связан с Яндексом. ## 📄 Дисклеймер Материал публикуется в исследовательских целях: показать, как устроена активация и облачный конфиг устройства, и что можно добавить к нему после получения доступа. Не используйте это на чужих устройствах. ## 🔗 Связанные проекты - **[zvuk-yandex-station-player](https://github.com/derangedabbot/zvuk-yandex-station-player)** — плеер «Звука» для этой же станции: Rust, один статический бинарник. Играет «Мой поток», управляется из панели и голосом. Собран поверх этой инфраструктуры (root, набор musl-утилит, панель, распорядитель звука).