Кукбук BPMkitStand
Свободный диспетчер стендов BPMSoft: что это, как поставить, как жить с ним каждый день и как вынести управление на удалённые хосты. Документ для оператора и администратора сразу — всё по плиткам, ищите через поиск сверху.
Что такое 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, только стандартная библиотека
Как это собрано — за один взгляд
projects.json у оператора один и тот же для обоих случаев.Для кого
⚙️Администратор
- Стенды под контролем без консоли
- Удалённые контуры через агентов
💻Разработчик
- Быстрый рестарт и хвост лога
- Очистка Redis в один клик
🎯РП / пресейл
- Демо-стенды: поднять перед показом
- Видно, что живо, а что нет
🤖Пользователь BPMkit
- Общий реестр с AI-агентом
- Стенд из дашборда сразу виден агенту
Установка и первый запуск
🖥️Что нужно на машине оператора
- 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:
- С дополнениями (хранилище секретов + нативное окно):
- Запуск:
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):
Вариант B — venv (если pipx нет или окружение нужно своё):
Дополнение [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'.
127.0.0.1, и это правильно —
порт наружу не открываем. Смотрим через SSH-проброс, в три шага.🖥️1. На сервере
URL с сессионным токеном хаб печатает в консоль — он понадобится на шаге 3.
🔀2. С рабочей машины
Локальный порт 8770 пробрасывается на loopback сервера.
🌐3. В браузере у себя
Открываем тот самый URL с токеном из вывода хаба.
Шаги 2–5 — одинаково в обеих ОС
127.0.0.1:8770, печатает URL с сессионным токеном и открывает браузер.
Если команда «не распознана» — тот же запуск модулем работает всегда: python -m standkit_hub
(разбор — в разделе Траблшутинг).
projects.json руками
(раздел Реестр стендов). Регистрируется уже существующий стенд: каталог, БД и дистрибутив
должны быть готовы заранее.[secrets]) — так значение не попадёт в историю команд:
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 занял чужой сервис — хаб честно напишет об этом и возьмёт свободный
Обновление диспетчера
⬆️Как обновиться
- Остановите работающий диспетчер:
Ctrl+Cв его консоли (закрытие окна браузера процесс не останавливает) - Обновите пакет — командой того способа, которым ставили:
pip install -U standkit # Windows / обычный pip pipx upgrade standkit # Linux, установка через pipx ~/.venvs/standkit/bin/pip install -U standkit # Linux, установка в venv
- Запустите заново
standkit-hubи обновите вкладку —Ctrl+F5, чтобы браузер взял свежую статику - Проверьте версию: кнопка ⓘ в шапке → «О программе» (или
pip show standkit/pipx list)
🧭Что важно знать
- Ваши данные не трогаются: реестр стендов, конфиг дашборда и секреты живут в профиле пользователя, а не внутри пакета
- Агентов обновляйте вместе с хабом — на каждом хосте тем же обновлением пакета
- После обновления агента — рестарт службы:
sudo systemctl restart standkit-agentлибоnssm restart standkit-agent - Ярлык и установленное PWA-приложение пересоздавать не нужно
- Что изменилось —
docs/CHANGELOG.mdв репозитории - Конкретная версия и откат — пакет живёт на PyPI, ставить из git не нужно
pip.Ключи запуска standkit-hub
| Ключ | По умолчанию | Зачем |
|---|---|---|
--host | 127.0.0.1 |
Адрес, на котором слушать. Loopback — безопасный дефолт; менять без TLS нельзя (см. Безопасность) |
--port | 8770 |
Порт фиксирован осознанно: браузер узнаёт дашборд по origin и помнит тему и кэш. 0 — эфемерный порт |
--config | профиль пользователя | Свой путь к конфигу хаба вместо %APPDATA%\BPMkit\standkit-hub.json |
--no-browser | выкл | Не открывать браузер автоматически |
--desktop | выкл | Нативное окно вместо браузера. Требует extra standkit[desktop]; без него хаб предупредит и откроет браузер |
--insecure | выкл | Осознанный обход fail-closed-проверки bind. Только dev/тест |
--install-shortcut / --uninstall-shortcut | — | Создать/удалить ярлык на рабочем столе и выйти, не поднимая сервер |
Где что лежит
| Что | Windows | Linux |
|---|---|---|
| Реестр стендов | %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 в текущем каталоге. Первый запуск сам создаёт папку реестра,
чтобы показанный путь вёл в реальное место, а не «в никуда».Дашборд: экран за экраном
Три вкладки: Стенды — повседневная работа, Локальный агент — нужен только хосту удалённых стендов, Настройки — пути, интервал опроса, федерация.
Колонки таблицы
| Колонка | Что показывает |
|---|---|
| Стенд | Имя записи в реестре. Рядом может быть бейдж вне диспетчера — стенд поднят мимо дашборда |
| Транспорт | Как дашборд дотягивается до стенда: 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
❓Справка под рукой
- Кнопка ? в шапке открывает этот кукбук
- Он входит в поставку и работает офлайн — файл можно открыть с диска и при остановленном диспетчере
- Та же ссылка — в окне «О программе» (ⓘ)
🌓Тема и обновление
- Тема хранится в конфиге хаба и применяется до загрузки скриптов — светлой вспышки нет
- Интервал автообновления — в «Настройках»
- Кнопка «Обновить» форсирует опрос
Реестр стендов
Реестр — единственный источник правды о стендах. Один и тот же файл
projects.json используют дашборд, агент и MCP BPMkit: стенд, заведённый в диспетчере,
сразу виден AI-агенту — и наоборот.
📄Где лежит
Порядок резолва пути — первое подходящее:
BPMSOFT_PROJECTS_FILE, если файл существует- канонический путь профиля
./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]):
Подробнее — плитки «Секреты» в разделе Установка.
➕Регистрация ≠ провижининг
- Кнопка «Зарегистрировать стенд» привязывает уже существующий стенд к реестру
- Каталог, база и дистрибутив должны существовать заранее
- Развернуть стенд «с нуля» диспетчер не умеет — это зона BPMkit
Отдельной CLI-команды регистрации в пакете нет — три рабочих пути:
- кнопка «Зарегистрировать стенд» в дашборде
- правка
projects.jsonруками по образцуprojects.sample.json Registry.add_existing()из Python — рецепт ниже, для сервера без дашборда
Обязательные поля:
- всегда —
nameиstand_dir - при
host_kind=docker—docker_containerили compose-пара - при
kestrel—stand_dllиdotnet
Ключевые поля записи
| Поле | Значения | Смысл |
|---|---|---|
transport | local · agent |
Где управлять стендом: тем же процессом или через агента на его хосте. ssh/winrm схема допускает, но логика не реализована |
host_kind | kestrel (по умолчанию) · iis · docker · k8s |
Как стенд хостится. Поле независимо от transport — см. Виды хостинга |
stand_dir | путь | Каталог стенда: логи, pid, проверка усыновления |
stand_dll, dotnet | BPMSoft.WebHost.dll, dotnet |
Чем и что запускать в режиме kestrel |
stand_host, stand_port | 127.0.0.1, 5000 |
Адрес web-хоста: HTTP-проба, ссылка в таблице, поиск владельца порта при усыновлении |
stand_scheme, verify_tls | http · https; true/false |
Схема HTTP-пробы и ссылки «Открыть стенд»; для самоподписанного сертификата — verify_tls: false.
Дефолты http/true сохраняют прежнее поведение — см. Виды хостинга |
db_type, db_host, db_port, db_name | postgres · mssql |
Куда стучаться пробой БД (в бесплатной версии — только «открыт ли порт») |
redis_host, redis_port, redis_db | хост, порт, номер БД | Проба Redis и очистка кэша. Если redis_db не задан, дашборд пробует вытащить его из конфигурации стенда |
secret_ref_db, secret_ref_admin | standkit:<стенд>:db |
Ссылки на секреты, не сами секреты |
agent_url, agent_secret_ref | https://host:8765 |
Только при transport: agent — адрес агента и ссылка на его токен |
description, customer | текст | Человеческие пометки, ни на что не влияют |
Один стенд — две разные записи в двух реестрах
Самая частая ошибка при удалённом управлении. Стенд живёт на своём хосте, и там он для агента локальный. Удалённым он становится только в реестре оператора — это отдельная запись в другом файле. Синхронизировать реестры между собой не нужно.
| Что | Реестр на хосте стенда (читает агент) | Реестр оператора (читает дашборд) |
|---|---|---|
| transport | local — агент поднимает стенд у себя | 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 на хосте —
перезапустите службу агента, иначе увидите прежнюю картину и будете искать несуществующую проблему.Модалка «Зарегистрировать стенд» — поле за полем
📡Кто на самом деле работает
При 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_refstand_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 URLagent_url при agent |
http://example-host:8765 |
Схема — как реально слушает агент: поднят с --insecure → http://,
с --tls-cert/--tls-key → https://. Порт здесь агентский (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 / Portstand_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 / Deploymentk8s_* при k8s |
stand-a |
k8s_deployment обязателен; пустой k8s_namespace означает default |
| Тип БД / DB host / DB port / DB name | для agent-стенда можно пропустить | Пробу БД делает тот, кто владеет стендом: при agent — агент по своей записи, поля хаба не
используются. При local проба честно проверяет только «открыт ли порт» |
Redis host / Redis portredis_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. Питон берём из того окружения, куда поставлен пакет.
- Файл и каталог создаются сами при
save()— заводить их заранее не нужно add_existing()валидирует запись: пропущенное обязательное поле для выбранногоhost_kindвылезет сразу, а не при первом старте- После правки реестра — перезапуск агента
📁 Минимальная запись локального стенда (kestrel)▶
- Полный образец всех вариантов — файл
projects.sample.jsonв репозитории - Реальный
projects.jsonв git не коммитится
🖼️ Модалка «Зарегистрировать стенд» — как она выглядит▶
Разбор всех полей — таблица «Модалка «Зарегистрировать стенд» — поле за полем» выше.
Здесь — только внешний вид: запись уходит в общий projects.json, править файл руками не обязательно.
iis: поля iis_site / iis_app_pool
и кнопка «Определить автоматически» появились по значению списка «Хостинг».Виды хостинга
Два независимых измерения: transport — где вы управляете стендом,
host_kind — как стенд хостится на своей машине.
Комбинируются свободно: transport=agent + host_kind=docker —
это удалённый контейнер, которым управляет агент на его хосте.
host_kind не роняет чтение реестра — откат на kestrel.Что делает диспетчер под капотом
host_kind | Обязательные поля | Старт / стоп / рестарт и «жив ли» |
|---|---|---|
| kestrel | — | Запуск dotnet <stand_dll> скрытым процессом + pidfile. «Жив» — процесс по pid и HTTP-ответ |
| iis | iis_site и/или iis_app_pool |
Через appcmd. «Стенд» = его Site: диспетчер стартует и останавливает сайт и намеренно не трогает App Pool (пул может быть общим с другими приложениями). Пул задействуется, только если iis_site не задан вовсе |
| docker | docker_container или пара docker_compose_file + docker_compose_service |
docker start|stop|restart, состояние — State.Status: приостановленный
контейнер зелёным не показывается.Для compose — docker compose up -d|stop|restart
с точным сравнением имени сервиса |
| k8s | k8s_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(порт опубликован на хост) или реальное имя/адрес хоста
🔎Где взять порт
- В
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_scheme—http(по умолчанию) илиhttps: схема, по которой идёт HTTP-проба и строится ссылка «Открыть стенд» в таблицеverify_tls— проверять ли цепочку сертификатов; по умолчаниюtrue- Для самоподписанного сертификата дев-контура нужен
false - Дефолты полностью повторяют прежнее поведение — старые реестры править не нужно. Обратная сторона: обновление пакета само по себе ничего не чинит, поля надо выставить
- Пока чекбокс скрыт (схема
http), его значение на сервер не уходит вовсе — в записи остаётся дефолтverify_tls: true - Мусор в
stand_schemeне роняет чтение реестра: откат наhttp, а несоответствие поймает валидация записи
🩺Как это выглядит без настройки
- Стенд за 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внутри него не досбирается - Каталога по указанному пути нет — источник считается недоступным, а не «поищем рядом»: тихий фолбэк маскировал бы опечатку в реестре
🪟IIS и .NET Framework — без изменений
- Приоритет по-прежнему у
iis_stdout_log_dir: задан — берётся он - Логи BPMSoft под .NET Framework лежат в подпапках-датах (
Logs\2026_08_17\Application.log) — и список файлов, и чтение обходят каталог рекурсивно - IIS-бэкенд читает сначала плоские
*.logв каталоге и только при их отсутствии спускается в подпапки - Панель «Текущее состояние» берёт самый свежий файл за сегодня (дневных логов .NET бывают сотни), и только если сегодня записей нет — самый свежий вообще
🛰️Стенд за агентом: каталог не на этой машине
- Путь в записи описывает файловую систему хоста стенда, а проверяет его хаб — у себя. Локально такого пути нет никогда
- Поэтому вместо неверного «каталог не найден» хаб пишет прямо:
- POSIX-путь удалённого стенда показывается прямыми слэшами даже на Windows-хабе — раньше в сообщение уезжало
\opt\stands\… - Хвост лога такого стенда смотрите на его хосте или через API агента:
GET /stand/<имя>/logs?n=100
Redis в реестре: поля модели, а не мешок extra
🧩Два нормальных поля
redis_hostиredis_port— поля записи стенда и поля формы регистрации, по образцуdb_host/db_port- До этого адрес жил только в нетипизированном
extra: ни в форме, ни в модели его не было — искать было нечего - Номер базы (
redis_db) — отдельная история: он включает кнопку 🗑 и задаётся пока правкой файла реестра
📍Адрес — с точки зрения хоста пробы
- Пробу делает та сторона, что управляет стендом: ядро на этой машине или агент на хосте стенда
- Redis внутри compose без проброса наружу агенту виден, а оператору — нет
- Не проброшен вообще — будет
down, а неunknown: адрес задан, ответа нет. Та же грабля, что сstand_host
✅Валидация и старые реестры
- Пара валидна только целиком — половина даёт ошибку записи, а не тихий
unknown - Тексты ошибок:
redis_host задан без корректного redis_port (1–65535)иredis_port задан без redis_host - Порт вне диапазона 1–65535 тоже отбивается на валидации
- Реестры, где ключи лежали в
extra, продолжают работать без правок - Приоритет у полей модели,
extra— фолбэк
Удалённые стенды и агент
Стенды на других хостах — виртуалки, серверы, контуры заказчика — управляются из того же
дашборда через федерацию лёгких агентов. На хосте стенда поднимается standkit_agent,
дашборд ходит к нему по HTTPS с Bearer-токеном.
standkit в крошечной HTTP-обёртке: поведение старта, стопа и логов
идентично локальному. Стенд объявляется удалённым одним полем transport: "agent".Прежде чем ставить: агент — это удалённое исполнение кода
🧱Порт — только своим
Firewall на конкретные адреса управляющего контура, а не «любой адрес».
🔓--insecure — разовый
Только первая проверка связи в доверенной сети; для постоянной работы TLS обязателен.
Связка с --host 0.0.0.0 на публичном IP = Bearer-токен открытым текстом через интернет.
🔑agent.env — chmod 600
Токен не показывать в консоли и на скриншотах.
При утечке — перевыпустить.
🗝️Пароли БД — только ссылкой
db_password в projects.json не писать.
Вместо него — secret_ref_db.
Где что лежит на хосте агента
📄Реестр стендов агента
- Агент читает свой
projects.json— в нём стенды этого хоста - Они остаются
transport: "local": агент поднимает их у себя. Удалённым стенд становится только в реестре оператора - Путь задаётся флагом
--registry - Без флага — тот же порядок, что у дашборда:
BPMSOFT_PROJECTS_FILE→ профиль пользователя →./projects.json - Для службы всегда задавайте
--registryабсолютным путём: у сервисной учётной записи свой профиль, и «тот самый» файл из вашей домашней папки она не увидит - Реестр агента и реестр оператора — разные файлы, синхронизировать их не нужно
🗂️Рекомендуемая раскладка
Каталоги должны принадлежать сервисной учётной записи агента, а не root/Администратору.
Установка агента: подготовка хоста
Тот же пакет standkit, что у оператора, — агент входит в него. Нужен Python 3.10+
и dotnet для запуска стендов (для iis/docker/k8s — соответствующий CLI).
Системный Python в Linux не трогаем: службе нужен предсказуемый путь к интерпретатору, поэтому для неё — именно venv, а не pipx.
Файл /opt/standkit/projects.json (Windows — C:\ProgramData\standkit\projects.json).
Достаточно стендов этой машины; секретов в нём нет — только ссылки.
Стенды здесь остаются transport: "local". Файл пишется руками или скриптом —
рецепт Registry.add_existing() в разделе Реестр стендов.
⚠️ В stand_host — адрес обращения к стенду с этого хоста, а не адрес слушания:
0.0.0.0 здесь даёт ложный http: down.
Для контейнеров порт берётся из docker ps — см. Виды хостинга.
Токены не передаются в командной строке открытым текстом — только ссылкой на секрет. Заведите разные токены для управления и для чтения.
На сервере без keyring (обычный случай) значение задаётся переменной окружения.
Имя — STANDKIT_SECRET__ плюс ссылка в верхнем регистре, где всё, кроме букв и цифр, заменено на _.
| Ссылка на секрет в реестре | Переменная окружения |
|---|---|
standkit:stand-a:db | STANDKIT_SECRET__STANDKIT_STAND_A_DB |
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 |
Три грабли подряд, все живые.
| Грабля | Что видно | Лечение |
|---|---|---|
Забыт префикс 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]):
CN серверного сертификата — то имя хоста, по которому к агенту будет ходить дашборд.
Запуск, служба и проверка связи
Сначала убедитесь, что агент поднимается и видит стенды, и только потом оформляйте службу.
--token-ref обязателен — без него argparse просто откажет,
это самая частая первая ошибка. Все ключи — --help.
Каталоги по умолчанию — ~/.standkit/run, ~/.standkit/logs,
~/.standkit/audit.log. У службы свой $HOME, поэтому
--run-dir/--log-dir/--audit-log задавайте явно.
Для локальной отладки достаточно loopback без TLS:
Linux: готовый least-privilege юнит лежит в репозитории —
standkit_agent/deploy/standkit-agent.service.
В установленный пакет он не входит: возьмите из репозитория или напишите по образцу ниже.
Подставьте свои пути, добавьте EnvironmentFile с токенами и включите службу.
В ExecStart — только абсолютные пути: у службы нет ни ~, ни вашего PATH.
User= должен совпадать с владельцем agent.env, иначе
Permission denied на файле 600.
Каталоги --run-dir/--log-dir задаём явно — иначе агент уйдёт
в $HOME сервисной учётки, которого при ProtectHome=yes фактически нет.
Windows: проще всего через NSSM под выделенной учётной записью —
никогда не под LocalSystem.
Учётной записи нужно право «Log on as a service». Полные заметки —
standkit_agent/deploy/windows-service.md.
Firewall: входящий порт агента — только для адресов управляющего контура, не «любой адрес».
Проверять лучше readonly-токеном, чтобы ничего не запустить.
В PowerShell curl — это алиас Invoke-WebRequest: флаги
-s -H он не понимает и падает с ParameterBindingException.
Нужен либо настоящий curl.exe, либо родная команда:
Подключение из дашборда
🔗Запись в реестре оператора
- Тот же токен задайте секретом и на стороне оператора — врезка «Токен агента живёт в двух местах» ниже
- Стенд появится в общем списке, в колонке «Транспорт» будет
agent - Кнопки старт/стоп/рестарт, статус и логи работают так же — запросы прозрачно уходят к агенту
🧾Что умеет агент
| Путь | Скоуп | Действие |
|---|---|---|
GET /stands | read | список стендов реестра агента |
GET /stand/{имя}/status | read | health-статус |
GET /stand/{имя}/logs?n=100 | read | последние N строк лога |
POST /stand/{имя}/start | control | запустить |
POST /stand/{имя}/stop | control | остановить |
POST /stand/{имя}/restart | control | перезапустить |
POST /stand/{имя}/adopt | control | усыновить процесс, поднятый вне диспетчера |
Эндпоинта /health у агента нет — не ищите: живость проверяется
GET /stands с токеном.
Без заголовка Authorization любой маршрут ответит отказом. Мониторингу отдавайте
readonly-токен: он видит статусы и логи, но ничего не запускает и не гасит.
409 adopt_required — это фича, а не ошибка. Так агент отвечает на
stop/restart стенда, поднятого мимо диспетчера.
Он возвращает описание найденного процесса-кандидата и ничего не трогает. Согласие
передаётся явно: повторить с ?force=1 либо вызвать POST /stand/<имя>/adopt.
Тихого kill по номеру порта не бывает.
Токен агента живёт в двух местах
📡Сторона агента
Значение лежит в 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 |
⏱️Переменная задаётся ДО старта хаба
- Процесс получает окружение в момент создания — заданная позже (или в другом окне терминала) переменная в уже запущенный хаб не попадёт
- Симптом ровно один: дашборд показывает ошибку секрета при, казалось бы, выставленном значении
- Ярлык на рабочем столе запускает хаб через
pythonwи наследует окружение так же — поменяли токен, перезапустите хаб - Проверить, что значение вообще видно процессу, проще всего тем же интерпретатором, которым запускается хаб
🔐Для постоянной работы — keyring, а не env одной сессии
- Машина оператора — обычно десктоп с рабочим хранилищем, в отличие от headless-хоста агента
- Нужен extra:
pip install standkit[secrets] - Кнопка «Задать секрет…» в «Настройках» → «Агент (расширенное)» пишет значение для ссылки из соседнего поля — это токены локального агента
- Для чужой ссылки (
agent_secret_refудалённого стенда) надёжнее однострочник — он кладёт значение в тот же keyring: - Значение из keyring переживает перезагрузку и не светится в истории команд
Переменная окружения приоритетнее keyring: забытый в профиле старый
STANDKIT_SECRET__… будет молча побеждать свежее значение из хранилища.
👁️Хабу можно выдать readonly-токен
- Если с этой машины нужен только просмотр,
agent_secret_refможет указывать на readonly-токен агента - Статусы и логи будут работать, а старт/стоп/рестарт честно получат
403 forbidden: insufficient scope— вместо тихой работы с полными правами «на всякий случай» - Типовой случай — дежурная или мониторинговая машина, где случайное нажатие «Стоп» стоит дороже удобства
- Токены и ссылки на них разные:
…:agent-tokenдля управления,…:agent-readonly-tokenдля чтения
Вкладка «Локальный агент» — это про текущую машину
🎛️Что делает вкладка
Запускает и останавливает агента на текущей машине с параметрами из «Настроек».
Нужна администратору хоста, где живут стенды.
🏠Когда агент не нужен вовсе
Если стенды запущены на той же машине, откуда вы ими управляете (transport: local).
Ядро работает с ними напрямую.
👀Куда делся блок настроек
«Агент (расширенное)» скрыт, пока в реестре нет удалённых стендов.
Безопасность
И дашборд, и агент управляют процессами — к обоим применяется модель угроз управляющего контура, а не обычного прикладного 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.Траблшутинг
Сначала таблица «симптом → причина → что сделать», ниже — разборы частых случаев. Общее правило: диспетчер старается объяснять причину словами, поэтому читайте текст ошибки целиком — в нём обычно уже есть рецепт.
Быстрая таблица
| Симптом | Вероятная причина | Что сделать |
|---|---|---|
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 |
Сверить имя по таблице в разделе Удалённые стенды; вместо echo — printf '%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 наружу не выходит.
| Что задать | Зачем | Если не задать |
|---|---|---|
--run-dir | pid-файлы стендов | Дефолт ~/.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/скрипту, но не дашборду, см. Удалённые стенды
Границы и что дальше
BPMkitStand — молодой проект и честно об этом пишет. Ниже — что входит в бесплатную версию, что относится к платному BPMkit и что пока каркас.
Бесплатно (MIT) и платно
| BPMkitStand — бесплатно, MIT | BPMkit — платно |
|---|---|
| Старт / стоп / рестарт стенда | Провижининг нового стенда «с нуля» |
| 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/— почему сделано именно так
docs/CHANGELOG.md в репозитории; сообщите о расхождении,
и кукбук поправят.