SSD Nodes Learn 🎉 VPS от $4.99/мес
Руководства Matt ConnorАвтор: Matt Connor

Как развернуть OpenTag для @упоминаний агента

Разверните OpenTag v0.9.0 на VPS: TLS, подписи webhook, права токенов и безопасные настройки доставят @упоминания из Slack и GitHub в coding agent.

Что делает OpenTag при упоминании агента

OpenTag превращает упоминание через @ в ветке Slack или issue на GitHub в запуск coding agent на принадлежащей вам машине. Кто-то оставляет в issue комментарий @opentag investigate this. Listener получает событие от платформы, проверяет его подпись, сопоставляет упоминание с привязанным проектом, запускает coding agent в локальной копии репозитория и публикует результат в той же ветке.

Проект распространяется по лицензии MIT и находится в репозитории amplifthq/opentag. По состоянию на August 2026 последний tagged release — v0.9.0, опубликованный 28 July 2026; он поставляется как npm package. Официального container image нет, поэтому фиксировать нужно версию npm package. Все команды ниже фиксируют эту версию.

Это становится проектом для VPS, а не для ноутбука, из-за части, связанной с GitHub. GitHub отправляет события репозитория HTTP-запросом на URL, который вы регистрируете один раз. Поэтому этот URL должен отвечать по тому же адресу и завтра.

Четыре компонента

Listener принимает события платформы. У каждой платформы свой listener. Listener GitHub — это HTTP endpoint на порту 3050 по пути /github/webhooks. Listener Slack Events API работает на порту 3040 по адресу /slack/events. Slack также поддерживает Socket Mode. В этом режиме приложение открывает исходящий WebSocket, поэтому входящий порт не требуется.

Dispatcher координирует работу. По умолчанию он слушает порт 3030, хранит состояние запусков в локальном файле базы данных, заданном через OPENTAG_DATABASE_PATH, и записывает audit trail для каждого запуска. Ничто за пределами этого сервера не должно обращаться к этому порту.

Runner — это локальный daemon. Он опрашивает систему на наличие задач, принимает запуск в работу, удерживает lease и по умолчанию отправляет heartbeat каждые 15 секунд, пока запуск выполняется. Он отклоняет любой принятый запуск, если target проекта отсутствует или не входит в allowlist в его собственной конфигурации. Эта проверка не позволяет событию GitHub направить ваш agent в репозиторий, который вы не привязали.

Executor — это сам coding agent. OpenTag запускает его через ACP (agent client protocol) — протокол JSON-RPC, работающий через standard input и output. Поэтому agent запускается как дочерний процесс в рабочем каталоге, который ему передаёт OpenTag. Встроенные имена включают echo, codex, claude-code, cursor, opencode, hermes и openclaw. Начните с echo — executor, с которым поставляется пример конфигурации. Это позволяет проверить весь путь до того, как model получит доступ к вашему коду.

Порядок всегда одинаков: событие платформы, проверка подписи, запись запуска, принятие в работу, agent, ответ в thread.

Почему ноутбука и туннеля недостаточно

В руководстве по настройке GitHub предлагается выполнить ngrok http 3050 и вставить адрес туннельного хоста в webhook репозитория. Это работает первые десять минут. Адрес бесплатного туннеля меняется при каждом перезапуске процесса и перестаёт существовать, когда ноутбук переходит в спящий режим. GitHub сохраняет старый URL для payload и продолжает отправлять запросы по нему. Поэтому вкладка Recent Deliveries в настройках webhook заполняется ошибками, а в обсуждении нет сообщений. Это может оставаться незамеченным неделю, потому что webhook, который ничего не делает, выглядит так же, как бот, о котором никто не сообщил.

VPS устраняет обе причины сбоев. DNS-имя не меняется, поэтому однажды вставленный URL для payload остаётся корректным. Сервер не переходит в спящий режим, поэтому на комментарий в 02:00 будет отправлен ответ. Сначала правильно настройте сервер: в разделе первые десять минут на новом VPS описаны пользователь для входа и firewall, которые предполагает это руководство.

Slack — исключение. В Socket Mode он устанавливает исходящее соединение и не требует публичного URL, поэтому развертывание только Slack может оставаться закрытым. У GitHub нет эквивалентного режима. Webhook репозитория использует входящие HTTP-запросы. Значит, требуется публичная конечная точка, TLS (transport layer security) и проверка подписи.

Развертывание Self-host OpenTag в Ubuntu из закреплённого релиза

Для OpenTag v0.9.0 требуется Node.js 22 или новее. В собственном репозитории Ubuntu 24.04 доступен Node 18, поэтому установите Node.js из NodeSource.

curl -fsSL https://deb.nodesource.com/setup_22.x -o nodesource_setup.sh
sudo -E bash nodesource_setup.sh
sudo apt install -y nodejs
node -v

Команда node -v должна вывести v22 или более новую версию. В Node 20 при установке появляется предупреждение EBADENGINE, а CLI может завершиться с ошибкой после запуска.

Выделите для сервиса отдельную учётную запись. Агент работает с правами этого пользователя, поэтому не используйте свою учётную запись и root. В разделе Отдельные пользователи с минимальными правами на VPS объясняется, почему этот дополнительный шаг оправдан.

sudo adduser --disabled-password --gecos "" opentag
sudo loginctl enable-linger opentag
sudo npm install -g @opentag/cli@0.9.0
command -v opentag

Команда command -v opentag должна вывести путь, например /usr/bin/opentag. Настройка linger важна в Linux: OpenTag устанавливает фоновый сервис через systemd, а пользовательский сервис без linger останавливается сразу после закрытия SSH-сеанса.

Запустите настройку от имени этого пользователя.

sudo -iu opentag opentag setup

Мастер настройки запрашивает 6 параметров: язык CLI, локальный адрес прослушивания, coding agent, локальный проект для работы, учётные данные платформы для сохранения и способ запуска. Оставьте адрес прослушивания на 127.0.0.1, поскольку nginx завершает TLS и пересылает запросы на этот адрес. Поэтому порты прослушивания не должны быть доступны извне. Для GitHub мастер также запрашивает репозиторий в формате owner/repo, разрешение на открытие pull request, порт webhook (3050 по умолчанию) и токен. В конце выберите режим фонового сервиса. Если конфигурация уже существует и нужно установить сервис без запросов, используйте opentag setup --service.

Конфигурация сохраняется в /home/opentag/.config/opentag/config.json, а состояние выполнения — в /home/opentag/.local/state/opentag. После записи файла проверьте эти параметры вручную.

{
  "runnerId": "runner_local",
  "dispatcherUrl": "http://localhost:3030",
  "runnerToken": "...",
  "approvalMode": "ask",
  "repositories": []
}

Предпочтительнее использовать runnerToken — bearer token, ограниченный областью runner, — вместо устаревшего общего pairingToken. Если не заменить учётные данные ссылкой на секрет, конфигурационный файл хранит их в открытом виде. Ссылка на секрет считывает значение из переменной окружения или из файла на диске при запуске. В любом случае этот файл является самым чувствительным объектом на сервере: установите режим 600, назначьте владельцем opentag и никогда не размещайте файл внутри git-репозитория. Подробнее это объясняется в разделе как не передавать секреты AI-агентам.

Проверьте установку до публикации сервиса.

sudo -iu opentag opentag doctor
sudo -iu opentag opentag status

Команда opentag doctor проверяет dispatcher, bindings, checkouts и executors. Команда opentag status выводит конфигурацию и состояние выполнения. После появления запусков её можно ограничить одним запуском. Исправьте всё, что сообщает doctor, прежде чем подключать платформу к этому серверу.

Настройте TLS перед сервисом и откройте только два пути

nginx завершает TLS и передаёт запросы ровно по двум путям. Для всех остальных путей возвращается 404. Поэтому сканирование хоста не раскрывает, какие сервисы работают за ним.

Создайте обычный блок server для порта 80 в /etc/nginx/sites-available/opentag с двумя указанными ниже location, затем разрешите Certbot добавить часть конфигурации для TLS.

sudo apt install -y nginx certbot python3-certbot-nginx
sudo ln -s /etc/nginx/sites-available/opentag /etc/nginx/sites-enabled/opentag
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d opentag.example.com

nginx -t выводит syntax is ok и test is successful. Это единственная проверка между опечаткой и перезагрузкой, после которой сайт перестанет работать. В разделе Certbot в Ubuntu 24.04 с nginx описаны продление сертификата и причины сбоев проверки ACME (automatic certificate management environment). Готовый блок выглядит так.

server {
    listen 443 ssl;
    server_name opentag.example.com;

    ssl_certificate     /etc/letsencrypt/live/opentag.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/opentag.example.com/privkey.pem;

    client_max_body_size 2m;

    location = /github/webhooks {
        proxy_pass http://127.0.0.1:3050;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }

    location = /slack/events {
        proxy_pass http://127.0.0.1:3040;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }

    location / {
        return 404;
    }
}

Директива = в location = /github/webhooks выполняет точное сопоставление, а proxy_pass без символов после порта передаёт исходный URI без изменений. Если убрать =, будут передаваться также все пути внутри /github/webhooks/. Это открывает больше поверхности, чем требуется listener.

Правила firewall остаются ограниченными.

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw status

Порты 3030, 3040 и 3050 не открываются. Убедитесь, что они привязаны к loopback, а не ко всем интерфейсам.

sudo ss -tlnp

Каждая строка OpenTag должна иметь вид 127.0.0.1:3030 или аналогичный. Строка с 0.0.0.0:3050 означает, что listener доступен всему Интернету, а его блокирует только ufw. Достаточно одной ошибки в firewall, чтобы открыть триггер агента. В разделе основы firewall ufw объясняется, как именно работает этот запрет по умолчанию.

Две проверки подтверждают корректность внешнего входа. curl -I https://opentag.example.com/ возвращает 404 от nginx. Это показывает, что сертификат действителен, а catch-all закрыт. Запрос к /slack/events или /github/webhooks без подписи никогда не должен возвращать 200.

Проверяйте каждую подпись, поскольку URL общедоступен

URL для отправки данных может найти любой. Он хранится в настройках репозитория, истории браузера и на снимке экрана, прикреплённом к заявке. Подпись — единственное, что отличает настоящую доставку GitHub от запроса, введённого вручную.

GitHub подписывает каждую доставку с помощью секрета webhook и передаёт результат в заголовке x-hub-signature-256. OpenTag проверяет этот заголовок по значению platforms.github.webhookSecret. В примечаниях по усилению защиты проекта это правило сформулировано прямо: не принимайте неподписанные события источника на /github/webhooks. Slack подписывает каждый запрос с помощью SLACK_SIGNING_SECRET и добавляет временную метку, поэтому перехваченное тело запроса нельзя повторно отправить через несколько часов.

Пропуск этой проверки создаёт серьёзный риск. Непроверяемая конечная точка принимает вручную сформированный payload issue_comment, содержащий @opentag, после чего OpenTag запускает coding agent с вашим токеном в вашем checkout по инструкциям постороннего пользователя. Ответ отправляется в любую ветку, указанную в поддельном payload.

OpenTag добавляет ещё два уровня защиты. Доставки от источника отслеживаются по ID доставки, поэтому повторная доставка того же события не запускает второй процесс. Вызовы Runner принимают ключи идемпотентности, поэтому повторная отправка запроса возвращает успешный результат, не добавляя новое событие аудита.

Ограничения скорости настраиваются и должны быть включены. OPENTAG_RATE_LIMIT_WINDOW_MS и OPENTAG_RATE_LIMIT_MAX_REQUESTS ограничивают частоту запросов, OPENTAG_MAX_REQUEST_BODY_BYTES ограничивает размер тела запроса, а слишком большой payload отклоняется с ошибкой 413 request_body_too_large. OPENTAG_RATE_LIMIT_DISABLED=true предназначен для локальной разработки и не должен использоваться на общедоступном сервере. В тех же примечаниях есть ещё одно правило: общедоступный relay URL должен использовать HTTPS, а CLI разрешает обычный HTTP только для localhost.

Какие области доступа на самом деле нужны боту?

В GitHub OpenTag использует fine-grained personal access token, а не GitHub App. В документации указано, что вариант с App запланирован и пока не является стандартной настройкой CLI. Это приводит к важному последствию, которое часто упускают: бот оставляет комментарии от имени пользователя, создавшего токен. Создайте его под учётной записью, имя которой вы готовы видеть в каждом ответе при разборе обращений.

Задайте области доступа так, как указано в руководстве по настройке. Выберите Only select repositories и укажите один репозиторий. Предоставьте Issues: Read and write и Pull requests: Read and write. Этого достаточно, чтобы прочитать упоминание и ответить в обсуждении.

Обратите внимание, чего здесь нет: доступа на запись к коду. OpenTag не отправляет ветки, если preparePullRequestBranch не установлено в значение true. Для токена, который записывает код, предусмотрена отдельная настройка githubApplyToken, поэтому этот токен не должен совпадать с токеном, который оставляет комментарии. Разделяйте их и не включайте токен с доступом на запись, пока путь чтения и комментирования не проработает несколько недель.

Не используйте токен с Contents: Read and write для All repositories. Любой пользователь, который может оставлять комментарии в одном из этих репозиториев, сможет управлять агентом с правами на фиксацию изменений, а в журнале аудита это будет записано как действие владельца токена. Расширяйте область доступа по одному репозиторию за раз, после того как агент подтвердит свою надёжность.

В Slack областями доступа бота являются app_mentions:read, chat:write, reactions:write и channels:history. Для private channels также нужны groups:history и подписка на событие message.groups. Для Socket Mode требуется app-level token с connections:write, который начинается с xapp-. channels:history читает историю сообщений в public channels, куда добавлен бот. Поэтому добавляйте бота в нужные каналы, а не во все.

Проведите одну задачу от начала до конца

Сначала настройте webhook. В репозитории откройте Settings, затем Webhooks и Add webhook. URL для отправки данных — https://opentag.example.com/github/webhooks, тип содержимого — application/json, а секрет — тот, который сгенерировала система настройки. Подпишитесь только на Issue comments и Pull request review comments.

GitHub сразу после сохранения отправляет тестовую доставку ping. Откройте Recent Deliveries и проверьте, достиг ли запрос сервера вообще. Ответ 502 означает, что nginx не смог подключиться к listener. Это локальная проблема, а не проблема GitHub.

Теперь проверьте работу. Откройте issue с описанием ошибки и добавьте комментарий:

@opentag triage this. Reproduce the report against the current main branch, then reply with the file and function most likely responsible, plus the test you would write first.

Порядок событий должен быть таким. Recent Deliveries регистрирует доставку issue_comment с ответом 2xx. Dispatcher регистрирует запуск. Runner получает его и начинает отправлять heartbeat. Executor открывает checkout и выполняет работу. Ответ появляется в виде комментария в той же ветке issue. В sudo -iu opentag opentag status отображается выполняющийся запуск, поэтому вы можете наблюдать за ним, а не предполагать, что происходит.

Перед первым рабочим запуском установите approvalMode в значение ask. В режиме ask запуск приостанавливается и ожидает подтверждения пользователя перед любым изменением состояния. Режимы auto и autonomous также доступны. Их можно использовать позже в репозитории, где вы уже изучили журналы запусков за месяц.

В Slack тот же запуск начинается с /bind owner/repo в канале, затем следует упоминание. Бот также отвечает на /help, /status, /doctor, /stop и /unbind confirm. Ограничьте круг пользователей, которые могут изменять привязки, с помощью OPENTAG_SLACK_BINDING_ADMIN_USER_IDS. Это список Slack user ID, разделённых запятыми. Привязка сопоставляет публичный канал с checkout на вашем сервере.

Triage — хороший первый сценарий, потому что он читает данные и ничего не изменяет, а результат легко проверить. Следующий уровень — Review. В этом сценарии агент комментирует diff, а не issue: self-hosted агент для проверки pull request использует ту же архитектуру, но работает с pull request. Если во время работы агенту нужно обращаться к вашим системам, используйте MCP-серверы на VPS.

Что происходит, если агент ошибается у всех на виду?

Он будет ошибаться. Вопрос в том, во что это обойдётся.

Ошибочный ответ в публичной задаче — это комментарий от имени, которое узнает ваша команда, а GitHub отправляет его всем подписчикам сразу после публикации. Удаление комментария не отозвёт письмо. То же относится к уведомлению Slack. Планируйте работу с учётом того, что ответ может оказаться ошибочным публично, а не с расчётом на правильный ответ в приватном режиме.

Четыре решения ограничивают ущерб. Они важнее любого написанного вами промпта.

  • Запускайте в режиме ask: агент предлагает решение, человек его утверждает, а ошибочный план стоит одного нажатия.
  • Оставьте для preparePullRequestBranch значение по умолчанию false. Тогда худший результат неудачного запуска — ошибочный комментарий, а не изменение ветки.
  • Для начала привяжите один репозиторий и один канал. Runner отклоняет любой запуск, если целевой проект находится за пределами его локального списка разрешённых объектов. Поэтому непривязанный репозиторий не сможет сам подключить агент.
  • Храните токен для комментирования отдельно от токена для применения изменений. Тогда отзыв прав на запись не остановит triage.

В Slack есть команда /stop для запуска, который идёт в неправильном направлении. Каждый запуск также оставляет запись аудита с упоминанием, которое его запустило, и выполненными агентом действиями. По этой записи вы затем определяете, где произошла ошибка.

Социальная сторона не менее важна, чем конфигурация. Поместите бота в один канал, где люди ожидают присутствия автоматизированного инструмента и знают, что он может ошибаться. Уверенный ошибочный ответ в канале из сорока человек, которые считают, что его проверил человек, обойдётся дороже, чем сэкономленное время на triage. Укажите в описании канала, кто отвечает за бота и кто проверяет его вывод.

Резервные копии, обновления и фиксация версии

Всё необходимое хранится в двух путях: /home/opentag/.config/opentag/config.json и /home/opentag/.local/state/opentag. В первом находятся учётные данные, во втором — история запусков и файл базы данных. Создавайте резервные копии обоих путей с правами доступа 600 и храните их отдельно от сервера. При их утрате потребуется заново создать токены и привязки, но не восстанавливать сервер целиком.

Обновление состоит из смены версии и перезапуска.

sudo npm install -g @opentag/cli@0.9.0
sudo -iu opentag opentag service stop
sudo -iu opentag opentag service start
sudo -iu opentag opentag doctor

Зафиксируйте версию вместо отслеживания @latest. Это программное обеспечение запускает coding agent в вашем репозитории с действующим токеном, поэтому выпуск, опубликованный ночью, становится нерассмотренным изменением этой системы. Политика безопасности не переносит исправления в старые версии, а исправления выходят только в новейшем выпуске. Поэтому фиксация версии означает, что вы читаете журнал изменений и обновляетесь намеренно. Это не означает, что нужно навсегда оставаться на v0.9.0. История по июль 2026 года показывает, что каждый месяц выходит несколько выпусков. Это веская причина читать примечания к выпускам перед каждым обновлением версии.

FAQ

Нужен ли VPS для запуска OpenTag или достаточно ноутбука?

Для одного Slack достаточно ноутбука: Socket Mode открывает исходящее WebSocket-соединение, поэтому входящий порт не нужен. С GitHub ситуация отличается. Webhook репозитория доставляет запросы по входящему HTTP на URL, который вы регистрируете один раз. Поэтому этот адрес должен оставаться неизменным и отвечать на запросы, пока вы спите. Адрес tunnel host из бесплатной учётной записи меняется после каждого перезапуска, а GitHub продолжает отправлять запросы на старый адрес. Это отображается как ошибки во вкладке Recent Deliveries репозитория и как отсутствие сообщений в треде. VPS с постоянным DNS-именем и сертификатом устраняет обе проблемы.

Какие разрешения GitHub нужны OpenTag?

Подойдёт fine-grained personal access token с ограничением Only select repositories, которому назначены разрешения Issues: Read and write и Pull requests: Read and write. Этого достаточно, чтобы прочитать упоминание и ответить в треде. Доступ на запись к коду не нужен, если только вы не установите preparePullRequestBranch в значение true, чтобы OpenTag отправлял ветки. Для этого предусмотрен отдельный githubApplyToken, поэтому токен для записи кода можно отделить от токена для комментариев. Не используйте токен для всех репозиториев с разрешением contents write. Любой пользователь, который может оставлять комментарии в одном из таких репозиториев, тогда сможет управлять агентом, имеющим право создавать коммиты.

Как остановить ошибочный запуск?

В Slack для этого предусмотрена команда /stop. На сервере opentag status показывает выполняемые процессы, а opentag service stop останавливает daemon. Это завершает весь pipeline, а не только один запуск. Чтобы не останавливать процесс вручную, установите approvalMode в значение ask. Тогда перед любыми изменениями запуск будет приостанавливаться и ожидать подтверждения пользователя. Оставьте preparePullRequestBranch в значении false, чтобы ошибочный запуск создавал комментарий, а не ветку.

Почему webhook возвращает 502, а в треде нет сообщений?

Код 502 возвращает nginx, а не OpenTag. Он означает, что proxy не смог подключиться к listener. /var/log/nginx/error.log покажет connect() failed (111: Connection refused) while connecting to upstream. Listener остановлен либо использует порт, отличный от указанного в строке proxy_pass. Выполните sudo ss -tlnp и убедитесь, что на порту 127.0.0.1:3050 есть listener для GitHub, а на порту 127.0.0.1:3040 — listener для Slack. Затем выполните opentag doctor, чтобы проверить bindings и executors.

#opentag#ai-agents#slack#github#webhooks#self-hosting