Кукбук BPMkitStand

Свободный диспетчер стендов BPMSoft: что это, как поставить, как жить с ним каждый день и как вынести управление на удалённые хосты. Документ для оператора и администратора сразу — всё по плиткам, ищите через поиск сверху.

01

Что такое BPMkitStand

Коротко

  • Бесплатный (MIT) диспетчер стендов BPMSoft
  • Локальный веб-дашборд в браузере вместо команд в PowerShell
  • Локальные и удалённые стенды — одним списком
  • Ядро — чистая стандартная библиотека Python, Windows и Linux

🛠️Что делает

  • Список стендов из общего реестра и состояние в реальном времени
  • Старт / стоп / рестарт с честной обратной связью
  • Логи текущей сессии, открытие папок логов
  • Очистка Redis-кэша стенда
  • Регистрация уже существующего стенда в реестре

🚧Чего не делает

  • Не разворачивает стенд «с нуля» — это провижининг, зона BPMkit
  • Не деплоит пакеты и не правит конфигурацию платформы
  • Не делает операций с БД, кроме проверки, что порт отвечает
  • Полная разделительная линия — раздел Границы

Три части продукта

⚙️standkit — ядро

  • Движок жизненного цикла над реестром
  • Health-пробы: процесс / HTTP / порт БД / порт Redis
  • Чтение хвоста лога, Secret-first доступ к секретам
  • Без веб-слоя — годится как библиотека

🖥️standkit_hub — дашборд

  • То, что видит оператор: веб-интерфейс в браузере
  • Сам себя отдаёт по HTTP, без CDN и сборки
  • Ставится только на машину оператора
  • Опционально — нативное окно (--desktop)

📡standkit_agent — агент

  • Лёгкий headless-демон на хосте удалённого стенда
  • То же ядро, обёрнутое в крошечный HTTP/RPC-слой
  • Нужен только для удалённого управления
  • Windows и Linux, только стандартная библиотека

Как это собрано — за один взгляд

Машина оператора Браузер: BPMkit Дашборд вкладка, PWA или нативное окно standkit_hub http.server на 127.0.0.1:8770 standkit — ядро жизненный цикл, пробы, логи projects.json — общий реестр Хосты удалённых стендов standkit_agent на каждом хосте transport: agent HTTPS + Bearer-токен, mTLS Локальные стенды transport: local — та же машина kestrel / iis / docker / k8s
Дашборд — федеративный клиент: локальными стендами управляет через ядро напрямую, удалёнными — через агентов. Реестр projects.json у оператора один и тот же для обоих случаев.

Для кого

⚙️Администратор

  • Стенды под контролем без консоли
  • Удалённые контуры через агентов

💻Разработчик

  • Быстрый рестарт и хвост лога
  • Очистка Redis в один клик

🎯РП / пресейл

  • Демо-стенды: поднять перед показом
  • Видно, что живо, а что нет

🤖Пользователь BPMkit

  • Общий реестр с AI-агентом
  • Стенд из дашборда сразу виден агенту
🧩 Место в экосистеме. BPMkitStand — бесплатная часть экосистемы BPMkit и полноценный диспетчер сам по себе. Companion-версия на той же кодовой базе дополнительно даёт автообновление MCP BPMkit и контроль лицензии; она поставляется в составе установщика MCP-клиента.
02

Установка и первый запуск

🖥️Что нужно на машине оператора

  • Python 3.10+
  • Windows 10 / 11 или Linux
  • Браузер (Chrome, Edge, Firefox — любой современный)
  • Сетевой доступ до стендов и их БД

📦Что нужно на хосте стенда

  • kestrel: установленный dotnet
  • iis: Windows + IIS, диспетчер от администратора
  • docker: Docker Engine, для compose — Compose V2
  • k8s: kubectl в PATH и рабочий kubeconfig

🔓Что не нужно

  • Никаких сторонних веб-фреймворков и GUI-тулкитов
  • Ни одной pip-зависимости у ядра и агента
  • Интернета в рантайме: фронтенд без CDN, работает офлайн
  • Отдельного сервера — дашборд локальный

Шаг 1 — поставить пакет (по-разному в Windows и Linux)

Пакет один и называется standkit — в нём сразу ядро, дашборд и агент, обязательных зависимостей у него нет. Отличается только способ установки: в Windows пакет обычно ставят прямо в пользовательский Python, в Linux системный Python трогать нельзя.

🪟Windows

  • Python 3.10+ с python.org; при установке отметьте «Add python.exe to PATH»
  • Ставим обычным pip:
pip install standkit
  • С дополнениями (хранилище секретов + нативное окно):
pip install "standkit[secrets,desktop]"
  • Запуск: standkit-hub; если консоль его не видит — py -m standkit_hub
  • Ярлык на рабочий стол: standkit-hub --install-shortcut

🐧Linux

В современных дистрибутивах системный Python помечен как «externally managed» (PEP 668): обычный pip install в него откажет с error: externally-managed-environment.

Это не про standkit — так ведут себя все пакеты. Ставим в изолированное окружение.

Вариант A — pipx (рекомендуется: изоляция + команды сразу в PATH):

sudo apt install pipx # Debian/Ubuntu; иначе: python3 -m pip install --user pipx pipx ensurepath # добавляет ~/.local/bin в PATH — нужен новый вход в shell pipx install "standkit[secrets]" standkit-hub

Вариант B — venv (если pipx нет или окружение нужно своё):

python3 -m venv ~/.venvs/standkit ~/.venvs/standkit/bin/pip install "standkit[secrets]" ~/.venvs/standkit/bin/standkit-hub # при желании — короткая команда: sudo ln -s ~/.venvs/standkit/bin/standkit-hub /usr/local/bin/standkit-hub

Дополнение [desktop] (нативное окно) на Linux требует WebKitGTK в системе; без него просто пользуйтесь браузером. К уже поставленному pipx-пакету дополнение добавляется инъекцией: pipx inject standkit keyring.

Разворачиваете агента на Linux-хосте стенда — есть отдельный подробный кукбук docs/COOKBOOK_LINUX.md: сервисный аккаунт, systemd-юнит, TLS/mTLS, ротация токена и таблица типичных ошибок с реальными сообщениями.

--break-system-packages — не на рабочем сервере

На Debian/Ubuntu системный Python обслуживает сам apt; сломав его, вы теряете управление пакетами машины.

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

🐍Скрипты — питоном того окружения, куда поставили пакет

Системный python3 модуля не видит — будет No module named 'standkit_agent'.

# ставили через pipx ~/.local/share/pipx/venvs/standkit/bin/python # ставили в venv ~/.venvs/standkit/bin/python
🖧 Дашборд на headless-сервере. Хаб слушает только 127.0.0.1, и это правильно — порт наружу не открываем. Смотрим через SSH-проброс, в три шага.

🖥️1. На сервере

standkit-hub --no-browser

URL с сессионным токеном хаб печатает в консоль — он понадобится на шаге 3.

🔀2. С рабочей машины

# с рабочей машины ssh -L 8770:127.0.0.1:8770 \ user@example-host

Локальный порт 8770 пробрасывается на loopback сервера.

🌐3. В браузере у себя

http://127.0.0.1:8770

Открываем тот самый URL с токеном из вывода хаба.

Шаги 2–5 — одинаково в обеих ОС

Запустите дашборд Хаб слушает 127.0.0.1:8770, печатает URL с сессионным токеном и открывает браузер. Если команда «не распознана» — тот же запуск модулем работает всегда: python -m standkit_hub (разбор — в разделе Траблшутинг).
standkit-hub
Зарегистрируйте стенды Кнопка «Зарегистрировать стенд» на вкладке «Стенды» — или правка projects.json руками (раздел Реестр стендов). Регистрируется уже существующий стенд: каталог, БД и дистрибутив должны быть готовы заранее.
Задайте секреты В реестре хранятся только ссылки на секреты. Самый простой путь — кнопка «Задать секрет…» в «Настройках» дашборда. Из терминала (нужно дополнение [secrets]) — так значение не попадёт в историю команд:
python -c "import getpass; from standkit.secrets import set_secret; set_secret('standkit:my-stand:db', getpass.getpass('Значение: '))"
На машине без keyring (headless-сервер, служба) секрет задаётся переменной окружения — см. плитку «Секреты» ниже.
Сделайте ярлык и проверьте Ярлык: standkit-hub --install-shortcut. В Chrome/Edge дашборд дополнительно ставится как приложение (PWA): своё окно, иконка в панели задач, место в Alt+Tab. Дальше — «Первая проверка» ниже.

Секреты: три источника, приоритет сверху вниз

🌱1. Переменная окружения

  • Имя — STANDKIT_SECRET__ + ссылка на секрет в ВЕРХНЕМ регистре
  • Все не-буквенно-цифровые символы в ссылке заменяются на _
  • standkit:client-uat:agent-token
    STANDKIT_SECRET__STANDKIT_CLIENT_UAT_AGENT_TOKEN
  • Единственный рабочий путь на headless-хосте и для службы

🔐2. Системный keyring

  • Нужен extra standkit[secrets]
  • Задать: кнопка «Задать секрет…» в дашборде или однострочник выше
  • Служба под сервисной учёткой без сессии до keyring обычно не достучится

📄3. Открытое поле в реестре

  • Явный фолбэк (например db_password) — сознательно наименьший приоритет
  • Для прод-контуров не использовать
  • ⚠️ Отдельного CLI (python -m standkit.secrets …) в пакете нет — такая команда молча ничего не делает

Первая проверка

  • В консоли — строка дашборд слушает 127.0.0.1:8770 и URL с токеном
  • В браузере открылась вкладка «BPMkit Дашборд», индикатор в правом верхнем углу — зелёный «онлайн»
  • На вкладке «Стенды» видны ваши стенды (или пустая таблица, если реестр пуст)
  • Клик по строке стенда наполняет панель «Текущее состояние» внизу

🔁Повторный запуск

  • Хаб — single-instance: второй клик по ярлыку не поднимает второй диспетчер, а открывает браузер на уже работающем
  • Закрытие окна браузера не останавливает хаб — процесс живёт дальше (у него нет idle-выключения)
  • Остановить: Ctrl+C в консоли хаба (завершается штатно, без трейсбека)
  • Если порт 8770 занял чужой сервис — хаб честно напишет об этом и возьмёт свободный

Обновление диспетчера

⬆️Как обновиться

  1. Остановите работающий диспетчер: Ctrl+C в его консоли (закрытие окна браузера процесс не останавливает)
  2. Обновите пакет — командой того способа, которым ставили:
    pip install -U standkit # Windows / обычный pip pipx upgrade standkit # Linux, установка через pipx ~/.venvs/standkit/bin/pip install -U standkit # Linux, установка в venv
  3. Запустите заново standkit-hub и обновите вкладку — Ctrl+F5, чтобы браузер взял свежую статику
  4. Проверьте версию: кнопка в шапке → «О программе» (или pip show standkit / pipx list)

🧭Что важно знать

  • Ваши данные не трогаются: реестр стендов, конфиг дашборда и секреты живут в профиле пользователя, а не внутри пакета
  • Агентов обновляйте вместе с хабом — на каждом хосте тем же обновлением пакета
  • После обновления агента — рестарт службы: sudo systemctl restart standkit-agent либо nssm restart standkit-agent
  • Ярлык и установленное PWA-приложение пересоздавать не нужно
  • Что изменилось — docs/CHANGELOG.md в репозитории
  • Конкретная версия и откат — пакет живёт на PyPI, ставить из git не нужно
pip install "standkit==0.6.1" # пример: откат на конкретный выпуск pipx install --force "standkit==0.6.1" # то же через pipx pip index versions standkit # какие версии вообще есть
🤖 Не путать с обновлением MCP BPMkit. Бесплатный диспетчер обновляется вручную командой выше. Автообновление самого MCP BPMkit и контроль лицензии — это Companion-версия из установщика MCP-клиента, а не pip.

Ключи запуска standkit-hub

КлючПо умолчаниюЗачем
--host127.0.0.1 Адрес, на котором слушать. Loopback — безопасный дефолт; менять без TLS нельзя (см. Безопасность)
--port8770 Порт фиксирован осознанно: браузер узнаёт дашборд по origin и помнит тему и кэш. 0 — эфемерный порт
--configпрофиль пользователя Свой путь к конфигу хаба вместо %APPDATA%\BPMkit\standkit-hub.json
--no-browserвыклНе открывать браузер автоматически
--desktopвыкл Нативное окно вместо браузера. Требует extra standkit[desktop]; без него хаб предупредит и откроет браузер
--insecureвыкл Осознанный обход fail-closed-проверки bind. Только dev/тест
--install-shortcut / --uninstall-shortcut Создать/удалить ярлык на рабочем столе и выйти, не поднимая сервер

Где что лежит

ЧтоWindowsLinux
Реестр стендов %APPDATA%\BPMkit\projects.json ~/.config/BPMkit/projects.json
Конфиг дашборда %APPDATA%\BPMkit\standkit-hub.json ~/.config/BPMkit/standkit-hub.json
Каталог pid-файлов и логов запуска Задаются в «Настройках» (поля «Каталог pid-файлов» и «Каталог логов»)
Аудит-лог агента ~/.standkit/audit.log, если не задан --audit-log
📌 Путь к реестру резолвится по порядку: переменная окружения BPMSOFT_PROJECTS_FILE → профиль пользователя → ./projects.json в текущем каталоге. Первый запуск сам создаёт папку реестра, чтобы показанный путь вёл в реальное место, а не «в никуда».
03

Дашборд: экран за экраном

Три вкладки: Стенды — повседневная работа, Локальный агент — нужен только хосту удалённых стендов, Настройки — пути, интервал опроса, федерация.

Дашборд BPMkitStand, вкладка «Стенды», тёмная тема Дашборд BPMkitStand, вкладка «Стенды», светлая тема
Вкладка «Стенды»: таблица состояния, действия построчно и панель «Текущее состояние» с хвостом лога выбранного стенда. Скриншот меняется вместе с темой этого документа.

Колонки таблицы

КолонкаЧто показывает
СтендИмя записи в реестре. Рядом может быть бейдж вне диспетчера — стенд поднят мимо дашборда
ТранспортКак дашборд дотягивается до стенда: local (напрямую через ядро) или agent (по HTTP к агенту хоста)
ПроцессБейдж вердикта: up down unknown (состояние выяснить не удалось) или «проверяется…» на первом опросе. Причина от бэкенда хостинга — во всплывающей подсказке бейджа. Во время прогрева после «Запустить» вместо бейджа — «Запускается…» со спиннером
HTTPАдрес web-хоста стенда ссылкой — открывает стенд в новой вкладке. Цвет ссылки = результат HTTP-пробы. Схема берётся из stand_scheme записи: у стенда за TLS и проба, и ссылка идут на https://. Пунктирное подчёркивание = у пробы есть что сказать: наведите мышь — в подсказке причина отказа и URL, по которому стучались
БДИмя базы стенда из реестра, окрашенное по результату пробы (открыт ли TCP-порт БД). Подсказки у этой колонки нет: объяснять пока нечего — адрес задан либо не задан
RedisНомер базы Redis (redis_db) — тот самый, который очистит кнопка 🗑; цвет — результат пробы адреса redis_host/redis_port. Прочерк — номера базы в записи нет. Подсказка при наведении различает «адрес не задан» и «задан, но недоступен» — плитки ниже
ДействияКнопки-иконки: ▶ запустить · ■ остановить · ⟳ перезапустить · 🗑 очистить Redis. Неприменимые гаснут: ▶ у запущенного, ■ и ⟳ у остановленного, 🗑 — пока в записи нет номера базы Redis (redis_db): очищать «какую-нибудь» базу диспетчер не станет

Подсказка в ячейке: почему проба красная

Пунктирное подчёркивание под значением = у пробы есть причина отказа. Наведите мышь — диспетчер объясняет отказ словами и называет поля реестра, которыми он лечится. Раньше на этом месте было одно слово down, одинаковое для закрытого порта, таймаута и ошибки TLS.

🌐HTTP: причина и фактический URL

  • «сервер ответил не по протоколу HTTP» + «похоже, стенд за TLS: задайте stand_scheme=https (и verify_tls=false для self-signed)» — самый частый случай: запрос по http:// ушёл в TLS-порт
  • «соединение отклонено — на 10.0.0.10:5000 никто не слушает» — порт закрыт, стенд погашен или адрес не тот
  • «сертификат не прошёл проверку: …» — и подсказка снять флаг «Проверять сертификат» (verify_tls=false)
  • Реже: «нет ответа за 1.5 с», «имя хоста не разрешается», «сервер ответил 502»
  • Хвост подсказки — URL пробы (URL: http://10.0.0.10:5000/). Именно в нём чаще всего и ошибка; секретов в нём нет — логин и query-строка вырезаются

🧠Redis: «не настроено» ≠ «недоступно»

  • «адрес Redis не задан в реестре (redis_host/redis_port)» — стенду просто не сказали, где Redis. Это не авария, состояние честно остаётся unknown
  • «Redis не отвечает на 10.0.0.10:6379 (адрес проверяется с хоста, где выполняется проба)» — адрес есть, ответа нет: вот это уже отказ
  • Раньше обе ситуации выглядели одинаково, и было непонятно, чинить запись реестра или сеть
  • Оба поля задаются в форме регистрации — см. Реестр стендов
💡 Подсказку формирует та сторона, которая делает пробу: у стенда за агентом причину считает агент на его хосте. Если у такого стенда подсказки нет, а у локального есть — обновлять нужно в первую очередь агента, а не дашборд.

🎬Честный старт

  • Дашборд поднимает процесс стенда и держит спиннер до реального ответа по HTTP
  • «Запущено» не рапортуется по факту создания процесса — прогрев не выдаётся за готовность
  • Остановка мягкая: сначала штатное завершение с ожиданием, принудительное — только потом
  • Стоп, Рестарт и Очистка Redis спрашивают подтверждение

🤝Стенд «вне диспетчера»

  • Стенд, поднятый руками, не имеет pid-файла — обычные Стоп/Рестарт по нему невозможны
  • Диспетчер предлагает усыновление: найти владельца порта и взять процесс под управление
  • Берёт только с явного подтверждения и только при совпадении трёх улик сразу — см. Безопасность
  • Не совпало — отказ с указанием, какой именно процесс занимает порт

Мелочи, которые экономят время

📂Два места логов

  • Сплит-кнопка «Открыть папку логов» помнит последний выбор
  • Стрелка — выбор источника: логи стенда или логи BPMkit-проекта
  • Пункт BPMkit гаснет, если проекта у стенда нет

Компактный режим

  • Кнопка ▭ в шапке — узкое окно-виджет
  • Только имена, состояния и старт/стоп
  • Открывается и напрямую: ?view=compact

Справка под рукой

  • Кнопка ? в шапке открывает этот кукбук
  • Он входит в поставку и работает офлайн — файл можно открыть с диска и при остановленном диспетчере
  • Та же ссылка — в окне «О программе» (ⓘ)

🌓Тема и обновление

  • Тема хранится в конфиге хаба и применяется до загрузки скриптов — светлой вспышки нет
  • Интервал автообновления — в «Настройках»
  • Кнопка «Обновить» форсирует опрос
⚠️ Красный индикатор и баннер сверху означают, что вкладка потеряла связь с диспетчером: процесс хаба остановлен или перезапущен. Таблица в этот момент гасится — то, что на экране, уже неактуально. Если появился возраст снапшота («данные от …»), пробы отстают: проверьте доступность стендов и агентов.
04

Реестр стендов

Реестр — единственный источник правды о стендах. Один и тот же файл projects.json используют дашборд, агент и MCP BPMkit: стенд, заведённый в диспетчере, сразу виден AI-агенту — и наоборот.

📄Где лежит

Порядок резолва пути — первое подходящее:

  1. BPMSOFT_PROJECTS_FILE, если файл существует
  2. канонический путь профиля
  3. ./projects.json в текущем каталоге
  • Windows: %APPDATA%\BPMkit\projects.json
  • Linux: $XDG_CONFIG_HOME/BPMkit/projects.json, иначе ~/.config/BPMkit/projects.json
  • Стенды — под ключом projects, имя записи = имя стенда

На чистой машине файла нет — это норма. Чтение несуществующего реестра даёт пустой список, а сам файл вместе с каталогом создаётся при первой записи.

🔐Secret-first

  • В реестре нет паролей и токенов — только ссылки на секреты (secret_ref_*, agent_secret_ref)
  • Значение живёт в хранилище машины и задаётся отдельно
  • Порядок поиска значения: переменная окружения STANDKIT_SECRET__… → keyring → открытый фолбэк из реестра
  • Задать: кнопка «Задать секрет…» в «Настройках» дашборда или однострочник (нужен extra [secrets]):
python -c "import getpass; from standkit.secrets import set_secret; set_secret('standkit:my-stand:db', getpass.getpass('Значение: '))"

Подробнее — плитки «Секреты» в разделе Установка.

Регистрация ≠ провижининг

  • Кнопка «Зарегистрировать стенд» привязывает уже существующий стенд к реестру
  • Каталог, база и дистрибутив должны существовать заранее
  • Развернуть стенд «с нуля» диспетчер не умеет — это зона BPMkit

Отдельной CLI-команды регистрации в пакете нет — три рабочих пути:

  • кнопка «Зарегистрировать стенд» в дашборде
  • правка projects.json руками по образцу projects.sample.json
  • Registry.add_existing() из Python — рецепт ниже, для сервера без дашборда

Обязательные поля:

  • всегда — name и stand_dir
  • при host_kind=dockerdocker_container или compose-пара
  • при kestrelstand_dll и dotnet

Ключевые поля записи

ПолеЗначенияСмысл
transportlocal · agent Где управлять стендом: тем же процессом или через агента на его хосте. ssh/winrm схема допускает, но логика не реализована
host_kindkestrel (по умолчанию) · iis · docker · k8s Как стенд хостится. Поле независимо от transport — см. Виды хостинга
stand_dirпутьКаталог стенда: логи, pid, проверка усыновления
stand_dll, dotnetBPMSoft.WebHost.dll, dotnet Чем и что запускать в режиме kestrel
stand_host, stand_port127.0.0.1, 5000 Адрес web-хоста: HTTP-проба, ссылка в таблице, поиск владельца порта при усыновлении
stand_scheme, verify_tlshttp · https; true/false Схема HTTP-пробы и ссылки «Открыть стенд»; для самоподписанного сертификата — verify_tls: false. Дефолты http/true сохраняют прежнее поведение — см. Виды хостинга
db_type, db_host, db_port, db_namepostgres · mssql Куда стучаться пробой БД (в бесплатной версии — только «открыт ли порт»)
redis_host, redis_port, redis_dbхост, порт, номер БД Проба Redis и очистка кэша. Если redis_db не задан, дашборд пробует вытащить его из конфигурации стенда
secret_ref_db, secret_ref_adminstandkit:<стенд>:db Ссылки на секреты, не сами секреты
agent_url, agent_secret_refhttps://host:8765 Только при transport: agent — адрес агента и ссылка на его токен
description, customerтекстЧеловеческие пометки, ни на что не влияют

Один стенд — две разные записи в двух реестрах

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

ЧтоРеестр на хосте стенда (читает агент)Реестр оператора (читает дашборд)
transportlocal — агент поднимает стенд у себяagent — дашборд ходит к агенту по HTTP
Адрес агентаagent_url + agent_secret_ref
Как запускаетсяstand_dll/dotnet, docker_container, IIS-сайт…Не важно — запуск делает агент
СекретыПароль БД стенда (secret_ref_db)Токен агента (agent_secret_ref)
Путь файлаЗадаётся агенту явно: --registry /opt/standkit/projects.jsonПрофиль оператора: %APPDATA%\BPMkit\projects.json
🔁 Агент читает реестр при старте. Поправили projects.json на хосте — перезапустите службу агента, иначе увидите прежнюю картину и будете искать несуществующую проблему.

Модалка «Зарегистрировать стенд» — поле за полем

🧭 Ключевой факт — прочитать до заполнения формы. Запись agent-стенда в реестре хаба — это маршрут, а не настройки стенда: половина полей либо не нужна, либо значит не то, что кажется.

📡Кто на самом деле работает

При transport=agent пробы и старт/стоп выполняет агент по своей записи реестра.

Хаб только проксирует запрос: GET /stand/<имя>/status.

🧭Зачем тогда запись у хаба

Для маршрутизации: куда идти и каким токеном представиться.

Это agent_url и agent_secret_ref.

🔗И ещё для одной вещи

Для ссылки «Открыть стенд» в таблице.

Её хаб строит сам из stand_scheme, stand_host и stand_port.

🧩Как форма себя ведёт

  • Постоянная часть: имя, транспорт, хостинг, каталоги, схема/host/port, БД, Redis
  • Условные блоки открываются по выбору списка: agent_* — по транспорту, iis_*/docker_*/k8s_* — по хостингу
  • Чекбокс «Проверять сертификат» появляется по схеме https
  • Скрытый блок на сервер не уходит вовсе: заполнили IIS-поля, передумали и выбрали docker — их значения в реестр не поедут
  • Пустое текстовое поле не перетирает дефолт модели: это «как по умолчанию», а не «пусто»
  • Паролей в форме нет по дизайну — только agent_secret_ref, ссылка на секрет

🎯Минимум для стенда за агентом

  • Имя как у агента — символ в символ
  • Транспорт agent, хостинг оставить kestrel
  • agent_url + agent_secret_ref
  • stand_dir — путь на удалённом хосте
  • Host/Port — чтобы работала ссылка «Открыть стенд»
  • Блоки БД и Redis можно не трогать: их пробу делает агент
Поле формыЧто вписатьЗачем и чем грозит ошибка
Имя стенда
name
stand-a Ключ записи в реестре. Для agent-стенда обязан точно совпадать с именем в реестре агента — оно уходит прямо в URL /stand/stand-a/status. Допустимы буквы, цифры, ., _, -
Транспорт
transport
agent local — стенд на этой же машине, всё делает ядро; agent — стенд на другом хосте, хаб ходит к агенту по HTTP
Хостинг
host_kind
kestrel При transport=agent бэкенд хостинга на стороне хаба не вызывается вообще. Выбрали docker/k8s/iis «за компанию» — включится валидация и потребует профильные поля
Agent URL
agent_url при agent
http://example-host:8765 Схема — как реально слушает агент: поднят с --insecurehttp://, с --tls-cert/--tls-keyhttps://. Порт здесь агентский (8765), а не порт стенда
Ссылка на секрет агента
agent_secret_ref при agent
standkit:stand-a:agent-token Пустое поле форму пройдёт (валидация требует только agent_url), но первый же запрос упадёт с не задан agent_url/agent_secret_ref.
Значение токена — на стороне хаба, см. Удалённые стенды
Каталог стенда
stand_dir
/opt/bpmsoft/stand-a Единственное обязательное поле кроме имени (stand_dir не может быть пустым). Для agent-стенда это путь на удалённом хосте: по нему работает агент, хаб в этот каталог не заглядывает
Каталог логов
logs_dir
обычно пусто Необязательное. Пусто → берётся подкаталог logs внутри каталога стенда, без учёта регистра (Logs и LOGS тоже находятся). Заполнять, только если логи вынесены за пределы каталога стенда
Схема
stand_scheme
http · https Схема HTTP-пробы и ссылки «Открыть стенд». Дефолт http; стенд за TLS на http:// не отвечает вовсе — проба покажет ложный down на живом стенде
Проверять сертификат
verify_tls при https
галка включена Снимать только для дев-контура с самоподписанным сертификатом. Пока выбрана схема http, блок скрыт и на сервер не уходит вовсе — в реестре остаётся дефолт true
Host / Port
stand_host, stand_port
example-host + порт, проброшенный наружу При local — адрес HTTP-пробы; при agentтолько ссылка «Открыть стенд». Оставленный 127.0.0.1 означает машину оператора, и ссылка приведёт в никуда
IIS-сайт / Пул приложений
iis_site, iis_app_pool при iis
имя сайта и пула Нужна хотя бы одна из двух. Кнопка «Определить автоматически» сопоставляет каталог стенда и порт с реальными сайтами через appcmd на этой машине — для стенда за агентом она не поможет
Контейнер / Compose-файл / Compose-сервис
docker_* при docker
stand-a-web Нужен либо docker_container, либо пара compose-файл + compose-сервис. Половина compose-пары валидацию не проходит
Namespace / Deployment
k8s_* при k8s
stand-a k8s_deployment обязателен; пустой k8s_namespace означает default
Тип БД / DB host / DB port / DB nameдля agent-стенда можно пропустить Пробу БД делает тот, кто владеет стендом: при agent — агент по своей записи, поля хаба не используются. При local проба честно проверяет только «открыт ли порт»
Redis host / Redis port
redis_host, redis_port
127.0.0.1 + 6379 Адрес — с точки зрения хоста, где выполняется проба (ядро на этой машине либо агент на хосте стенда), а не машины оператора.
Пара задаётся целиком: половина — ошибка регистрации (redis_port задан без redis_host). Подробности — Виды хостинга

Раньше приходилось править JSON руками — больше нет

  • stand_scheme — список «Схема»
  • verify_tls — чекбокс «Проверять сертификат» (появляется при https)
  • redis_host / redis_port — пара полей внизу формы
  • logs_dir — поле «Каталог логов»

Через файл остались редкие ключи: redis_db, secret_ref_db, secret_ref_admin, distrib_dir, нестандартные stand_dll/dotnet.

🔁Реестр агента читается при старте

  • Имя в форме обязано совпасть с именем в реестре агента — а тот прочитан один раз, при запуске службы
  • Поправили projects.json на хосте стенда — перезапустите агента
  • Иначе хаб получит 404 на имя, которое в файле уже есть
  • Реестр хаба перечитывается сам — его перезапускать не нужно

🛑Агент не стартовал на 0.0.0.0 — это не сбой

  • Отказ старта на не-loopback адресе без TLS — сработавшая fail-closed проверка validate_bind_security
  • Лечится не флагом «отключить», а сертификатами: --tls-cert + --tls-key
  • --insecure — только для проверки связи в доверенной сети
  • С --insecure и в agent_url должно стоять http://, иначе хаб получит ошибку TLS-рукопожатия
🐍 Регистрация стенда на сервере без дашбордарецепт

Дашборд на headless-хосте неудобен (слушает только 127.0.0.1), CLI регистрации нет — поэтому запись добавляется из Python. Питон берём из того окружения, куда поставлен пакет.

/opt/standkit/venv/bin/python - <<'PY' from standkit.models import Stand from standkit.registry import Registry reg = Registry.load("/opt/standkit/projects.json") reg.add_existing(Stand.from_dict("stand-a", { "transport": "local", "host_kind": "docker", "docker_container": "stand-a-web", "stand_dir": "/opt/bpmsoft/stand-a", "stand_host": "127.0.0.1", # адрес ОБРАЩЕНИЯ с хоста, не 0.0.0.0 "stand_port": 5010, # левый порт из docker ps "db_type": "postgres", "db_host": "127.0.0.1", "db_port": 5433, "db_name": "stand_a", "secret_ref_db": "standkit:stand-a:db", "redis_host": "127.0.0.1", "redis_port": 6379, "redis_db": 2, })) reg.save("/opt/standkit/projects.json") print("ok") PY
  • Файл и каталог создаются сами при save() — заводить их заранее не нужно
  • add_existing() валидирует запись: пропущенное обязательное поле для выбранного host_kind вылезет сразу, а не при первом старте
  • После правки реестра — перезапуск агента
📁 Минимальная запись локального стенда (kestrel)
{ "default": "", "projects": { "dev-local": { "transport": "local", "stand_dir": "C:\\BPMSoft\\dev-local", "stand_dll": "BPMSoft.WebHost.dll", "dotnet": "dotnet", "stand_host": "127.0.0.1", "stand_port": 5000, "db_type": "postgres", "db_host": "127.0.0.1", "db_port": 5432, "db_name": "dev_local", "secret_ref_db": "standkit:dev-local:db" } } }
  • Полный образец всех вариантов — файл projects.sample.json в репозитории
  • Реальный projects.json в git не коммитится
🖼️ Модалка «Зарегистрировать стенд» — как она выглядит

Разбор всех полей — таблица «Модалка «Зарегистрировать стенд» — поле за полем» выше. Здесь — только внешний вид: запись уходит в общий projects.json, править файл руками не обязательно.

Форма «Зарегистрировать стенд» с выбранным хостингом iis
Форма с выбранным хостингом iis: поля iis_site / iis_app_pool и кнопка «Определить автоматически» появились по значению списка «Хостинг».
05

Виды хостинга

Два независимых измерения: transportгде вы управляете стендом, host_kindкак стенд хостится на своей машине.

Комбинируются свободно: transport=agent + host_kind=docker — это удалённый контейнер, которым управляет агент на его хосте.

transport — ГДЕ управлять local процессом самого диспетчера agent через standkit_agent по HTTPS ssh · winrm задел, логика не реализована × host_kind — КАК стенд хостится kestrel dotnet + pidfile (дефолт) iis appcmd, управление сайтом docker docker / compose k8s kubectl scale / rollout Общее правило вердикта ответ CLI авторитетнее открытого TCP-порта
Поля независимы. Неизвестное значение host_kind не роняет чтение реестра — откат на kestrel.

Что делает диспетчер под капотом

host_kindОбязательные поляСтарт / стоп / рестарт и «жив ли»
kestrel Запуск dotnet <stand_dll> скрытым процессом + pidfile. «Жив» — процесс по pid и HTTP-ответ
iisiis_site и/или iis_app_pool Через appcmd. «Стенд» = его Site: диспетчер стартует и останавливает сайт и намеренно не трогает App Pool (пул может быть общим с другими приложениями). Пул задействуется, только если iis_site не задан вовсе
dockerdocker_container или пара docker_compose_file + docker_compose_service docker start|stop|restart, состояние — State.Status: приостановленный контейнер зелёным не показывается.
Для compose — docker compose up -d|stop|restart с точным сравнением имени сервиса
k8sk8s_deployment Старт — scale --replicas=N, стоп — scale --replicas=0, рестарт — rollout restart с ожиданием выката. «Жив» — число готовых реплик

Docker: какой адрес писать в реестр

Здесь ошибаются практически все, а расплата — ложный http: down при живом стенде.

Диспетчер (и агент) работают на хосте, вне контейнера. Значит в stand_host/stand_port идёт адрес, по которому до стенда достучаться с хоста.

Не адрес слушания и не внутренний IP контейнера.

🚫0.0.0.0 — неверно

  • Это адрес для bind внутри контейнера, а не адрес для обращения
  • Проба по нему не ходит — стенд живой, а в таблице down
  • Пишите 127.0.0.1 (порт опубликован на хост) или реальное имя/адрес хоста

🔎Где взять порт

docker ps --format '{{.Names}}\t{{.Ports}}'
  • В 127.0.0.1:5010->5002/tcp нужен левый порт — 5010
  • Строка вида 5000/tcp без стрелки = порт объявлен, но наружу не опубликован; с хоста его нет

📌Внутренний IP не писать

  • 172.17.x.x работает, но меняется при пересоздании контейнера
  • То же правило для db_host/db_port и redis_host/redis_port
  • Нет redis_host/redis_port — проба вернёт unknown, а не down
  • Это «не настроено», а не авария: в подсказке ячейки так и написано — «адрес Redis не задан в реестре»

Стенд за TLS: stand_scheme и verify_tls

Оба поля задаются из формы «Зарегистрировать стенд» — править projects.json руками больше не нужно.

Схема — выпадающий список. «Проверять сертификат» — чекбокс, который появляется только при https: при http флаг ничего не значит и потому не показывается.

Разбор формы поле за полем — в разделе Реестр стендов.

🔐Два поля записи

  • stand_schemehttp (по умолчанию) или https: схема, по которой идёт HTTP-проба и строится ссылка «Открыть стенд» в таблице
  • verify_tls — проверять ли цепочку сертификатов; по умолчанию true
  • Для самоподписанного сертификата дев-контура нужен false
  • Дефолты полностью повторяют прежнее поведение — старые реестры править не нужно. Обратная сторона: обновление пакета само по себе ничего не чинит, поля надо выставить
  • Пока чекбокс скрыт (схема http), его значение на сервер не уходит вовсе — в записи остаётся дефолт verify_tls: true
  • Мусор в stand_scheme не роняет чтение реестра: откат на http, а несоответствие поймает валидация записи
"stand_host": "127.0.0.1", "stand_port": 5001, "stand_scheme": "https", "verify_tls": false

🩺Как это выглядит без настройки

  • Стенд за TLS на http:// не отвечает вовсе — проба честно возвращает down при живых process и db
  • С самоподписанным сертификатом и verify_tls: true проба падает на проверке сертификата — тоже down
  • Симптом один и тот же: «стенд открывается в браузере, а в дашборде красный HTTP»
  • verify_tls: false ослабляет проверку только для https://-адресов — на обычный http это не влияет

🛡️IIS: только от администратора

  • appcmd читает конфигурацию IIS — нужны права администратора
  • Членства в IIS_IUSRS недостаточно
  • Запускайте диспетчер «от имени администратора», иначе любая IIS-операция падает по правам
  • Если IIS-службы остановлены — диспетчер прямо посоветует поднять WAS/W3SVC, а не спишет всё на права

🐳Docker и k8s: нужен CLI

  • docker (и плагин compose) либо kubectl должны быть в PATH процесса диспетчера
  • Для k8s — доступный kubeconfig и права на get/scale/rollout/logs
  • Нет CLI — состояние честно помечается как «не выяснено», а не выдаётся за «жив»

💬Состояние объясняется словами

  • «контейнер приостановлен», «контейнер в цикле перезапуска», «деплоймент масштабирован в 0 реплик», «готово 1 из 3 реплик»
  • Вердикт по TCP-порту помечается как неопределённый — «зелёный по порту» не выдаётся за подтверждённый ответ CLI
  • Порт может держать инфраструктура (http.sys у IIS, Service у k8s) даже при погашенном стенде

Каталог логов: как диспетчер его находит

Порядок один и тот же и для панели «Текущее состояние», и для кнопки «Открыть папку логов», и для IIS-бэкенда — общий резолв, а не три копии строки "logs".

🔠Регистр имени больше не важен

  • BPMSoft раскладывает логи в Logs — с заглавной
  • Windows это всё время прощал: файловая система регистронезависима, и жёсткое logs попадало в Logs
  • На Linux тот же путь не существовал — дашборд честно писал «каталог не найден» при живых логах
  • Теперь подкаталог ищется по факту: имена сравниваются без учёта регистра, подходят logs, Logs, LOGS
  • Жёсткой замены на Logs нет — контуры с нижним регистром не сломались
  • Если рядом лежат и logs, и Logs (на Linux так бывает), приоритет у точного совпадения — результат не зависит от порядка обхода

🗂️Нестандартная раскладка — поле logs_dir

  • Задаётся в форме регистрации, поле «Каталог логов»; пусто — работает поиск по каталогу стенда
  • Значение используется как есть: подкаталог logs внутри него не досбирается
  • Каталога по указанному пути нет — источник считается недоступным, а не «поищем рядом»: тихий фолбэк маскировал бы опечатку в реестре
"stand_dir": "/opt/stands/stand-a", "logs_dir": "/var/log/bpmsoft/stand-a"

🪟IIS и .NET Framework — без изменений

  • Приоритет по-прежнему у iis_stdout_log_dir: задан — берётся он
  • Логи BPMSoft под .NET Framework лежат в подпапках-датах (Logs\2026_08_17\Application.log) — и список файлов, и чтение обходят каталог рекурсивно
  • IIS-бэкенд читает сначала плоские *.log в каталоге и только при их отсутствии спускается в подпапки
  • Панель «Текущее состояние» берёт самый свежий файл за сегодня (дневных логов .NET бывают сотни), и только если сегодня записей нет — самый свежий вообще

🛰️Стенд за агентом: каталог не на этой машине

  • Путь в записи описывает файловую систему хоста стенда, а проверяет его хаб — у себя. Локально такого пути нет никогда
  • Поэтому вместо неверного «каталог не найден» хаб пишет прямо:
лог недоступен (источник «Стенд»: каталог логов живёт на хосте стенда; хаб проверяет его локально и потому не видит — /opt/stands/stand-a/logs)
  • POSIX-путь удалённого стенда показывается прямыми слэшами даже на Windows-хабе — раньше в сообщение уезжало \opt\stands\…
  • Хвост лога такого стенда смотрите на его хосте или через API агента: GET /stand/<имя>/logs?n=100

Redis в реестре: поля модели, а не мешок extra

🧩Два нормальных поля

  • redis_host и redis_port — поля записи стенда и поля формы регистрации, по образцу db_host/db_port
  • До этого адрес жил только в нетипизированном extra: ни в форме, ни в модели его не было — искать было нечего
  • Номер базы (redis_db) — отдельная история: он включает кнопку 🗑 и задаётся пока правкой файла реестра
"redis_host": "127.0.0.1", "redis_port": 6379, "redis_db": 2

📍Адрес — с точки зрения хоста пробы

  • Пробу делает та сторона, что управляет стендом: ядро на этой машине или агент на хосте стенда
  • Redis внутри compose без проброса наружу агенту виден, а оператору — нет
  • Не проброшен вообще — будет down, а не unknown: адрес задан, ответа нет. Та же грабля, что с stand_host

Валидация и старые реестры

  • Пара валидна только целиком — половина даёт ошибку записи, а не тихий unknown
  • Тексты ошибок: redis_host задан без корректного redis_port (1–65535) и redis_port задан без redis_host
  • Порт вне диапазона 1–65535 тоже отбивается на валидации
  • Реестры, где ключи лежали в extra, продолжают работать без правок
  • Приоритет у полей модели, extra — фолбэк
06

Удалённые стенды и агент

Стенды на других хостах — виртуалки, серверы, контуры заказчика — управляются из того же дашборда через федерацию лёгких агентов. На хосте стенда поднимается standkit_agent, дашборд ходит к нему по HTTPS с Bearer-токеном.

Машина оператора standkit_hub федеративный клиент локальное ядро + N агентов один список стендов на экране Хост A (Windows) standkit_agent start / stop / restart / status / logs Хост N (Linux) standkit_agent то же ядро standkit внутри HTTPS + Bearer-токен, mTLS HTTPS + Bearer-токен, mTLS
Агент — это то же ядро standkit в крошечной HTTP-обёртке: поведение старта, стопа и логов идентично локальному. Стенд объявляется удалённым одним полем transport: "agent".

Прежде чем ставить: агент — это удалённое исполнение кода

🛑 Правило №1. Агент по HTTP запускает и останавливает процессы на своём хосте. В недоверенную сеть — никогда открытым HTTP: только TLS с клиентской аутентификацией (mTLS) либо loopback внутри VPN / SSH-туннеля.

🧱Порт — только своим

Firewall на конкретные адреса управляющего контура, а не «любой адрес».

ufw allow from <IP> to any port 8765 proto tcp

🔓--insecure — разовый

Только первая проверка связи в доверенной сети; для постоянной работы TLS обязателен.

Связка с --host 0.0.0.0 на публичном IP = Bearer-токен открытым текстом через интернет.

🔑agent.envchmod 600

Токен не показывать в консоли и на скриншотах.

При утечке — перевыпустить.

🗝️Пароли БД — только ссылкой

db_password в projects.json не писать.

Вместо него — secret_ref_db.

Где что лежит на хосте агента

📄Реестр стендов агента

  • Агент читает свой projects.json — в нём стенды этого хоста
  • Они остаются transport: "local": агент поднимает их у себя. Удалённым стенд становится только в реестре оператора
  • Путь задаётся флагом --registry
  • Без флага — тот же порядок, что у дашборда: BPMSOFT_PROJECTS_FILE → профиль пользователя → ./projects.json
  • Для службы всегда задавайте --registry абсолютным путём: у сервисной учётной записи свой профиль, и «тот самый» файл из вашей домашней папки она не увидит
  • Реестр агента и реестр оператора — разные файлы, синхронизировать их не нужно

🗂️Рекомендуемая раскладка

Linux /opt/standkit/venv/ # окружение с пакетом standkit /opt/standkit/projects.json # реестр стендов этого хоста /opt/standkit/tls/agent.crt # серверный сертификат агента /opt/standkit/tls/agent.key # ключ, chmod 600, владелец — сервисный аккаунт /opt/standkit/tls/clients-ca.crt # CA клиентских сертификатов (mTLS) /opt/standkit/logs/audit.log # аудит-лог /opt/standkit/run/ # pid-файлы стендов Windows C:\ProgramData\standkit\projects.json C:\ProgramData\standkit\tls\... C:\ProgramData\standkit\logs\audit.log

Каталоги должны принадлежать сервисной учётной записи агента, а не root/Администратору.

Установка агента: подготовка хоста

Поставить пакет в изолированное окружение

Тот же пакет standkit, что у оператора, — агент входит в него. Нужен Python 3.10+ и dotnet для запуска стендов (для iis/docker/k8s — соответствующий CLI).

Системный Python в Linux не трогаем: службе нужен предсказуемый путь к интерпретатору, поэтому для неё — именно venv, а не pipx.

Linux sudo useradd --system --no-create-home --shell /usr/sbin/nologin standkit sudo mkdir -p /opt/standkit/{tls,logs,run} sudo python3 -m venv /opt/standkit/venv sudo /opt/standkit/venv/bin/pip install standkit sudo chown -R standkit:standkit /opt/standkit Windows (PowerShell от администратора) python -m venv C:\ProgramData\standkit\venv C:\ProgramData\standkit\venv\Scripts\pip install standkit
Положить реестр стендов этого хоста

Файл /opt/standkit/projects.json (Windows — C:\ProgramData\standkit\projects.json). Достаточно стендов этой машины; секретов в нём нет — только ссылки.

Стенды здесь остаются transport: "local". Файл пишется руками или скриптом — рецепт Registry.add_existing() в разделе Реестр стендов.

{ "default": "", "projects": { "client-uat": { "transport": "local", "stand_dir": "/opt/bpmsoft/client-uat", "stand_dll": "BPMSoft.WebHost.dll", "dotnet": "dotnet", "stand_host": "127.0.0.1", "stand_port": 5000, "db_type": "postgres", "db_host": "127.0.0.1", "db_port": 5432, "db_name": "client_uat", "secret_ref_db": "standkit:client-uat:db" } } }

⚠️ В stand_host — адрес обращения к стенду с этого хоста, а не адрес слушания: 0.0.0.0 здесь даёт ложный http: down.

Для контейнеров порт берётся из docker ps — см. Виды хостинга.

Задать токены агента

Токены не передаются в командной строке открытым текстом — только ссылкой на секрет. Заведите разные токены для управления и для чтения.

На сервере без keyring (обычный случай) значение задаётся переменной окружения. Имя — STANDKIT_SECRET__ плюс ссылка в верхнем регистре, где всё, кроме букв и цифр, заменено на _.

python3 -c "import secrets; print(secrets.token_urlsafe(32))" # сгенерировать токен # /etc/standkit/agent.env — chmod 600, владелец = пользователь, под которым идёт агент STANDKIT_SECRET__STANDKIT_CLIENT_UAT_AGENT_TOKEN=<control-токен> STANDKIT_SECRET__STANDKIT_CLIENT_UAT_AGENT_READONLY_TOKEN=<readonly-токен>
Ссылка на секрет в реестреПеременная окружения
standkit:stand-a:dbSTANDKIT_SECRET__STANDKIT_STAND_A_DB
standkit:stand-a:agent-tokenSTANDKIT_SECRET__STANDKIT_STAND_A_AGENT_TOKEN
standkit:client-uat:agent-readonly-tokenSTANDKIT_SECRET__STANDKIT_CLIENT_UAT_AGENT_READONLY_TOKEN

Три грабли подряд, все живые.

ГрабляЧто видноЛечение
Забыт префикс STANDKIT_SECRET__ — имя выглядит правильным SecretError на старте Дописать префикс; полное имя — в таблице выше
Владелец agent.env не совпал с пользователем службы: файл 600 под root, агент под ubuntu Permission denied sudo chown ubuntu:ubuntu /etc/standkit/agent.env
Имя посчитали через echo … | tr — на конце лишний _ от перевода строки SecretError при верной на вид ссылке printf '%s' либо вписать имя руками

Keyring на headless-хосте обычно нерабочий (нет DBus и кошелька) — для сервера основной путь именно env.

На десктопе значение можно положить в keyring (нужно дополнение [secrets]):

python -c "import getpass; from standkit.secrets import set_secret; set_secret('standkit:client-uat:agent-token', getpass.getpass())"
Выпустить сертификаты Нужны для доступа по сети: серверный сертификат агента и, крайне желательно, CA клиентских сертификатов для mTLS. Агент только потребляет готовые PEM — выпуск и ротация на вашей PKI. Минимальный самоподписанный вариант для закрытого контура:
openssl req -x509 -newkey rsa:4096 -nodes -days 365 \ -keyout /opt/standkit/tls/agent.key -out /opt/standkit/tls/agent.crt \ -subj "/CN=client-host" openssl req -x509 -newkey rsa:4096 -nodes -days 365 \ -keyout /opt/standkit/tls/clients-ca.key -out /opt/standkit/tls/clients-ca.crt \ -subj "/CN=BPMkitStand clients CA" sudo chmod 600 /opt/standkit/tls/*.key && sudo chown standkit:standkit /opt/standkit/tls/*

CN серверного сертификата — то имя хоста, по которому к агенту будет ходить дашборд.

Запуск, служба и проверка связи

Проверить запуск руками

Сначала убедитесь, что агент поднимается и видит стенды, и только потом оформляйте службу.

--token-ref обязателен — без него argparse просто откажет, это самая частая первая ошибка. Все ключи — --help.

Каталоги по умолчанию — ~/.standkit/run, ~/.standkit/logs, ~/.standkit/audit.log. У службы свой $HOME, поэтому --run-dir/--log-dir/--audit-log задавайте явно.

sudo -u standkit env $(cat /etc/standkit/agent.env | xargs) \ /opt/standkit/venv/bin/python -m standkit_agent \ --host 0.0.0.0 --port 8765 \ --registry /opt/standkit/projects.json \ --token-ref standkit:client-uat:agent-token \ --readonly-token-ref standkit:client-uat:agent-readonly-token \ --tls-cert /opt/standkit/tls/agent.crt \ --tls-key /opt/standkit/tls/agent.key \ --tls-client-ca /opt/standkit/tls/clients-ca.crt \ --audit-log /opt/standkit/logs/audit.log

Для локальной отладки достаточно loopback без TLS:

python -m standkit_agent --registry ./projects.json --token-ref standkit:client-uat:agent-token
Оформить службой

Linux: готовый least-privilege юнит лежит в репозитории — standkit_agent/deploy/standkit-agent.service.

В установленный пакет он не входит: возьмите из репозитория или напишите по образцу ниже. Подставьте свои пути, добавьте EnvironmentFile с токенами и включите службу.

[Service] Type=simple User=standkit Group=standkit WorkingDirectory=/opt/standkit EnvironmentFile=/etc/standkit/agent.env ExecStart=/opt/standkit/venv/bin/python -m standkit_agent \ --host 0.0.0.0 --port 8765 \ --registry /opt/standkit/projects.json \ --token-ref standkit:client-uat:agent-token \ --tls-cert /opt/standkit/tls/agent.crt \ --tls-key /opt/standkit/tls/agent.key \ --tls-client-ca /opt/standkit/tls/clients-ca.crt \ --run-dir /opt/standkit/run \ --log-dir /opt/standkit/logs \ --audit-log /opt/standkit/logs/audit.log Restart=on-failure NoNewPrivileges=yes ProtectSystem=strict ProtectHome=yes ReadWritePaths=/opt/standkit/run /opt/standkit/logs

В ExecStart — только абсолютные пути: у службы нет ни ~, ни вашего PATH.

User= должен совпадать с владельцем agent.env, иначе Permission denied на файле 600.

Каталоги --run-dir/--log-dir задаём явно — иначе агент уйдёт в $HOME сервисной учётки, которого при ProtectHome=yes фактически нет.

sudo cp standkit-agent.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable --now standkit-agent systemctl status standkit-agent

Windows: проще всего через NSSM под выделенной учётной записью — никогда не под LocalSystem.

nssm install standkit-agent C:\ProgramData\standkit\venv\Scripts\python.exe -m standkit_agent --registry C:\ProgramData\standkit\projects.json --token-ref standkit:client-uat:agent-token … nssm set standkit-agent ObjectName .\standkit-agent <пароль>

Учётной записи нужно право «Log on as a service». Полные заметки — standkit_agent/deploy/windows-service.md.

Открыть доступ и проверить с машины оператора

Firewall: входящий порт агента — только для адресов управляющего контура, не «любой адрес».

Проверять лучше readonly-токеном, чтобы ничего не запустить.

sudo ufw allow from <IP оператора> to any port 8765 proto tcp # Linux / macOS curl --cacert agent.crt --cert client.crt --key client.key \ -H "Authorization: Bearer <readonly-токен>" \ https://client-host:8765/stands

В PowerShell curl — это алиас Invoke-WebRequest: флаги -s -H он не понимает и падает с ParameterBindingException.

Нужен либо настоящий curl.exe, либо родная команда:

curl.exe -s -H "Authorization: Bearer <токен>" https://client-host:8765/stands Invoke-RestMethod -Uri https://client-host:8765/stands ` -Headers @{ Authorization = "Bearer <токен>" }

Подключение из дашборда

🔗Запись в реестре оператора

{ "projects": { "client-uat": { "transport": "agent", "agent_url": "https://client-host:8765", "agent_secret_ref": "standkit:client-uat:agent-token", "stand_dir": "/opt/bpmsoft/client-uat", "stand_port": 5000, "db_type": "postgres", "db_host": "db-host", "db_port": 5432, "db_name": "client_uat" } } }
  • Тот же токен задайте секретом и на стороне оператора — врезка «Токен агента живёт в двух местах» ниже
  • Стенд появится в общем списке, в колонке «Транспорт» будет agent
  • Кнопки старт/стоп/рестарт, статус и логи работают так же — запросы прозрачно уходят к агенту

🧾Что умеет агент

ПутьСкоупДействие
GET /standsreadсписок стендов реестра агента
GET /stand/{имя}/statusreadhealth-статус
GET /stand/{имя}/logs?n=100readпоследние N строк лога
POST /stand/{имя}/startcontrolзапустить
POST /stand/{имя}/stopcontrolостановить
POST /stand/{имя}/restartcontrolперезапустить
POST /stand/{имя}/adoptcontrolусыновить процесс, поднятый вне диспетчера

Эндпоинта /health у агента нет — не ищите: живость проверяется GET /stands с токеном.

Без заголовка Authorization любой маршрут ответит отказом. Мониторингу отдавайте readonly-токен: он видит статусы и логи, но ничего не запускает и не гасит.

409 adopt_required — это фича, а не ошибка. Так агент отвечает на stop/restart стенда, поднятого мимо диспетчера.

Он возвращает описание найденного процесса-кандидата и ничего не трогает. Согласие передаётся явно: повторить с ?force=1 либо вызвать POST /stand/<имя>/adopt.

Тихого kill по номеру порта не бывает.

Токен агента живёт в двух местах

🔑 Один и тот же control-токен заводится дважды — на хосте агента и на машине оператора. Ссылка на секрет при этом одна и та же, разное только хранилище значения.

📡Сторона агента

Значение лежит в agent.env.

Им агент проверяет входящие запросы.

🖥️Сторона оператора

Значение лежит в keyring или в окружении процесса хаба.

Им хаб представляется.

🔗Ссылка общая

В обоих реестрах — standkit:stand-a:agent-token.

Резолвит хаб у себя, в своём процессе: переменная окружения → keyring → открытый фолбэк.

🧮Имя переменной считается по формуле

STANDKIT_SECRET__ плюс ссылка в ВЕРХНЕМ регистре, где всё, кроме букв и цифр, заменено на _. Формула одна и та же на обеих сторонах — у агента и у хаба.

Ссылка на секретПеременная окружения
standkit:stand-a:agent-token STANDKIT_SECRET__STANDKIT_STAND_A_AGENT_TOKEN
standkit:client-uat:agent-readonly-token STANDKIT_SECRET__STANDKIT_CLIENT_UAT_AGENT_READONLY_TOKEN
Windows (PowerShell) — текущая сессия $env:STANDKIT_SECRET__STANDKIT_STAND_A_AGENT_TOKEN = "<control-токен>" python -m standkit_hub Linux / macOS export STANDKIT_SECRET__STANDKIT_STAND_A_AGENT_TOKEN="<control-токен>" python3 -m standkit_hub

⏱️Переменная задаётся ДО старта хаба

  • Процесс получает окружение в момент создания — заданная позже (или в другом окне терминала) переменная в уже запущенный хаб не попадёт
  • Симптом ровно один: дашборд показывает ошибку секрета при, казалось бы, выставленном значении
  • Ярлык на рабочем столе запускает хаб через pythonw и наследует окружение так же — поменяли токен, перезапустите хаб
  • Проверить, что значение вообще видно процессу, проще всего тем же интерпретатором, которым запускается хаб
python -c "from standkit.secrets import has_secret; print(has_secret('standkit:stand-a:agent-token'))"

🔐Для постоянной работы — keyring, а не env одной сессии

  • Машина оператора — обычно десктоп с рабочим хранилищем, в отличие от headless-хоста агента
  • Нужен extra: pip install standkit[secrets]
  • Кнопка «Задать секрет…» в «Настройках» → «Агент (расширенное)» пишет значение для ссылки из соседнего поля — это токены локального агента
  • Для чужой ссылки (agent_secret_ref удалённого стенда) надёжнее однострочник — он кладёт значение в тот же keyring:
  • Значение из keyring переживает перезагрузку и не светится в истории команд
python -c "import getpass; from standkit.secrets import set_secret; set_secret('standkit:stand-a:agent-token', getpass.getpass('Токен: '))"

Переменная окружения приоритетнее keyring: забытый в профиле старый STANDKIT_SECRET__… будет молча побеждать свежее значение из хранилища.

👁️Хабу можно выдать readonly-токен

  • Если с этой машины нужен только просмотр, agent_secret_ref может указывать на readonly-токен агента
  • Статусы и логи будут работать, а старт/стоп/рестарт честно получат 403 forbidden: insufficient scope — вместо тихой работы с полными правами «на всякий случай»
  • Типовой случай — дежурная или мониторинговая машина, где случайное нажатие «Стоп» стоит дороже удобства
  • Токены и ссылки на них разные: …:agent-token для управления, …:agent-readonly-token для чтения

Вкладка «Локальный агент» — это про текущую машину

🎛️Что делает вкладка

Запускает и останавливает агента на текущей машине с параметрами из «Настроек».

Нужна администратору хоста, где живут стенды.

🏠Когда агент не нужен вовсе

Если стенды запущены на той же машине, откуда вы ими управляете (transport: local).

Ядро работает с ними напрямую.

👀Куда делся блок настроек

«Агент (расширенное)» скрыт, пока в реестре нет удалённых стендов.

07

Безопасность

И дашборд, и агент управляют процессами — к обоим применяется модель угроз управляющего контура, а не обычного прикладного API. Защита включена по умолчанию; ослабить её можно только явным флагом.

🖥️Дашборд: secure-defaults

  • Слушает только 127.0.0.1
  • Fail-closed: внешний адрес без TLS — хаб не стартует; обход только явным --insecure
  • Сессионный токен в URL, дальше — сессионная cookie
  • Мутации защищены проверкой токена и заголовка Origin (CSRF)
  • Стоп, Рестарт и Очистка Redis — с подтверждением

📡Агент: secure-defaults

  • Bind по умолчанию 127.0.0.1, fail-closed на внешнем адресе без TLS
  • TLS 1.2+ с AEAD/ECDHE-шифрами, сжатие выключено; mTLS отсекает чужого клиента на хендшейке
  • Bearer-токен сравнивается только константным по времени сравнением
  • Два скоупа: control (управление + чтение) и readonly
  • Lockout по IP после серии неудач; append-only JSON-аудит — без токенов и секретов
  • Лимиты тела запроса, таймауты, валидация имени стенда

Усыновление: три улики сразу

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

  • порт процесса совпадает со stand_port записи;
  • имя образа входит в allowlist (dotnet, BPMSoft.WebHost, w3wp);
  • рабочий каталог, путь исполняемого файла или командная строка ведут внутрь stand_dir.

Не совпало хотя бы одно — усыновления нет, вы получите отказ с указанием, какой процесс занимает порт. Тихого kill не бывает: без явного подтверждения процесс не трогается, согласие не запоминается и не переиспользуется. У агента adopt отнесён к управляющим действиям — readonly-токен усыновлять не может, каждая попытка идёт в аудит.

Рекомендуемые топологии

1️⃣Loopback + управляющий контур

  • Агент слушает 127.0.0.1, доступ — через SSH-туннель / VPN / WireGuard
  • Порт агента наружу не публикуется вообще
  • Самый простой безопасный вариант, он же дефолт

2️⃣mTLS за firewall

  • TLS + обязательный клиентский сертификат (--tls-client-ca)
  • Подключится только держатель сертификата от доверенного CA
  • Источники ограничены allowlist на firewall

Категорически нельзя

  • Публиковать порт агента в интернет
  • Выставлять его в недоверенную сеть открытым HTTP
  • Запускать агента под root / LocalSystem
  • Держать токен в открытых конфигах и в истории команд

Чек-лист перед продом

  • Агент на loopback либо с TLS (лучше mTLS); порт не в интернете
  • Отдельный сервис-аккаунт без root, hardening из systemd-юнита применён
  • Токены криптостойкие, в secret-store; control и readonly — разные
  • Firewall-allowlist источников
  • Аудит-лог пишется, собирается и ротируется; мониторятся denied и 429
  • Права на ключ TLS корректные (0600), ротация продумана
  • --insecure на этом хосте не используется
🔎 Полная модель угроз, разбор активов и остаточные пункты — SECURITY.md в репозитории. О проблемах безопасности сообщайте приватно мейнтейнеру, не через публичные issue.
08

Траблшутинг

Сначала таблица «симптом → причина → что сделать», ниже — разборы частых случаев. Общее правило: диспетчер старается объяснять причину словами, поэтому читайте текст ошибки целиком — в нём обычно уже есть рецепт.

Быстрая таблица

СимптомВероятная причинаЧто сделать
standkit-hub: «не является внутренней или внешней командой» / command not found Каталог, куда pip кладёт консольные команды (Scripts), не в PATH Запускать модулем: python -m standkit_hub. Разбор — плитка ниже
Linux: error: externally-managed-environment при pip install Системный Python защищён от установки пакетов (PEP 668) — так во всех свежих дистрибутивах pipx install "standkit[secrets]" либо venv — см. Установка.
Защиту флагом --break-system-packages не ломать
Linux: поставил через pipx, команды нет ~/.local/bin не в PATH pipx ensurepath и новый вход в shell
Агент пишет, что секрет не найден (служба) У сервисной учётной записи нет доступа к keyring — типично для headless-хоста Передать токен переменной STANDKIT_SECRET__… через EnvironmentFile (chmod 600) — см. Удалённые стенды
SecretError при, казалось бы, верной ссылке Забыт префикс STANDKIT_SECRET__ либо лишний _ в конце от echo … | tr Сверить имя по таблице в разделе Удалённые стенды; вместо echoprintf '%s'
/etc/standkit/agent.env: Permission denied Файл 600 принадлежит root, а служба идёт под другим пользователем sudo chown <пользователь службы> /etc/standkit/agent.env
the following arguments are required: --token-ref У агента нет значения по умолчанию для control-токена Добавить --token-ref standkit:<стенд>:agent-token
No module named 'standkit_agent' в скрипте Скрипт запущен системным python3, а пакет стоит в venv/pipx Звать питон окружения: /opt/standkit/venv/bin/python или ~/.local/share/pipx/venvs/standkit/bin/python
RegistryError: Стенд не найден Регистрация не выполнялась — или ушла в другой файл реестра Проверить путь: агенту всегда задавать --registry явно; рецепт регистрации — в разделе Реестр стендов
http: down при живом стенде Причин несколько, и дашборд её называет: наведите мышь на адрес в колонке HTTP (пунктирное подчёркивание = есть подсказка) «сервер ответил не по протоколу HTTP» → stand_scheme: "https";
«соединение отклонено» → адрес/порт с точки зрения хоста пробы.
Дерево решений — в раскрывашке ниже
Статус не изменился после правки реестра Агент читает projects.json при старте sudo systemctl restart standkit-agent
409 adopt_required от агента Стенд поднят вне диспетчера — согласия на усыновление не было Это штатный отказ: повторить с ?force=1 или вызвать /adopt
No module named standkit_hub Пакет поставлен в другой интерпретатор или в venv, который не активирован Ставить тем же питоном, которым запускаете: python -m pip install standkit
После обновления интерфейс прежний Старый процесс хаба ещё жив либо браузер держит статику Остановить хаб (Ctrl+C), запустить заново, обновить вкладку Ctrl+F5
Дашборд открылся не на 8770Порт занял чужой сервис Прочитать строку в консоли — там реальный порт; либо освободить 8770
Баннер «нет связи», таблица погашенаПроцесс диспетчера остановлен или перезапущен Запустить standkit-hub заново и обновить вкладку
«Сессия не подтверждена» (401)Cookie не пережила полное закрытие браузера или хаб перезапущен Перезапустить диспетчер и открыть URL с токеном из консоли
IIS-операция падает по правамДиспетчер запущен без elevation Запустить «от имени администратора». Членства в IIS_IUSRS недостаточно
IIS: «состояние не выяснено», службы IISОстановлены WAS / W3SVC sc start WAS или iisreset /start
Стоп/Рестарт отвечает отказом с описанием процессаСтенд поднят вне диспетчера, pid неизвестен Подтвердить усыновление — или остановить процесс тем же способом, каким запускали
Кнопка «Очистить Redis» неактивна В записи нет номера базы: кнопку включает redis_db, а цвет ячейки даёт проба redis_host/redis_port — это разные вещи redis_host/redis_port — из формы регистрации; redis_db пока задаётся правкой записи в реестре (см. Реестр стендов)
Агент падает сразу после старта: Отказ старта: каталог логов … недоступен на запись пользователю … Каталог run/логов/аудита принадлежит другому пользователю — типично после ручных запусков из-под root Одна строка отказа вместо traceback, и в ней уже есть рецепт: выдать права или задать свои пути --run-dir / --log-dir / --audit-log. Разбор — в раскрывашке ниже
Отказ старта: домашний каталог … недоступен … дефолты ~/.standkit/… нерабочие Сервисный аккаунт заведён с --no-create-home, а дефолты путей упираются в $HOME Задать все три пути явно. Агент намеренно не создаёт ~/.standkit в несуществующем $HOME и говорит об этом до старта
Отказ старта: не удалось привязаться к 127.0.0.1:8765 — порт уже занят другим процессом Порт держит прежний агент: ручной запуск, который забыли остановить, или недоперезапущенная служба Найти владельца и снять: ss -ltnp | grep 8765, затем pkill -f standkit_agent — либо поднять агента на другом порту через --port
CERTIFICATE_VERIFY_FAILED / «сертификат агента не доверенный» при обращении хаба к агенту Сертификат агента самоподписанный, а хаб проверяет цепочку по системному хранилищу Указать сертификат (или CA) агента в записи стенда — поле agent_ca. verify_tls здесь ни при чём: он относится к пробе стенда, а не к каналу до агента. Разбор всех вариантов ошибки — в раскрывашке ниже
Логи стенда на Linux: «каталог не найден», хотя каталог есть закрыто BPMSoft пишет в Logs с заглавной; имя каталога было зашито строкой logs, и на регистрозависимой ФС не находилось Обновить пакет: имя ищется без учёта регистра. Нестандартная раскладка задаётся полем logs_dir, см. Виды хостинга
«каталог логов живёт на хосте стенда; хаб … не видит» Стенд за агентом: каталог принадлежит чужой файловой системе, а хаб проверяет путь у себя Это не ошибка, а честная формулировка вместо неверного «каталог не найден». Хвост лога смотрите на хосте стенда или через API агента: GET /stand/<имя>/logs?n=100
Агент не стартует, пишет про fail-closedВнешний адрес без TLS Добавить --tls-cert/--tls-key (или --insecure — только dev)
401 unauthorizedТокен у оператора ≠ токену на агенте Сверить секрет по одному и тому же *-ref с обеих сторон
403 forbidden: insufficient scopeУправление readonly-токеном Использовать control-токен (--token-ref)
429 too many failed attemptsСработал lockout по IP Подождать окно блокировки, проверить токен
В дашборде стенд с ошибкой связиАгент недоступен, таймаут или сертификат Проверить сеть и firewall, срок действия и CN сертификата
TLS-хендшейк отклонёнНет валидного клиентского сертификата (mTLS) Выпустить клиентский сертификат из того же CA, что в --tls-client-ca

Разборы частых случаев

⌨️ standkit-hub — «команда не распознана»частое

Пакет установлен, но консоль команду не видит. Причина почти всегда одна: pip кладёт консольные команды (standkit-hub, standkit-agent) в свой каталог Scripts, а его нет в PATH. Особенно часто — при установке с ключом --user и в свежем окружении.

  • Работает всегда — запуск модулем. Ключи те же, что у standkit-hub:
    python -m standkit_hub
    На Windows, если python тоже не резолвится, — через лаунчер: py -m standkit_hub. Агент точно так же: python -m standkit_agent.
  • Узнать, куда положены команды:
    python -c "import sysconfig; print(sysconfig.get_path('scripts'))"
    Вывод — тот самый каталог; в нём должен лежать standkit-hub.exe (Windows) или standkit-hub (Linux). Нет файла — пакет поставлен другим питоном, см. следующий пункт.
  • Добавить каталог в PATH. Windows: «Параметры» → «Переменные среды» → Path → добавить путь из команды выше, затем открыть новую консоль (старая PATH не перечитывает). Linux: строка export PATH="$HOME/.local/bin:$PATH" в ~/.bashrc.
  • Сверить интерпретаторы. Ставить нужно тем же питоном, которым запускаете:
    python -m pip install standkit python -m pip show standkit
    Если pip show ничего не находит — установка ушла в другой Python (частый случай: несколько версий или venv), поставьте заново командой выше.
  • Ярлык вместо команды. Один раз создать ярлык на рабочем столе — и консоль больше не нужна:
    python -m standkit_hub --install-shortcut
🟢 Процесс «жив», а стенд не открывается
  • Посмотрите колонку HTTP: жив процесс ≠ отвечает web-хост, стенд может ещё прогреваться
  • Откройте панель «Текущее состояние» — в хвосте лога обычно видно, на чём он встал (БД, лицензия, порт)
  • Если вердикт помечен как неопределённый, стенд «зелёный по порту»: порт держит инфраструктура (http.sys у IIS, Service у k8s), а сам стенд погашен
  • Проверьте пробы БД и Redis — часто стенд не поднимается именно из-за них
⏳ Стенд долго стартует, спиннер не гаснет
  • Это нормальное поведение: диспетчер ждёт реального HTTP-ответа, а не факта создания процесса
  • BPMSoft на .NET Framework прогревается заметно дольше, чем kestrel-стенд
  • Остановка пула IIS у прогретого стенда может занять десятки секунд — у изменяющих IIS-операций свой увеличенный таймаут
  • Если ожидание затянулось — смотрите хвост лога, а не перезапускайте вслепую
📜 В панели состояния пусто, хотя логи есть
  • BPMSoft под .NET Framework пишет логи в подпапки-даты (Logs\ГГГГ_ММ_ДД\Application.log) — они читаются
  • Плоские логи kestrel имеют приоритет; если в каталоге нет ни того, ни другого, панель останется пустой
  • Кнопка «Открыть папку логов» откроет каталог в проводнике — там видно, пишется ли файл вообще
  • Второй источник в меню кнопки — логи BPMkit-проекта; он гаснет, если проекта у стенда нет
👯 Кажется, диспетчер запущен дважды
  • Так больше не бывает: перед стартом проверяется, не работает ли диспетчер уже, и второй экземпляр не поднимается
  • Ярлык стартует хаб без окна, поэтому закрытие браузера процесс не останавливает
  • Полностью остановить — Ctrl+C в консоли хаба или снять процесс
🔑 Секрет задан, но операция всё равно падает
  • Проверьте, что ссылка в реестре и ключ, под которым вы клали значение, совпадают дословно
  • Значение ищется по порядку: переменная окружения → keyring → фолбэк; переменная перекрывает keyring
  • Для агента ссылка должна совпадать на обеих сторонах — у оператора и на хосте агента
  • Хранилище секретов ставится extra: pip install "standkit[secrets]"
🌡️ Данные в таблице подозрительно старые
  • Возраст снапшота показывается, только когда данные заметно устарели или пробы ещё не выполнялись
  • Частая причина — недоступный стенд или агент за firewall: опрос агентов последовательный, таймауты складываются
  • Кнопка «Обновить» форсирует проход; интервал автообновления — в «Настройках»
🌐 http: down, а стенд живойчастое

Не гадайте: наведите мышь на адрес в колонке HTTP — пунктирное подчёркивание значит, что подсказка есть. Дальше по тексту подсказки.

Текст подсказкиЧто это и что делать
«сервер ответил не по протоколу HTTP» + «похоже, стенд за TLS…» Проба по http:// ушла в TLS-порт. Схема https в форме регистрации; сертификат самоподписанный — снять «Проверять сертификат»
«сертификат не прошёл проверку: …» Схема уже верная, не доверяем сертификату стенда. Дев-контур — verify_tls: false; прод — выпустить сертификат доверенным CA
«соединение отклонено — на 10.0.0.10:5000 никто не слушает» Порт закрыт или адрес не тот. Docker: нужен левый порт из docker ps и адрес, видимый с хоста, а не 0.0.0.0
«нет ответа за 1.5 с» Адрес верный, но ответа нет вовремя: стенд ещё прогревается либо между хабом и стендом firewall
«имя хоста не разрешается» DNS. Проверьте имя из stand_host с той машины, что делает пробу (для agent-стенда — с хоста агента)
«сервер ответил 502» / «сервер ответил 500» Стенд отвечает — ломается он сам или обратный прокси перед ним. Смотрите хвост лога, а не реестр. Коды меньше 500 (401/403 до логина) отказом не считаются
  • В конце подсказки — URL, по которому реально стучались: сверьте схему, хост, порт
  • Причину считает та сторона, что делает пробу: для стенда за агентом — агент, его и обновлять первым
  • Обновление пакета само по себе down не лечит: stand_scheme/verify_tls надо выставить, дефолты повторяют прежнее поведение
🧱 Агент не стартует под systemd

Отказ по путям проверяется до открытия сокета и до строки «слушаю …»: если в журнале есть «слушаю», значит preflight пройден. Каждый отказ — одна строка с полным путём, именем пользователя и подсказкой, какой флаг задать; traceback наружу не выходит.

[standkit-agent] Отказ старта: каталог логов /opt/standkit/logs недоступен на запись пользователю standkit — задайте --log-dir или выдайте права
[standkit-agent] Отказ старта: домашний каталог /home/standkit недоступен (не существует или не каталог), поэтому дефолты ~/.standkit/run, ~/.standkit/logs и ~/.standkit/audit.log нерабочие — при запуске под сервисным аккаунтом (useradd --no-create-home, systemd) задайте все три пути явно
Что задатьЗачемЕсли не задать
--run-dirpid-файлы стендовДефолт ~/.standkit/run — у аккаунта с --no-create-home его нет
--log-dirлоги стендов, запущенных агентомТот же дефолт в $HOME; при чужом владельце каталога — отказ по правам
--audit-logфайл аудита операций агентаЧастый случай: файл создан из-под root при ручном прогоне, служба под standkit в него не пишет
  • Проверяется и сам файл аудита, а не только его каталог: каталог писуч, а дописать в чужой файл нельзя
  • Каталог, которого ещё нет, — не ошибка: агент создаст его сам, если писуч ближайший существующий родитель
  • Лечение прав:
    sudo install -d -o standkit -g standkit -m 750 /opt/standkit/run /opt/standkit/logs sudo chown -R standkit:standkit /opt/standkit/run /opt/standkit/logs
  • Порт занят — отдельная строка того же вида: «не удалось привязаться к 127.0.0.1:8765 — порт уже занят другим процессом (возможно, агент уже запущен)». Владелец ищется ss -ltnp | grep 8765
  • Порт меньше 1024 — «нет прав на привязку к этому порту»: берите непривилегированный через --port
  • Ещё до путей проверяется fail-closed по адресу: внешний адрес без TLS агент не примет — см. Удалённые стенды
🔏 Хаб не доверяет сертификату агентаTLS

Ключевая путаница: verify_tls относится к пробе стенда, а канал «хаб → агент» настраивается своей парой — agent_ca (файл сертификата или CA агента) и agent_verify_tls. Оператор находит verify_tls, выключает его и обоснованно считает, что проверку уже отключил, — а ошибка та же. Оба поля агента есть в форме регистрации, в блоке, который открывается при транспорте agent (см. Реестр стендов).

Что пишет дашбордЧто этоРецепт
«сертификат агента не доверенный (self-signed certificate) — укажите agent_ca (путь к сертификату агента) в записи стенда» CA агента не известен машине оператора Положить agent.crt (или CA, которым он выпущен) на машину хаба и указать путь в поле «Сертификат агента (agent_ca)». Промежуточный CA — собрать в файле полную цепочку
«имя в сертификате агента не совпадает с адресом (Hostname mismatch …) — имя в agent_url должно совпадать с CN/SAN сертификата агента» В agent_url имя или IP, которых нет в сертификате Обращаться ровно по тому имени, что в сертификате, либо перевыпустить его с нужным SAN. agent_ca тут не поможет — проверяется не издатель, а имя
«файл сертификата агента не найден: /etc/standkit/agent.crt (поле agent_ca записи стенда)» Путь указан, но файла нет или он не читается процессом хаба Абсолютный путь на машине хаба, читаемый пользователем, под которым запущен хаб. Соседний вариант того же — «не удалось прочитать сертификат агента …»
Регистрация не проходит: «agent_ca имеет смысл только при agent_url на https:// …» Сертификат задан, а канал до агента — голый http Это намеренный отказ, а не придирка: молча проигнорированная настройка — ровно та ловушка, из-за которой оператор ищет причину не там. Переведите агента на TLS или уберите agent_ca
  • Проверить сам агент, минуя дашборд:
    curl --cacert /path/to/agent.crt https://example-host:8765/stands -H "Authorization: Bearer <токен>"
    curl отвечает, а хаб нет — дело именно в доверии на стороне хаба, а не в агенте или токене
  • Путь в agent_ca — путь на машине хаба, а не на хосте агента: одна и та же запись реестра живёт с обеих сторон, и существование файла на валидации не проверяется — отказ будет в момент обращения, с указанием пути
  • Альтернатива на весь процесс сразу: переменная SSL_CERT_FILE с путём к CA, выставленная до запуска хаба (после старта процесса она уже не действует). Годится, когда агентов много и CA у них общий
  • agent_verify_tls: false — крайняя мера для дев-контура: канал остаётся шифрованным, но подмена агента перестаёт ловиться. Для прода — доверенный CA
  • Не путайте с mTLS: agent_ca — это доверие к серверу. Агент, поднятый с --tls-client-ca, требует ещё и клиентский сертификат, а полей для него у дашборда нет — такой агент отвечает curl/скрипту, но не дашборду, см. Удалённые стенды
09

Границы и что дальше

BPMkitStand — молодой проект и честно об этом пишет. Ниже — что входит в бесплатную версию, что относится к платному BPMkit и что пока каркас.

Бесплатно (MIT) и платно

BPMkitStand — бесплатно, MITBPMkit — платно
Старт / стоп / рестарт стендаПровижининг нового стенда «с нуля»
Health-пробы: процесс, HTTP, TCP-порт БД и RedisГлубокие операции с БД: создание, бэкап, восстановление
Хвост и открытие логовДеплой пакетов, кастомизация JS и C#
Реестр стендов: чтение и записьГенерация документов, git-онбординг пакетов
Secret-first доступ к секретамАдминистративные операции над живым стендом: роли, права, данные
Федерация агентов, TLS/mTLS, аудитАвтообновление MCP и контроль лицензии — Companion

🟡 каркас / заглушки

  • Нет CLI регистрации стенда — только кнопка в дашборде, правка файла или Registry.add_existing()
  • Глубокие пробы БД и Redis (SELECT 1 / PING) — пока только проверка порта
  • Живой follow лога — сейчас периодический tail
  • UI-индикатор host_kind в таблице
  • Браузинг и скачивание логов удалённых стендов

🔴 не реализовано

  • Транспорты ssh и winrm — схема допускает, логики нет
  • PKI: выпуск и ротация сертификатов, ротация токенов «на лету»
  • Per-stand ACL: скоуп бинарный на весь реестр агента
  • Маппинг CN клиентского сертификата в скоуп
  • Параллельный опрос агентов (сейчас последовательный)

🟢 где смотреть правду

  • docs/BACKLOG.md — «упомянуто в коде, но не реализовано», со ссылками на символы
  • docs/ROADMAP.md — что сделано, ближайшее, бэклог
  • docs/CHANGELOG.md — история по версиям
  • docs/adr/ — почему сделано именно так
📈 Документ описывает поведение версии 0.8.x. Если что-то в кукбуке разошлось с тем, что вы видите на экране, источник правды — docs/CHANGELOG.md в репозитории; сообщите о расхождении, и кукбук поправят.