standkit 0.5.2__tar.gz → 0.6.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 (84) hide show
  1. {standkit-0.5.2/standkit.egg-info → standkit-0.6.0}/PKG-INFO +15 -2
  2. {standkit-0.5.2 → standkit-0.6.0}/README.md +13 -0
  3. {standkit-0.5.2 → standkit-0.6.0}/SECURITY.md +34 -0
  4. {standkit-0.5.2 → standkit-0.6.0}/docs/ARCHITECTURE.md +8 -4
  5. standkit-0.6.0/docs/BACKLOG.md +86 -0
  6. standkit-0.6.0/docs/CHANGELOG.md +111 -0
  7. standkit-0.6.0/docs/ROADMAP.md +69 -0
  8. standkit-0.6.0/docs/adr/0003-remote-log-browsing.md +63 -0
  9. standkit-0.6.0/docs/adr/0004-iis-site-scoped-lifecycle.md +66 -0
  10. {standkit-0.5.2 → standkit-0.6.0}/pyproject.toml +2 -2
  11. {standkit-0.5.2 → standkit-0.6.0}/standkit/__init__.py +1 -1
  12. standkit-0.6.0/standkit/adopt.py +704 -0
  13. standkit-0.6.0/standkit/health.py +243 -0
  14. {standkit-0.5.2 → standkit-0.6.0}/standkit/hosting.py +329 -30
  15. {standkit-0.5.2 → standkit-0.6.0}/standkit/lifecycle.py +167 -20
  16. standkit-0.6.0/standkit/platform.py +263 -0
  17. {standkit-0.5.2 → standkit-0.6.0/standkit.egg-info}/PKG-INFO +15 -2
  18. {standkit-0.5.2 → standkit-0.6.0}/standkit.egg-info/SOURCES.txt +11 -0
  19. {standkit-0.5.2 → standkit-0.6.0}/standkit_agent/security.py +4 -1
  20. {standkit-0.5.2 → standkit-0.6.0}/standkit_agent/server.py +40 -7
  21. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/__main__.py +28 -6
  22. standkit-0.6.0/standkit_hub/client.py +225 -0
  23. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/config.py +31 -0
  24. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/logs_browser.py +150 -9
  25. standkit-0.6.0/standkit_hub/poller.py +228 -0
  26. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/server.py +786 -71
  27. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/web/app.js +660 -41
  28. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/web/index.html +94 -30
  29. standkit-0.6.0/standkit_hub/web/manifest.webmanifest +41 -0
  30. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/web/style.css +305 -4
  31. standkit-0.6.0/tests/test_adopt.py +404 -0
  32. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hosting.py +24 -3
  33. standkit-0.6.0/tests/test_hub_fast_paint.py +462 -0
  34. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hub_logs_browser.py +56 -13
  35. standkit-0.6.0/tests/test_hub_pwa_and_compact.py +220 -0
  36. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hub_server.py +140 -18
  37. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hub_shortcut.py +15 -0
  38. standkit-0.6.0/tests/test_lifecycle.py +364 -0
  39. standkit-0.5.2/standkit/health.py +0 -183
  40. standkit-0.5.2/standkit/platform.py +0 -165
  41. standkit-0.5.2/standkit_hub/client.py +0 -157
  42. standkit-0.5.2/tests/test_lifecycle.py +0 -153
  43. {standkit-0.5.2 → standkit-0.6.0}/LICENSE +0 -0
  44. {standkit-0.5.2 → standkit-0.6.0}/MANIFEST.in +0 -0
  45. {standkit-0.5.2 → standkit-0.6.0}/docs/HOSTING.md +0 -0
  46. {standkit-0.5.2 → standkit-0.6.0}/docs/REMOTE_STANDS.md +0 -0
  47. {standkit-0.5.2 → standkit-0.6.0}/docs/adr/0001-hosting-backends.md +0 -0
  48. {standkit-0.5.2 → standkit-0.6.0}/docs/adr/0002-k8s-backend.md +0 -0
  49. {standkit-0.5.2 → standkit-0.6.0}/projects.sample.json +0 -0
  50. {standkit-0.5.2 → standkit-0.6.0}/setup.cfg +0 -0
  51. {standkit-0.5.2 → standkit-0.6.0}/standkit/logs.py +0 -0
  52. {standkit-0.5.2 → standkit-0.6.0}/standkit/models.py +0 -0
  53. {standkit-0.5.2 → standkit-0.6.0}/standkit/registry.py +0 -0
  54. {standkit-0.5.2 → standkit-0.6.0}/standkit/secrets.py +0 -0
  55. {standkit-0.5.2 → standkit-0.6.0}/standkit.egg-info/dependency_links.txt +0 -0
  56. {standkit-0.5.2 → standkit-0.6.0}/standkit.egg-info/entry_points.txt +0 -0
  57. {standkit-0.5.2 → standkit-0.6.0}/standkit.egg-info/requires.txt +0 -0
  58. {standkit-0.5.2 → standkit-0.6.0}/standkit.egg-info/top_level.txt +0 -0
  59. {standkit-0.5.2 → standkit-0.6.0}/standkit_agent/__init__.py +0 -0
  60. {standkit-0.5.2 → standkit-0.6.0}/standkit_agent/__main__.py +0 -0
  61. {standkit-0.5.2 → standkit-0.6.0}/standkit_agent/audit.py +0 -0
  62. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/__init__.py +0 -0
  63. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/agent_control.py +0 -0
  64. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/assets/bpmkit-icon.ico +0 -0
  65. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/assets/icon.png +0 -0
  66. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/redis_min.py +0 -0
  67. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/security.py +0 -0
  68. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/shortcut.py +0 -0
  69. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/web/bpmkit-logo-dark.svg +0 -0
  70. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/web/bpmkit-logo.svg +0 -0
  71. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/web/favicon.png +0 -0
  72. {standkit-0.5.2 → standkit-0.6.0}/standkit_hub/web/favicon.svg +0 -0
  73. {standkit-0.5.2 → standkit-0.6.0}/tests/test_agent_security.py +0 -0
  74. {standkit-0.5.2 → standkit-0.6.0}/tests/test_agent_server_integration.py +0 -0
  75. {standkit-0.5.2 → standkit-0.6.0}/tests/test_health.py +0 -0
  76. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hub_agent_control.py +0 -0
  77. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hub_config.py +0 -0
  78. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hub_redis_min.py +0 -0
  79. {standkit-0.5.2 → standkit-0.6.0}/tests/test_hub_register.py +0 -0
  80. {standkit-0.5.2 → standkit-0.6.0}/tests/test_logs.py +0 -0
  81. {standkit-0.5.2 → standkit-0.6.0}/tests/test_models.py +0 -0
  82. {standkit-0.5.2 → standkit-0.6.0}/tests/test_registry.py +0 -0
  83. {standkit-0.5.2 → standkit-0.6.0}/tests/test_registry_resolver.py +0 -0
  84. {standkit-0.5.2 → standkit-0.6.0}/tests/test_secrets.py +0 -0
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: standkit
3
- Version: 0.5.2
3
+ Version: 0.6.0
4
4
  Summary: Свободное ядро (MIT) для управления жизненным циклом локальных и удалённых стендов BPMSoft: headless start/stop/restart, health-пробы, логи, реестр стендов. Часть экосистемы BPMkit.
5
5
  Author: standkit contributors
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/thinkquattro/BPMkitStand
8
- Project-URL: Экосистема BPMkit, https://github.com/thinkquattro/BPMkit-dev
8
+ Project-URL: Экосистема BPMkit, https://bpmkit.pro
9
9
  Classifier: Programming Language :: Python :: 3
10
10
  Classifier: Programming Language :: Python :: 3.10
11
11
  Classifier: Operating System :: OS Independent
@@ -140,6 +140,19 @@ BPMkitStand использует тот же реестр, что и MCP BPMkit
140
140
  в [SECURITY.md](SECURITY.md). Кратко: fail-closed bind на loopback, TLS 1.2+/mTLS для агента,
141
141
  скоупы токена, per-IP lockout, аудит без утечки секретов.
142
142
 
143
+ ## Документация проекта
144
+
145
+ - [docs/ROADMAP.md](docs/ROADMAP.md) — дорожная карта приложения (что сделано / ближайшее / бэклог).
146
+ - [docs/CHANGELOG.md](docs/CHANGELOG.md) — история изменений по версиям.
147
+ - [docs/BACKLOG.md](docs/BACKLOG.md) — честный список «упомянуто в коде, но не реализовано» (заделы/заглушки) со ссылками на код.
148
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) · [docs/HOSTING.md](docs/HOSTING.md) · [docs/REMOTE_STANDS.md](docs/REMOTE_STANDS.md) · [SECURITY.md](SECURITY.md)
149
+ - Решения (ADR): [0001 — hosting backends](docs/adr/0001-hosting-backends.md) · [0002 — k8s backend](docs/adr/0002-k8s-backend.md) · [0003 — браузинг логов удалённых стендов (предложено)](docs/adr/0003-remote-log-browsing.md) · [0004 — IIS: жизненный цикл по Site](docs/adr/0004-iis-site-scoped-lifecycle.md)
150
+
151
+ > Статус: `standkit` — молодой проект (0.5.x). Ядро/агент/хаб работоспособны, но
152
+ > ряд возможностей — это **каркас/заделы** (например, глубокие пробы БД/Redis,
153
+ > транспорты ssh/winrm, живая приёмка Docker/k8s/IIS). Что именно ещё не
154
+ > дописано — прозрачно перечислено в [docs/BACKLOG.md](docs/BACKLOG.md).
155
+
143
156
  ## BPMkitStand и Companion
144
157
 
145
158
  Бесплатная версия — полноценный диспетчер стендов. **Companion-версия** дополнительно даёт
@@ -116,6 +116,19 @@ BPMkitStand использует тот же реестр, что и MCP BPMkit
116
116
  в [SECURITY.md](SECURITY.md). Кратко: fail-closed bind на loopback, TLS 1.2+/mTLS для агента,
117
117
  скоупы токена, per-IP lockout, аудит без утечки секретов.
118
118
 
119
+ ## Документация проекта
120
+
121
+ - [docs/ROADMAP.md](docs/ROADMAP.md) — дорожная карта приложения (что сделано / ближайшее / бэклог).
122
+ - [docs/CHANGELOG.md](docs/CHANGELOG.md) — история изменений по версиям.
123
+ - [docs/BACKLOG.md](docs/BACKLOG.md) — честный список «упомянуто в коде, но не реализовано» (заделы/заглушки) со ссылками на код.
124
+ - [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) · [docs/HOSTING.md](docs/HOSTING.md) · [docs/REMOTE_STANDS.md](docs/REMOTE_STANDS.md) · [SECURITY.md](SECURITY.md)
125
+ - Решения (ADR): [0001 — hosting backends](docs/adr/0001-hosting-backends.md) · [0002 — k8s backend](docs/adr/0002-k8s-backend.md) · [0003 — браузинг логов удалённых стендов (предложено)](docs/adr/0003-remote-log-browsing.md) · [0004 — IIS: жизненный цикл по Site](docs/adr/0004-iis-site-scoped-lifecycle.md)
126
+
127
+ > Статус: `standkit` — молодой проект (0.5.x). Ядро/агент/хаб работоспособны, но
128
+ > ряд возможностей — это **каркас/заделы** (например, глубокие пробы БД/Redis,
129
+ > транспорты ssh/winrm, живая приёмка Docker/k8s/IIS). Что именно ещё не
130
+ > дописано — прозрачно перечислено в [docs/BACKLOG.md](docs/BACKLOG.md).
131
+
119
132
  ## BPMkitStand и Companion
120
133
 
121
134
  Бесплатная версия — полноценный диспетчер стендов. **Companion-версия** дополнительно даёт
@@ -75,6 +75,40 @@
75
75
  `LockPersonality`, `RestrictNamespaces`, `SystemCallFilter=@system-service`,
76
76
  выделенный не-root `User=`/`Group=`, урезанные `ReadWritePaths`.
77
77
 
78
+ ## 4.1. Усыновление стендов, поднятых вне диспетчера
79
+
80
+ Диспетчер умеет брать под управление kestrel-стенд, запущенный мимо него
81
+ (вручную из консоли, скриптом, чужой сессией): он находит владельца
82
+ TCP-порта стенда и, после подтверждения, записывает pidfile, чтобы обычные
83
+ Стоп/Рестарт заработали. Это **расширение RCE-поверхности**: появляется путь,
84
+ в котором управляющий контур убивает процесс, найденный по номеру порта, а не
85
+ запущенный им самим. Поэтому механизм ограничен так:
86
+
87
+ - **Три обязательных условия валидации** (все сразу, а не любое из них, см.
88
+ `standkit.adopt.validate_candidate`): порт процесса совпадает со
89
+ `stand.stand_port`; имя образа входит в allowlist (`dotnet`,
90
+ `BPMSoft.WebHost`, `w3wp`); рабочий каталог, путь исполняемого файла или
91
+ командная строка указывают **внутрь** `stand.stand_dir`. Не выполнено хотя
92
+ бы одно — усыновления нет, пользователь получает отказ с указанием, какой
93
+ именно процесс занимает порт.
94
+ - **Никакого тихого kill.** Без явного согласия `stop`/`restart` бросают
95
+ `AdoptionRequired` с описанием кандидата и не трогают процесс. Согласие
96
+ передаётся отдельным параметром (`?force=1` в хабе, `POST
97
+ /stand/<name>/adopt` у агента) — оно не выводится и не «запоминается».
98
+ - **Скоуп и аудит.** У агента `adopt` отнесён к `CONTROL_ACTIONS`: readonly-
99
+ токен усыновлять не может, каждая попытка (ok/denied/error) пишется в
100
+ аудит-лог наравне с start/stop/restart.
101
+ - **CSRF и токен хаба.** Эндпоинты усыновления — мутации, поэтому проходят ту
102
+ же проверку сессионного токена и заголовка `Origin`, что и остальные
103
+ мутирующие вызовы (см. `standkit_hub.security`).
104
+ - **Протухший pidfile** (процесс из файла уже мёртв) удаляется до эскалации —
105
+ чтобы диспетчер не отправил сигнал по переиспользованному ОС pid.
106
+
107
+ Остаётся осознанным допущением: процесс, полностью совпавший по всем трём
108
+ уликам, считается стендом. Злоумышленник, уже способный запустить `dotnet` из
109
+ каталога стенда на этом хосте, находится внутри границы доверия — против него
110
+ защищает не эта проверка, а разграничение доступа к хосту.
111
+
78
112
  ## 5. Чек-лист развёртывания на прод
79
113
 
80
114
  - [ ] Агент слушает loopback ИЛИ настроен TLS (лучше mTLS). Порт не в интернет.
@@ -1,10 +1,10 @@
1
1
  # Архитектура standkit
2
2
 
3
3
  Этот документ — краткое зеркало архитектурных решений для контекста разработки
4
- в самом репозитории `standkit`. **Детальный дизайн, обоснования выбора модели
5
- и разбор альтернатив — в репозитории BPMkit: ADR-0019 и
6
- `docs/планы/companion_dispatcher_f_l2a_2026-07-23.md`.** Здесь — только то, что
7
- нужно держать перед глазами при правке кода этого репозитория.
4
+ в самом репозитории `standkit`: только то, что нужно держать перед глазами при
5
+ правке кода. Обоснования и разбор альтернатив по отдельным подсистемам —
6
+ в [adr/](adr/); дорожная карта — в [ROADMAP.md](ROADMAP.md), незакрытые заделы —
7
+ в [BACKLOG.md](BACKLOG.md).
8
8
 
9
9
  ## Модель: ядро + две оболочки
10
10
 
@@ -90,6 +90,10 @@ standkit_hub — MIT, stdlib-only локальный веб-дашборд (
90
90
 
91
91
  ## Что уже реализовано в каркасе vs TODO
92
92
 
93
+ > Полный, поддерживаемый список «упомянуто в коде, но не реализовано» со ссылками
94
+ > на конкретные символы/файлы — [BACKLOG.md](BACKLOG.md). Дорожная карта —
95
+ > [ROADMAP.md](ROADMAP.md).
96
+
93
97
  Рабочая минимальная логика, покрытая тестами: `standkit/models.py`,
94
98
  `standkit/registry.py`, `standkit/health.py` (быстрые пробы: `tcp_open`,
95
99
  `http_ok`, `process_alive`). Скелетные модули с явными `TODO` в докстрингах:
@@ -0,0 +1,86 @@
1
+ # Бэклог standkit / BPMkitStand
2
+
3
+ Честный список того, что **упомянуто в коде, но ещё не реализовано / не
4
+ настроено** (каркас, заглушки, зарезервированные значения), с точной ссылкой на
5
+ место в коде. Цель — чтобы пользователь пакета и разработчик не принимали
6
+ «задел» за готовую фичу.
7
+
8
+ Метка **[упомянут в коде]** = в коде есть символ/ветка/поле, но реального
9
+ поведения за ним нет (или оно возвращает заглушку).
10
+
11
+ Порядок дорожной карты — см. [ROADMAP.md](ROADMAP.md). Остаточная безопасность
12
+ агента — `SECURITY.md` §6.
13
+
14
+ ## Транспорты подключения к стенду
15
+
16
+ | Пункт | Статус | Упомянут в коде |
17
+ |------|--------|-----------------|
18
+ | Транспорт **`ssh`** | зарезервирован в enum, реализации нет → `NotImplementedError` | `standkit/models.py::Transport.SSH` («задел на будущее»); фолбэк-`raise` в `standkit_hub/client.py::FederatedClient.status/_dispatch_action/logs` |
19
+ | Транспорт **`winrm`** | то же | `standkit/models.py::Transport.WINRM`; там же в `client.py` |
20
+
21
+ Рабочие транспорты — только `local` и `agent` (оба реализованы; в UI регистрации
22
+ предлагаются только они). `ssh`/`winrm` достижимы лишь ручной правкой
23
+ `projects.json` и упадут с `NotImplementedError`.
24
+
25
+ ## Health-пробы
26
+
27
+ | Пункт | Статус | Упомянут в коде |
28
+ |------|--------|-----------------|
29
+ | Глубокая проба БД (`SELECT 1` через psycopg2/pyodbc) | заглушка, всегда `SKIPPED`; флаг `deep_db` из хаба не передаётся | `standkit/health.py::db_deep_check`, параметр `check_stand(deep_db=…)` |
30
+ | Глубокая проба Redis (`PING` через redis-py) | заглушка, всегда `SKIPPED` | `standkit/health.py::redis_deep_check`, `check_stand(deep_redis=…)` |
31
+ | `last_deploy` (состояние последнего деплоя) | всегда `UNKNOWN`, источник данных не определён, в UI не выводится | `standkit/health.py::check_stand` (комментарий `last_deploy`), `standkit/models.py::StandStatus.last_deploy` |
32
+
33
+ Рабочие пробы (без заглушек): процесс (pid/бэкенд), HTTP, БД/Redis по **открытому
34
+ TCP-порту** — их достаточно для дашборда.
35
+
36
+ ## Жизненный цикл (kestrel)
37
+
38
+ | Пункт | Статус | Упомянут в коде |
39
+ |------|--------|-----------------|
40
+ | Polling готовности веб-хоста после `start()` | не реализован (возвращается pid; готовность проверяется отдельно) | `standkit/lifecycle.py` (докстринг модуля) |
41
+ | Блокировка pidfile от гонки двух `start()` | не реализована | там же |
42
+ | Восстановление «потерянного» pidfile | **реализовано** — поиск владельца порта, валидация (порт + allowlist образа + рабочий каталог внутри `stand_dir`) и усыновление с подтверждением | `standkit/adopt.py`, `standkit/lifecycle.py::adopt` |
43
+ | Доп. окружение процесса (`ASPNETCORE_ENVIRONMENT` и т.п.) | «зона расширения следующей итерации» | `standkit/lifecycle.py::_kestrel_start` |
44
+ | Graceful stop (SIGTERM→таймаут→SIGKILL / CTRL_BREAK_EVENT) | **реализовано** — эскалация мягко → таймаут → жёстко | `standkit/platform.py::stop` |
45
+ | Рабочий каталог процесса для WOW64 (32-битный стенд под 64-битным Python) | не реализовано — усыновление таких процессов даёт честный отказ | `standkit/adopt.py::_windows_process_cwd` |
46
+ | Windows Job Object / Linux double-fork | упрощённо (CREATE_NO_WINDOW / setsid) | `standkit/platform.py` (докстринг модуля) |
47
+
48
+ ## Логи
49
+
50
+ | Пункт | Статус | Упомянут в коде |
51
+ |------|--------|-----------------|
52
+ | Обработка ротации лог-файла в `follow()` | не реализована (при пересоздании файла позиция «уезжает») | `standkit/logs.py::follow` |
53
+ | Постраничное чтение очень больших логов | частично: `tail(max_bytes=…)` читает хвост; полноценной постраничной навигации нет | `standkit/logs.py::tail` |
54
+
55
+ ## Хаб / клиент
56
+
57
+ | Пункт | Статус | Упомянут в коде |
58
+ |------|--------|-----------------|
59
+ | Параллельный опрос агентов | **реализовано** — `ThreadPoolExecutor`, порядок результатов сохраняет порядок реестра | `standkit_hub/client.py::FederatedClient.status_all` |
60
+ | Кэш/дебаунс частых опросов | **реализовано** — фоновый поллер держит снапшот состояния, конфиг и реестр кэшируются по отпечатку файла | `standkit_hub/poller.py`, `standkit_hub/server.py::build_snapshot` |
61
+ | Ярлык на рабочем столе для **macOS** | осознанно не реализовано — возвращается понятный статус | `standkit_hub/shortcut.py` (докстринг) |
62
+ | Нативный трей (иконка со сводным состоянием, баллоны при падении) | не реализовано — потребовало бы `pystray`/`Pillow` против инварианта stdlib-only. Вместо него PWA-установка и компактный режим `?view=compact` | `standkit_hub/web/manifest.webmanifest` |
63
+ | Автозапуск хаба при входе в систему | не реализовано; механика ярлыка есть | `standkit_hub/shortcut.py` |
64
+
65
+ ## Секреты
66
+
67
+ | Пункт | Статус | Упомянут в коде |
68
+ |------|--------|-----------------|
69
+ | CLI-обёртка `set/get/status/rotate/delete` | только программное API; CLI нет | `standkit/secrets.py` (комментарий «Бэклог следующих итераций») |
70
+ | Файловый фолбэк `secrets.enc` (машины без keyring) | не реализован | там же |
71
+
72
+ ## Приёмка (тестировалось только на моках)
73
+
74
+ | Пункт | Статус |
75
+ |------|--------|
76
+ | **Docker / k8s / IIS** бэкенды | юнит-тесты на моках зелёные, **живая приёмка на реальном контуре не проводилась** (см. [ADR-0001](adr/0001-hosting-backends.md) «🔴 крит до релиза»). Тесты: `tests/test_hosting.py` (везде `monkeypatch` `subprocess.run`) |
77
+ | Усыновление стенда (kestrel) | **живая приёмка проведена** на реальном стенде, поднятом мимо диспетчера: поиск владельца порта, признание своего стенда и отказ для чужого, отсутствие действий без подтверждения, остановка по подтверждению, протухший pidfile. Именно она вскрыла, что на Windows рабочий каталог процесса приходится читать из PEB — путь к стенду не встречается ни в `ExecutablePath`, ни в командной строке |
78
+ | Усыновление стенда (IIS) | не проверено на живом контуре: автоопределение сайта, снятие зависшего `w3wp`, различение «сайт остановлен / пул остановлен / порт держит http.sys» |
79
+
80
+ ## Крупные направления (отдельные ADR)
81
+
82
+ | Пункт | Статус | ADR |
83
+ |------|--------|-----|
84
+ | Браузинг/скачивание логов удалённых стендов | предложено, не реализовано | [ADR-0003](adr/0003-remote-log-browsing.md) |
85
+ | Остаточная безопасность агента (PKI, ротация токенов, per-stand ACL, CN→scope, ротация аудита, bounded thread pool, chunked) | не реализовано | `SECURITY.md` §6 |
86
+ | Companion (платная редакция: автоапдейт/лицензия/подпись) | вне этого репозитория | экосистема BPMkit |
@@ -0,0 +1,111 @@
1
+ # Changelog — standkit / BPMkitStand
2
+
3
+ Формат — [Keep a Changelog](https://keepachangelog.com/ru/1.1.0/); версии —
4
+ [SemVer](https://semver.org/lang/ru/). Даты — 2026 год.
5
+
6
+ ## [0.6.0] — 2026-07-27
7
+
8
+ ### Добавлено
9
+ - **Усыновление стендов, поднятых вне диспетчера.** Стенд, запущенный руками
10
+ (`dotnet BPMSoft.WebHost.dll` из консоли, скриптом, чужой сессией), теперь
11
+ можно остановить и перезапустить: диспетчер находит владельца порта,
12
+ проверяет, что это действительно процесс ЭТОГО стенда (порт + имя образа из
13
+ allowlist + рабочий каталог внутри `stand_dir`), и берёт его под управление —
14
+ **только с явного подтверждения**. Без подтверждения процесс не трогается.
15
+ В таблице стендов появился бейдж «вне диспетчера».
16
+ - **IIS: автоопределение сайта** по physical path и биндингу для стенда,
17
+ развёрнутого мимо диспетчера; снятие зависшего `w3wp` по pid; различение
18
+ состояний «сайт остановлен» / «пул остановлен» / «порт держит http.sys».
19
+ - **PWA.** `manifest.webmanifest` + иконки: Chrome и Edge предлагают
20
+ «Установить приложение» — дашборд получает отдельное окно без адресной
21
+ строки, свою иконку в панели задач и место в Alt+Tab. Зависимостей не
22
+ добавилось.
23
+ - **Компактный режим** `?view=compact` — узкое окно-виджет: имена стендов,
24
+ состояния и старт/стоп. Доступен ярлыком из установленного PWA.
25
+ - `GET /api/stands?probe=0` — слепок реестра без сетевых проб, и
26
+ `GET /api/events` — SSE-поток обновлений состояния.
27
+
28
+ ### Изменено
29
+ - **Дашборд открывается мгновенно.** Раньше первый экран ждал полного обхода
30
+ всех стендов: пробы шли последовательно, до 9 секунд на стенд, а конфиг и
31
+ реестр перечитывались с диска на каждый запрос. Три недоступных стенда за
32
+ firewall давали около 27 секунд серого экрана. Теперь состояние собирает
33
+ фоновый поток, а страница рисуется сразу и обновляется по мере готовности.
34
+ - **Фиксированный порт по умолчанию — 8770** (с откатом на свободный, если
35
+ занят). Побочный эффект прежнего эфемерного порта: браузер каждый запуск
36
+ считал дашборд новым сайтом, из-за чего сбрасывалась выбранная тема и
37
+ заново качалась статика.
38
+ - Тема хранится в конфиге хаба и применяется до загрузки скриптов — тёмная
39
+ больше не мигает светлой при открытии.
40
+ - Настройка «интервал автообновления» наконец работает: раньше значение из
41
+ формы ни на что не влияло, интервал был жёстко зашит.
42
+ - **Форма настроек разгружена**: сразу видны четыре основных поля, параметры
43
+ федерации и демона-агента убраны под спойлеры, причём блок агента скрыт
44
+ целиком, пока в реестре нет удалённых стендов. У полей появились пояснения
45
+ и подсказки со значениями по умолчанию.
46
+ - Остановка стенда стала мягкой: сначала штатное завершение с ожиданием и
47
+ только потом принудительное — стенд успевает закрыть соединения с БД и
48
+ Redis. Раньше на Windows сразу шёл `taskkill /T /F`.
49
+ - Статика отдаётся с валидаторами кэша (`ETag`, `304`).
50
+
51
+ ## [0.5.2] — 2026-07-25
52
+
53
+ ### Изменено
54
+ - **IIS: жизненный цикл по Site.** Стоп/рестарт/детект работают по **Site**
55
+ стенда; **App Pool не трогаем** (может быть общим с другими приложениями).
56
+ App Pool задействуется только когда `iis_site` не задан. Детект «жив» — по
57
+ состоянию сайта; открытый TCP-порт больше не маскирует остановленный сайт
58
+ (http.sys держит порт). См. [ADR-0004](adr/0004-iis-site-scoped-lifecycle.md).
59
+ - **Читаемые ошибки внешних команд.** Вывод `appcmd`/`docker`/`kubectl`
60
+ декодируется utf-8 → OEM (cp866) → cp1251 — больше не «кракозябры».
61
+ - **Хаб — тяжёлые логи IIS.** Панель «Текущее состояние» берёт логи только за
62
+ сегодня и читает лишь хвост файла (до 4 МБ) — дневной IIS-лог в сотни МБ не
63
+ грузится целиком.
64
+ - Упаковка: SPDX-выражение лицензии (`license = "MIT"`, `license-files`) —
65
+ убран deprecation-warning setuptools.
66
+
67
+ ### Добавлено
68
+ - Подсказка «запустите диспетчер от имени администратора» при ошибках `appcmd`
69
+ из-за нехватки прав (IIS требует elevation).
70
+ - Кнопка «Обновить» даёт обратную связь (блокировка + «Обновление…» + время).
71
+ - Колонка **Redis** в таблице стендов с номером базы.
72
+ - Обратная связь и надёжность первого запуска: папка реестра создаётся при старте
73
+ диспетчера и при первой записи.
74
+
75
+ ### Исправлено
76
+ - **Честный отказ** при остановке стенда, запущенного вне диспетчера (нет
77
+ pidfile): вместо мнимого «остановлено» — понятная ошибка.
78
+ - Логи стенда ищутся в подпапках по датам (рекурсивный листинг) — раньше хаб
79
+ ошибочно сообщал «в каталоге нет файлов».
80
+ - Модалки не закрываются при выделении текста мышью с отпусканием за краем окна.
81
+ - Колонки таблицы не «прыгают» при смене статуса; HTTP-значение — кликабельная
82
+ ссылка; кнопки в модалках не слипаются.
83
+ - `Ctrl+C` завершает `standkit-hub` чисто, без трейсбека `KeyboardInterrupt`.
84
+ - Дедуп SVG-ассетов; TODO-докстринги урезаны до ссылок на документацию/бэклог.
85
+
86
+ ## [0.5.1] — 2026-07-24
87
+
88
+ Технический релиз: агрегировал правки хаба/IIS/логов и упаковки (позже целиком
89
+ переиздано в 0.5.2 из ветки `main`).
90
+
91
+ ## [0.5.0] — 2026-07-24
92
+
93
+ ### Добавлено
94
+ - **Kubernetes** hosting backend (`host_kind=k8s`) через `kubectl`
95
+ (scale/rollout/get/logs) — [ADR-0002](adr/0002-k8s-backend.md).
96
+ - **Регистрация стенда из UI** — модалка «Зарегистрировать стенд» в дашборде
97
+ (пишет в общий `projects.json`).
98
+ - Hosting backends kestrel/iis/docker — [ADR-0001](adr/0001-hosting-backends.md)
99
+ (v0.4.0), k8s — v0.5.0.
100
+
101
+ ## [0.3.7] — 2026-07
102
+
103
+ ### Добавлено
104
+ - Первый публичный релиз ядра `standkit` + агента `standkit_agent` + веб-дашборда
105
+ `standkit_hub` на PyPI; фронт-README, `docs/REMOTE_STANDS.md`, скриншоты.
106
+
107
+ [0.6.0]: https://pypi.org/project/standkit/0.6.0/
108
+ [0.5.2]: https://pypi.org/project/standkit/0.5.2/
109
+ [0.5.1]: https://pypi.org/project/standkit/0.5.1/
110
+ [0.5.0]: https://pypi.org/project/standkit/0.5.0/
111
+ [0.3.7]: https://pypi.org/project/standkit/0.3.7/
@@ -0,0 +1,69 @@
1
+ # ROADMAP — standkit / BPMkitStand
2
+
3
+ Дорожная карта **приложения** (свободное ядро `standkit` + дашборд
4
+ `standkit_hub` + агент `standkit_agent`). Полный список незакрытых «заделов» с
5
+ ссылками на код — [BACKLOG.md](BACKLOG.md). Архитектура — [ARCHITECTURE.md](ARCHITECTURE.md).
6
+ Решения — [adr/](adr/). История изменений — [CHANGELOG.md](CHANGELOG.md).
7
+
8
+ Текущая версия на PyPI: **0.5.2**. Python ≥ 3.10, ядро и агент — stdlib-only.
9
+
10
+ ## ✅ Сделано
11
+
12
+ - **Ядро `standkit`** — реестр стендов, модель `Stand` (`transport`
13
+ `local`/`agent`, `host_kind` kestrel/iis/docker/k8s), быстрые health-пробы
14
+ (процесс/HTTP/БД/Redis по TCP), жизненный цикл kestrel (headless start/stop/
15
+ restart, pidfile), Secret-first доступ к секретам (env→keyring→фолбэк).
16
+ - **Hosting backends** (ADR-0001/0002) — kestrel / IIS (`appcmd`) / Docker
17
+ (`docker`,`compose`) / Kubernetes (`kubectl`). Диспетчеризация по `host_kind`,
18
+ прозрачна для хаба/агента/клиента.
19
+ - **Дашборд `standkit_hub`** — веб-UI (stdlib `http.server` + vanilla JS): список
20
+ стендов + состояние, старт/стоп/рестарт (честный старт до HTTP-ok), очистка
21
+ Redis, тёмная тема, модалка «Зарегистрировать стенд» (пишет в общий
22
+ `projects.json`), панель «Текущее состояние» (tail лога текущей сессии),
23
+ федерация с удалёнными агентами.
24
+ - **Агент `standkit_agent`** — stdlib-only HTTP/RPC с прод-харденингом: TLS/mTLS,
25
+ fail-closed loopback, скоупы control/readonly, rate-limit/lockout, аудит.
26
+ - **Консольные точки входа** — `standkit-hub` / `standkit-gui` / `standkit-agent`.
27
+
28
+ ### Правки текущего цикла (0.5.1 → 0.5.2) — см. [CHANGELOG.md](CHANGELOG.md)
29
+
30
+ - IIS: жизненный цикл **по Site** (App Pool не трогаем — может быть общим),
31
+ корректный детект остановленного сайта (http.sys держит порт), читаемая ошибка
32
+ `appcmd` (OEM-декод) + подсказка «запустить от администратора» — [ADR-0004](adr/0004-iis-site-scoped-lifecycle.md).
33
+ - Честный отказ при остановке стенда, запущенного вне диспетчера (нет pidfile).
34
+ - Хаб: колонка Redis с номером базы; HTTP-значение — ссылка; колонки не «прыгают»;
35
+ модалки не закрываются при выделении текста; фидбэк кнопки «Обновить»; лёгкие
36
+ логи IIS (только за сегодня + чтение хвоста файла); первый запуск создаёт папку
37
+ реестра; Ctrl+C завершает дашборд без трейсбека.
38
+ - Упаковка: SPDX-лицензия (без deprecation-warning setuptools).
39
+
40
+ ## 🔴 Крит до «боевого» релиса
41
+
42
+ - **Живая приёмка Docker / k8s / IIS** на реальном контуре — сейчас только моки
43
+ (см. [ADR-0001](adr/0001-hosting-backends.md), [BACKLOG.md](BACKLOG.md)).
44
+
45
+ ## ⏭ Ближайшее
46
+
47
+ - **Браузинг/скачивание логов удалённых стендов** — [ADR-0003](adr/0003-remote-log-browsing.md)
48
+ (листинг папки логов агентом, выбор файла/даты, просмотр и скачивание).
49
+ - UI-индикатор `host_kind` в дашборде; `read_logs` бэкенда (iis/docker/k8s) в
50
+ панели «Текущее состояние».
51
+ - Глубокие health-пробы БД/Redis (`SELECT 1`/`PING`) под опциональные зависимости
52
+ — сейчас заглушки `SKIPPED` ([BACKLOG.md](BACKLOG.md)).
53
+
54
+ ## 🧊 Бэклог (без срока)
55
+
56
+ - Транспорты `ssh`/`winrm` (сейчас зарезервированы в enum, не реализованы).
57
+ - Жизненный цикл kestrel: polling готовности, блокировка pidfile, graceful stop,
58
+ Windows Job Object / Linux double-fork.
59
+ - Логи: ротация в `follow()`, постраничная навигация.
60
+ - Клиент: параллельный опрос агентов, кэш/дебаунс.
61
+ - Секреты: CLI-обёртка, файловый фолбэк `secrets.enc`.
62
+ - Остаточная безопасность агента — `SECURITY.md` §6.
63
+
64
+ Полный список с ссылками на код — [BACKLOG.md](BACKLOG.md).
65
+
66
+ ## 🌐 Экосистема (вне этого репозитория)
67
+
68
+ - **Companion** — платная редакция на той же кодовой базе: автоапдейт MCP,
69
+ лицензия/токен, подпись артефактов. Часть экосистемы BPMkit; здесь не живёт.
@@ -0,0 +1,63 @@
1
+ # ADR-0003 — Браузинг и скачивание логов удалённых стендов
2
+
3
+ - Статус: **Предложено** (не реализовано) — приоритетный бэклог
4
+ - Контекст-владелец: Владимир Терновский
5
+ - Связано: [ADR-0001](0001-hosting-backends.md) (ссылается сюда), [REMOTE_STANDS.md](../REMOTE_STANDS.md), [BACKLOG.md](../BACKLOG.md)
6
+
7
+ > Этот ADR фиксирует **намеченное** решение. Реализации в коде пока нет —
8
+ > зафиксирован как «должен быть на месте» в связке с ADR-0001, который на него
9
+ > ссылается.
10
+
11
+ ## Контекст и проблема
12
+
13
+ В дашборде есть «Открыть папку логов» — но это `os.startfile`/`xdg-open`
14
+ (`standkit_hub/logs_browser.py::open_folder`), т.е. открытие папки в файловом
15
+ менеджере **на машине оператора**. Для **удалённого** стенда (`transport=agent`)
16
+ открывать нечего: файлы лежат на хосте агента.
17
+
18
+ При этом у стендов BPMSoft логи организованы **папками по датам** (`папка = дата`,
19
+ внутри — тяжёлые дневные файлы). Оператору нужно: увидеть **листинг**, выбрать
20
+ **дату/файл**, **посмотреть** и при необходимости **скачать** (файл целиком или
21
+ папку архивом), не выкачивая гигабайты «на всякий случай».
22
+
23
+ ## Решение (намечено)
24
+
25
+ Агент отдаёт логи по HTTP; хаб даёт UI выбора/просмотра/скачивания. Все пути —
26
+ traversal-safe (как `sanitize_log_filename`).
27
+
28
+ ### Новые эндпоинты агента (`standkit_agent/server.py`, scope readonly)
29
+
30
+ - `GET /stand/{name}/logs/list` — листинг папки логов: файлы/подпапки с именем
31
+ (POSIX-путь относительно корня логов), размером и mtime; сортировка по свежести.
32
+ - `GET /stand/{name}/logs/file?path=…` — содержимое (или хвост) одного файла;
33
+ параметры `n`/`max_bytes` как у `standkit.logs.tail`.
34
+ - `GET /stand/{name}/logs/download?path=…` — скачивание: одиночный файл как есть
35
+ либо папка (напр. дата) ZIP-архивом; ленивое стриминг-скачивание без
36
+ предзагрузки в память.
37
+
38
+ ### Хаб (`standkit_hub/*`)
39
+
40
+ - Панель логов: выбор источника → **выбор даты/файла из листинга агента** →
41
+ просмотр хвоста → кнопка «Скачать» (файл / папку-архив).
42
+ - Для локального стенда — то же поверх `standkit.logs`/`logs_browser`
43
+ (единый UI, разные бэкенды доставки).
44
+
45
+ ### Затрагиваемые модули
46
+
47
+ `standkit_agent/server.py` (эндпоинты + traversal-guard + стриминг),
48
+ `standkit_hub/client.py`/`server.py`/`web/*` (UI листинга/просмотра/скачивания),
49
+ `standkit/logs.py` (переиспользование `tail(max_bytes=…)`; уже есть).
50
+
51
+ ## Границы и риски
52
+
53
+ - **Безопасность**: только scope `readonly`; строгая защита от path traversal
54
+ (корень логов стенда — единственная разрешённая зона); лимиты размера ответа.
55
+ - Большие архивы папок — стримить, не собирать в память; тайм-ауты/лимиты.
56
+ - Не ломать текущий локальный «Открыть папку логов» — он остаётся для локального
57
+ стенда.
58
+
59
+ ## Статус реализации
60
+
61
+ - [ ] Эндпоинты агента `logs/list` · `logs/file` · `logs/download`.
62
+ - [ ] UI хаба: листинг + выбор даты/файла + просмотр + скачивание.
63
+ - [ ] Тесты (traversal-safe, стриминг, листинг с датами).
@@ -0,0 +1,66 @@
1
+ # ADR-0004 — IIS: жизненный цикл по Site (App Pool не трогаем)
2
+
3
+ - Статус: **Принято / реализовано** (v0.5.2, 2026-07-25)
4
+ - Контекст-владелец: Владимир Терновский
5
+ - Связано: [ADR-0001](0001-hosting-backends.md) (вводит IIS-бэкенд), [HOSTING.md](../HOSTING.md)
6
+
7
+ ## Контекст и проблема
8
+
9
+ Первая реализация IIS-бэкенда (ADR-0001) на **стоп/рестарт** трогала и **сайт, и
10
+ App Pool** (`stop apppool` + `stop site`; рестарт — `recycle apppool`). На живом
11
+ стенде вскрылись три проблемы:
12
+
13
+ 1. **App Pool может быть общим** с другими приложениями. Остановка/рециклинг пула
14
+ кладёт и их — диспетчер обязан гасить **только стенд**, а не «полстраницы IIS».
15
+ 2. **Детект «жив» врал.** IIS/`http.sys` держит порт 80/443 на уровне ОС даже у
16
+ **остановленного** сайта (отдаёт 503). TCP-фолбэк показывал остановленный
17
+ стенд как «up».
18
+ 3. **Ошибка `appcmd` была нечитаемой** (OEM-кодировка cp866 декодировалась как
19
+ UTF-8 → «кракозябры»), а её первопричина — **нехватка прав администратора**
20
+ (`appcmd` читает `…\inetsrv\config\redirection.config`) — не была очевидна.
21
+
22
+ ## Решение
23
+
24
+ **«Стенд в IIS» = его Site.** Диспетчер управляет **сайтом** и не трогает App
25
+ Pool (пул — потенциально общая инфраструктура). Пул задействуется только как
26
+ единственный хэндл, когда `iis_site` не задан.
27
+
28
+ - **stop** → `appcmd stop site /site.name:<site>` (только сайт); при отсутствии
29
+ `iis_site` — `stop apppool`.
30
+ - **restart** → `stop site` + `start site` (App Pool **не рециклим**; recycle
31
+ пула — только когда сайт не задан).
32
+ - **start** → `start apppool` (если задан, чтобы сайт мог обслуживаться) +
33
+ `start site` — запуск не разрушителен для соседей.
34
+ - **is_running** → состояние **сайта** (`list site <site> /text:state == Started`);
35
+ при отсутствии сайта — состояние пула. TCP-фолбэк — **только** если `appcmd`
36
+ не дал определённого состояния (иначе открытый порт http.sys маскировал бы
37
+ остановленный сайт).
38
+
39
+ Смежно (тот же цикл, v0.5.2):
40
+
41
+ - **OEM-декод** вывода внешних команд: bytes → utf-8 → OEM (cp866) → cp1251
42
+ (`standkit/hosting.py::_decode_console`) — читаемые сообщения.
43
+ - **Подсказка про elevation**: при ошибках нехватки прав (`redirection.config`,
44
+ «отказано в доступе», код 1168, `0x80070005`) к тексту добавляется «запустите
45
+ диспетчер от имени администратора» (`_appcmd_checked`). Docker/k8s не затронуты.
46
+
47
+ ## Реализация
48
+
49
+ `standkit/hosting.py::IisBackend.{stop,restart,is_running}`,
50
+ `_decode_console`/`_oem_encoding`, `_appcmd_checked`. Обработчик действий хаба
51
+ ловит `HostingError` и отдаёт понятный текст (`standkit_hub/server.py`). Тесты —
52
+ `tests/test_hosting.py` (site-scoped стоп/рестарт, детект по сайту, OEM-декод,
53
+ подсказка про админа).
54
+
55
+ ## Требование эксплуатации
56
+
57
+ Диспетчер (`standkit-hub`) для IIS-операций **должен быть запущен «от имени
58
+ администратора»** — `appcmd` требует elevation; членства в `IIS_IUSRS`
59
+ недостаточно. См. [HOSTING.md](../HOSTING.md) → «Требования».
60
+
61
+ ## Границы
62
+
63
+ - Остановка только сайта прекращает приём запросов, но рабочий процесс пула
64
+ (`w3wp`) может дожить до idle-timeout, если пул обслуживает и другие приложения
65
+ — это осознанный компромисс ради «не задеть соседей». Опция «гасить выделенный
66
+ пул» с защитой «если пул общий — отказ» — возможное расширение (бэклог).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "standkit"
7
- version = "0.5.2"
7
+ version = "0.6.0"
8
8
  description = "Свободное ядро (MIT) для управления жизненным циклом локальных и удалённых стендов BPMSoft: headless start/stop/restart, health-пробы, логи, реестр стендов. Часть экосистемы BPMkit."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -23,7 +23,7 @@ dependencies = []
23
23
 
24
24
  [project.urls]
25
25
  Homepage = "https://github.com/thinkquattro/BPMkitStand"
26
- "Экосистема BPMkit" = "https://github.com/thinkquattro/BPMkit-dev"
26
+ "Экосистема BPMkit" = "https://bpmkit.pro"
27
27
 
28
28
  [project.optional-dependencies]
29
29
  # Опциональный backend хранения секретов (standkit.secrets: set_secret/
@@ -6,6 +6,6 @@ standkit — свободное (MIT) ядро управления жизнен
6
6
  и docs/ARCHITECTURE.md в корне репозитория.
7
7
  """
8
8
 
9
- __version__ = "0.5.2"
9
+ __version__ = "0.6.0"
10
10
 
11
11
  __all__ = ["__version__"]