Создание голосового навыка для Яндекс станции без vps и регистрации домена
У меня есть домашний медиасервер, который хотелось включать и выключать голосом через Яндекс Станцию.
Задача кажется простой, но на практике быстро упирается в инфраструктуру:
- Яндекс Диалоги требуют публичный HTTPS-домен
- Покупать домен ради одной домашней задачи мне не хотелось
- Настраивать TLS, reverse proxy, проброс портов и поддержку сертификатов - тоже
- ngrok как временное решение не подходит (из РФ он недоступен)
В этой статье покажу как я решил задачу без покупки домена, без ручной настройки SSL, используя:
- OpenWRT-роутер
- Wake-on-LAN
- NestJS
- Публичный HTTPS-туннель через tunyl
В статье будут вставки фрагментов кода, проект целиком можно посмотреть в гитхабе:
Задача и ограничения
Что есть:
- Домашний медиасервер
- Роутер на OpenWRT, доступный в локальной сети
- Яндекс Станция, которая должна: включать и выключать сервер
Что важно:
- Сервер находится за NAT
- Входящие подключения извне недоступны
- Яндекс требует HTTPS + домен
- Решение должно быть максимально простым в эксплуатации
Общая схема работы
- Яндекс Станция активирует навык
- Сервер Яндекс Диалогов отправляет HTTP-запрос
- Запрос приходит на приложение, запущенное на роутере
- Приложение:либо отправляет Wake-on-LAN пакетлибо делает HTTP-запрос для выключения сервера
Как включается и выключается сервер
Включение через Wake-on-LAN
Сервер включается через отправку UDP пакета на порт 9.
Сетевая карта ловит пакет и запускает систему.
На моей плате (Atermiter X99) WOL включён по умолчанию.
Выключение через HTTP
Для выключения сервера я использую обычный HTTP-запрос к локальному API сервера.
Рассматривал вариант с SSH (shutdown / reboot), но отказался:
- Лишний доступ
- Больше рисков
- Сложнее контролировать
Серверная часть: NestJS + TypeScript
В качестве HTTP-сервера используется nodejs приложение с использованием фреймворка NestJS.
Инициализация проекта:
Начинаем с e2e-теста
Я предпочитаю TDD, поэтому первым делом описываю e2e-тест, ориентируясь на документацию Яндекс Диалогов.
Документация: https://yandex.ru/dev/dialogs/alice/doc/ru/about
Пример теста:
Контроллер
Обработчик входящих запросов:
Обработка команд
Для сложных сценариев у Яндекса есть интенты, но в моём случае достаточно анализа ключевых слов.
Wake-on-LAN и выключение
Проверяем тесты
Безопасность: ограничение по IP
Приложение доступно из интернета, значит нужна защита.
Самый простой вариант — whitelist IP-адресов Яндекса.
Важно:
- При работе через reverse proxy можно доверять x-real-ip
- При прямом доступе — нельзя, злоумышленник может подменить этот заголовок
Список IP Яндекса: https://yandex.ru/ips
Как дать Яндексу HTTPS-доступ без домена
И вот тут появляется главный инфраструктурный вопрос.
Яндекс требует:
- Доменное имя
- HTTPS
Покупать домен, настраивать TLS, поддерживать сертификаты ради домашнего навыка не хотелось.
Для решения я использовал сервис tunyl который:
- Даёт бесплатный публичный HTTPS-домен
- Проксирует запросы на локальный порт
- Не требует DNS и настройки SSL
После создания сайта сервис отображает созданный домен и команду для подключения:
Нам необходимо запомнить <TOKEN> и домен, они нам пригодятся на следующих шагах
Интеграция tunyl прямо в приложение
Чтобы не запускать два процесса (отдельно наше приложение, а отдельно tunyl прокси), я подключил туннель прямо в main.ts:
Переменные окружения
Проверка через curl
Подключение навыка в Яндекс Диалогах
- Переходим: https://dialogs.yandex.ru/developer
- Создаём навык
- В поле Webhook URL указываем:
https://mrserver.tunyl.com/api/power
Во вкладке «Тестирование» можно проверить работу навыка текстом.
Итог
В результате получилось:
- Голосовое управление сервером
- Без покупки домена
- Без настройки SSL
- Без проброса портов
- С минимальной инфраструктурой
Подход хорошо подходит для:
- Домашних сервисов
- Демо-стендов
- Тестовых интеграций
- Любых сценариев, где нужен быстрый HTTPS-доступ к локальному приложению