Веб-панель Hermes
Веб-панель — это браузерный интерфейс для управления вашей установкой Hermes Agent. Вместо правки YAML-файлов и набора команд в CLI вы настраиваете параметры, ведёте API-ключи и следите за сессиями через аккуратный веб-интерфейс.
Авторизация в hosted-режиме идёт через OAuth Nous Portal; если вы хотите, чтобы панель ещё и общалась с настоящим бэкендом, команда hermes setup --portal подключит заодно шлюз модели и инструментов. Смотрите Nous Portal.
Быстрый старт
hermes dashboard
Команда поднимает локальный веб-сервер и открывает http://127.0.0.1:9119 в браузере. Панель работает целиком на вашей машине — за пределы localhost данные не уходят.
Опции
| Флаг | По умолчанию | Описание |
|---|---|---|
--port | 9119 | Порт, на котором запускается веб-сервер |
--host | 127.0.0.1 | Адрес привязки |
--no-open | — | Не открывать браузер автоматически |
--insecure | выкл. | Устарел, ничего не делает. Раньше обходил авторизацию при не-петлевой привязке; теперь авторизацию не отключает — публичной привязке всегда нужен провайдер авторизации (пароль или OAuth) |
--isolated | выкл. | При запуске из именованного профиля (worker dashboard) поднять отдельный сервер под этот профиль, а не перенаправлять на машинную панель |
# Свой порт
hermes dashboard --port 8080
# Привязка ко всем интерфейсам (на общих сетях — с осторожностью)
hermes dashboard --host 0.0.0.0
# Запуск без открытия браузера
hermes dashboard --no-open
Управление несколькими профилями
Панель — это управляющая поверхность уровня машины: один сервер ведёт каждый профиль на машине. Переключатель профилей в боковой панели (виден, когда профилей больше одного) решает, какой профиль читают и пишут управляющие страницы — Config, API Keys, Skills, MCP, Models и вкладка Chat следуют за ним. Пока выбран профиль, отличный от собственного профиля панели, янтарный баннер называет управляемый профиль, чтобы цель записи всегда была однозначной.
Выбор хранится в URL (?profile=<name>), поэтому глубокие ссылки вроде
http://127.0.0.1:9119/skills?profile=worker открываются с уже выбранным
профилем в переключателе и переживают обновление страницы.
Запуск панели через алиас профиля перенаправляет на машинную панель, а не поднимает второй сервер:
worker dashboard
# → уже запущена: открывает браузер на ?profile=worker
# → не запущена: поднимает машинную панель с предвыбранным "worker"
Панель, запущенная таким образом, всё время работы сервера держит worker как
запасную область: глубокая ссылка без ?profile= (например, ссылка
/chat?resume=<id>) всё равно открывается под worker, так что встроенный чат видит
MCP-серверы, модель и навыки этого профиля. Явный ?profile= в URL всегда главнее.
Передайте --isolated, чтобы отказаться от этого и поднять отдельный сервер,
ограниченный этим профилем (поведение до объединения — пригодится, если вы
намеренно выставляете панели разных профилей с разной авторизацией).
Вкладка Chat тоже следует за переключателем: чат в контексте профиля
запускает свой PTY-процесс с HERMES_HOME выбранного профиля, так что диалог
идёт с моделью, навыками, памятью и историей сессий именно этого профиля.
Смена профиля начинает свежую терминальную сессию.
Действия хаба (установка, обновление и удаление навыков, установка MCP, настройка наборов
инструментов) выполняются в области секретов целевого профиля — его .env и настроенных
источников секретов, — а не в окружении процесса панели; это касается и действий над профилем
default из панели машины.
Что остаётся пер-профильным и переключатель не поглощает: процессы шлюза
(управляйте ими через hermes -p <name> gateway …), база сессий каждого
профиля и cron-планировщики (страница Cron и так агрегирует профили со своим
фильтром).
Предварительные требования
FastAPI, Uvicorn и PTY-помощник платформы входят в основные зависимости Hermes.
Extra web добавляет точные ограничения версий для HTTP-стека. Extra pty пустой,
потому что его зависимости уже в ядре. Стандартная установка через PM включает web
в составе all.
Если эти зависимости повреждены, выполните hermes pm repair и перезапустите Hermes.
Для установки из исходников используйте рабочий процесс разработчика с PM.
Дополнения для мессенджеров и голоса запрашиваются отдельно — all их не подразумевает.
Если запустить hermes dashboard без зависимостей, команда подскажет, что доустановить. Когда фронтенд ещё не собран, а npm доступен, при первом запуске сборка пройдёт автоматически.
Вкладка Chat входит в любой запуск hermes dashboard — встроенная браузерная панель чата (тот самый TUI поверх PTY/WebSocket) доступна всегда, без дополнительных флагов.
Страницы
Status
Стартовая страница показывает живой обзор установки:
- Версия агента и дата релиза
- Статус шлюза — запущен/остановлен, PID, подключённые платформы и их состояние
- Активные сессии — счётчик сессий, активных за последние 5 минут
- Недавние сессии — список из 20 последних сессий с моделью, числом сообщений, расходом токенов и превью диалога
Страница статуса обновляется сама каждые 5 секунд.
Баннер нехватки ресурсов
Когда на хосте заканчивается память или диск, вверху панели появляется баннер (данные для него берутся из того же опроса статуса — без дополнительных запросов):
- «Your agent is almost out of memory and may restart» — свободная память системы упала до уровня elevated (< 128 МиБ или < 15%) или critical (< 64 МиБ или < 5%), по данным 30-секундного heartbeat шлюза.
- «Your agent restarted unexpectedly, most likely because it ran out of memory» — журнал жизненного цикла зафиксировал нештатный выход под нагрузкой на память при предыдущей загрузке (предполагаемый OOM-kill).
- Предупреждения о диске — том, на котором лежит
~/.hermes, почти заполнен (elevated при свободном месте меньше 512 МБ, critical — меньше 256 МБ).
Одновременно показывается только самое серьёзное активное предупреждение (disk critical > memory critical > OOM restart > disk elevated > memory elevated). Отклонение баннера привязано к текущей загрузке шлюза: закрыв одно предупреждение, вы увидите следующее активное; перезапуск шлюза или эскалация (elevated → critical) открывают баннер заново, а устаревший heartbeat не рисует ничего — вместо ложной тревоги.
Chat
Вкладка Chat встраивает полноценный TUI Hermes (тот же интерфейс, что и при hermes --tui) прямо в браузер. Всё, что доступно в терминальном TUI — slash-команды, выбор модели, карточки вызова инструментов, потоковый markdown, запросы clarify/sudo/approval, оформление скинов — работает здесь точно так же, потому что панель запускает настоящий бинарь TUI и рендерит его ANSI-вывод через xterm.js с WebGL-рендерером для попиксельно ровной раскладки ячеек.
Как это устроено:
/api/ptyоткрывает WebSocket, авторизованный сессионным токеном панели- Сервер запускает
hermes --tuiза POSIX-псевдотерминалом - Нажатия клавиш уходят в PTY; ANSI-вывод стримится обратно в браузер
- WebGL-рендерер xterm.js рисует каждую ячейку по целочисленной пиксельной сетке; отслеживание мыши (SGR 1006), широкие символы (Unicode 11) и глифы псевдографики рендерятся нативно
- Изменение размера окна браузера меняет размер TUI через аддон
@xterm/addon-fit - Молчащий PTY-сокет раз в 20 секунд шлёт маленький keepalive с изменением размера, чтобы обратные прокси с таймаутами простоя (nginx, Caddy) не рвали чат, который просто ждёт; прокси, ограничивающие общее время жизни соединения (некоторые туннели закрывают сокет примерно через 30 секунд независимо от трафика), всё равно его закроют — тогда чат сам переподключится к той же сессии; автоматические переподключения ставятся на паузу, пока вкладка браузера скрыта или вы на другой странице дашборда, и возобновляются по возвращении
Возобновление существующей сессии: на вкладке Sessions нажмите иконку воспроизведения (▶) рядом с нужной сессией. Это перебросит на /chat?resume=<id> и запустит TUI с флагом --resume, подгрузив всю историю.
Переключатель сессий (правая колонка): на вкладке Chat есть собственный список диалогов в стиле ChatGPT — узкая правая колонка рядом с терминалом, так что переключать диалоги можно, не покидая страницу. Сверху колонки — выбор модели, под ним сразу список сессий; терминал занимает большую часть экрана. Список показывает ваши недавние сессии для активного профиля: заголовок (а если его нет — превью сообщения), относительное время последней активности, число сообщений и канал-источник для не-CLI-сессий. Клик по строке возобновляет её на месте (терминал перезапускается с историей этого диалога); активная сессия подсвечена. New chat начинает свежую сессию, а кнопка обновления заново подтягивает список. Колонка только переключает — удаление, переименование, экспорт и массовая чистка остались на вкладке Sessions. На узких экранах она сворачивается в выезжающую панель.
Выбор рабочего пространства: свежий чат стартует там, где запущен процесс панели, — бесполезно, если вы управляете Hermes с телефона и хотите работать в ~/code/foo. Селектор workspace в колонке перечисляет те же каталоги, что знает боковая панель десктопа: ваши явные проекты (hermes projects) и все найденные git-репозитории (из сессий плюс сканирование desktop.repo_scan_roots), сначала недавно активные, а также пункт Other path… для всего остального. Выбор запоминается для каждого профиля и применяется к следующему New chat (возобновлённая сессия сохраняет свой рабочий каталог); кнопка пересканирования заново обходит корни поиска на хосте, так что репозиторий, только что склонированный по SSH, появляется без перезапуска. Несуществующий путь отклоняется с ошибкой, а не приводит к молчаливому старту в каталоге запуска. Работает через GET /api/chat/workspaces и параметр cwd WebSocket /api/pty.
Что понадобится:
- Node.js (то же требование, что и для
hermes --tui; бандл TUI собирается при первом запуске) ptyprocess— основная зависимость на POSIX- POSIX-ядро (Linux, macOS или WSL2). Терминальной панели
/chatнужен именно POSIX-PTY — у нативного Python под Windows аналога нет, поэтому на нативной Windows-установке остальная часть панели (сессии, задачи, метрики, редактор конфигурации) работает, а вкладка/chatпокажет баннер с предложением воспользоваться WSL2.
Закройте вкладку браузера — и PTY на сервере корректно завершается. Повторное открытие запускает свежую сессию.
Чтобы направить Hermes Desktop на панель, запущенную на другой машине, вместо собственного встроенного бэкенда, смотрите раздел про удалённый бэкенд ниже.
Подключение Hermes Desktop к удалённому бэкенду
Обычно Hermes Desktop поднимает собственный локальный бэкенд, но умеет подключаться и к панели на удалённой машине (виртуалка, домашний сервер и т. п.) через Settings → Gateways → Remote gateway. Именно отсюда чаще всего растут жалобы вида «Desktop пишет, что бэкенд готов, а чат не работает»: проверка готовности в Desktop охватывает меньше, чем реально требуется живому подключению чата.
hermes dashboard
«Удалённый бэкенд», к которому подключается Desktop, и есть процесс hermes dashboard на удалённой машине — тот самый сервер, что описан на этой странице. Он должен быть поднят и доступен ещё до того, как любой из шагов ниже начнёт иметь значение; Desktop подключается к нему, но сам его не запускает. Держите процесс под systemd/tmux и т. п., чтобы он переживал выход из сессии и перезагрузки. Шлюз (Telegram/Discord/Slack и т. п.) — это отдельный долгоживущий процесс; если вы полагаетесь на каналы сообщений, запускайте его независимо — десктоп подключается не к нему.
Проба «удалённый бэкенд готов» в Desktop бьёт только в GET /api/status — публичный эндпоинт, который отвечает, как только на хосте поднята любая панель. Живое подключение чата — это отдельный WebSocket на /api/ws (и /api/pty), и этот сокет закрыт ещё двумя проверками, которых проба статуса не касается:
- Нужна авторизация. Когда панель привязана к не-петлевому адресу, она включает свой контур авторизации. Защитите её логином и паролем (встроенный провайдер логин/пароль); Desktop входит один раз и переиспользует полученную сессию для WebSocket через одноразовый тикет. Без настроенного провайдера панель на не-петлевом адресе отказывается стартовать.
- Хост привязки должен пропускать клиента и совпадать с заголовком Host. Привязка к петле (
127.0.0.1) принимает только петлевых клиентов, так что удалённая машина отсекается на уровне сокета независимо от учётных данных. Привяжитесь к не-петлевому адресу (--host 0.0.0.0), чтобы защита по peer-IP пропустила удалённого клиента. URL, который вы вводите в Desktop, должен доставать панель по тому же хосту, к которому она привязана: защита от DNS-rebinding требует совпадения заголовка Host.
Настройка удалённой панели
Задайте логин и пароль, затем запустите панель с привязкой к достижимому адресу. Для сервиса systemd:
[Service]
EnvironmentFile=%h/.hermes/.env
ExecStart=/path/to/venv/bin/python -m hermes_cli.main dashboard \
--host 0.0.0.0 --port 9119 --no-open
Restart=always
RestartSec=10
# Exit 78 (EX_CONFIG) is a deliberate refusal ("this host is already served by PID … on
# another port"); parking on it beats an infinite restart loop with nothing listening.
RestartPreventExitStatus=78
Один бэкенд обслуживает весь хост, поэтому если hermes dashboard находит живой бэкенд, через
который нельзя обслужить указанные вами --host/--port, команда отказывается с кодом выхода 78
и называет владельца. Остановите тот бэкенд или дайте сервису собственный выделенный сервер через
--isolated. Собственные loopback-бэкенды десктопного приложения владение хостом не заявляют,
так что они уживаются вместе без каких-либо флагов.
где ~/.hermes/.env содержит:
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=choose-a-strong-password
HERMES_DASHBOARD_BASIC_AUTH_SECRET=<32+ random bytes; openssl rand -base64 32>
Затем в Desktop укажите Remote URL (например, http://VM_IP:9119) и нажмите Sign in с этим логином и паролем. Полный набор параметров — в разделе провайдер логин/пароль.
С любой машины проверьте, что панель объявляет провайдер логин/пароль:
curl -s http://VM_IP:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["basic"]auth_required: trueи"basic"в списке провайдеров → вход через Sign in в Desktop сработает.auth_required: false→ привязка петлевая, либо контур не включился. Привяжитесь к не-петлевому адресу.auth_required: true, но провайдера"basic"нет → переменные окружения с логином/паролем не загружены. Сначала разберитесь с ними.
Если /api/status показывает включённый контур с провайдером "basic", а Desktop всё равно не подключается после входа, проблема уже за пределами базовой настройки — снимите свежий desktop.log (Settings → Gateways → Open logs) плюс логи панели за то же окно повторной попытки и поищите код закрытия /api/ws (4403 = чат-WS отклонён защитой запроса, например несовпадение Host/peer; 4401 = тикет WS не прошёл авторизацию).
Config
Редактор config.yaml на основе форм. Все 150+ полей конфигурации автоматически подбираются из DEFAULT_CONFIG и раскладываются по вкладкам-категориям:

- model — модель по умолчанию, провайдер, базовый URL, параметры рассуждений
- terminal — бэкенд (local/docker/ssh/modal), таймаут, настройки оболочки
- display — скин, прогресс инструментов, отображение возобновления, настройки спиннера
- agent — максимум итераций, таймаут шлюза, тариф сервиса
- delegation — лимиты субагентов, усилие рассуждений
- memory — выбор провайдера, настройки внедрения контекста
- approvals — режим подтверждения опасных команд (smart/manual/off)
- И дальше — у каждой секции config.yaml есть свои поля в форме
Поля с известным набором допустимых значений (бэкенд terminal, скин, режим подтверждений и т. п.) рисуются как выпадающие списки. Булевы — как переключатели. Всё остальное — текстовое поле ввода.
Действия:
- Save — сразу записывает изменения в
config.yaml - Reset to defaults — возвращает все поля к значениям по умолчанию (но не сохраняет, пока вы не нажмёте Save)
- Export — выгружает текущую конфигурацию в JSON
- Import — загружает JSON-файл конфигурации, заменяя текущие значения
Изменения конфигурации вступают в силу в следующей сессии агента или после перезапуска шлюза. Веб-панель правит тот же файл config.yaml, из которого читают hermes config set и шлюз.
API Keys
Управление файлом .env, где хранятся API-ключи и учётные данные. Ключи сгруппированы по категориям:
- LLM Providers — OpenRouter, Anthropic, OpenAI, DeepSeek и т. д.
- Tool API Keys — Browserbase, Firecrawl, Tavily, Keenable, ElevenLabs и т. д.
- Messaging Platforms — бот-токены Telegram, Discord, Slack и т. д.
- Agent Settings — несекретные переменные окружения вроде
API_SERVER_ENABLED
По каждому ключу видно:
- Задан ли он сейчас (с замаскированным превью значения)
- Описание, для чего он нужен
- Ссылку на страницу регистрации/получения ключа у провайдера
- Поле ввода, чтобы задать или обновить значение
- Кнопку удаления
Продвинутые и редко используемые ключи по умолчанию спрятаны под переключателем.
Sessions
Просмотр и разбор всех сессий агента. В каждой строке — заголовок сессии, иконка платформы-источника (CLI, Telegram, Discord, Slack, cron), название модели, число сообщений, число вызовов инструментов и время с последней активности. Живые сессии помечены пульсирующим бейджем.
- Search — полнотекстовый поиск по всему содержимому сообщений через FTS5. В результатах подсвечиваются фрагменты, а при разворачивании страница сама прокручивается к первому совпадению.
- Stats — сводная панель показывает общее число сессий, сколько активно в хранилище, сколько в архиве, сколько всего сообщений и разбивку по источникам.
- Expand — кликните по сессии, чтобы подгрузить всю историю сообщений. Сообщения раскрашены по роли (user, assistant, system, tool) и отрисованы как Markdown с подсветкой синтаксиса.
- Tool calls — сообщения ассистента с вызовами инструментов показывают сворачиваемые блоки с именем функции и JSON-аргументами.
- Rename — задайте или сбросьте заголовок сессии прямо на месте (иконка карандаша).
- Filter — вкладки Chats / Automation / All сужают список: Chats (по умолчанию) показывает разговоры с людьми и прячет шум автоматики (сессии cron, инструментов, API, ACP); Automation — только их; All — всё подряд. Выпадающий список источников сужает до одного канала (например, только Telegram). Поиск учитывает активный фильтр.
- Export — выгрузка сессии (метаданные + полная история сообщений) в JSON (иконка загрузки).
- Prune — кнопка «Prune old sessions» в шапке удаляет завершённые сессии старше N дней.
- Delete — удаление сессии вместе с историей сообщений по иконке корзины.

Logs
Просмотр логов агента, шлюза и ошибок с фильтрацией и живым отслеживанием хвоста.
- File — переключение между файлами логов
agent,errorsиgateway - Level — фильтр по уровню: ALL, DEBUG, INFO, WARNING или ERROR
- Component — фильтр по компоненту-источнику: all, gateway, agent, tools, cli или cron
- Lines — сколько строк показывать (50, 100, 200 или 500)
- Auto-refresh — включает живой хвост, опрашивающий новые строки лога каждые 5 секунд
- Color-coded — строки лога окрашены по серьёзности (красный для ошибок, жёлтый для предупреждений, приглушённый для отладки)
Analytics
Аналитика по использованию и стоимости, рассчитанная из истории сессий. Выберите период (7, 30 или 90 дней) и увидите:
- Сводные карточки — всего токенов (вход/выход), процент попаданий в кэш, итоговая оценочная или фактическая стоимость, общее число сессий и среднесуточное значение
- График токенов по дням — столбчатая диаграмма с накоплением по входным и выходным токенам за день; при наведении всплывает разбивка и стоимость
- Таблица разбивки по дням — дата, число сессий, входные токены, выходные токены, доля попаданий в кэш и стоимость за каждый день
- Разбивка по моделям — таблица с каждой использованной моделью, числом её сессий, расходом токенов и оценочной стоимостью
Cron
Создание и управление запланированными cron-задачами, которые выполняют промпты агента по расписанию.
- Create — задайте имя (необязательно), промпт, cron-выражение (например,
0 9 * * *) и цель доставки (local, Telegram, Discord, Slack или email) - Job list — у каждой задачи видно имя, превью промпта, выражение расписания, бейдж состояния (enabled/paused/error), цель доставки, время последнего и следующего запуска
- Pause / Resume — переключение задачи между активным и приостановленным состоянием
- Edit — открывает заранее заполненное модальное окно, чтобы поменять промпт, расписание, имя или цель доставки задачи
- Trigger now — немедленный запуск задачи вне обычного расписания
- Delete — безвозвратное удаление cron-задачи
Profiles
Создание и управление профилями — изолированными экземплярами Hermes со своей конфигурацией, навыками и сессиями.
- Карточки профилей — у каждой видно модель/провайдер, число навыков, состояние шлюза, описание и бейджи (активный, по умолчанию, алиас)
- Create — имя + необязательные clone-from-default / clone-everything / no-bundled-skills, описание и модель; отдельная страница Profile Builder (
/profiles/new) даёт полный сценарий (модель, MCP, навыки) - Manage skills & tools — переход на страницу Skills в контексте этого профиля (выставляет переключатель профиля в боковой панели)
- Set as active — переключает «липкий» дефолт, который подхватят будущие запуски CLI/шлюза (то же, что
hermes profile use). Это не меняет то, чем управляет панель — за это отвечает переключатель профиля - Edit model / description / SOUL — встроенные редакторы, пишущие в этот профиль
- Rename / Delete — только для именованных профилей
Skills
Просмотр, поиск и переключение установленных навыков и наборов инструментов, а также установка новых из хаба. Навыки загружаются из ~/.hermes/skills/ и сгруппированы по категориям.
- Search — фильтрация установленных навыков и наборов инструментов по имени, описанию или категории
- Category filter — нажимайте «пилюли» категорий, чтобы сузить список (например, MLOps, MCP, Red Teaming, AI)
- Toggle — включение или отключение отдельных навыков переключателем. Изменения вступают в силу в следующей сессии.
- Toolsets — отдельный вид показывает встроенные наборы инструментов (операции с файлами, веб-браузинг и т. д.) с их статусом активности, требованиями к настройке и списком входящих инструментов
- Browse hub — третий вид ищет по хабу навыков сразу во всех источниках (то же, что
hermes skills search), ставит любой результат по идентификатору с живым логом установки и предлагает кнопку «Update all» для обновления установленных навыков.

MCP
Управление MCP-серверами без CLI. Тот же блок mcp_servers в config.yaml, из которого читает hermes mcp.
Ваши MCP-серверы:
- Add — зарегистрировать HTTP/SSE-сервер (URL) или stdio-сервер (команда + аргументы), с необязательными переменными окружения
KEY=VALUEдля stdio-серверов - Enable / disable — включить или выключить сервер, не удаляя его. Выключенный сервер остаётся в конфигурации, чтобы потом его можно было снова включить. Вступает в силу при следующем перезапуске шлюза.
- Test — подключиться к серверу, перечислить его инструменты и отключиться — проверка соединения до того, как агент начнёт от него зависеть
- Remove — удалить сервер из конфигурации
- Секретные по виду значения переменных окружения в списке маскируются
Catalog: просмотр одобренных Nous MCP-серверов (встроенный каталог optional-mcps/) с установкой любого из них в один клик. Записи, которым нужны API-ключи, запрашивают их прямо на месте; значения уходят в .env. Это тот же каталог, что используют hermes mcp catalog / hermes mcp install.

Webhooks
Управление динамическими подписками на вебхуки. Сначала платформа вебхуков должна быть включена в настройках мессенджеров; если это не так, страница показывает подсказку.
- Create — имя, описание, фильтр событий, цель доставки, необязательный режим прямой доставки и промпт для агента. При создании страница показывает URL маршрута и одноразовый HMAC-секрет, который нужно скопировать.
- Enable / disable — включить или выключить подписку. Выключенные маршруты остаются в файле подписок, но шлюз отклоняет приходящие по ним события (403). Шлюз перечитывает файл на лету, так что изменение вступает в силу на следующем событии — перезапуск не нужен.
- List — у каждой подписки видно её URL, события и цель доставки
- Delete — удалить подписку

Pairing
Одобрение и отзыв пользователей мессенджеров без CLI — так удалённый администратор подключает пользователей Telegram/Discord и т. п. к привязанному шлюзу. Полный паритет с hermes pairing.
- Pending requests — у каждого видно платформу, код, пользователя и возраст запроса, с кнопкой Approve
- Approved users — у каждого видно платформу и пользователя, с кнопкой Revoke
- Clear pending — сбросить все непогашенные коды привязки

Channels
Подключение Hermes к любой платформе сообщений прямо из браузера — полный паритет с hermes setup gateway. Страница перечисляет все поддерживаемые каналы (Telegram, Discord, Slack, Matrix, Mattermost, WhatsApp, Signal, BlueBubbles/iMessage, Email, SMS/Twilio, DingTalk, Feishu/Lark, WeCom, WeChat, QQ Bot, Yuanbao, а также API-сервер и эндпоинты вебхуков) с их живым статусом подключения.
- Configure — открывает форму под конкретную платформу ровно с теми полями, что нужны этому каналу (бот-токен, app-токен, URL сервера, список разрешений и т. п.). Секреты рисуются как поля пароля и хранятся замаскированными; пустое поле сохраняет прежнее значение. Обязательные поля помечены и проверяются. Ссылка «Setup guide» ведёт к документации по учётным данным платформы.
- Enable / disable — включить или выключить канал. Учётные данные остаются на диске; меняется только активное состояние.
- Test — проверить, настроен ли канал, включён ли он и сообщает ли шлюз о живом подключении.
- Restart gateway — учётные данные пишутся в
~/.hermes/.env, а флаг включения — вconfig.yaml; шлюз подключает каждый включённый канал при следующем перезапуске, который можно запустить прямо со страницы. На установке системного уровня (hermes gateway install --system) дашборд выполняет перезапуск черезsudo -n, поэтому пользователю дашборда нужен sudo без пароля; без него запрос сразу завершится ошибкой, вместо того чтобы сообщить о перезапуске, который CLI затем отклонит.

System
Сводная панель администрирования для операций уровня всей установки:
- Host — живая системная статистика: ОС / ядро, архитектура, имя хоста, версии Python и Hermes, число ядер CPU и загрузка, память, использование диска под домашним каталогом Hermes, аптайм и средняя загрузка. (CPU/память/диск берутся из
psutil, когда он установлен; поля идентификации показываются всегда.) Версия Hermes сопровождается бейджем статуса обновления (актуально / отстаёт на N коммитов) и кнопкой Check for updates. Когда обновление доступно для установки через git, кнопка Update now открывает диалог подтверждения — с числом коммитов, которые подтянутся, — прежде чем запуститьhermes updateв фоне. На установках через Docker/Nix панель не может применить обновление на месте, поэтому вместо этого показывает правильную внешнюю команду. - Nous Portal — статус входа, активный провайдер инференса и таблица маршрутизации Tool Gateway (какие инструменты идут через Portal, а какие — локально), со ссылкой на управление подпиской. Доступный только для чтения зеркальный вид
hermes portal. - Skill curator — статус фонового обслуживания навыков (активен / приостановлен, интервал, последний запуск) с pause/resume и кнопкой запуска прямо сейчас. Зеркалит
hermes curator. - Gateway — запуск, остановка и перезапуск шлюза сообщений с живым статусом (запущен/остановлен, PID, состояние)
- Memory — выбор внешнего провайдера памяти (или только встроенного) и сброс встроенных хранилищ
MEMORY.md/USER.md - Credential pool — добавление и удаление ротируемых API-ключей, которые агент перебирает по кругу (для каждого провайдера). Ключи в списке маскируются; необработанное значение доходит только до агента.
- Operations — запуск
doctor, аудита безопасности, создание бэкапа, восстановление из архива, обновление навыков, показ разбивки размера системного промпта, генерация дампа поддержки или миграция конфигурации под устаревшие настройки. Каждое действие порождает фоновую задачу, чей живой лог стримится на страницу. - Checkpoints — посмотреть размер теневого хранилища
/rollbackи очистить его - Shell hooks — список настроенных хуков с их статусом согласия и исполняемости, создание хука (событие, команда, matcher, таймаут, с явным предоставлением согласия) и удаление. Хуки запускают произвольные команды, поэтому форма создания несёт предупреждение о безопасности, а сам хук срабатывает только после выданного согласия.



Создание shell-хука (обратите внимание на чекбокс согласия и предупреждение о запуске произвольных команд):

Веб-панель читает и пишет ваш файл .env, где лежат API-ключи и секреты. По умолчанию она привязана к 127.0.0.1 — доступ только с вашей локальной машины, без входа. Привязка к любому не-петлевому адресу (включая 0.0.0.0) включает контур авторизации: сервер откажется стартовать, пока не настроен провайдер авторизации — логин с паролем или OAuth.
Slash-команда /reload
Тот же PR с панелью добавляет в интерактивный CLI slash-команду /reload. Поменяв API-ключи через веб-панель (или правкой .env напрямую), вызовите /reload в активной CLI-сессии, чтобы подхватить изменения без перезапуска:
You → /reload
Reloaded .env (3 var(s) updated)
Команда перечитывает ~/.hermes/.env в окружение работающего процесса. Удобно, когда вы добавили ключ нового провайдера через панель и хотите тут же им воспользоваться.
REST API
Веб-панель отдаёт REST API, которое потребляет фронтенд. Эти эндпоинты можно дёргать и напрямую — для автоматизации:
Семейства управляющих эндпоинтов — /api/config, /api/env, /api/skills,
/api/tools/toolsets, /api/mcp,
/api/model/{info,options,auxiliary,set,recommended-default},
/api/cron/{delivery-targets,blueprints}, /api/audio/voice-config,
/api/ops/debug-share, /api/learning/{graph,node} и /api/dashboard/plugins/hub —
принимают необязательный query-параметр ?profile=<name> (или "profile" в
теле JSON для записи), который ограничивает чтение/запись HERMES_HOME этого
профиля. Опущен — берётся собственный профиль панели. Неизвестные имена
профилей возвращают 404. WebSocket /api/pty принимает тот же параметр,
чтобы запустить чат под выбранным профилем.
GET /api/status
Возвращает версию агента, статус шлюза, состояния платформ и число активных сессий.
В ответе также есть два справочных блока про ресурсы (на итоговый вердикт
components/overall они никогда не влияют):
memory— сводка из 30-секундного heartbeat шлюза и журнала жизненного цикла. Поля:pressure(ok/elevated/critical/unknown),gateway_rss_mb,system_total_mb,system_available_mb,swap_used_mb,sampled_at,boot_id,last_boot_unclean,last_boot_suspected_oom.pressureстановитсяelevatedпри свободной системной памяти ниже 128 МиБ (или 15%) иcritical— ниже 64 МиБ (или 5%); это те же пороги, при которых последующий нештатный выход пометят как предполагаемый OOM-kill. Heartbeat старше 150 секунд (или с меткой времени из будущего) сохраняет числа, но понижаетpressureдоunknown— чтобы последний замер мёртвого шлюза не выдавал себя за живые данные.disk— живой замерshutil.disk_usage()тома, на котором лежит~/.hermes. Поля:pressure,free_mb,total_mb,used_percent,sampled_at.pressureстановитсяelevatedпри свободном месте ниже 512 МБ (или ≥85% занято при запасе меньше 4 ГБ) иcritical— ниже 256 МБ (или ≥95% занято при запасе меньше 1 ГБ).
Оба сборщика данных fail-safe: любая ошибка замера понижает блок до
{"pressure": "unknown"}, а не роняет сам эндпоинт статуса. Числа
намеренно грубые (целые МБ, целые проценты), поскольку /api/status публичен.
GET /api/chat/workspaces
Каталоги, в которых может стартовать свежая сессия вкладки Chat: проекты профиля (с папками) и найденные git-репозитории (root, label, sessions, last_active), плюс default_cwd (куда попадает чат, если ничего не выбрано) и home. ?scan=1 сначала пересканирует desktop.repo_scan_roots на хосте. Используйте вместе с /api/pty?cwd=<path>, который при отсутствующем каталоге отказывает (fail closed).
GET /api/sessions
Возвращает 20 последних сессий с метаданными (модель, счётчики токенов, метки времени, превью).
GET /api/config
Возвращает текущее содержимое config.yaml в виде JSON.
GET /api/config/defaults
Возвращает значения конфигурации по умолчанию.
GET /api/config/schema
Возвращает схему с описанием каждого поля конфигурации — тип, описание, категория и варианты выбора, где применимо. Фронтенд по ней рисует правильный виджет ввода для каждого поля.
PUT /api/config
Сохраняет новую конфигурацию. Тело: {"config": {...}}.
GET /api/env
Возвращает все известные переменные окружения с их статусом задано/не задано, замаскированными значениями, описаниями и категориями.
PUT /api/env
Задаёт переменную окружения. Тело: {"key": "VAR_NAME", "value": "secret"}.
DELETE /api/env
Удаляет переменную окружения. Тело: {"key": "VAR_NAME"}.
GET /api/sessions/{session_id}
Возвращает метаданные одной сессии.
GET /api/sessions/{session_id}/messages
Возвращает ограниченную страницу истории сообщений, включая вызовы инструментов и метки времени. По умолчанию отдаёт последние 500 сообщений в хронологическом порядке. Для явной постраничной навигации используйте limit (максимум 500), offset и order=oldest|latest.
GET /api/sessions/search
Полнотекстовый поиск по содержимому сообщений. Параметр запроса: q. Возвращает ID совпавших сессий с подсвеченными фрагментами.
DELETE /api/sessions/{session_id}
Удаляет сессию вместе с историей сообщений. Возвращает 409 Conflict, если у сессии есть активная аренда хода или блокировка сжатия.
GET /api/logs
Возвращает строки лога. Параметры запроса: file (agent/errors/gateway), lines (количество), level, component.
GET /api/analytics/usage
Возвращает аналитику по токенам, стоимости и сессиям. Параметр запроса: days (по умолчанию 30). Ответ включает разбивку по дням и агрегаты по моделям.
GET /api/cron/jobs
Возвращает все настроенные cron-задачи с их состоянием, расписанием и историей запусков.
POST /api/cron/jobs
Создаёт новую cron-задачу. Тело: {"prompt": "...", "schedule": "0 9 * * *", "name": "...", "deliver": "local"}.
POST /api/cron/jobs/{job_id}/pause
Приостанавливает cron-задачу.
POST /api/cron/jobs/{job_id}/resume
Возобновляет приостановленную cron-задачу.
POST /api/cron/jobs/{job_id}/trigger
Немедленно запускает cron-задачу вне расписания.
DELETE /api/cron/jobs/{job_id}
Удаляет cron-задачу.
GET /api/skills
Возвращает все навыки с их именем, описанием, категорией и статусом включения.
PUT /api/skills/toggle
Включает или отключает навык. Тело: {"name": "skill-name", "enabled": true}.
GET /api/tools/toolsets
Возвращает все наборы инструментов с их меткой, описанием, списком инструментов и статусом active/configured.
Эндпоинты администрирования
На них держатся страницы MCP, Channels, Webhooks, Pairing и System. Все находятся за тем же контуром авторизации, что и остальная часть /api/.
| Метод и путь | Назначение |
|---|---|
GET /api/mcp/servers | Список настроенных MCP-серверов (значения env замаскированы) |
POST /api/mcp/servers | Добавить сервер. Тело: {name, url?, command?, args?, env?, auth?} |
POST /api/mcp/servers/{name}/test | Подключиться, перечислить инструменты, отключиться |
PUT /api/mcp/servers/{name}/enabled | Включить / выключить сервер |
DELETE /api/mcp/servers/{name} | Удалить сервер |
GET /api/mcp/catalog | Просмотр одобренного Nous каталога MCP |
POST /api/mcp/catalog/install | Установить запись каталога (с нужными env) |
GET /api/messaging/platforms | Список всех каналов сообщений со статусом и полями настройки под платформу |
PUT /api/messaging/platforms/{id} | Настроить канал. Тело: {enabled?, env?, clear_env?} (env пишется в .env, enabled — в config.yaml) |
POST /api/messaging/platforms/{id}/test | Сообщить, настроен ли канал, включён и подключён |
GET /api/pairing | Список ожидающих и одобренных пользователей мессенджеров |
POST /api/pairing/approve | Одобрить код. Тело: {platform, code} |
POST /api/pairing/revoke | Отозвать пользователя. Тело: {platform, user_id} |
POST /api/pairing/clear-pending | Сбросить все ожидающие коды |
GET /api/webhooks | Список подписок и статус включения платформы |
POST /api/webhooks | Создать подписку (возвращает одноразовый секрет) |
DELETE /api/webhooks/{name} | Удалить подписку |
GET /api/credentials/pool | Список ротируемых ключей в пуле (замаскированы) |
POST /api/credentials/pool | Добавить ключ. Тело: {provider, api_key, label?} |
DELETE /api/credentials/pool/{provider}/{index} | Удалить ключ (индекс с 1) |
GET /api/memory | Активный провайдер + доступные провайдеры + размеры встроенных файлов |
PUT /api/memory/provider | Выбрать провайдер (пусто = только встроенный) |
POST /api/memory/reset | Сброс встроенной памяти. Тело: {target: all|memory|user} |
POST /api/gateway/start · /stop · /restart | Жизненный цикл шлюза (в фоне) |
POST /api/ops/doctor · /security-audit · /backup · /import | Диагностика и обслуживание (в фоне; хвост через /api/actions/{name}/status) |
GET /api/ops/hooks | Настроенные shell-хуки + статус списка разрешений |
GET /api/ops/checkpoints · POST .../prune | Осмотр / очистка хранилища /rollback |
POST /api/ops/hooks · DELETE /api/ops/hooks | Создать / удалить shell-хук (по согласию) |
GET /api/system/stats | Статистика хоста — ОС, CPU, память, диск, аптайм |
GET /api/hermes/update/check | Сообщить о доступности обновления (отставание в коммитах, способ установки) без применения. Для установок через git, которые отстают, дополнительно возвращает список commits (sha, summary, author, at) с тем, что изменилось. ?force=1 сбрасывает 24-часовой кэш (проверка идёт через GitHub API, а не git fetch) |
GET /api/curator · PUT .../paused · POST .../run | Статус куратора навыков + pause/resume + запуск |
GET /api/portal | Авторизация Nous Portal + маршрутизация Tool Gateway (только чтение) |
POST /api/ops/prompt-size · /dump · /config-migrate | Диагностика (в фоне) |
PUT /api/webhooks/{name}/enabled | Включить / выключить маршрут вебхука |
POST /api/skills/hub/install · /uninstall · /update | Действия с хабом навыков (в фоне) |
GET /api/skills/hub/search | Поиск по хабу навыков во всех источниках |
GET /api/sessions/stats | Статистика хранилища сессий |
PATCH /api/sessions/{id} | Переименовать / заархивировать сессию |
GET /api/sessions/{id}/export | Экспорт сессии (метаданные + сообщения) в JSON |
POST /api/sessions/prune | Удалить завершённые сессии старше N дней |
PUT /api/cron/jobs/{id} | Редактировать промпт / расписание / имя / доставку cron-задачи |
Авторизация (gated-режим)
Когда панель привязана к публичному или не-петлевому адресу — к любому, кроме 127.0.0.1 / localhost, — Hermes Agent включает контур авторизации. Каждый запрос должен нести проверенную сессионную куку, иначе его отбрасывает на страницу входа. В комплекте идут три провайдера:
- Логин/пароль — самый простой способ навесить авторизацию на self-hosted / on-prem / homelab-панель. Без внешнего провайдера идентичности. Используйте только в доверенной сети или за VPN — не для публичного интернета.
- OAuth (Nous Portal) — для hosted-развёртываний и любой панели, доступной из публичного интернета; рекомендуемый путь для удалённого подключения Hermes Desktop. Каждый вход проверяется против вашего аккаунта Nous, поэтому именно этот провайдер пригоден для выхода в интернет.
- Self-hosted OIDC — чтобы подключить собственный провайдер идентичности через стандартный OpenID Connect (Keycloak, Auth0, Okta, Google, GitHub через OIDC-мост и т. д.). Nous Portal тут не участвует; подходит для выхода в интернет, если впереди стоит совместимый OIDC-сервер.
Операторские панели, привязанные к петле, всё это не затрагивает — ни авторизации, ни страницы входа.
Когда включается контур
| Флаги | Контур авторизации | Сценарий |
|---|---|---|
hermes dashboard (по умолчанию — привязка к 127.0.0.1) | ВЫКЛ. | Локальная разработка |
hermes dashboard --host 0.0.0.0 | ВКЛ. | Удалённый / прод — защитите провайдером логин/пароль или OAuth |
Контур включён тогда и только тогда, когда хост привязки — не 127.0.0.1, ::1 и не localhost. Привязка к 0.0.0.0 (и к любому адресу RFC1918 или локальной сети) контур включает. Устаревший флаг --insecure его больше не отключает — он принимается ради обратной совместимости, но игнорируется, с предупреждением.
--insecure ничего не делает — авторизацию он не отключает
После усиления защиты в июне 2026 --insecure больше не обходит авторизацию панели: не-петлевой привязке всегда нужен провайдер авторизации (логин с паролем или OAuth). Нужна панель без авторизации — привязывайтесь к 127.0.0.1 и ходите через SSH-туннель или Tailscale.
Семантика fail-closed
Если контур должен бы включиться, но ни один DashboardAuthProvider не зарегистрирован (нет плагина Nous, нет своего плагина), hermes dashboard отказывается привязываться с явным сообщением об ошибке. Здесь нет запасного варианта «default-deny, но пропускаем всё» — неправильно настроенная gated-панель просто не стартует.
Если вы запускаете hermes dashboard --host 0.0.0.0 интерактивно (в настоящем терминале), а провайдер ещё не настроен, Hermes не просто падает — он предлагает настроить провайдера на месте: выбираете логин и пароль (в config.yaml пишется dashboard.basic_auth, и панель работает через считаные секунды) либо OAuth (вас отправят к hermes dashboard register). Неинтерактивные вызовы — Docker/s6, CI, запуск через конвейер — запрос пропускают и упираются в описанную выше fail-closed-ошибку, так что автоматический деплой без авторизации по-прежнему не стартует.
Провайдер по умолчанию: Nous Research
Встроенный плагин plugins/dashboard_auth/nous установлен всегда и загружается автоматически. Он сам регистрирует DashboardAuthProvider с именем nous, как только настроен client ID.
Поскольку каждый вход проверяется против Nous Portal и защищён вашим аккаунтом Nous, именно провайдер Nous пригоден для выставления панели в публичный интернет.
Регистрация панели
Чтобы использовать провайдер Nous, нужен OAuth client ID (вид agent:{id}). Получить его можно двумя путями:
-
CLI —
hermes dashboard register. Запустите на хосте, где живёт панель. Команда разрешает ваш существующий вход в Nous (сначала выполнитеhermes setup, если вы не вошли), регистрирует self-hosted OAuth-клиента в Portal и записываетHERMES_DASHBOARD_OAUTH_CLIENT_IDв~/.hermes/.envза вас. Необязательные флаги:--name(читаемая метка, иначе сгенерируется автоматически) и--redirect-uri(публичный HTTPS callback URL для хоста, смотрящего в интернет).hermes dashboard register # ✓ Registered dashboard "swift_falcon" # …writes HERMES_DASHBOARD_OAUTH_CLIENT_ID to ~/.hermes/.env -
GUI — страница Local Dashboards. Откройте
/local-dashboardsв Nous Portal, чтобы регистрировать, именовать, вести и отзывать self-hosted панели из браузера. Скопируйте полученный client IDagent:{id}вHERMES_DASHBOARD_OAUTH_CLIENT_ID(env) илиdashboard.oauth.client_id(config.yaml). Здесь же отзывается панель, зарегистрированная через CLI.
Конфигурация
Плагин читает из двух источников, причём переменная окружения побеждает, когда задана непустой:
config.yaml — канонический источник:
dashboard:
oauth:
client_id: agent:01HXYZ… # required to engage the gate
token_leeway: 60 # optional; seconds of clock-skew tolerance
Переменные окружения — операторские переопределения:
| Переменная env | Переопределяет | Формат | Кем выдаётся |
|---|---|---|---|
HERMES_DASHBOARD_OAUTH_CLIENT_ID | dashboard.oauth.client_id | agent:{instance_id} | hermes dashboard register |
По соглашению Hermes Agent (~/.hermes/.env — только для API-ключей и секретов) рекомендуемое место для этих значений — config.yaml: для локальной разработки, on-prem и любого развёртывания, которым вы управляете напрямую. Путь через переменную окружения существует, чтобы инъекция секретов хостинг-платформы могла подсовывать client_id для каждого деплоя, не правя config.yaml внутри образа — в этом его главное назначение.
Пустые значения env считаются незаданными, так что выданный-но-незаполненный секрет платформы не сможет случайно перекрыть валидную запись в config.yaml.
dashboard.oauth.token_leeway (только config.yaml) — допуск на расхождение часов, в секундах: он применяется к клеймам exp/nbf/iat токена доступа Portal при проверке JWT. По умолчанию 60 (RFC 7519 §4.1.4–4.1.6), так что хост, чьи часы отстают от часов Portal, не проваливает вход; 0 возвращает строгую проверку, а недопустимое значение (не разбирается, отрицательное, не конечное число) безопасно сводится к 0. У собственного OIDC-провайдера есть такая же настройка: dashboard.oauth.self_hosted.id_token_leeway.
Если ни один источник не даёт client_id, плагин сообщает конкретную причину, а fail-closed-ошибка привязки панели подсказывает, что именно поправить:
Refusing to bind dashboard to 0.0.0.0 — the auth gate engages on
non-loopback binds, but no auth providers are registered.
Bundled providers reported these issues:
• nous: HERMES_DASHBOARD_OAUTH_CLIENT_ID is not set (and
dashboard.oauth.client_id in config.yaml is empty). …
Configure an auth provider before exposing the dashboard:
• Password: set dashboard.basic_auth.username + password_hash in config.yaml
• OAuth: run `hermes dashboard register` (Nous Portal) or install a
DashboardAuthProvider plugin.
There is no unauthenticated public-bind option — to keep it local, bind
127.0.0.1 and tunnel in (SSH / Tailscale).
Разобранный пример: Nous Research
От залогиненной установки Hermes до панели за контуром Nous — в три шага.
1. Войдите и зарегистрируйте панель. hermes dashboard register берёт ваш текущий вход в Nous, выдаёт OAuth-клиента и записывает HERMES_DASHBOARD_OAUTH_CLIENT_ID в ~/.hermes/.env за вас:
hermes setup # если вы ещё не вошли в Nous Portal
hermes dashboard register
# ✓ Registered dashboard "swift_falcon"
# …writes HERMES_DASHBOARD_OAUTH_CLIENT_ID to ~/.hermes/.env
2. Запустите панель на достижимом адресе. Не-петлевая привязка включает контур OAuth, а только что записанный client_id активирует провайдер nous:
hermes dashboard --host 0.0.0.0 --port 9119 --no-open
3. Войдите. Откройте http://<host>:9119/ — вас перебросит на /login. Нажмите Sign in with Nous Research → пройдите авторизацию в Portal → вернётесь на авторизованную панель. Проверьте контур с любой машины:
curl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["nous"]
GET /api/auth/me после этого вернёт проверенную сессию (provider: nous). Для хоста, смотрящего в интернет, регистрируйтесь с --redirect-uri https://hermes.example.com/auth/callback и задайте HERMES_DASHBOARD_PUBLIC_URL, чтобы OAuth-callback разрешался в ваш публичный URL (смотрите Переопределение публичного URL).
Провайдер логин/пароль (без OAuth IDP)
Если возиться с OAuth-провайдером идентичности не хочется — это self-hosted-развёртывание из серии «просто поставь пароль на мою панель», — встроенный плагин plugins/dashboard_auth/basic регистрирует DashboardAuthProvider с именем basic, который авторизует по логину и паролю вместо OAuth-редиректа.
Он встраивается в тот же контур, что и OAuth-провайдер: контур включается на не-петлевой привязке, страница входа рисует форму учётных данных для этого провайдера (вместо кнопки «Log in with X»), а всё, что идёт после входа — сессионные куки, прозрачное обновление, тикеты WS, выход, журнал аудита — идентично OAuth-пути. Сессии — это самодостаточные HMAC-подписанные токены, которые провайдер чеканит сам, так что ни базы данных, ни внешнего IDP не требуется. Хэширование пароля — через scrypt из стандартной библиотеки (никаких сторонних зависимостей).
Провайдер логин/пароль рассчитан на self-hosted / on-prem / homelab-панели в доверенной сети или доступные только через VPN. За ним стоит одна общая учётная запись без внешнего провайдера идентичности, MFA и отдельных пользователей, поэтому он не годится для прямого выставления панели в публичный интернет. Для панели, смотрящей в интернет, берите провайдер Nous Research (или собственный self-hosted OIDC / кастомный OAuth).
Конфигурация
Как и провайдер Nous, он читает из config.yaml (канонически), а переменные окружения побеждают, когда заданы непустыми. Активируется он только при заданном username плюс либо password_hash (предпочтительно), либо password — иначе это no-op, так что OAuth-пользователей и операторов на петле он не трогает.
config.yaml:
dashboard:
basic_auth:
username: admin
# Preferred — no plaintext at rest. Compute with:
# python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('PW'))"
password_hash: "scrypt$16384$8$1$…$…"
# ...or a plaintext password (hashed in-memory at load; less safe at rest):
# password: "s3cret"
secret: "<32+ random bytes, base64 or hex>" # token-signing key
session_ttl_seconds: 43200 # optional; access-token lifetime (default 12h)
Переопределения через окружение:
| Переменная env | Переопределяет | Примечания |
|---|---|---|
HERMES_DASHBOARD_BASIC_AUTH_USERNAME | dashboard.basic_auth.username | обязательна для активации |
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH | dashboard.basic_auth.password_hash | предпочтительно (без открытого пароля на диске) |
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD | dashboard.basic_auth.password | открытый текст; побеждает password_hash из конфигурации, чтобы можно было ротировать через env |
HERMES_DASHBOARD_BASIC_AUTH_SECRET | dashboard.basic_auth.secret | ключ подписи токенов |
HERMES_DASHBOARD_BASIC_AUTH_TTL_SECONDS | dashboard.basic_auth.session_ttl_seconds | время жизни access-токена |
secret для стабильных сессий
Когда secret пуст, на каждый процесс генерируется случайный ключ подписи. Для одиночного процесса это нормально, но при перезапуске каждая сессия инвалидируется, и сессии не работают между несколькими воркерами. Задайте явный secret для развёртываний, переживающих перезапуск или работающих в несколько воркеров.
Эндпоинт /auth/password-login ограничен по частоте на IP клиента (по умолчанию 10 попыток в минуту → HTTP 429) и для неизвестных пользователей и неверных паролей возвращает одну и ту же общую ошибку 401 Invalid credentials, так что его нельзя использовать как оракул для перебора имён.
Разобранный пример: логин/пароль
От нуля до панели за паролем в доверенной сети — в три шага.
1. Задайте учётные данные в ~/.hermes/.env. Захэшируйте пароль, чтобы открытого текста на диске не осталось, и задайте стабильный секрет подписи, чтобы сессии переживали перезапуски:
# Вычислите scrypt-хэш выбранного пароля:
HASH=$(python -c "from plugins.dashboard_auth.basic import hash_password; print(hash_password('choose-a-strong-password'))")
cat >> ~/.hermes/.env <<EOF
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH=$HASH
HERMES_DASHBOARD_BASIC_AUTH_SECRET=$(openssl rand -base64 32)
EOF
chmod 600 ~/.hermes/.env
2. Запустите панель на достижимом адресе. Не-петлевая привязка включает контур, а логин + хэш активируют провайдер basic:
hermes dashboard --host 0.0.0.0 --port 9119 --no-open
3. Войдите. Откройте http://<host>:9119/ — вас перебросит на /login с формой учётных данных (а не кнопкой «Sign in with X»). Введите admin / ваш пароль → попадёте на авторизованную панель. Проверьте контур с любой машины:
curl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["basic"]
GET /api/auth/me затем вернёт проверенную сессию (provider: basic). Держите это за VPN — смотрите предупреждение выше; для публичного хоста берите вместо этого провайдер Nous Research или self-hosted OIDC.
Как написать свой провайдер пароля
basic — лишь одна реализация точки расширения. Зарегистрировать провайдер пароля может любой плагин: выставьте supports_password = True в своём подклассе DashboardAuthProvider и реализуйте complete_password_login(*, username, password) -> Session (бросайте InvalidCredentialsError при отказе, ProviderError, если ваше хранилище недоступно). Методы OAuth start_login / complete_login для чисто парольного провайдера можно оставить заглушками NotImplementedError. Это путь для LDAP-bind, базы учётных данных или любой другой не-редиректной схемы авторизации — форму, маршрут, куки и обновление фреймворк берёт на себя.
Self-hosted OIDC-провайдер
Если у вас свой провайдер идентичности, встроенный плагин plugins/dashboard_auth/self_hosted авторизует панель против него по стандартному OpenID Connect — без кода под конкретный IDP и без участия Nous Portal. Он проверен на совместимость и работает с любым соответствующим стандарту OIDC-сервером:
Authentik · Keycloak · Zitadel · Authelia · Auth0 · Okta · Google · …
Как и провайдер Nous, он загружается автоматически и регистрирует себя только после настройки, так что для панелей на петле это no-op.
Конфигурация
Настройте issuer и client_id (публичный PKCE-клиент — без client secret). Плагин подтягивает authorization_endpoint, token_endpoint и jwks_uri вашего IDP из {issuer}/.well-known/openid-configuration, так что URL эндпоинтов вы нигде не прописываете руками.
config.yaml — канонический источник:
dashboard:
oauth:
provider: self-hosted
self_hosted:
issuer: https://auth.example.com/application/o/hermes/ # required
client_id: hermes-dashboard # required
scopes: "openid profile email" # optional (this is the default)
id_token_leeway: 60 # optional; seconds of clock-skew tolerance
Переменные окружения — операторские переопределения (env побеждает config.yaml, когда задана непустой; пустое значение считается незаданным):
| Переменная env | Переопределяет | Примечания |
|---|---|---|
HERMES_DASHBOARD_OIDC_ISSUER | dashboard.oauth.self_hosted.issuer | URL издателя OIDC — обязателен |
HERMES_DASHBOARD_OIDC_CLIENT_ID | dashboard.oauth.self_hosted.client_id | Публичный client id — обязателен |
HERMES_DASHBOARD_OIDC_SCOPES | dashboard.oauth.self_hosted.scopes | По умолчанию openid profile email |
В своём IDP зарегистрируйте публичное приложение/клиента с грантом authorization-code + PKCE (S256) и добавьте callback панели в число разрешённых redirect URI. Callback — это <публичный URL панели>/auth/callback (как панель выводит свой публичный URL за прокси, смотрите Переопределение публичного URL).
Что он проверяет
Провайдер проверяет ID-токен OpenID Connect (RS256/ES256) против обнаруженного jwks_uri, с клеймами iss и aud, привязанными к вашим настроенным issuer и client_id. Клеймы времени (exp/nbf/iat) проверяются с допуском на расхождение часов в 60 секунд (RFC 7519 §4.1.4–4.1.6), так что хост панели, чьи часы отстают от часов IDP, не проваливает вход; настраивается через dashboard.oauth.self_hosted.id_token_leeway в config.yaml (любое значение меньше 60 ужесточает допуск, 0 возвращает строгую проверку, недопустимое значение безопасно сводится к 0). Стандартные клеймы OIDC ложатся на сессию панели так:
| Поле сессии | Клейм(ы) |
|---|---|
user_id | sub (обязателен) |
email | email |
display_name | name → preferred_username → nickname → email |
org_id | org_id / organization, иначе склеенные groups |
Идентичность устанавливает именно ID-токен — access-токен трактуется как непрозрачный (спецификация OIDC не требует, чтобы он был JWT). URL эндпоинтов обязаны быть HTTPS (петлевой http:// разрешён для локальных IDP), а заявленный в документе обнаружения issuer должен совпадать с вашим настроенным (разница в завершающем слэше допускается). Refresh-токены, когда IDP их выдаёт, используются для тихой повторной авторизации через стандартный грант refresh_token; при выходе вызывается revocation_endpoint IDP по RFC 7009, если он объявлен.
Конфиденциальные клиенты (с
client_secret) пока не поддерживаются — настройте публичного клиента с PKCE, что обычно и выбирают для панели, обращённой к браузеру.
Разобранный пример: Keycloak
Keycloak — один из самых простых self-hosted OIDC-серверов для локального теста: он запускается одним контейнером в dev-режиме (БД в памяти) и отдаёт хрестоматийный OIDC discovery. Этот разбор доведёт вас от нуля до рабочего входа в панель за пару минут.
1. Запустите Keycloak с заранее настроенным realm. Сохраните этот экспорт realm как realm-hermes.json — он задаёт realm hermes, публичного PKCE-клиента (hermes-dashboard) и тестового пользователя, всё это импортируется при старте, так что в админке ничего нажимать не нужно:
{
"realm": "hermes",
"enabled": true,
"clients": [
{
"clientId": "hermes-dashboard",
"name": "Hermes Agent Dashboard",
"enabled": true,
"publicClient": true,
"standardFlowEnabled": true,
"protocol": "openid-connect",
"redirectUris": ["http://localhost:9119/auth/callback"],
"webOrigins": ["http://localhost:9119"],
"attributes": { "pkce.code.challenge.method": "S256" }
}
],
"users": [
{
"username": "testuser",
"enabled": true,
"emailVerified": true,
"email": "testuser@example.com",
"firstName": "Test",
"lastName": "User",
"credentials": [
{ "type": "password", "value": "testpassword", "temporary": false }
]
}
]
}
Запустите его (Keycloak 26+), смонтировав этот файл в каталог импорта:
docker run --rm -p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
-v "$PWD/realm-hermes.json:/opt/keycloak/data/import/realm-hermes.json:ro" \
quay.io/keycloak/keycloak:26.0 \
start-dev --import-realm
После старта realm объявляет стандартный OIDC discovery по адресу
http://localhost:8080/realms/hermes/.well-known/openid-configuration (issuer
http://localhost:8080/realms/hermes). Админ-консоль — по адресу
http://localhost:8080/ (admin / admin).
2. Направьте панель на него. Плагин self-hosted допускает петлевой http://-issuer (для любого не-петлевого issuer требуется HTTPS), так что локальный Keycloak работает как есть:
export HERMES_DASHBOARD_OIDC_ISSUER="http://localhost:8080/realms/hermes"
export HERMES_DASHBOARD_OIDC_CLIENT_ID="hermes-dashboard"
export HERMES_DASHBOARD_PUBLIC_URL="http://localhost:9119"
hermes dashboard --host 0.0.0.0 --port 9119 --no-open
HERMES_DASHBOARD_PUBLIC_URL сообщает панели, что её OAuth-callback —
http://localhost:9119/auth/callback, тот самый redirect URI, что realm зарегистрировал
выше. Привязка к 0.0.0.0 (не-петлевая) и включает контур OAuth.
3. Войдите. Откройте http://localhost:9119/ — вас перебросит на /login. Нажмите Sign in with Self-Hosted OIDC → авторизуйтесь в Keycloak как testuser / testpassword → вернётесь на авторизованную панель. В боковой панели появится Logged in as Test User via self-hosted, а GET /api/auth/me вернёт проверенную сессию (provider: self-hosted, email: testuser@example.com).
Если вы привязываетесь или заходите с другого хоста/порта, добавьте
…/auth/callbackэтого origin в Valid redirect URIs клиента в админ-консоли Keycloak (Clients → hermes-dashboard → Settings). Та же схема работает для Authentik, Zitadel, Authelia и других OIDC-серверов — отличаются только URL issuer и интерфейс регистрации клиента.
Переопределение публичного URL
По умолчанию панель восстанавливает OAuth-callback URL из запроса — X-Forwarded-Host + X-Forwarded-Proto + X-Forwarded-Prefix (когда uvicorn настроен с proxy_headers=True, что start_server включает под контуром). Это работает из коробки за обратным прокси, который корректно проставляет все три заголовка.
Для развёртываний за обратными прокси, которые ненадёжно пробрасывают эти заголовки (ручные конфигурации nginx, on-prem-ингрессы, деплои с кастомным доменом и частичными цепочками прокси), задайте dashboard.public_url (или HERMES_DASHBOARD_PUBLIC_URL) равным полному публичному URL, по которому достижима панель:
dashboard:
public_url: "https://dashboard.example.com/hermes"
trusted_proxies:
- "172.20.0.5"
Когда значение задано, OAuth-callback URL становится <public_url>/auth/callback буквально — X-Forwarded-Prefix на этом пути игнорируется, потому что оператор явно объявил публичный URL. Так задумано: добавление префикса сверху давало бы двойной префикс в распространённом случае, когда префикс уже зашит в public_url.
Хост из public_url также принимается как точное значение HTTP-заголовка Host и WebSocket Origin. Это нужно для обратного прокси, который сохраняет обращённое к браузеру имя хоста, а сам форвардит запрос на панель, привязанную к 127.0.0.1. Wildcard’ы и совпадение по суффиксу не допускаются, так что хост-самозванец вроде dashboard.example.com.evil.test защита от DNS-rebinding по-прежнему отклонит.
Объявление не-петлевого public_url всегда включает контур авторизации панели — даже если сам бэкенд привязан к петле. Сначала настройте провайдер пароля или OAuth: без него Hermes откажется стартовать (fail-closed). Так локальный сессионный токен SPA не превращается через прокси в механизм удалённой авторизации. Uvicorn в этом режиме тоже включает обработку proxy-заголовков. Петлевым прокси доверие выдаётся автоматически. Если TLS-терминатор подключается из другого контейнера или с другого хоста, добавьте его точный IP-адрес в dashboard.trusted_proxies, либо — если адрес динамический — ограниченный CIDR под выделенную прокси-сеть:
dashboard:
public_url: "https://dashboard.example.com/hermes"
trusted_proxies:
- "172.20.0.0/24"
Только перечисленные узлы могут передавать X-Forwarded-Proto и X-Forwarded-For. Hermes всегда сохраняет доверие к петле и отклоняет *, 0.0.0.0/0 и ::/0. Доверие сети означает, что метаданные форвардинга сможет подсунуть любой контейнер или машина в этой сети, так что предпочитайте точный IP прокси или выделенную прокси-only сеть. Без записи в trusted-proxy клиенты за этим прокси делят его лимит частоты входа по паролю и лимит нативного входа, а события аудита авторизации записывают адрес прокси.
# Бэкенд остаётся достижим только с этой машины.
hermes dashboard --host 127.0.0.1 --port 9119 --no-open
Направьте TLS-терминирующий обратный прокси на http://127.0.0.1:9119 и используйте тот же внешний origin в dashboard.public_url.
Tailscale Serve — один из примеров такой схемы развёртывания: он умеет терминировать HTTPS только для tailnet на хосте https://<machine>.<tailnet>.ts.net, форвардя на петлевую панель. Используйте этот точный HTTPS-origin как dashboard.public_url. Он всё равно считается не-петлевым обращённым к браузеру origin’ом и потому требует провайдера авторизации панели; выставлять сервис в публичный интернет для этого не нужно.
Приоритет тот же, что и у остальных настроек панели — env побеждает config.yaml:
| Источник | Путь переопределения | Когда использовать |
|---|---|---|
dashboard.public_url в config.yaml | HERMES_DASHBOARD_PUBLIC_URL | Локальная разработка / on-prem (канонически) |
Переменная env HERMES_DASHBOARD_PUBLIC_URL | — | Секреты хостинг-платформы / CI |
| (не задано) | — | По умолчанию — восстановление из заголовков X-Forwarded-* |
Валидация отклоняет значения без схемы http:// / https://, без хоста или содержащие кавычки, угловые скобки, пробельные или управляющие символы. Некорректное значение молча проваливается к восстановлению из заголовков, чтобы вход продолжал работать, а не отправлял пользователя на враждебный URL.
Примечание:
public_urlпереопределяет только OAuth-callback URL. Флаг кукиSecureпо-прежнему управляетсяrequest.url.scheme, аX-Forwarded-Protoучитывается, только если подключающийся узел петлевой либо перечислен вtrusted_proxies. Сочетайте HTTPS-значениеpublic_urlс терминацией TLS и ограниченной записью trusted-proxy, когда прокси стоит не на петле.
Поток OAuth
Провайдер реализует контракт Nous Portal OAuth v1 — грант authorization-code с PKCE (S256):
- Пользователь заходит на
/без сессионной куки → контур редиректит на/login. - Страница входа показывает кнопку «Continue with Nous Research» →
/auth/login?provider=nous. - Сервер прячет состояние PKCE в недолговечную куку и редиректит пользователя на
https://portal.nousresearch.com/oauth/authorize?…. - Пользователь авторизуется в Portal и попадает на
/auth/callback?code=…&state=…. - Сервер обменивает код на access-токен через
POST /api/oauth/token, проверяет подпись JWT против JWKS Portal (/.well-known/jwks.json) и ставит кукуhermes_session_at. - Пользователя редиректит на
/(или на исходный deep-link через query-параметрnext=).
У access-токенов TTL 15 минут. В контракте v1 refresh-токена нет — когда токен истекает, fetch-обёртка SPA ловит 401-конверт и делает полную навигацию обратно на /login, чтобы прогнать поток заново.
Устанавливаемые куки
| Имя | Время жизни | Примечания |
|---|---|---|
hermes_session_at | TTL токена (15 мин) | HttpOnly, SameSite=Lax, Secure при HTTPS |
hermes_session_pkce | 10 мин | HttpOnly; держит верификатор PKCE + подсказку провайдера на время цикла. SameSite=None + Secure по HTTPS (должна пережить цепочку кросс-сайтовых редиректов IDP — Chromium отбрасывает куки SameSite=Lax, выставленные на 302 в кросс-сайтовой цепочке); SameSite=Lax на петлевом HTTP |
hermes_session_rt | в v1 не используется | Зарезервирована на будущее; не пишется, когда refresh_token пуст |
У всех трёх Path=/. Сессионные куки — SameSite=Lax; PKCE-кука — SameSite=None, когда выставляется по HTTPS (смотрите таблицу). Флаг Secure ставится, когда панель достижима по HTTPS (определяется по схеме URL запроса — учитывает X-Forwarded-Proto от вышестоящего терминатора TLS под proxy_headers=True).
Выход
Виджет в боковой панели показывает Logged in as <user_id…> via nous с иконкой выхода. По клику POST-запрос на /auth/logout очищает все куки dashboard-auth и редиректит обратно на /login.
Журнал аудита
Каждое начало входа, успех, отказ и провал проверки сессии пишется JSON-строкой в $HERMES_HOME/logs/dashboard-auth.log. Чувствительные поля (access_token, refresh_token, code, code_verifier, state, заголовок Authorization) перед записью маскируются.
Кастомные провайдеры
Чтобы подключить не-Nous OAuth-провайдер (например, Google, GitHub, свой OIDC), создайте плагин, регистрирующий DashboardAuthProvider:
# ~/.hermes/plugins/dashboard-auth-myidp/__init__.py
from hermes_cli.dashboard_auth import DashboardAuthProvider, Session, LoginStart
class MyIdPProvider(DashboardAuthProvider):
name = "myidp"
display_name = "My Identity Provider"
def start_login(self, *, redirect_uri): ...
def complete_login(self, *, code, state, code_verifier, redirect_uri): ...
def verify_session(self, *, access_token): ...
def refresh_session(self, *, refresh_token): ...
def revoke_session(self, *, refresh_token): ...
def register(ctx):
ctx.register_dashboard_auth_provider(MyIdPProvider())
Страница входа перечисляет все зарегистрированные провайдеры; несколько провайдеров можно навесить рядом, и пользователь выбирает один на /login.
Неинтерактивная авторизация по bearer-токену
Помимо интерактивного входа человека (сессионные куки плюс обновление), ABC DashboardAuthProvider поддерживает неинтерактивный режим машина-машине — через supports_token = True и verify_token(token=...). Когда провайдер это объявляет, входящий Authorization: Bearer <token> проверяется, и при успехе к запросу цепляется TokenPrincipal (request.state.token_principal) — для тех эндпоинтов, которые провайдер пометил как доступные по токену. Ни кук, ни редиректов, ни обновления.
Первый встроенный потребитель — провайдер drain (plugins/dashboard_auth/drain): nous-account-service выдаёт на каждого агента секрет через HERMES_DASHBOARD_DRAIN_SECRET, а провайдер сверяет с ним входящие bearer-токены сравнением за постоянное время и регистрирует /api/gateway/drain как доступный по токену. Он закрывается наглухо: слабый или короткий секрет (< 256 бит) отвергается при регистрации, и эндпоинт остаётся выключенным; при незаданной переменной окружения это no-op. Поведенческие ручки (scope, min_secret_chars) живут под dashboard.drain_auth в config.yaml.
Свои провайдеры могут реализовать supports_token/verify_token точно так же и открыть собственные эндпоинты для машинного доступа.
Проверка, что контур включён
# Быстрый путь через переменную окружения.
HERMES_DASHBOARD_OAUTH_CLIENT_ID=agent:test \
hermes dashboard --host 0.0.0.0
# Или то же самое через config.yaml (рекомендуется для локальной разработки / on-prem):
#
# dashboard:
# oauth:
# client_id: agent:test
#
# и просто:
hermes dashboard --host 0.0.0.0
# Проверьте состояние контура через /api/status:
curl -s http://127.0.0.1:9119/api/status | jq '.auth_required, .auth_providers'
# true
# ["nous"]
React-компонент StatusPage панели показывает те же поля под «Web server». Виджет AuthWidget в боковой панели выводит текущую идентичность после входа.
Подключение Hermes Desktop к удалённому бэкенду
Hermes Desktop умеет управлять бэкендом Hermes на другой машине (VPS, домашний сервер, Mac Mini за Tailscale). В приложении это живёт под Settings → Gateways → Remote gateway, где запрашивают Remote URL и способ Sign in. (О самом десктопном приложении — установка, настройки, чат — смотрите страницу Hermes Desktop.)
Удалённую панель вы защищаете одним из встроенных провайдеров авторизации, а десктоп входит против того, который объявляет бэкенд. Для бэкенда, достижимого за пределами вашей машины — VPS, публичный хост, что угодно с выходом в интернет, — рекомендуемый провайдер это OAuth (Nous Portal) (зарегистрируйте его через hermes dashboard register и входите через Sign in with Nous Research). Встроенный провайдер логин/пароль — самый быстрый вариант, когда бэкенд в доверенной локальной сети или доступен только через VPN, но он не годится для прямого выставления в публичный интернет. Привязка панели к не-петлевому адресу включает её контур авторизации; после входа Desktop сам переиспользует сессию для чат-WebSocket — токен копировать и вставлять не нужно.
Рецепт ниже идёт по пути логин/пароль, потому что его быстрее всего поднять в доверенной сети; путь OAuth смотрите в разделе Провайдер по умолчанию: Nous Research.
На бэкенде (удалённая машина)
# 1. Задайте учётные данные входа в панель в ~/.hermes/.env (файл секретов, 0600).
cat >> ~/.hermes/.env <<'EOF'
HERMES_DASHBOARD_BASIC_AUTH_USERNAME=admin
HERMES_DASHBOARD_BASIC_AUTH_PASSWORD=choose-a-strong-password
# Рекомендуется: стабильный секрет подписи, чтобы сессии переживали перезапуски.
HERMES_DASHBOARD_BASIC_AUTH_SECRET=$(openssl rand -base64 32)
EOF
chmod 600 ~/.hermes/.env
# 2. Запустите панель с привязкой к достижимому адресу. Не-петлевая привязка
# включает контур авторизации; провайдер логин/пароль берёт вход на себя.
hermes dashboard --no-open --host 0.0.0.0 --port 9119
Не хотите хранить пароль открытым текстом? Используйте вместо этого HERMES_DASHBOARD_BASIC_AUTH_PASSWORD_HASH со scrypt-хэшем — полный набор параметров смотрите в разделе Провайдер логин/пароль.
Если вы запускаете панель как сервис systemd, ~/.hermes/.env подхватывается автоматически, когда у юнита есть EnvironmentFile=%h/.hermes/.env, так что учётные данные оказываются в окружении уже при загрузке.
Панель читает и пишет ваш .env (API-ключи, секреты) и может запускать команды агента. Показанная здесь настройка логин/пароль рассчитана на доверенную сеть — никогда не выставляйте защищённую паролем панель напрямую в открытый интернет. Прячьте её за VPN. Tailscale — чистый вариант: привяжитесь к tailscale-IP машины (--host <tailscale-ip>) и используйте http://<tailscale-ip>:9119 как Remote URL. Достучаться смогут только устройства вашего tailnet. Чтобы добраться до бэкенда через публичный интернет, используйте вместо этого провайдер OAuth (Nous Portal).
В Hermes Desktop
Settings → Gateways → Remote gateway:
- Remote URL —
http://<backend-host>:9119(префиксы пути вроде/hermesподдерживаются, если вы ставите впереди обратный прокси) - Sign in — приложение определяет шлюз логин/пароль и показывает кнопку Sign in; нажмите её и введите учётные данные из шага 1
- Save and reconnect — переключает десктопную оболочку на удалённый бэкенд
Сессия обновляется сама и переживает перезапуски, когда на бэкенде задан HERMES_DASHBOARD_BASIC_AUTH_SECRET.
Переопределение через переменную окружения
Вместо настройки в приложении можно направить десктоп на бэкенд переменной окружения до его запуска. Когда задан HERMES_DESKTOP_REMOTE_URL, он перекрывает сохранённый в приложении URL (панель Gateway settings показывает бейдж «env override» и блокирует редактирование); войти через Sign in с логином и паролем всё равно нужно из панели.
| Переменная env | Значение |
|---|---|
HERMES_DESKTOP_REMOTE_URL | http://<backend-host>:9119 |
Устранение неполадок
- «Remote gateway incomplete» — вы не ввели удалённый URL.
- Вход падает с 401 / «Invalid credentials» — логин или пароль не совпадает с
HERMES_DASHBOARD_BASIC_AUTH_USERNAME/HERMES_DASHBOARD_BASIC_AUTH_PASSWORDбэкенда. Бэкенд возвращает одну и ту же общую ошибку для неизвестного пользователя и неверного пароля, так что проверьте и то, и другое. Подтвердите контур черезcurl -s http://<host>:9119/api/status | jq '.auth_required, .auth_providers'— должно вернутьсяtrueи присутствовать"basic". - Нет кнопки «Sign in» — вместо неё просят сессионный токен — провайдер логин/пароль не активен (
/api/statusне покажет"basic"). Убедитесь, что заданы логин и пароль (или хэш пароля) и процесс панели их загрузил. - Разлогинивает при каждом перезапуске — задайте
HERMES_DASHBOARD_BASIC_AUTH_SECRETстабильным значением; иначе ключ подписи генерируется заново при каждой загрузке. - Connection refused / таймаут — бэкенд привязан к
127.0.0.1(по умолчанию) вместо достижимого адреса, либо порт блокирует фаервол/VPN. Привяжитесь к0.0.0.0или tailscale-IP и откройте порт для вашей доверенной сети.
CORS
Веб-сервер ограничивает CORS только origin’ами localhost:
http://localhost:9119/http://127.0.0.1:9119(продакшен)http://localhost:3000/http://127.0.0.1:3000http://localhost:5173/http://127.0.0.1:5173(dev-сервер Vite)
Если вы запускаете сервер на своём порту, этот origin добавляется автоматически.
Разработка
Если вы контрибьютите во фронтенд веб-панели:
# Терминал 1: запустите бэкенд API
hermes dashboard --no-open
# Терминал 2: запустите dev-сервер Vite с HMR
cd web/
npm install
npm run dev
Dev-сервер Vite на http://localhost:5173 проксирует запросы /api на FastAPI-бэкенд по адресу http://127.0.0.1:9119.
Фронтенд собран на React 19, TypeScript, Tailwind CSS v4 и компонентах в стиле shadcn/ui. Продакшен-сборки выходят в hermes_cli/web_dist/, откуда FastAPI-сервер отдаёт их как статический SPA.
Сборка требовательна к процессору и памяти (нативный бандлер Rolldown на Rust из Vite 8 распараллеливается по ядрам), поэтому по умолчанию она ограничена по ресурсам: куча V8 ограничена (--max-old-space-size, размер берётся из лимита памяти контейнера), а пул потоков нативного бандлера — половиной доступных ядер (RAYON_NUM_THREADS). Это защищает небольшие VPS от скачков загрузки CPU выше 200% и от OOM во время сборки (#63338). Переопределяется через HERMES_WEB_BUILD_MAX_OLD_SPACE_SIZE / HERMES_WEB_BUILD_THREADS; HERMES_WEB_BUILD_LIGHT=1 ужесточает ограничения (1 поток, 1 ГБ кучи), если хост не может отдать сборке полный процессор.
Автоматическая сборка при обновлении
Когда вы запускаете hermes update, фронтенд автоматически пересобирается, если доступен npm. Так панель остаётся в синхроне с обновлениями кода. Если npm не установлен, обновление пропускает сборку фронтенда, и hermes dashboard соберёт его при первом запуске.
Темы и плагины
Панель поставляется с восемью встроенными темами и расширяется пользовательскими темами, вкладками-плагинами и серверными API-маршрутами — всё в режиме drop-in, без клонирования репозитория.
Переключайте темы на лету из верхней панели — нажмите иконку палитры рядом с переключателем языка. Выбор сохраняется в config.yaml под dashboard.theme и восстанавливается при загрузке страницы.
Меняйте шрифт независимо в том же селекторе — секция Font под списком тем переопределяет шрифт интерфейса у любой активной темы. Выбор держится при переключении тем (config.yaml → dashboard.font); чтобы сбросить его и вернуться к родному шрифту активной темы, выберите Theme default.
Встроенные темы:
| Тема | Характер |
|---|---|
Hermes Teal (default) | Тёмный бирюзовый + кремовый, системные шрифты, комфортные отступы |
Hermes Teal (Large) (default-large) | То же, что default, но с текстом 18px и более просторными отступами |
Nous Blue (nous-blue) | Фирменные синие акценты Nous и воздушные отступы |
Midnight (midnight) | Глубокий сине-фиолетовый, Inter + JetBrains Mono |
Ember (ember) | Тёплый малиновый + бронза, Spectral serif + IBM Plex Mono |
Mono (mono) | Оттенки серого, IBM Plex, компактно |
Cyberpunk (cyberpunk) | Неоновый зелёный на чёрном, Share Tech Mono |
Rosé (rose) | Розовый + слоновая кость, Fraunces serif, просторно |
Чтобы собрать свою тему, добавить вкладку-плагин, встроиться в слоты оболочки или выставить REST-эндпоинты под плагин, смотрите Расширение панели — полный гайд охватывает:
- YAML-схему темы — палитра, типографика, layout, ассеты, componentStyles, colorOverrides, customCSS
- Варианты раскладки —
standard,cockpit,tiled - Манифест плагина, SDK, слоты оболочки, слоты уровня страницы (встраивание виджетов во встроенные страницы без их подмены), серверные FastAPI-маршруты
- Полный совмещённый разбор «тема плюс плагин» (демо кокпита Strike Freedom)
- Обнаружение, перезагрузку и устранение неполадок