standkit 0.3.7__tar.gz → 0.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. standkit-0.5.0/MANIFEST.in +5 -0
  2. {standkit-0.3.7/standkit.egg-info → standkit-0.5.0}/PKG-INFO +5 -2
  3. {standkit-0.3.7 → standkit-0.5.0}/README.md +4 -1
  4. standkit-0.5.0/SECURITY.md +119 -0
  5. standkit-0.5.0/docs/ARCHITECTURE.md +111 -0
  6. standkit-0.5.0/docs/HOSTING.md +165 -0
  7. standkit-0.5.0/docs/REMOTE_STANDS.md +271 -0
  8. standkit-0.5.0/docs/adr/0001-hosting-backends.md +144 -0
  9. standkit-0.5.0/docs/adr/0002-k8s-backend.md +51 -0
  10. standkit-0.5.0/projects.sample.json +167 -0
  11. {standkit-0.3.7 → standkit-0.5.0}/pyproject.toml +1 -1
  12. {standkit-0.3.7 → standkit-0.5.0}/standkit/__init__.py +1 -1
  13. {standkit-0.3.7 → standkit-0.5.0}/standkit/health.py +18 -2
  14. standkit-0.5.0/standkit/hosting.py +524 -0
  15. {standkit-0.3.7 → standkit-0.5.0}/standkit/lifecycle.py +87 -9
  16. {standkit-0.3.7 → standkit-0.5.0}/standkit/models.py +62 -0
  17. {standkit-0.3.7 → standkit-0.5.0/standkit.egg-info}/PKG-INFO +5 -2
  18. {standkit-0.3.7 → standkit-0.5.0}/standkit.egg-info/SOURCES.txt +11 -0
  19. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/server.py +151 -1
  20. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/web/app.js +118 -0
  21. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/web/index.html +120 -0
  22. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/web/style.css +68 -0
  23. standkit-0.5.0/tests/test_hosting.py +772 -0
  24. standkit-0.5.0/tests/test_hub_register.py +298 -0
  25. standkit-0.5.0/tests/test_models.py +212 -0
  26. standkit-0.3.7/tests/test_models.py +0 -73
  27. {standkit-0.3.7 → standkit-0.5.0}/LICENSE +0 -0
  28. {standkit-0.3.7 → standkit-0.5.0}/setup.cfg +0 -0
  29. {standkit-0.3.7 → standkit-0.5.0}/standkit/logs.py +0 -0
  30. {standkit-0.3.7 → standkit-0.5.0}/standkit/platform.py +0 -0
  31. {standkit-0.3.7 → standkit-0.5.0}/standkit/registry.py +0 -0
  32. {standkit-0.3.7 → standkit-0.5.0}/standkit/secrets.py +0 -0
  33. {standkit-0.3.7 → standkit-0.5.0}/standkit.egg-info/dependency_links.txt +0 -0
  34. {standkit-0.3.7 → standkit-0.5.0}/standkit.egg-info/entry_points.txt +0 -0
  35. {standkit-0.3.7 → standkit-0.5.0}/standkit.egg-info/requires.txt +0 -0
  36. {standkit-0.3.7 → standkit-0.5.0}/standkit.egg-info/top_level.txt +0 -0
  37. {standkit-0.3.7 → standkit-0.5.0}/standkit_agent/__init__.py +0 -0
  38. {standkit-0.3.7 → standkit-0.5.0}/standkit_agent/__main__.py +0 -0
  39. {standkit-0.3.7 → standkit-0.5.0}/standkit_agent/audit.py +0 -0
  40. {standkit-0.3.7 → standkit-0.5.0}/standkit_agent/security.py +0 -0
  41. {standkit-0.3.7 → standkit-0.5.0}/standkit_agent/server.py +0 -0
  42. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/__init__.py +0 -0
  43. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/__main__.py +0 -0
  44. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/agent_control.py +0 -0
  45. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/assets/bpmkit-icon.ico +0 -0
  46. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/assets/icon.png +0 -0
  47. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/client.py +0 -0
  48. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/config.py +0 -0
  49. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/logs_browser.py +0 -0
  50. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/redis_min.py +0 -0
  51. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/security.py +0 -0
  52. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/shortcut.py +0 -0
  53. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/web/bpmkit-logo-dark.svg +0 -0
  54. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/web/bpmkit-logo.svg +0 -0
  55. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/web/favicon.png +0 -0
  56. {standkit-0.3.7 → standkit-0.5.0}/standkit_hub/web/favicon.svg +0 -0
  57. {standkit-0.3.7 → standkit-0.5.0}/tests/test_agent_security.py +0 -0
  58. {standkit-0.3.7 → standkit-0.5.0}/tests/test_agent_server_integration.py +0 -0
  59. {standkit-0.3.7 → standkit-0.5.0}/tests/test_health.py +0 -0
  60. {standkit-0.3.7 → standkit-0.5.0}/tests/test_hub_agent_control.py +0 -0
  61. {standkit-0.3.7 → standkit-0.5.0}/tests/test_hub_config.py +0 -0
  62. {standkit-0.3.7 → standkit-0.5.0}/tests/test_hub_logs_browser.py +0 -0
  63. {standkit-0.3.7 → standkit-0.5.0}/tests/test_hub_redis_min.py +0 -0
  64. {standkit-0.3.7 → standkit-0.5.0}/tests/test_hub_server.py +0 -0
  65. {standkit-0.3.7 → standkit-0.5.0}/tests/test_hub_shortcut.py +0 -0
  66. {standkit-0.3.7 → standkit-0.5.0}/tests/test_lifecycle.py +0 -0
  67. {standkit-0.3.7 → standkit-0.5.0}/tests/test_logs.py +0 -0
  68. {standkit-0.3.7 → standkit-0.5.0}/tests/test_registry.py +0 -0
  69. {standkit-0.3.7 → standkit-0.5.0}/tests/test_registry_resolver.py +0 -0
  70. {standkit-0.3.7 → standkit-0.5.0}/tests/test_secrets.py +0 -0
@@ -0,0 +1,5 @@
1
+ include projects.sample.json
2
+ include README.md
3
+ include LICENSE
4
+ include SECURITY.md
5
+ recursive-include docs *.md
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: standkit
3
- Version: 0.3.7
3
+ Version: 0.5.0
4
4
  Summary: Свободное ядро (MIT) для управления жизненным циклом локальных и удалённых стендов BPMSoft: headless start/stop/restart, health-пробы, логи, реестр стендов. Часть экосистемы BPMkit.
5
5
  Author: standkit contributors
6
6
  License: MIT
@@ -98,6 +98,8 @@ BPMkitStand состоит из **ядра и двух оболочек**:
98
98
  Запуск стенда честный: дашборд поднимает `dotnet <stand_dll>` и держит спиннер до реального
99
99
  ответа web-хоста по HTTP, а не рапортует «запущено» по факту создания процесса.
100
100
 
101
+ Виды хостинга (kestrel/iis/docker) — см. [docs/HOSTING.md](docs/HOSTING.md).
102
+
101
103
  ## Удалённые стенды
102
104
 
103
105
  Стенды на других хостах (виртуалки, серверы, контуры заказчика) управляются через **федерацию
@@ -130,7 +132,8 @@ standkit-hub
130
132
  ## Реестр стендов
131
133
 
132
134
  BPMkitStand использует тот же реестр, что и MCP BPMkit — единый `projects.json`.
133
- Образец формата — [`projects.sample.json`](projects.sample.json).
135
+ Образец формата — [`projects.sample.json`](projects.sample.json). Стенд можно
136
+ зарегистрировать прямо из дашборда (кнопка «Зарегистрировать стенд»).
134
137
 
135
138
  ## Безопасность
136
139
 
@@ -73,6 +73,8 @@ BPMkitStand состоит из **ядра и двух оболочек**:
73
73
  Запуск стенда честный: дашборд поднимает `dotnet <stand_dll>` и держит спиннер до реального
74
74
  ответа web-хоста по HTTP, а не рапортует «запущено» по факту создания процесса.
75
75
 
76
+ Виды хостинга (kestrel/iis/docker) — см. [docs/HOSTING.md](docs/HOSTING.md).
77
+
76
78
  ## Удалённые стенды
77
79
 
78
80
  Стенды на других хостах (виртуалки, серверы, контуры заказчика) управляются через **федерацию
@@ -105,7 +107,8 @@ standkit-hub
105
107
  ## Реестр стендов
106
108
 
107
109
  BPMkitStand использует тот же реестр, что и MCP BPMkit — единый `projects.json`.
108
- Образец формата — [`projects.sample.json`](projects.sample.json).
110
+ Образец формата — [`projects.sample.json`](projects.sample.json). Стенд можно
111
+ зарегистрировать прямо из дашборда (кнопка «Зарегистрировать стенд»).
109
112
 
110
113
  ## Безопасность
111
114
 
@@ -0,0 +1,119 @@
1
+ # SECURITY — модель безопасности standkit-агента
2
+
3
+ > Аудитория: инженеры и self-hosted администраторы, разворачивающие
4
+ > `standkit_agent` на хостах стендов. Клиентский (заказчицкий) документ по
5
+ > модели удалённой работы готовится отдельно на основе этого файла.
6
+ > Статус кода: прод-харденинг выполнен (v0.2 агента), см. чек-лист и
7
+ > остаточные пункты ниже.
8
+
9
+ ## 1. Что защищаем (assets) и почему это критично
10
+
11
+ `standkit_agent` — headless-демон на хосте стенда, который по HTTP(S) умеет
12
+ **запускать, останавливать и перезапускать процессы стенда** и читать его
13
+ логи. То есть агент по своей природе — **поверхность удалённого исполнения
14
+ кода (RCE)**: тот, кто получит контроль-доступ к агенту, управляет жизненным
15
+ циклом процессов на хосте. Поэтому к нему применяется модель угроз как к
16
+ элементу управляющего контура (management plane), а не как к обычному
17
+ прикладному API.
18
+
19
+ Активы: (1) контроль над процессами стенда; (2) содержимое логов стенда;
20
+ (3) сам хост (через возможность запускать процессы). Агент НЕ хранит секретов
21
+ клиента и НЕ содержит контента платформы — только оркестрацию.
22
+
23
+ ## 2. Противники и границы доверия
24
+
25
+ - **Сетевой злоумышленник** (тот же сегмент/периметр): перехват трафика,
26
+ попытка подключиться к порту агента, brute-force токена.
27
+ - **Утечка токена** (лог, история команд, скриншот): попытка использовать
28
+ украденный Bearer-токен.
29
+ - **Инсайдер с доступом к сети управления**: злоупотребление легитимным
30
+ доступом — здесь работает аудит.
31
+
32
+ Граница доверия: агент доверяет только клиентам, прошедшим (а) TLS/mTLS-
33
+ транспорт и (б) аутентификацию по скоупу. Всё остальное — недоверенное.
34
+
35
+ ## 3. Модель развёртывания (как разворачивать безопасно)
36
+
37
+ Рекомендуемые топологии (в порядке предпочтения):
38
+
39
+ 1. **Loopback + управляющий контур.** Агент слушает `127.0.0.1`, доступ к
40
+ нему — только через SSH-туннель / WireGuard / VPN до хоста. Наружу порт
41
+ агента не публикуется вообще. Самый простой безопасный вариант — дефолт.
42
+ 2. **mTLS за firewall.** Агент слушает управляющий интерфейс с TLS +
43
+ обязательным клиентским сертификатом (`--tls-client-ca`). Подключиться
44
+ может только держатель сертификата, подписанного доверенным CA (например,
45
+ Companion оператора). Источники ограничены firewall-allowlist.
46
+
47
+ **Категорически нельзя:** публиковать порт агента в интернет; выставлять его в
48
+ недоверенную сеть открытым HTTP; запускать под root/LocalSystem; хранить токен
49
+ в открытых конфигах/истории команд.
50
+
51
+ ## 4. Secure-defaults, действующие в коде (v0.2)
52
+
53
+ - **Bind по умолчанию `127.0.0.1`** (не `0.0.0.0`).
54
+ - **Fail-closed**: non-loopback host без TLS → агент **не стартует**
55
+ (`InsecureBindError`) ещё до открытия сокета. Обход — только явный
56
+ `--insecure` (dev/loopback) с громким предупреждением в stderr.
57
+ - **TLS**: `PROTOCOL_TLS_SERVER`, минимум **TLS 1.2**, консервативный набор
58
+ AEAD/ECDHE-шифров (forward secrecy), выключено сжатие (CRIME).
59
+ - **mTLS**: `--tls-client-ca` → `CERT_REQUIRED`; клиент без валидного
60
+ сертификата отсекается на TLS-хендшейке, до обработчика. CN — в аудит.
61
+ - **AuthN**: Bearer-токен, сравнение только `hmac.compare_digest`
62
+ (анти-timing). **Скоупы**: `control` (start/stop/restart + чтение) и
63
+ `readonly` (только чтение).
64
+ - **Lockout**: 5 неудачных аутентификаций с одного IP за 300 c → `429` до
65
+ конца окна (настраивается).
66
+ - **Аудит**: append-only JSON-lines (`--audit-log`, дефолт
67
+ `~/.standkit/audit.log`), каждая операция (ok/denied/error) с src_ip,
68
+ идентичностью, действием и кодом. **Токены никогда не пишутся.**
69
+ - **Input-hardening**: лимит тела 64 КБ (проверка до чтения), кап `n` логов
70
+ 10000, таймаут соединения 30 c, whitelist-валидация имени стенда, `400`
71
+ вместо `500`/креша на некорректный ввод, `405` на PUT/DELETE/PATCH.
72
+ - **systemd least-privilege** (`deploy/standkit-agent.service`):
73
+ `NoNewPrivileges`, `ProtectSystem=strict`, `ProtectHome`, `PrivateTmp`,
74
+ `PrivateDevices`, `CapabilityBoundingSet=` (пусто), `MemoryDenyWriteExecute`,
75
+ `LockPersonality`, `RestrictNamespaces`, `SystemCallFilter=@system-service`,
76
+ выделенный не-root `User=`/`Group=`, урезанные `ReadWritePaths`.
77
+
78
+ ## 5. Чек-лист развёртывания на прод
79
+
80
+ - [ ] Агент слушает loopback ИЛИ настроен TLS (лучше mTLS). Порт не в интернет.
81
+ - [ ] Отдельный сервис-аккаунт без root; systemd-hardening из unit применён.
82
+ - [ ] Токены сгенерированы криптостойко, лежат в secret-store (env/keyring),
83
+ не в открытых конфигах; разные токены на control и readonly.
84
+ - [ ] Firewall-allowlist источников (только Companion оператора / сеть
85
+ управления).
86
+ - [ ] Аудит-лог пишется на защищённый носитель; настроен внешний сбор/ротация
87
+ (см. остаточные пункты) и мониторинг `denied`/`429`.
88
+ - [ ] TLS-сертификаты и приватный ключ — с корректными правами (0600), выданы
89
+ доверенным CA; продумана ротация.
90
+ - [ ] Проверено, что `--insecure` НЕ используется на этом хосте.
91
+
92
+ ## 6. Остаточные пункты (осознанные TODO следующих итераций)
93
+
94
+ Не закрыты в v0.2 и требуют внимания перед массовым прод-развёртыванием:
95
+
96
+ - **PKI/сертификаты**: агент только потребляет готовые PEM. Выпуск, раздача и
97
+ ротация серверных/клиентских сертификатов — пока забота оператора. Нужен
98
+ задокументированный процесс (или интеграция с внутренним CA).
99
+ - **Ротация токенов «на лету»** без рестарта агента — не реализована.
100
+ - **Per-stand ACL**: control-скоуп управляет всеми стендами реестра агента;
101
+ разбивки «токен/CN → конкретный стенд» нет.
102
+ - **CN→scope маппинг** для mTLS: сейчас скоуп всегда определяется Bearer-
103
+ токеном, CN сертификата — только для аудита.
104
+ - **Ротация/размерный кап аудита**: до-аутентификационные ответы (`404`/`405`)
105
+ и поток `denied` могут наливать файл; нужна ротация (logrotate/размерный
106
+ лимит) и защита от переполнения диска.
107
+ - **Кап числа потоков**: `ThreadingHTTPServer` не ограничивает число
108
+ одновременных обработчиков — при флуде соединений возможно исчерпание
109
+ ресурсов. Нужен bounded thread pool / лимит соединений (плюс внешний
110
+ rate-limit на reverse-proxy).
111
+ - **`Transfer-Encoding: chunked`**: лимит тела опирается на `Content-Length`;
112
+ chunked-запрос лимит не учитывает. Явно отклонять chunked или считать длину.
113
+ - **Windows-служба**: обёртка через `sc.exe`/pywin32 с least-privilege — не
114
+ входила в объём v0.2 (есть заметки в `deploy/windows-service.md`).
115
+
116
+ ## 7. Сообщение об уязвимостях
117
+
118
+ До публикации репозитория и политики раскрытия — сообщать о проблемах
119
+ безопасности приватно мейнтейнеру проекта, не через публичные issue.
@@ -0,0 +1,111 @@
1
+ # Архитектура standkit
2
+
3
+ Этот документ — краткое зеркало архитектурных решений для контекста разработки
4
+ в самом репозитории `standkit`. **Детальный дизайн, обоснования выбора модели
5
+ и разбор альтернатив — в репозитории BPMkit: ADR-0019 и
6
+ `docs/планы/companion_dispatcher_f_l2a_2026-07-23.md`.** Здесь — только то, что
7
+ нужно держать перед глазами при правке кода этого репозитория.
8
+
9
+ ## Модель: ядро + две оболочки
10
+
11
+ ```
12
+ standkit — MIT, движок жизненного цикла, stdlib-only, без сети "наружу",
13
+ без лицензионно-чувствительного контента BPMSoft.
14
+ standkit_agent — MIT, stdlib-only HTTP-обёртка вокруг ядра, кроссплатформенная
15
+ (Windows/Linux), разворачивается на КАЖДОМ хосте стенда.
16
+ standkit_hub — MIT, stdlib-only локальный веб-дашборд (http.server) +
17
+ vanilla JS/CSS фронтенд, федеративный клиент N агентов +
18
+ локального ядра, ставится ТОЛЬКО на машину оператора.
19
+ Опциональная нативная оболочка — pywebview (extra [desktop]).
20
+ ```
21
+
22
+ Веб-дашборд (вариант A) заменил прежнюю PySide6/Qt-оболочку `standkit_gui`
23
+ (удалена целиком): та же роль ("диспетчер на машине оператора"), но без
24
+ тяжёлой GUI-зависимости — браузер универсален, фронтенд отдаёт сам хаб.
25
+
26
+ Границы зависимостей — принципиальны и проверяются на уровне импортов:
27
+ - `standkit/*`, `standkit_agent/*` и `standkit_hub/*` (серверная часть) —
28
+ STDLIB-ONLY, никаких сторонних веб-фреймворков/GUI-тулкитов.
29
+ - `standkit_hub/__main__.py` — единственное место, где опционально
30
+ импортируется `pywebview` (только под `--desktop`, в try/except; при
31
+ отсутствии extra `[desktop]` хаб печатает понятное сообщение и падает
32
+ обратно в системный браузер, а не роняется исключением импорта).
33
+ - `standkit` не тянет сторонних пакетов вообще (`dependencies = []` в
34
+ `pyproject.toml`) — секреты (`standkit/secrets.py`) используют `keyring`
35
+ только опционально, через `try/except ImportError`.
36
+ - Фронтенд `standkit_hub/web/*` — vanilla JS/CSS, без CDN и без шага сборки
37
+ (работает офлайн, отдаётся тем же `http.server`, что и API).
38
+
39
+ ## Транспорт: `local` | `agent` (задел `ssh` / `winrm`)
40
+
41
+ Поле `transport` в записи реестра (`standkit/models.py::Transport`) определяет,
42
+ как ядро/GUI достаёт до конкретного стенда:
43
+
44
+ - `local` — стенд поднимается тем же процессом, что вызывает `standkit`
45
+ (`standkit/lifecycle.py` поверх `standkit/platform.py`, headless-процесс
46
+ через `subprocess`).
47
+ - `agent` — управление идёт по HTTP к `standkit_agent`, слушающему на
48
+ `agent_url`, с Bearer-токеном, разрешаемым через `agent_secret_ref`
49
+ (Secret-first, см. `standkit/secrets.py`).
50
+ - `ssh` / `winrm` — **схема допускает** эти значения (см.
51
+ `projects.sample.json`, запись `example-future-ssh`), но логика НЕ
52
+ реализована: `Stand.from_dict` толерантен к неизвестным будущим значениям
53
+ (не роняет чтение реестра), а код, требующий конкретный транспорт
54
+ (`lifecycle._require_local`, `client.FederatedClient._dispatch_action`),
55
+ явно бросает `NotImplementedError`/`LifecycleError` для нереализованных
56
+ транспортов, а не тихо делает не то.
57
+
58
+ ## Кроссплатформенность
59
+
60
+ - Все пути — `pathlib.Path`, без хардкода `C:\...`.
61
+ - Запуск процесса стенда (`standkit/platform.py::spawn_hidden`) — раздельные
62
+ ветки для `sys.platform == "win32"` (скрытое консольное окно,
63
+ `CREATE_NO_WINDOW` + отдельная группа процессов) и POSIX
64
+ (`start_new_session=True`, эквивалент `setsid`).
65
+ - `standkit_agent` — только `stdlib` (`http.server`, `subprocess`, `socket`,
66
+ `urllib`), потому что стенды BPMSoft на .NET штатно живут и под Linux, а
67
+ агент должен разворачиваться на голом хосте без сборки колёс под
68
+ конкретную ОС/архитектуру.
69
+ - Деплой агента как службы ОС — раздельные шаблоны:
70
+ `standkit_agent/deploy/standkit-agent.service` (systemd, Linux) и
71
+ `standkit_agent/deploy/windows-service.md` (заметки NSSM/Task
72
+ Scheduler/pywin32, Windows).
73
+
74
+ ## Границы free (MIT) / paid
75
+
76
+ `standkit` — воронка на платный продукт **BPMkit**. Разделительная линия:
77
+
78
+ | В standkit (MIT, бесплатно) | В BPMkit (платно) |
79
+ |-----------------------------------------------------|-----------------------------------------------------|
80
+ | start/stop/restart headless-процесса стенда | Провижининг нового стенда "с нуля" (`provision_stand`) |
81
+ | Health-пробы: процесс/HTTP/TCP-порт БД и Redis | Глубокие БД-операции (`db_create/restore/backup`) |
82
+ | Tail/follow лог-файла | Деплой пакетов (WSC/UBS), кастомизация JS/C# |
83
+ | Реестр стендов (чтение/запись `projects.json`) | Генерация документов, git-онбординг пакетов стенда |
84
+ | Secret-first доступ к секретам (обёртка над keyring) | Административные операции над живым стендом (ESQ, роли, права) |
85
+
86
+ `standkit.registry.Registry.add_existing()` — это **привязка уже
87
+ существующего стенда** к реестру standkit, не провижининг: каталог/БД/
88
+ дистрибутив должны существовать заранее. Полноценный провижининг —
89
+ исключительно зона BPMkit.
90
+
91
+ ## Что уже реализовано в каркасе vs TODO
92
+
93
+ Рабочая минимальная логика, покрытая тестами: `standkit/models.py`,
94
+ `standkit/registry.py`, `standkit/health.py` (быстрые пробы: `tcp_open`,
95
+ `http_ok`, `process_alive`). Скелетные модули с явными `TODO` в докстрингах:
96
+ `standkit/lifecycle.py` (нет graceful stop с эскалацией SIGKILL/таймаутом),
97
+ `standkit/platform.py` (нет Job Object на Windows, нет double-fork на Linux),
98
+ `standkit/health.py::db_deep_check/redis_deep_check` (заглушки — требуют
99
+ опциональных зависимостей `psycopg2`/`pyodbc`/`redis-py`), `standkit_hub`
100
+ (опрос стендов — по кнопке "Обновить"/интервалу polling из фронтенда, а не
101
+ push/SSE; live follow лога — TODO, сейчас только периодический tail).
102
+
103
+ `standkit_agent` прошёл прод-харденинг (см. `standkit_agent/security.py`,
104
+ `standkit_agent/audit.py`, README.md → раздел «Безопасность»): TLS/mTLS,
105
+ fail-closed bind-defaults (loopback-only без явного `--insecure`), скоупы
106
+ control/readonly с `hmac.compare_digest`, rate limiting/lockout по IP,
107
+ структурный аудит-лог, input-hardening. Осознанно не реализовано (TODO):
108
+ полноценная PKI/ротация сертификатов и токенов "на лету", per-stand ACL
109
+ (сейчас скоуп бинарный на уровне всего реестра агента), CN→scope маппинг для
110
+ mTLS. Деплой-шаблоны (`standkit_agent/deploy/`) обновлены под least privilege
111
+ (systemd sandboxing, выделенный не-root сервисный аккаунт на Windows).
@@ -0,0 +1,165 @@
1
+ # Виды хостинга стенда (`host_kind`)
2
+
3
+ Как стенд BPMSoft **хостится** на своей машине — отдельное измерение от того,
4
+ **где** им управлять (`transport`: `local`/`agent`). Полный дизайн — см.
5
+ [ADR-0001](adr/0001-hosting-backends.md).
6
+
7
+ | `transport` | *где* управлять стендом | local — процессом самого standkit; agent — через удалённый `standkit_agent` |
8
+ |---|---|---|
9
+ | `host_kind` | *как* стенд хостится | kestrel / iis / docker / k8s |
10
+
11
+ Оба поля независимы: например, `transport=agent` + `host_kind=docker` — это
12
+ удалённый контейнер, которым управляет агент на своём хосте.
13
+
14
+ ## `host_kind` и связанные поля
15
+
16
+ По умолчанию `host_kind = "kestrel"` (текущее поведение standkit — headless
17
+ `dotnet <stand_dll>` + pidfile). Неизвестное/будущее значение `host_kind` в
18
+ реестре не роняет чтение — откатывается на `kestrel`.
19
+
20
+ | `host_kind` | Поле | Обязательность | Назначение |
21
+ |---|---|---|---|
22
+ | `iis` | `iis_site` | одно из двух (`iis_site` и/или `iis_app_pool`) | имя IIS-сайта |
23
+ | `iis` | `iis_app_pool` | одно из двух | имя Application Pool (приоритетно для recycle/state) |
24
+ | `iis` | `iis_stdout_log_dir` | опционально | папка stdout-логов ASP.NET Core (иначе `<stand_dir>\logs`) |
25
+ | `docker` | `docker_container` | контейнер ИЛИ compose-пара | имя/ID контейнера (одиночный режим) |
26
+ | `docker` | `docker_compose_file` | вместе с `docker_compose_service` | путь к compose-файлу |
27
+ | `docker` | `docker_compose_service` | вместе с `docker_compose_file` | имя сервиса в compose |
28
+ | `k8s` | `k8s_deployment` | обязательно | имя Kubernetes Deployment |
29
+ | `k8s` | `k8s_namespace` | опционально (пусто → `default`) | namespace кластера |
30
+ | `k8s` | `k8s_context` | опционально (пусто → текущий контекст kubeconfig) | контекст kubectl |
31
+ | `k8s` | `k8s_container` | опционально | имя контейнера в поде (для `read_logs` при нескольких контейнерах) |
32
+ | `k8s` | `k8s_replicas` | опционально, по умолчанию `1` | число реплик при `start` |
33
+
34
+ `Stand.validate()` проверяет обязательность этих полей для `iis`/`docker`/`k8s`.
35
+
36
+ ## Примеры реестра
37
+
38
+ ### IIS
39
+
40
+ ```json
41
+ {
42
+ "example-iis": {
43
+ "transport": "local",
44
+ "host_kind": "iis",
45
+ "iis_site": "BPMSoft-example-iis",
46
+ "iis_app_pool": "BPMSoft-example-iis-pool",
47
+ "iis_stdout_log_dir": "C:\\inetpub\\wwwroot\\example-iis\\logs",
48
+ "stand_dir": "C:\\inetpub\\wwwroot\\example-iis",
49
+ "stand_host": "127.0.0.1",
50
+ "stand_port": 5000
51
+ }
52
+ }
53
+ ```
54
+
55
+ ### Docker (одиночный контейнер)
56
+
57
+ ```json
58
+ {
59
+ "example-docker": {
60
+ "transport": "local",
61
+ "host_kind": "docker",
62
+ "docker_container": "bpmsoft-example-docker",
63
+ "stand_dir": "/opt/bpmsoft/example-docker",
64
+ "stand_host": "127.0.0.1",
65
+ "stand_port": 5000
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Docker (compose-сервис)
71
+
72
+ ```json
73
+ {
74
+ "example-docker-compose": {
75
+ "transport": "local",
76
+ "host_kind": "docker",
77
+ "docker_compose_file": "/opt/bpmsoft/example/docker-compose.yml",
78
+ "docker_compose_service": "webhost",
79
+ "stand_dir": "/opt/bpmsoft/example",
80
+ "stand_host": "127.0.0.1",
81
+ "stand_port": 5000
82
+ }
83
+ }
84
+ ```
85
+
86
+ ### Kubernetes (k8s)
87
+
88
+ ```json
89
+ {
90
+ "example-k8s": {
91
+ "transport": "local",
92
+ "host_kind": "k8s",
93
+ "k8s_namespace": "bpmsoft",
94
+ "k8s_deployment": "bpmsoft-example-k8s",
95
+ "k8s_context": "example-cluster",
96
+ "k8s_container": "webhost",
97
+ "k8s_replicas": 1,
98
+ "stand_dir": "/opt/bpmsoft/example-k8s",
99
+ "stand_host": "127.0.0.1",
100
+ "stand_port": 5000
101
+ }
102
+ }
103
+ ```
104
+
105
+ Полный образец схемы — [`projects.sample.json`](../projects.sample.json)
106
+ (записи `example-iis`, `example-docker`, `example-k8s`).
107
+
108
+ ## Что делается «под капотом»
109
+
110
+ Диспетчеризация — `standkit.lifecycle` (start/stop/restart/is_running) и
111
+ `standkit.hosting.get_backend(stand)`:
112
+
113
+ - **kestrel** — без изменений: `dotnet <stand_dll>` + pidfile
114
+ (`standkit.lifecycle`/`standkit.platform`).
115
+ - **iis** — через `appcmd.exe`
116
+ (`%windir%\system32\inetsrv\appcmd.exe`):
117
+ - старт: `start apppool /apppool.name:<pool>` и/или `start site /site.name:<site>`;
118
+ - стоп: `stop apppool` / `stop site`;
119
+ - рестарт: `recycle apppool /apppool.name:<pool>` (graceful) либо stop+start сайта;
120
+ - проверка «жив»: `list apppool <pool> /text:state` → `Started`; иначе по сайту;
121
+ фолбэк — открытый TCP-порт стенда.
122
+ - **docker** — через CLI `docker` / `docker compose`:
123
+ - одиночный контейнер: `docker start|stop|restart <container>`,
124
+ `docker inspect -f "{{.State.Running}}" <container>`,
125
+ `docker logs --tail N <container>`;
126
+ - compose-сервис: `docker compose -f <file> up -d|stop|restart <service>`,
127
+ состояние — парсинг `docker compose -f <file> ps`,
128
+ логи — `docker compose -f <file> logs --tail N <service>`.
129
+ - **k8s** — через CLI `kubectl` (базовые аргументы — `kubectl [--context
130
+ <k8s_context>] -n <k8s_namespace|default>`):
131
+ - старт: `... scale deployment/<deployment> --replicas=<k8s_replicas|1>`;
132
+ - стоп: `... scale deployment/<deployment> --replicas=0` (в Kubernetes нет
133
+ отдельной команды "остановить" — общепринятый эквивалент "стопа");
134
+ - рестарт: `... rollout restart deployment/<deployment>`;
135
+ - проверка «жив»: `... get deployment <deployment> -o
136
+ jsonpath={.status.readyReplicas}` → число > 0; фолбэк — открытый TCP-порт
137
+ стенда;
138
+ - логи: `... logs deployment/<deployment> --tail N [-c <k8s_container>]`.
139
+
140
+ Health-проба «процесс» (`standkit.health.check_stand`) для `iis`/`docker`/`k8s`
141
+ консультируется с соответствующим бэкендом, с сохранением фолбэка на TCP-порт
142
+ стенда (если бэкенд не подтвердил «жив», но порт открыт — стенд считается
143
+ живым: он может быть поднят и вручную).
144
+
145
+ ## Требования
146
+
147
+ - **IIS**: платформа — только Windows; агент/служба standkit должна иметь
148
+ права на управление IIS (обычно — членство в группе `IIS_IUSRS` недостаточно,
149
+ нужны права на `appcmd.exe`/WAS, см. документацию IIS по правам на удалённое
150
+ администрирование). Живая приёмка — на стенде с реальным IIS.
151
+ - **Docker**: установленный Docker Engine (`docker` в PATH процесса
152
+ standkit); для compose-режима — плагин `docker compose` (Compose V2).
153
+ - **Kubernetes**: установленный `kubectl` в PATH процесса standkit и
154
+ доступный kubeconfig/контекст (переменная `KUBECONFIG` либо
155
+ `~/.kube/config`) с правами на `get`/`scale`/`rollout`/`logs` над
156
+ Deployment в указанном namespace.
157
+
158
+ ## Ограничения
159
+
160
+ - UI-индикатор `host_kind` в дашборде `standkit_hub` — вне зоны ADR-0001
161
+ (бэклог).
162
+
163
+ См. также: [ADR-0001](adr/0001-hosting-backends.md),
164
+ [REMOTE_STANDS.md](REMOTE_STANDS.md) (транспорт `agent`, ортогонален
165
+ `host_kind`).