Сводка
Последние алерты
Все алертыИнфраструктура
ОткрытьСсылки контента
Все ссылкиПоследние версии приложений
| Приложение | Версия | Код | Тип | Загружена |
|---|
Прошивки
| Версия | Код | Тип | Добавлена |
|---|
Последние действия
Весь журнал| Время | Кто | Действие | Объект | Итог |
|---|
ИИ-консультант
Разбирается в коде продуктов КРУСТ, уточняет детали и по вашему подтверждению ставит формализованную задачу в бэклог GitLab — её разберёт Антон.
Выберите диалог
Расскажите, что случилось
Выберите продукт и опишите проблему своими словами: я разберусь, где это живёт в коде, уточню детали и подготовлю задание для разработчиков — подсказки ниже.
Проекты
Устройства онлайн
Активность
Серверы проекта
Инфраструктура| Сервер | Роль | Статус | Последний сигнал | Версия |
|---|
Экраны проекта
Коннектор
канал управления к серверу проектаКто имеет доступ
Пользователи| Пользователь | Доступ | Закрытые разделы |
|---|
Инфраструктура
обновлено —
Состояние серверов
Распределение по статусам
Мониторинг серверов
| Сервер | Окружение | Статус | CPU | Память | Задержка | Последняя проверка |
|---|
Мониторинг
Сводка ресурсов
Сервер
Сервисы
| Сервис | Проверка | Состояние | Задержка | Аптайм 24 ч | Сбои | Последняя проверка | 24 ч |
|---|
Ссылки контента
только неотвечающие адреса| Вид | Название | URL | Состояние | Когда |
|---|
Алерты
Правила уведомлений
| Проект | Уровень | Канал | Упоминания | Вкл |
|---|
Каналы
Watchdog
Последние доставки
Правила мониторинга
| Правило | Сигнал сейчас | Состояние | Последняя проверка | Куда отправлять |
|---|
| Важность | Алерт | Тип | С какого времени | Доставка | Закрыт |
|---|
Аналитика
Активные устройства
DAUWAUРежимы за период
Версии APK
Прошивки
Активность по дням
События по дням
всеошибкиТоп событий
Ошибки по версиям APK
| Версия APK | Устройств | С ошибками | Доля с ошибками | Ошибок | Событий |
|---|
Устройства
Добавить устройство| Устройство | Владелец | Версия | Последний сигнал | Активность 30 дн | Состояние | Действия |
|---|
Владелец
Устройство
Активность
Режимы за 30 дней
Сессии устройства
| Семья | Выдан | Последний refresh | IP | Истекает | Состояние |
|---|
Опасная зона
Журнал устройства
Последние события
События
Виды за 30 дней
| Время | Устройство | Уровень | Вид | Сообщение | Версия | props |
|---|
Ошибки
| Хэш / начало стека | Всего | Устройств | Версии | Последний | Первый |
|---|
| Дата | Устройство | Приложение | Retrace | Начало стека |
|---|
Отчёт об ошибке
← К спискуСтек
Расшифрованный стек
Ошибки сервера
| Уровень | Проблема | Событий | Пользователей | Последнее | Состояние |
|---|
Ошибка сервера
← К спискуСообщение
Стек последнего события
Теги
Логи устройств
| Получен | Устройство | Приложение | Пометка | Размер | Начало |
|---|
Лог устройства
← К спискуСодержимое
Ссылки
Состояние адресов из справочников и прошивок: отвечает ли ссылка и когда её проверяли. Сами записи — на экране «Справочники».
| Вид | Название | URL | Состояние | Когда |
|---|
Справочники
Что лежит в каталоге устройства: радио, подкасты, новости, телеканалы, геопозиции. Живы ли адреса — на экране «Ссылки».
Источники
Внешние сервисы, откуда устройство берёт контент и данные. Свои таблицы — на экране «Справочники».
Релизы
Загрузить APK
| Версия | Тип | Ссылка | SHA-256 | Changelog | Добавлена |
|---|
Обновление Android по воздуху (ВОЙСА 2.0)
Пакеты из архива сборок прошивки (на хабе 185 /srv/aosp/ota-archive/<версия>/) копируются в каталог прошивок узла, затем регистрируются по ota-manifest.json. Новый пакет сначала в тестовом канале: его получают только приборы с признаком «тестовый канал». ТИШЕ пакеты не предлагаются.
| Сборка | Android | Канал | Полный | Инкрементальный | Добавлен |
|---|
Регистрация
Окно ОТК
| Hardware ID | Источник | IP первый / последний | Попытки | Первая / последняя | Статус | Действия |
|---|
Пользователи
| Пользователь | Роль | Проекты | 2FA | ИИ-консультант |
|---|
ГигаЧат
Конфигурации
| Название конфигурации | Модель | Область | Токенов на сообщение | Токенов в день | Штраф / температура | Обновлена |
|---|
| Неделя с | Активных устройств | Упёрлись в лимит | Запросов | Посчитано |
|---|
Устройства по неделям
| Неделя с | Устройство | Запросов | Лимит достигнут | Последний запрос |
|---|
Учётки РГБС
| Логин | Состояние | Устройство | Добавлена | Изменена |
|---|
Документация API
Как устройство входит на сервер и какие ручки ему доступны. Адрес сервера и режим входа — из спецификации проекта.
Как сервер отвечает
Общие правила ответов. У каждой ручки в справочнике ниже есть кнопка «Ответ» с её кодами и примером тела.
Тело успеха
Успех — это код 200 и тело JSON той ручки, которую вызвали. Исключение — выдача файла: GET /files/download отдаёт сам APK или mapping-файл потоком application/octet-stream, а не JSON. Списки идут объектом {"data": [ … ]} либо массивом верхнего уровня — смотрите пример в блоке «Ответ» конкретной ручки.
Токены
Входы отдают пару JWT в полях access_token и refresh_token, тип — token_type: "Bearer". У v2 к ним добавлено expires_in — сколько секунд живёт access (3600, то есть 60 минут). Access-токен идёт в заголовке Authorization: Bearer … для всех остальных ручек. В документации значения токенов замаскированы: наружу их не показываем.
Ошибки
У ошибок два конверта. Внешние интеграции (погода, Rutube, Castbox, Muzofond, WolframAlpha, GigaChat, YouTube) при сбое отвечают 418 {"detail":{"code","error","reason"}}, где code — это статус, с которым ответил или не ответил внешний сервис: 503 — сеть недоступна, 504 — таймаут. Чужой 403 сервер наружу не пробрасывает: для устройства 403 значит «нет заголовка Authorization», и путать эти случаи нельзя. Входы, валидация и телеметрия отвечают проще — {"detail": "текст"} или {"detail": { … }}. У ответа «Outdated» на входе v2 рядом с detail лежит server_time. ЛитРес выпадает из этих правил: он проксирует статусы 404/429/502/504 как есть, а тело ошибки кладёт в корень ответа — {"status":"failure","code","reason"}.
| Код | Что значит на этом сервере |
|---|---|
200 | Успех. Тело — JSON ручки; у /files/download — файл (application/octet-stream) |
401 | Токен неверен, просрочен, устройство заблокировано или не зарегистрировано. На входе v2 — «Outdated» (часы ушли, есть server_time) или «Invalid credentials» |
403 | Нет заголовка Authorization. Чужой 403 внешней службы сюда не попадает — он превращается в 418 |
404 | Объекта нет: пустой справочник прошивок, ненайденная запись, файл или город |
418 | Сбой внешней интеграции: {"detail":{"code","error","reason"}}, code — статус внешнего сервиса (503 сеть, 504 таймаут) |
422 | Тело или параметры не по форме |
429 | Превышен лимит частоты — по IP или по устройству. Подождать минуту |
503 | Интеграция на сервере не настроена (нет ключа или адреса): WolframAlpha, GigaChat, YouTube, погода |
Порядок в списках и пагинация
Где у ручки есть параметр order_by, список отсортирован по нему. Где его нет, порядок не гарантируется: считайте такой ответ множеством, а не последовательностью, и сортируйте на клиенте, если порядок важен. Постраничная выдача есть у каталога ЛитРес (GET /litresapi/page, параметры limit и offset) и у видео Rutube (page_number с 1). Остальные справочники отдаются целиком.
Поля, которые встречаются чаще всего
| Структура | Поля |
|---|---|
| Токены входа | access_token, refresh_token — JWT; token_type = «Bearer»; expires_in — секунды жизни access (только v2); на legacy-входе с X-Yoda-Auth: 2 ещё secret_status и, при выдаче, device_secret |
Прошивка (/firmwares/last-version) | последняя версия по version_code (числу, не строке version); поле is_test отделяет тестовый канал от боевого |
Ответ на события (/device-events) | accepted — принято, duplicates — повторы по id, rejected — массив {index, id, reason}, где reason — имя первого поля, которое не прошло проверку |
| Событие в пачке | id (UUID, ключ идемпотентности), ts (время устройства в UTC), level (debug…fatal), name вида категория.действие, необязательные session_id, seq, message, props, duration_ms |
Отчёт о падении (/crashs) | stack_trace и его stack_hash, версия сборки (app_version, app_version_code, app_build_git), hardware_id, fw_version_code |
Вход устройств v2
У каждого устройства свой секрет, выданный сервером. Вход подписывается HMAC-SHA256 с меткой времени и одноразовым nonce. Производству это ничего не стоит: APK общий для всех, секрет устройство получает по сети на ОТК или позже, в окне, открытом оператором.
Почему не общий ключ в прошивке
Сейчас все устройства подписывают вход одним Fernet-ключом, зашитым в APK. Отозвать доступ одному устройству нельзя, а ключ уже светился в истории репозитория. v2 закрывает это по пунктам:
| Ситуация | Как отвечает v2 |
|---|---|
| Отзыв одного устройства | У каждого аппарата свой секрет: оператор отзывает его, остальной парк ничего не замечает. Общий ключ так не отзовёшь — он один на всех. |
| Утечка секрета | Скомпрометирован один аппарат, а не парк. |
| Повтор перехваченного входа | Не проходит: метка времени в подписи строго растёт (сервер хранит последнюю), nonce одноразовый. |
| Пустые часы после года на складе | Перед входом клиент спрашивает GET /devices/v2/time; на рассинхрон сервер отвечает 401 Outdated с полем server_time — клиент берёт его и повторяет сразу. |
| Кража refresh-токена | Ротация: каждое обновление выдаёт новую пару, старый refresh перестаёт действовать. Повтор старого — признак компрометации: сервер отзывает всю семью токенов. |
| Производство | APK общий на весь парк; секрет приходит по сети на ОТК. |
| Хранение секрета | Только Keystore/EncryptedSharedPreferences; наружу уходит только HMAC. Оператор сверяет устройство по отпечатку sha256(secret)[0:16] в админке. |
Шаг 1. Получить секрет — один раз в жизни устройства
POST /devices/login?signature=<Fernet как сейчас> X-Yoda-Auth: 2
Ответ 200 — как раньше (access_token, refresh_token, token_type), плюс поле secret_status и, при выдаче, device_secret — 43 символа base64url, показывается один раз. Без заголовка X-Yoda-Auth ответ полностью прежний: старые прошивки ничего не замечают.
secret_status | Что значит | Что делать клиенту |
|---|---|---|
issued | Секрет выдан впервые | Сохранить в Keystore/EncryptedSharedPreferences, перейти на v2 |
reissued | Устройство перепрошили после ОТК: выдан новый секрет, старые токены отозваны | То же |
already_issued | Секрет уже выдавался, повторно не выдаётся | Если секрета нет локально — сказать голосом «нужна регистрация», повторять не чаще раза в час |
not_registered | Устройство не зарегистрировано (ответ без токенов) | Ждать регистрации, повторять раз в 10 минут |
not_allowed | Зарегистрировано, но окно выдачи не открыто | Работать по старому входу, повторять раз в час |
disabled | v2 на сервере выключен | Работать по старому входу |
На ОТК устройство подключается к заводскому Wi-Fi и делает этот же шаг: сервер узнаёт адрес завода и сам регистрирует устройство (issued). Экрана нет — клиент проговаривает результат голосом.
Шаг 2. Синхронизировать часы
GET /devices/v2/time → {"server_time": 1756141200}
Разница с локальными часами больше 60 секунд — скорректировать: после года на складе RTC пустой. Лимит — 60 запросов в минуту с адреса.
Шаг 3. Войти
POST /devices/v2/login
Content-Type: application/json
{"hardware_id": "KT-…", "timestamp": 1756141200,
"nonce": "<16–64 символа [A-Za-z0-9_-]>", "signature": "<hex>"}
signature = hex(HMAC-SHA256(key = секрет как UTF-8-строка, message = hardware_id + "|" + timestamp + "|" + nonce)). timestamp — unix-секунды по синхронизированным часам, строго больше метки предыдущего успешного входа. nonce — случайный, новый на каждый запрос.
val mac = Mac.getInstance("HmacSHA256").apply { init(SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256")) }
val signature = mac.doFinal("$hardwareId|$timestamp|$nonce".toByteArray(Charsets.UTF_8)).joinToString("") { "%02x".format(it) }
Ответ 200: {"access_token", "refresh_token", "token_type": "Bearer", "expires_in": 3600}. Access-токен — в Authorization: Bearer … для всех ручек, как и раньше.
Шаг 4. Ошибки входа
| Ответ | Причина | Действие |
|---|---|---|
401 {"detail":"Outdated","server_time":N} | Часы ушли больше чем на ±300 с | Взять server_time и повторить сразу |
401 {"detail":"Invalid credentials"} | Секрет не выдан или отозван, неверная подпись, повтор метки, блокировка или неизвестное устройство (сервер создаёт заявку на регистрацию) | Один раз попробовать шаг 1 — он честно ответит secret_status. Дальше не зацикливаться: повтор через 10 минут, потом раз в час; голосом «устройство не зарегистрировано» (защитную причину устройство не раскрывает — настоящую оператор видит в админке) |
422 | Формат полей | Ошибка клиента |
429 | Лимит: 30 в минуту с адреса, 10 в минуту на устройство | Подождать минуту |
Эти ответы можно увидеть живьём: «Проверить вход v2» ниже прогонит вход тестового устройства, а у ручки /devices/v2/time в справочнике есть «Выполнить». Ошибка формата даёт 422, лимит даёт 429, неверная подпись или устаревшие часы дают 401.
Токены и год на складе
Access живёт 60 минут, refresh — 30 дней с ротацией: POST /devices/v2/refresh {"refresh_token"} возвращает новую пару, старый refresh недействителен. Повтор старого refresh сервер считает компрометацией и отзывает всю семью — тогда снова шаги 2–3. POST /devices/v2/logout {"refresh_token"} отзывает семью (всегда 200). Перевыпуск секрета оператором или блокировка отзывают все токены: следующий запрос получит 401, дальше шаги 2–3; если секрет отозван, шаг 1 ответит reissued или issued (already_issued приходит только когда секрет уже выдан и цел).
Секрет не протухает. Устройство пролежало год на складе — refresh истёк, но хватает шага 2 (часы) и шага 3 по сохранённому секрету. От оператора ничего не нужно.
Проверить вход v2
Пульт прогонит вход на сервере с тестовым устройством: возьмёт время сервера, посчитает подпись HMAC и вызовет /devices/v2/login. Боевые устройства и ключи не трогаются, секрет не показывается. Тестового устройства нет — кнопка честно скажет, что для этого нужно.
Справочник маршрутов API устройств
Собирается из маршрутов работающего сервера проекта, поэтому не отстаёт от кода. Полная спецификация OpenAPI с примерами ответов — по тому же адресу: /admin/api/docs/openapi.json.
У каждой ручки есть кнопка «Ответ»: раскроется таблица её HTTP-кодов и пример тела (значения секретов и персональных данных в примерах замаскированы сервером). У GET-ручек чтения рядом есть кнопка «Форма»: раскроется форма с полями по параметрам ручки и строка curl, а «Выполнить» вызовет ручку на сервере и покажет ответ здесь. Публичные ручки (GET без токена) идут как есть; остальные чтения выполняются с токеном тестового устройства — сервер получает его сам, наружу не отдаёт. Входы и мутации (POST) из документации не выполняются — для них рядом пример входа. Ошибки из таблиц гида (401, 422, 429) придут тем же ответом.
| Дата и время | Кто | Что сделал | С кем / чем | Проект | IP | Детали | Результат |
|---|
Безопасность
Консультант приостановлен
Лестница защиты закрывает ИИ-консультанта, не всю учётную запись: риск 3 — сразу, риск 2 — три запроса за сутки, отказ модели — два за сутки, квота — в два разных дня за неделю. Владельца лестница не блокирует. Что делать — docs/runbooks/consult-abuse.md.
| Пользователь | Причина | С |
|---|
Сигналы защиты
| Время | Пользователь | Источник | Риск | Категории и детали |
|---|
Последние угрозы
| Время | IP | Цель | Тип | Попыток | Решение |
|---|
Устройства
Защита входа
Защита устройств
Белый список
Блокировка по IP
| Адрес | Где блокируется |
|---|
Учётная запись
Автономный режим Гаечки
В этом режиме Гаечка сама сливает MR в целевую ветку после чистых кругов ревью и зелёных сборок — Антон получает уведомление, но не подтверждает. Без режима всё как раньше: MR ждёт человека.
Двухфакторная защита
Отсканируйте в Google Authenticator
Секрет для ручного ввода
Ссылка otpauth://
Смена пароля
Сессии
| Создана | Активность | Истекает | IP | Браузер | Статус |
|---|