Кукбук 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.
--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.127.0.0.1 и это правильно:
не открывайте его порт наружу. Запустите standkit-hub --no-browser, а с рабочей машины
пробросьте порт по SSH — ssh -L 8770:127.0.0.1:8770 user@host — и откройте
http://127.0.0.1:8770 у себя. 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в репозитории - Свежий
mainдо релиза (только если нужна ещё не выпущенная правка):pip install --force-reinstall "git+https://github.com/thinkquattro/BPMkitStand.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:// |
| БД | Имя базы стенда из реестра, окрашенное по результату пробы (открыт ли TCP-порт БД) |
| Redis | Номер базы Redis стенда — тот самый, который будет очищен кнопкой 🗑. Прочерк — Redis у стенда не настроен |
| Действия | Кнопки-иконки: ▶ запустить · ■ остановить · ⟳ перезапустить · 🗑 очистить Redis. Неприменимые гаснут: ▶ у запущенного, ■ и ⟳ у остановленного, 🗑 — если Redis не настроен |
🎬Честный старт
- Дашборд поднимает процесс стенда и держит спиннер до реального ответа по 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 на хосте —
перезапустите службу агента, иначе увидите прежнюю картину и будете искать несуществующую проблему.🐍 Регистрация стенда на сервере без дашбордарецепт▶
Дашборд на headless-хосте неудобен (слушает только 127.0.0.1), CLI регистрации нет —
поэтому запись добавляется из Python. Питон берём из того окружения, куда поставлен пакет.
- Файл и каталог создаются сами при
save()— заводить их заранее не нужно add_existing()валидирует запись: пропущенное обязательное поле для выбранногоhost_kindвылезет сразу, а не при первом старте- После правки реестра — перезапуск агента
📁 Минимальная запись локального стенда (kestrel)▶
- Полный образец всех вариантов — файл
projects.sample.jsonв репозитории - Реальный
projects.jsonв git не коммитится
🧭 Модалка «Зарегистрировать стенд» — что в ней▶
- Имя стенда, транспорт и вид хостинга — два выпадающих списка сверху
- Условные поля появляются по выбору:
agent_*для агента,iis_*/docker_*/k8s_*для хостинга - Для IIS есть кнопка «Определить автоматически»: сайт и пул находятся по каталогу стенда и биндингу порта
- Каталог, host/port и параметры БД — остальная часть формы
- Запись уходит в общий
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: это «не настроено», а не авария
Стенд за TLS: stand_scheme и verify_tls
🔐Два поля записи
stand_scheme—http(по умолчанию) илиhttps: схема, по которой идёт HTTP-проба и строится ссылка «Открыть стенд» в таблицеverify_tls— проверять ли цепочку сертификатов; по умолчаниюtrue, для самоподписанного сертификата дев-контура нуженfalse- Дефолты полностью повторяют прежнее поведение — старые реестры править не нужно
- Мусор в
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) даже при погашенном стенде
Удалённые стенды и агент
Стенды на других хостах — виртуалки, серверы, контуры заказчика — управляются из того же
дашборда через федерацию лёгких агентов. На хосте стенда поднимается standkit_agent,
дашборд ходит к нему по HTTPS с Bearer-токеном.
standkit в крошечной HTTP-обёртке: поведение старта, стопа и логов
идентично локальному. Стенд объявляется удалённым одним полем transport: "agent".Связка
--insecure + --host 0.0.0.0 на публичном IP означает, что Bearer-токен
уходит открытым текстом через интернет. Поэтому: (1) порт закрыть firewall'ом на конкретные адреса
(ufw allow from <IP> to any port 8765 proto tcp); (2) --insecure — только на
первую проверку связи в доверенной сети, для постоянной работы TLS обязателен; (3) agent.env —
chmod 600, токен не показывать в консоли и на скриншотах, при утечке перевыпускать;
(4) пароли БД в projects.json (db_password) не писать — только
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 — см. Виды хостинга.STANDKIT_SECRET__ плюс ссылка
в верхнем регистре, где всё, кроме букв и цифр, заменено на _.
| Ссылка на секрет в реестре | Переменная окружения |
|---|---|
standkit:survey9:db | STANDKIT_SECRET__STANDKIT_SURVEY9_DB |
standkit:survey9:agent-token | STANDKIT_SECRET__STANDKIT_SURVEY9_AGENT_TOKEN |
standkit:client-uat:agent-readonly-token | STANDKIT_SECRET__STANDKIT_CLIENT_UAT_AGENT_READONLY_TOKEN |
STANDKIT_SECRET__ —
имя выглядит правильным, а на старте SecretError. (2) Владелец agent.env должен совпадать
с пользователем службы: файл 600 под root и агент под ubuntu дают
Permission denied (sudo chown ubuntu:ubuntu /etc/standkit/agent.env).
(3) Имя переменной, посчитанное через echo … | tr, получает лишний _ на конце из-за
перевода строки — используйте printf '%s' или просто впишите имя руками.
Keyring на headless-хосте обычно нерабочий (нет DBus и кошелька) — для сервера основной путь именно env.
На десктопе можно положить в keyring: python -c "import getpass; from standkit.secrets import set_secret; set_secret('standkit:client-uat:agent-token', getpass.getpass())"
(нужно дополнение [secrets]).CN серверного сертификата — то имя хоста, по которому к агенту будет ходить дашборд.--token-ref обязателен — без него argparse просто откажет, это самая частая первая ошибка.
Каталоги по умолчанию — ~/.standkit/run, ~/.standkit/logs, ~/.standkit/audit.log:
у службы свой $HOME, поэтому --run-dir/--log-dir/--audit-log
задавайте явно. Все ключи — --help.
python -m standkit_agent --registry ./projects.json --token-ref standkit:client-uat:agent-token.standkit_agent/deploy/standkit-agent.service (в установленный пакет он не входит, возьмите из репозитория
или напишите по образцу ниже). Подставьте свои пути, добавьте EnvironmentFile с токенами и включите службу.
ExecStart — только абсолютные пути: у службы нет ни ~, ни вашего
PATH. User= должен совпадать с владельцем agent.env, иначе
Permission denied на файле 600. Каталоги --run-dir/--log-dir задаём явно —
иначе агент уйдёт в $HOME сервисной учётки, которого при ProtectHome=yes фактически нет.
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.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 по номеру порта не бывает.
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 при живом стенде |
stand_host=0.0.0.0, порт не опубликован наружу — или стенд за TLS без stand_scheme |
Взять адрес обращения с хоста и левый порт из docker ps; для стенда за TLS — stand_scheme: "https" (и verify_tls: false для self-signed), см. Виды хостинга |
| Статус не изменился после правки реестра | Агент читает 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 у стенда не настроен в реестре | Добавить redis_host / redis_port / redis_db в запись |
| Агент не стартует, пишет про 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: опрос агентов последовательный, таймауты складываются
- Кнопка «Обновить» форсирует проход; интервал автообновления — в «Настройках»
Границы и что дальше
BPMkitStand — молодой проект и честно об этом пишет. Ниже — что входит в бесплатную версию, что относится к платному BPMkit и что пока каркас.
Бесплатно (MIT) и платно
| BPMkitStand — бесплатно, MIT | BPMkit — платно |
|---|---|
| Старт / стоп / рестарт стенда | Провижининг нового стенда «с нуля» |
| Health-пробы: процесс, HTTP, TCP-порт БД и Redis | Глубокие операции с БД: создание, бэкап, восстановление |
| Хвост и открытие логов | Деплой пакетов, кастомизация JS и C# |
| Реестр стендов: чтение и запись | Генерация документов, git-онбординг пакетов |
| Secret-first доступ к секретам | Административные операции над живым стендом: роли, права, данные |
| Федерация агентов, TLS/mTLS, аудит | Автообновление MCP и контроль лицензии — Companion |
🟡 каркас / заглушки
- Нет CLI регистрации стенда — только кнопка в дашборде, правка
projects.jsonилиRegistry.add_existing()из Python - Глубокие пробы БД и 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 в репозитории; сообщите о расхождении,
и кукбук поправят.