← Инженерные заметки

Root на Яндекс Станции без вскрытия: свой LLM в колонке

Неоновая иллюстрация: умная колонка в центре кадра со светящимся кольцом, над ней
окно загрузки прошивки с прогресс-баром, вокруг — терминал, серверные стойки,
дом с сервером, звуковая волна, отвёртка и кот с кружкой кофе

Помните волну экспериментов с перепрошивкой Android TV — лаунчеры, деинсталляция лишнего софта, root? Кажется, очередь дошла до умных колонок. У меня под рукой оказалось три Яндекс Станции трёх поколений, и идея простая: сохранить привычные возможности Алисы, но научить колонку выполнять команды умного дома через собственный мост и LLM. Корпуса вскрывать не пришлось.

Контекст: три подопытных

КолонкаМодельПлатформаОсобенность
Станция Лайт 1YNDX-00025yandexmicro, Amlogic AXGуже была на стенде с прошлых серий
Станция МидиYNDX-00054cucumberroot берётся прямо с factory-образа, без даунгрейда
Станция Мини 1 (2019)YNDX-0004yandexmini, Amlogic A113XRAM 236 МБ, NAND 256 МБ — главный герой дня

Целевая архитектура: на колонке работает Quasar Bridge — форк quasar-bridge на Go. Он перехватывает голосовой WebSocket колонки, запросы по умному дому обрабатывает локально через LLM и Home Assistant (по MCP — Model Context Protocol, протоколу подключения инструментов), а музыку и погоду честно проксирует в Яндекс. ASR и синтез речи остаются облачными — их трогать незачем.

Исходная проблема

Прошивки Яндекса, выпущенные после 21 октября 2026 года, перестали принимать подменный сертификат стенда. То есть проверенный путь «поднял DNS-сервер — получил ADB» для свежих версий закрыт. Нужно было вернуть доверие устройства, не вскрывая корпус.

Требования к решению:

  • root без разборки и пайки;
  • Алиса остаётся Алисой: музыка, погода, ответы — как раньше;
  • голосовые команды умного дома идут через мой LLM, а не через облачный сценарий;
  • доступ закрепляется: переживает OTA и перезагрузки.

Почему DNS, а не USB

Для Мини 1 существует USB-путь (TPL-загрузчик Amlogic), и я честно начал с него: поставил инструменты, поднял виртуальную машину с x86_64 — и обнаружил, что Mac вообще не видит устройство. Ни нативно, ни в виртуалке с пробросом USB.

Копать дальше (кабель? порт? ревизия платы?) не стал: причина так и не установлена, это честно фиксирую как неразобранную ветку. DNS-метод быстрее и уже оплачен инфраструктурой.

ПутьПлюсыМинусы
USB-root (TPL)не нужна сеть и стендMac не видит устройство; диагностика кабелей — отдельный квест
DNS-подмена + откатотлажено на двух колонках, без вскрытиянужен стенд и factory-образ
Пайка UARTуниверсальнокорпус, гарантия, руки

Стенд и factory-образ как тайм-капсула доверия

Стенд — самописный DNS/HTTPS-сервер из арсенала сообщества hackalice.me. Он подменяет три домена (quasar.yandex.net, quasar.yandex.ru, scbh.yandex.net), отдаёт настоящий ответ Яндекса с одной инъекцией — включённым сетевым ADB, — и умеет раздавать прошивки для отката. Всё остальное форвардится в реальный интернет.

Ключевая мысль: заводской (recovery) образ старый — 2019 года — и он по-прежнему доверяет подменному серверу. Поэтому цепочка универсальна:

  1. сброс колонки (питание → зажать кнопку микрофона → питание → ~5 секунд до жёлтой подсветки) грузит factory-образ 2019 года;
  2. factory доверяет стенду, стенд предлагает откат на целевую прошивку;
  3. колонка скачивает 56 МБ образа, ставит его и приходит на get_sync_info уже с включённым adbd.

Установщик прошивок при этом отказался принимать файл: он ждёт сигнатуру PK/070701, а образ Мини — xz-сжатый cpio. Доверие восстановили контрольной суммой: crc32 совпал с официальным metadata.json, файл положили руками.

[OTA-AUTO] у колонки 1.3.4.12.532420178.20191021, у нас
1.305.4.16.3237824040.20250721 — отдаю манифест прошивки
>>> [OTA] файл отдан ПОЛНОСТЬЮ (56040760 б) <<<

Root подтверждён:

$ adb connect 192.0.2.18:5555
$ adb shell id
uid=0(root) gid=0(root)

От сброса до root — минут пятнадцать.

Грабли по дороге

Каждая — проверена на живом железе и стоила времени.

Привязка через приложение. Приложение «Дом с Алисой» требует Wi-Fi на телефоне, но его запросы к подменённым доменам умирают по TLS — вечный спиннер. Лечение: телефон в ту же сеть и вручную прописанный DNS 1.1.1.1. Тонкость iOS: ручной DNS применяется не сразу, помогает переподключение Wi-Fi.

busybox без gzip. tar -z на колонке отвечает invalid option -- 'z'. Распаковка пакетов — только через zcat | tar -xf -. А env не умеет -u: обёртки пакетного менеджера делают unset LD_LIBRARY_PATH вместо этого.

Read-only rootfs. Любые правки /etc начинаются с mount -o remount,rw /.

Hostname в роутере. MikroTik не показывал имя колонки, потому что dhcpcd шлёт FQDN-опцию вместо option 12. В /etc/dhcpcd.conf — hostname <имя> плюс fqdn disable, затем dhcpcd -n wlan0.

Детектив про OTA-цикл

Через какое-то время после перевода Мини на домашнюю сеть она начала мигать синим каждые секунд десять. Лог объяснил: колонка скачивала свежую прошивку (52 МБ), установка падала на подменённом ключе подписи — и цикл повторялся.

[updates] Downloading …/1.373.4.16.4829693628.20261104.zip …
[updates] Update is allowed by backend
[updates] Executing OTA script ...

Штатный блокировщик обновлений здесь бессилен: он вырезает updatesd, а на прошивке 1.305 updatesd поднимается через updater_switcher из launch.json — задачу, о которой скрипт не знает. Удаление скачанного образа тоже не спасает: колонка просто качает заново.

Лечение — убрать задачу updater_switcher из launch.json, убить процессы, снести /bkupgrade/update_*.img и перезагрузиться. С тех пор — тишина.

Мозги: Gemma против Gemma

Какую LLM подсунуть бриджу? Решил не верить вкусам и прогнать локальный бенчмарк: 16 фраз на моке Home Assistant MCP (комнаты, синонимы, устройства), temperature 0, ответ строго в JSON.

=== google/gemma-3-27b-it ===        итог: 15/16 верных, 1.75 с, 0.2522 ₽
=== google/gemma-4-26b-a4b-it ===    итог: 15/16 верных, 0.70 с, 0.2087 ₽

Качество одинаковое, латентность — в 2,5 раза лучше у Gemma 4 26B A4B (MoE-архитектура: 4B активных параметров), цена ниже на 17%. Все три колонки переведены на неё.

Попутно — две правки в форке, без которых жить плохо:

  • Нормализация service. Gemma 4 честно копирует сервис из промпта полным именем, и склейка домена давала несуществующий light.light.turn_on;
  • Retry подтверждения. Zigbee-выключатель обновляет состояние с задержкой, и мгновенная сверка ловила старое — «Не удалось подтвердить выполнение команды». Теперь до трёх чтений состояния с паузой 400 мс.

И одно осознанное ограничение: инвентарь планировщика урезан до света (QUASAR_HOME_DOMAINS=light) — 9 устройств вместо 177 сущностей. Токенов меньше, и исчез класс ошибок «модель сматчила запрос на чужой switch». Шторы и пылесос голосом пока недоступны — мне не нужно.

Экономика команды приятная: ~1090 входных токенов, из которых 1024 попадают в автоматический кэш префикса (тариф ниже примерно на порядок), плюс 20–60 выходных. Доли копейки за команду.

Результаты

  • Три колонки с root, SSH (dropbear на порту 2222, вход только по ed25519-ключу) и Entware; доступ закреплён chattr +i и переживает перезагрузки.
  • check-backends на всех трёх: LLM: ok; Home Assistant: 177 entities.
  • Алиса не потеряла ни одной штатной функции; команды умного дома идут через мой мост.

Ограничения и открытые вопросы

  • Одна фраза срабатывает на нескольких колонках сразу. Причина не подтверждена, арбитража в бридже нет; журналы conversations.jsonl пишутся — разберу по корреляции времени.
  • USB-путь для Мини так и не исследован (кабель? порт?) — запасной вариант, если DNS-метод когда-нибудь закроют.
  • Секреты на колонках лежат в открытых конфигах — нужна ротация и нормальное хранение.

Выводы

Свобода колонки — это не «сломать и заменить», а интерфейсы: factory-образ как тайм-капсула доверия, DNS как точка входа и нормальный API бриджа сверху. Паяльник остался в ящике, а модели выбирает бенчмарк, не вкус.

Источники

Обсудить в Telegram

Расскажите о задаче

Опишите задачу или проблему в двух словах. Отвечу в Telegram и честно скажу, чем могу быть полезен.