SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-09-08

Настройка HarnessRouter для объединения API агентов

Разверните HarnessRouter для Codex, Claude Code и Hermes через Docker. Узнайте, как настроить loopback bind, изменить стандартный логин и обеспечить TLS доступ к API.

Что устраняет HarnessRouter

Вы используете self-hosted версию HarnessRouter Community Edition, чтобы разместить один API перед несколькими agent harnesses на собственном сервере. Agent harness — это программа командной строки, которая управляет моделью в цикле: она поддерживает сессию, редактирует файлы, выполняет команды и передает прогресс выполнения обратно инициатору задачи. Codex, Claude Code и Hermes выполняют эту работу, но каждый из них требует своей установки, использует свой формат учетных данных и имеет собственное представление о том, что такое сессия. HarnessRouter запускает их все внутри одного контейнера и предоставляет единую HTTP-точку входа, единую систему авторизации и единое хранилище секретов.

В этом заключается основная идея, и стоит открыто сказать о цене такого решения. Вы добавляете на свой сервер контейнер, систему авторизации, том и процесс обновления, чтобы объединить несколько разрозненных компонентов в один. Если сегодня вы используете только один harness, такая конфигурация будет менее эффективной, чем прямая установка этого инструмента. Этот компромисс обсуждается в последнем разделе, поэтому ознакомьтесь с ним перед развертыванием.

Все приведенные ниже данные проверены для образа с тегом 0.5.5, загруженного 19 августа 2026 года. Проект выпускает новые теги практически ежедневно, поэтому проверяйте актуальный тег, который вы используете, вместо того чтобы полагаться на эту страницу спустя месяц. Команды взяты из README проекта по адресу github.com/HarnessRouter/harnessrouter.

Что на самом деле представляет собой Unified Harness Protocol

HarnessRouter реализует протокол Unified Harness Protocol (UHP), опубликованный на unifiedharnessprotocol.org. UHP описывает, как продукт запускает задачу на harness, отслеживает её выполнение, управляет сессиями и файлами, а также сообщает о сбоях. Спецификация версионируется по дате. Версия, актуальная на 19 августа 2026 года, датирована 2026-08-11; на сайте она названа черновиком стандарта, который «достаточно стабилен для разработки и версионируется для безопасного внесения изменений».

Внимательно отнеситесь к фразе «открытый стандарт». Одна и та же компания пишет спецификацию, эталонную реализацию и набор из 52 проверок на соответствие, который определяет, кто этому стандарту соответствует. Это обычное явление для столь молодого протокола, а лицензия Apache-2.0 позволяет форкнуть любую его часть. Это также означает, что UHP пока не является мульти-вендорным стандартом. Рассматривайте его как развивающийся протокол: полезный, динамичный и такой, от использования которого ваш собственный код должен иметь возможность отказаться без необходимости полной переработки.

Что потребуется перед началом работы

Docker и примерно 4 GB свободного места на диске. Также потребуется API-ключ от провайдера моделей, услуги которого вы уже оплачиваете. Размер образа составляет около 700 MB, остальное место на диске потребуется для CLI-инструментов агентов и рабочих областей, в которые они записывают данные. Внутри образа нет встроенной модели или пробного ключа, поэтому задачи будут завершаться ошибкой, пока вы не подключите провайдера. Сам HarnessRouter распространяется под лицензией Apache-2.0. На CLI-инструменты агентов эта лицензия не распространяется, поэтому они загружаются при первом запуске, а не поставляются в составе образа.

Запуск HarnessRouter с помощью одной команды docker run

docker pull harnessrouter/harnessrouter
docker run -d --name harnessrouter \
  -p 127.0.0.1:3000:3000 \
  -v harnessrouter:/data \
  harnessrouter/harnessrouter

Затем наблюдайте за запуском контейнера. Первый запуск происходит медленно, и логи объясняют причину.

docker logs -f harnessrouter

В процессе работы вы увидите строки, подобные этим:

installing Claude Code (Anthropic's terms apply)…
installing Codex (Apache-2.0)…
installing Hermes (check its upstream license before use)…

Дождитесь ready on :3000. Эта установка выполняется один раз для каждого тома, поэтому все последующие запуски занимают несколько секунд и не выводят никаких сообщений об установке.

Из этой загрузки следуют два факта, и оба важны при работе на VPS. Во-первых, для первой загрузки необходим исходящий доступ к сети. Образ не является автономным, поэтому сервер за фильтром исходящего трафика или без маршрута наружу зависнет на этом этапе и никогда не выведет ready on :3000. Он завершится с ошибкой при первом запуске, а не на этапе docker pull, что затрудняет диагностику. Во-вторых, вы устанавливаете стороннее программное обеспечение на условиях сторонних разработчиков. Claude Code поставляется на условиях Anthropic, а Hermes — на условиях своего вышестоящего проекта, поэтому ознакомьтесь с обоими соглашениями перед коммерческим использованием.

-v harnessrouter:/data создает именованный том Docker. Все постоянные данные хранятся в /data: базы данных SQLite, сохраненные файлы, хранилище секретов и рабочие области агентов. Удаление этого тома означает удаление экземпляра, включая ключи провайдеров и все транскрипты. Создавайте резервные копии при остановленном контейнере, так как копирование базы данных SQLite во время записи может привести к повреждению файла. Та же дисциплина «сначала остановка, затем копирование» применяется к любому контейнеру с состоянием на сервере, хотя детали зависят от сервиса, поскольку для PhotoPrism и Immich требуются свои собственные команды резервного копирования.

docker stop harnessrouter
docker run --rm -v harnessrouter:/data -v "$PWD":/backup alpine \
  tar czf /backup/harnessrouter-data.tgz -C / data
docker start harnessrouter

Вариант compose и строка, которую необходимо изменить

В репозитории поставляется файл compose. Он публикует "3000:3000", что означает все интерфейсы на хосте. Измените эту строку перед запуском на публичном сервере.

services:
  harnessrouter:
    image: harnessrouter/harnessrouter:0.5.5
    ports:
      - "127.0.0.1:3000:3000"
    env_file:
      - .env
    volumes:
      - harnessrouter-data:/data
    restart: unless-stopped

volumes:
  harnessrouter-data:

Два отличия от upstream: адрес привязки и зафиксированный тег версии вместо latest. Фиксация важна, так как в период с 9 по 18 августа 2026 года вышло шестнадцать версий, а среду выполнения агента, которая меняется без вашего ведома, сложно отлаживать. Затем скопируйте файл окружения, ограничьте права доступа к нему и запустите сервис.

cp .env.example .env
chmod 600 .env
docker compose up -d
docker compose logs -f

.env содержит ваш ключ провайдера в открытом виде, поэтому режим 600 — это минимум. Если подкоманда docker compose вам незнакома, шпаргалка по командам Docker Compose содержит все повседневные операции.

Почему порт публикуется на 127.0.0.1, а не на 0.0.0.0

-p 3000:3000 публикует порт на всех интерфейсах хоста. -p 127.0.0.1:3000:3000 публикует его только на loopback, что означает, что единственный способ доступа — это сам VPS. Контейнер всегда слушает порт 3000 внутри, поэтому вы меняете только левую часть. Проверьте текущую конфигурацию:

docker port harnessrouter
sudo ss -ltnp | grep 3000

ss вывод 127.0.0.1:3000 является корректным. 0.0.0.0:3000 означает, что консоль доступна из публичного интернета. В данном случае это опаснее, чем для большинства self-hosted приложений, так как консоль создает окружения, читает все транскрипты, запускает агентов и предоставляет этим агентам оболочку и реальную файловую систему в их рабочем пространстве. Она также хранит ключ провайдера, который вы подключили. Любой, кто получит доступ к незащищенной консоли, сможет прочитать вашу работу, выполнить команды и использовать ваш ключ.

Хостовый файрвол не защитит вас от этого. Docker публикует порты, записывая собственные правила в таблицу ядра nat, и они проверяются до того, как цепочка ufw начнет управление, поэтому опубликованный порт остается доступным, даже если sudo ufw status показывает его как запрещенный. Проводите тестирование с другого компьютера, а не с самого VPS, иначе проверка не будет иметь смысла. Это тот же урок, что и при запуске dsh headless на порту 3080: привязывайте сервис к loopback, а затем осознанно решайте, как вы будете получать к нему доступ.

Измените стандартные учетные данные перед началом работы

Войдите в систему по адресу http://localhost:3000, используя имя пользователя harnessrouter и пароль harnessrouter. Эти данные указаны в файле README, так как они являются временными заглушками, а не секретами. Контейнер будет предупреждать вас об этом при каждом запуске, пока вы их не измените:

using the DEFAULT password. Set HR_AUTH_PASSWORD, or change it from the profile page, before exposing this instance.

Измените их на странице профиля или задайте при запуске для автоматизированного развертывания. Переменные HR_AUTH_USER и HR_AUTH_PASSWORD переопределяют стандартные значения.

docker run -d --name harnessrouter \
  -p 127.0.0.1:3000:3000 \
  -v harnessrouter:/data \
  -e HR_AUTH_USER='you' \
  -e HR_AUTH_PASSWORD='the-password-you-chose' \
  harnessrouter/harnessrouter

Функция восстановления пароля по электронной почте отсутствует, так как в системе нет учетных записей и почтового сервера. Если вы потеряли пароль, удалите файл аутентификации в томе и перезапустите контейнер, после чего снова войдите с использованием стандартных данных.

docker stop harnessrouter
docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json
docker start harnessrouter

Параметр HR_AUTH_DISABLED=1 полностью отключает экран входа в систему. В файле README указано, что это допустимо только для «изолированного устройства, к которому никто не имеет доступа». VPS с публичным IP-адресом не является таким устройством, поэтому не отключайте проверку входа, если только вы не запускаете систему на локальном ноутбуке.

Проверьте свою версию, так как в старых версиях отсутствует шлюз аутентификации

К этому разделу следует отнестись со всей серьезностью. Версии 0.1.x и 0.2.0 поставлялись вообще без шлюза аутентификации: любой, кто мог подключиться к порту 3000, сразу получал доступ к консоли. Версия 0.3.0 стала первым релизом с поддержкой входа в систему. Эти старые теги до сих пор опубликованы и доступны для загрузки, поэтому старый зафиксированный тег или файл compose, скопированный у коллеги, могут привести к тому, что сегодня консоль без защиты окажется на публичном порту.

По состоянию на 19 августа 2026 года новейшим опубликованным тегом является 0.5.5 от 18 августа 2026 года, на который указывает latest. Проверьте, какая версия установлена у вас, а затем сравните её со списком тегов на Docker Hub:

docker image ls harnessrouter/harnessrouter

Все версии ниже 0.3.0 необходимо заменить немедленно, не откладывая это в очередь задач. Для всех версий, начиная с этой и выше, всё равно требуется сменить пароль, так как для того, кто сканирует порт 3000, отсутствие пароля и пароль по умолчанию — это одно и то же. Не считайте номера версий на этой странице актуальными. Они были верны на дату, указанную в начале документа, а этот проект обновляется быстро.

Подключение провайдера

Ничего не заработает, пока не подключен провайдер модели. Добавьте его на странице Integrations в консоли или передайте через docker run в переменные окружения. Значение должно быть в формате JSON, поэтому при вводе в оболочке его необходимо взять в кавычки:

-e HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC='{"name":"anthropic","provider":"anthropic","api_key":"sk-ant-…"}'

.env.example определяет одну переменную подключения для каждого семейства провайдеров: HR_SECRET_GLOBAL_HARNESS_CONN_ANTHROPIC для бэкенда claude-code, HR_SECRET_GLOBAL_HARNESS_CONN_OPENAI для бэкенда codex и HR_SECRET_GLOBAL_HARNESS_CONN_CUSTOM для любого OpenAI-совместимого эндпоинта, куда направляются запросы к агрегатору или собственному серверу инференса. Соответствующие переменные HR_SECRET_GLOBAL_HARNESS_POLICY_CLAUDE, HR_SECRET_GLOBAL_HARNESS_POLICY_CODEX и HR_SECRET_GLOBAL_HARNESS_POLICY_HERMES указывают, какое подключение использует каждый бэкенд по умолчанию. HR_SECRET_KEY — это отдельный параметр, он требуется только при подключении базы данных к агенту.

HR_BACKENDS выбирает, какие бэкенды будут загружены, как показано в HR_BACKENDS=claude,codex,hermes. Стоит знать об одной известной проблеме, прежде чем она возникнет: любое значение, в котором отсутствует hermes, приводит к немедленному завершению работы контейнера со статусом 1 без сообщения об ошибке. Вы увидите Exited (1) в docker ps -a через секунду после запуска, а docker logs не покажет ничего полезного. Оставляйте hermes в списке, пока разработчики не исправят это поведение. Если Hermes — единственный нужный вам инструмент, запуск агента Hermes на отдельном VPS будет более компактным вариантом развертывания.

Вызов API без использования консоли

Консоль не является обязательной. Один и тот же API обслуживает оба варианта, используя контракт в стиле Responses. Сначала выполните вход, чтобы получить session cookie:

curl -c hr.cookies http://localhost:3000/api/selfhost/login \
  -H 'content-type: application/json' \
  -d '{"username":"harnessrouter","password":"your-password"}'

Затем отправьте задачу, указав harness в metadata.harness_id и модель, которую фактически предоставляет ваш подключенный провайдер:

curl -s -b hr.cookies http://localhost:3000/api/harness/v1/responses \
  -H 'content-type: application/json' \
  -d '{"input":"Reply with exactly this and nothing else: it works.",
       "metadata":{"harness_id":"codex"},
       "model":"gpt-5.4-mini",
       "stream":false}'

JSON-объект, содержащий блок вывода и количество токенов, означает, что harness был выполнен. Изменение harness_id с codex на claude отправляет тот же запрос на другой harness, и эта возможность подмены является основной причиной существования данного программного обеспечения. Пользовательское подключение, описанное выше, показывает, как направить harness на совместимый с OpenAI эндпоинт, который вы уже хостите, аналогично тому, как настраивается самостоятельно размещенный harness DeepSeek на VPS.

Доступ с вашего ноутбука без публикации порта

Есть два способа, и ни один из них не требует открытия порта на 0.0.0.0.

SSH-туннель — самый простой вариант, не требующий установки дополнительного ПО на сервере. Он перенаправляет локальный порт на вашей машине на loopback-интерфейс VPS.

ssh -N -L 3000:127.0.0.1:3000 you@your-vps

Оставьте этот процесс запущенным и откройте http://localhost:3000 в браузере. Если SSH выводит bind: Address already in use, значит, порт 3000 на вашем ноутбуке уже занят. Выберите другой локальный порт с помощью -L 3100:127.0.0.1:3000 и перейдите по адресу порта 3100.

Терминирующий reverse proxy — решение для случаев, когда доступ нужен другим пользователям. Прокси хранит TLS (transport layer security) сертификат и перенаправляет запросы на loopback. В README приведена конфигурация для Caddy:

console.example.com {
    encode zstd gzip
    reverse_proxy 127.0.0.1:3000 {
        flush_interval -1      # agent turns stream for minutes; never buffer them
    }
}

flush_interval -1 — это строка, которую часто забывают. Агент генерирует токены потока в течение нескольких минут, а прокси, буферизирующий ответ, удерживает эти токены до завершения генерации. Из-за этого консоль кажется зависшей, а затем выводит всё содержимое сразу. Аналогичная настройка в Nginx — proxy_buffering off; внутри блока location. Что бы вы ни выбрали, убедитесь, что DNS-имя указывает на прокси, а контейнер слушает loopback. В статье Сравнение Nginx, Caddy и Traefik в качестве reverse proxy разобрано, какой вариант лучше подходит для вашей системы.

Запуск от имени отдельного пользователя, а не root

Демон Docker работает с правами root, а членство в группе docker равносильно получению прав root, так как участник группы может запустить контейнер, который примонтирует файловую систему хоста. Таким образом, добавление команды в группу docker фактически предоставляет права root на сервере, где хранится ваш ключ провайдера.

Простой вариант: создайте сервисную учетную запись, которая будет владельцем файла compose и .env, и храните эти файлы вне любых общих домашних каталогов.

sudo adduser --disabled-password --gecos "" harness
sudo install -d -o harness -g harness -m 750 /srv/harnessrouter

Более надежный вариант — rootless Docker, где сам демон работает от имени непривилегированного пользователя. Для этого требуется пакет uidmap для newuidmap и newgidmap, а также не менее 65536 подчиненных UID в /etc/subuid и /etc/subgid для пользователя. Пакет uidmap есть в репозиториях Ubuntu, а docker-ce-rootless-extras — нет: он поставляется из собственного apt-репозитория Docker по адресу download.docker.com, который добавляется при установке Docker engine. Если вы не устанавливали engine из этого репозитория, команда grep -rl download.docker.com /etc/apt/sources.list.d/ ничего не выведет, и приведенная ниже установка не найдет пакет.

sudo apt install -y uidmap docker-ce-rootless-extras
sudo loginctl enable-linger harness
sudo -iu harness
dockerd-rootless-setuptool.sh install
export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
systemctl --user enable --now docker

Параметр loginctl enable-linger здесь обязателен. Без него systemd-экземпляр пользователя завершает работу при закрытии последней сессии, поэтому контейнер остановится, как только вы выйдете из системы. Проверьте результат с помощью docker info, которая должна отобразить rootless в разделе Security Options. В режиме rootless невозможно привязать порты ниже 1024 без дополнительной настройки, что в данном случае не имеет значения, так как порт 3000 находится выше этого диапазона. Настройка самой учетной записи описана в создании пользователей с минимальными привилегиями на VPS.

Что ломается и что вы увидите

Контейнер завершается через секунду после запуска, а логи пусты. docker ps -a показывает Exited (1). Это проблема HR_BACKENDS, описанная выше: в вашем значении пропущен hermes. Верните его на место.

Первый запуск не завершается. Лог останавливается после строки installing, а ready on :3000 так и не появляется. Узел не может выйти в сеть для загрузки CLI агентов, так как их нет в образе. Исправьте исходящий маршрут или настройки прокси, затем перезапустите сервис.

Консоль загружается, но все задачи завершаются ошибкой. Не подключен ни один провайдер. В образе нет встроенных моделей или бесплатного уровня доступа, поэтому даже после входа в систему новый экземпляр не сможет ничего запустить.

Консоль зависает в процессе ответа при работе через прокси. Весь вывод появляется одним блоком только после завершения генерации. Это буферизация ответов. Установите flush_interval -1 в Caddy или proxy_buffering off; в Nginx.

Вы не можете подключиться к сервису с ноутбука, хотя туннель активен. Выполните docker port harnessrouter на сервере. Если команда ничего не выводит, значит, контейнер не публикует порты, так как он был запущен без -p.

Стоит ли это запускать?

Запуск оправдан, если вы действительно используете более одного инструмента (harness) и хотите иметь одну точку входа и одно хранилище учетных данных вместо трех для каждого. Также это имеет смысл, если вы создаете продукт поверх этого и хотите, чтобы выбор инструмента был параметром конфигурации, а не требовал переписывания кода. Именно это дает вам UHP, с учетом вышеупомянутой оговорки о том, насколько молод этот протокол.

Запуск не оправдан, если вы используете только один инструмент. Установка соответствующего CLI на сервер означает меньше движущихся частей, и между вами и инструментом не будет промежуточного этапа авторизации. Это также неверный подход, если вам нужно, чтобы несколько агентов взаимодействовали при выполнении одной задачи, а не один API перед несколькими инструментами; для этого существуют другие инструменты: см. мультиагентный инструмент, такой как Omnigent для реализации этого шаблона. В любом случае правила развертывания остаются неизменными. Привязка к loopback, измененный пароль, закрепленный тег версии 0.3.0 или выше и отдельный пользователь.

FAQ

Безопасно ли публиковать HarnessRouter на порту 3000?

Нет. Консоль создает harness, считывает все транскрипты, запускает агенты с доступом к оболочке и файловой системе, а также хранит ключ провайдера, который вы подключили, поэтому открытый порт делает всё это доступным извне. Публикуйте сервис на loopback с помощью -p 127.0.0.1:3000:3000 и обращайтесь к нему через SSH-туннель или reverse proxy с TLS-терминацией. Межсетевого экрана на хосте недостаточно: Docker записывает свои правила в таблицу ядра nat, поэтому опубликованный порт отвечает на запросы из интернета, даже если ufw показывает, что доступ запрещен. Проверьте это с помощью sudo ss -ltnp | grep 3000, команда должна вывести 127.0.0.1:3000.

В какой версии HarnessRouter появился экран входа?

0.3.0. Версии 0.1.x и 0.2.0 поставлялись без какой-либо аутентификации, оба тега до сих пор опубликованы и доступны для загрузки, поэтому любой, кто их использует, полагается лишь на то, что никто не обнаружит порт. По состоянию на 19 августа 2026 года новейшим тегом является 0.5.5 от 18 августа 2026 года. Выполните docker image ls harnessrouter/harnessrouter, чтобы увидеть текущую версию, сравните её со списком тегов на Docker Hub, а не с этой страницей, и измените пароль по умолчанию даже в актуальной версии.

Почему контейнер завершает работу сразу после настройки HR_BACKENDS?

Любое значение HR_BACKENDS, в котором отсутствует hermes, приводит к немедленному завершению работы контейнера со статусом 1 без сообщения об ошибке; это известная проблема, описанная в README проекта. Симптомом является Exited (1) в docker ps -a через секунду или две, при этом в docker logs нет полезной информации. Оставляйте hermes в списке, как в HR_BACKENDS=claude,codex,hermes, пока разработчики не исправят это в основной ветке.

Нужен ли HarnessRouter доступ в интернет при первом запуске?

Да. CLI агентов загружаются при первом запуске, а не поставляются внутри образа, так как каждый из них имеет собственную лицензию. Сервер без исходящего маршрута выводит строки installing и не может достичь ready on :3000. Загрузка происходит один раз для каждого тома, поэтому последующие запуски занимают несколько секунд и не требуют сети, за исключением связи с провайдером модели, которого вы подключили.

Я потерял пароль от консоли. Как восстановить доступ?

Функции сброса пароля через email нет, так как в системе отсутствуют учетные записи и почтовый сервер. Остановите контейнер, удалите /data/selfhost-auth.json из тома, запустите его снова, затем войдите с учетными данными по умолчанию и установите новый пароль на странице Profile. Если контейнер и том называются harnessrouter, выполните docker stop harnessrouter, затем docker run --rm -v harnessrouter:/data alpine rm -f /data/selfhost-auth.json, а после docker start harnessrouter.