SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor · Обновлено 2026-08-21

Настройка HarnessRouter для Codex, Claude Code и Hermes

Разверните HarnessRouter в Docker для управления агентами через единый API. Инструкция охватывает привязку loopback, смену стандартного пароля и настройку TLS доступа.

Что устраняет 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 — на условиях своего upstream-разработчика, поэтому ознакомьтесь с обоими соглашениями перед коммерческим использованием.

-v harnessrouter:/data создает именованный том Docker. Все постоянные данные хранятся в /data: базы данных SQLite, сохраненные файлы, хранилище секретов и рабочие области агентов. Если вы удалите этот том, вы удалите экземпляр, включая ключи провайдеров и все транскрипты. Создавайте резервную копию при остановленном контейнере, так как копирование базы данных SQLite во время записи может привести к получению нечитаемого файла. Та же дисциплина «сначала остановка, затем копирование» применяется к любому stateful-контейнеру на сервере, хотя детали зависят от сервиса, поскольку для 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:

Два отличия от апстрима: адрес привязки и зафиксированный тег версии вместо 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.

Измените их на странице Profile или задайте при запуске для автоматизированного развертывания. Переменные 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. Сначала выполните вход, чтобы получить сессионный 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 endpoint, который вы уже разместили, аналогично тому, как настраивается собственный 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 для данного пользователя.

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: в разделе Security Options должен появиться пункт rootless. В режиме 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 и хотите иметь одну точку входа и одно хранилище учетных данных вместо трех для каждого. Также это имеет смысл, если вы создаете продукт поверх этого и хотите, чтобы harness был параметром конфигурации, а не требовал переписывания кода. Именно это дает вам UHP, с учетом упомянутого выше предостережения о том, насколько молод этот протокол.

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

FAQ

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

Нет. Консоль создает окружения, читает все транскрипты, запускает агенты с доступом к оболочке и файловой системе, а также хранит ключ провайдера, который вы подключили, поэтому открытый порт делает всё это доступным для извне. Публикуйте сервис на loopback с помощью -p 127.0.0.1:3000:3000 и обращайтесь к нему через SSH-туннель или reverse proxy с TLS termination. Межсетевого экрана хоста недостаточно: 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, пока разработчики не исправят это в upstream.

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

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

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

Функции сброса пароля по почте нет, так как в системе отсутствуют учетные записи и почтовый сервер. Остановите контейнер, удалите /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.