remote-cmd-manager 2.4.0__tar.gz → 2.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 (87) hide show
  1. {remote_cmd_manager-2.4.0/remote_cmd_manager.egg-info → remote_cmd_manager-2.6.0}/PKG-INFO +23 -20
  2. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/README.md +18 -15
  3. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/pyproject.toml +15 -8
  4. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/__init__.py +18 -3
  5. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/_version.py +1 -1
  6. remote_cmd_manager-2.6.0/remote_cmd/api/__init__.py +22 -0
  7. {remote_cmd_manager-2.4.0/remote_cmd/core → remote_cmd_manager-2.6.0/remote_cmd/api}/host_manager.py +8 -2
  8. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/async_connection_pool.py +23 -4
  9. remote_cmd_manager-2.6.0/remote_cmd/core/budget.py +211 -0
  10. remote_cmd_manager-2.6.0/remote_cmd/core/host_manager.py +16 -0
  11. remote_cmd_manager-2.4.0/remote_cmd/service/_pool_policy.py → remote_cmd_manager-2.6.0/remote_cmd/core/pool_policy.py +5 -1
  12. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/sync_connection_pool.py +22 -4
  13. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/json_host_repository.py +61 -2
  14. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/sqlite_host_repository.py +43 -2
  15. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/_host_runner.py +31 -1
  16. remote_cmd_manager-2.6.0/remote_cmd/service/_pool_policy.py +16 -0
  17. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/_types.py +33 -1
  18. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/async_batch_executor.py +111 -47
  19. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/batch_executor.py +38 -11
  20. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/retry_policy.py +1 -1
  21. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/storage_factory.py +37 -11
  22. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/credential_guard.py +18 -0
  23. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/exceptions.py +38 -3
  24. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0/remote_cmd_manager.egg-info}/PKG-INFO +23 -20
  25. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/SOURCES.txt +6 -0
  26. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/requires.txt +3 -2
  27. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_async_batch_executor.py +51 -2
  28. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_batch_executor.py +6 -3
  29. remote_cmd_manager-2.6.0/tests/test_connection_budget.py +410 -0
  30. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_host_manager.py +14 -0
  31. remote_cmd_manager-2.6.0/tests/test_output_policy.py +172 -0
  32. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_pool_policy.py +10 -1
  33. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_release_metadata.py +13 -0
  34. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_repository.py +69 -0
  35. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_sqlite_repository.py +49 -2
  36. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_storage_factory.py +40 -2
  37. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/LICENSE +0 -0
  38. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/MANIFEST.in +0 -0
  39. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/config.example.yaml +0 -0
  40. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/basic_usage.py +0 -0
  41. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/deploy_script.py +0 -0
  42. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/nginx_batch_update.py +0 -0
  43. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/system_health_check.py +0 -0
  44. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/cli/__init__.py +0 -0
  45. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/cli/main.py +0 -0
  46. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/__init__.py +0 -0
  47. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/async_ssh_client.py +0 -0
  48. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/host.py +0 -0
  49. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/ssh_client.py +0 -0
  50. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/__init__.py +0 -0
  51. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/host_repository.py +0 -0
  52. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/__init__.py +0 -0
  53. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/credential_provider.py +0 -0
  54. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/host_service.py +0 -0
  55. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/ssh_service.py +0 -0
  56. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/task_runner.py +0 -0
  57. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/__init__.py +0 -0
  58. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/config.py +0 -0
  59. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/crypto.py +0 -0
  60. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/logging_utils.py +0 -0
  61. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/dependency_links.txt +0 -0
  62. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/entry_points.txt +0 -0
  63. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/top_level.txt +0 -0
  64. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/requirements.txt +0 -0
  65. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/setup.cfg +0 -0
  66. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/__init__.py +0 -0
  67. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/conftest.py +0 -0
  68. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/integration/conftest.py +0 -0
  69. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/integration/test_ssh_connection.py +0 -0
  70. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/performance/__init__.py +0 -0
  71. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/performance/conftest.py +0 -0
  72. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/performance/test_benchmarks.py +0 -0
  73. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_async_ssh_client.py +0 -0
  74. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_cli.py +0 -0
  75. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_config.py +0 -0
  76. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_credential_provider.py +0 -0
  77. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_crypto.py +0 -0
  78. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_exceptions.py +0 -0
  79. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_host.py +0 -0
  80. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_host_service.py +0 -0
  81. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_keyring_provider.py +0 -0
  82. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_logging_utils.py +0 -0
  83. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_retry_policy.py +0 -0
  84. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_ssh_client.py +0 -0
  85. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_ssh_service.py +0 -0
  86. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_sync_connection_pool.py +0 -0
  87. {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_task_runner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: remote_cmd_manager
3
- Version: 2.4.0
3
+ Version: 2.6.0
4
4
  Summary: Cross-platform SSH remote server management tool (Linux/macOS/Windows) — lightweight, zero-configuration CLI and Python API for managing servers over SSH. Supports host CRUD, batch commands, file transfer, credential encryption, async execution, and more.
5
5
  Author-email: Vae-Scrooge <vaescrooge@gmail.com>
6
6
  License: MIT
@@ -15,7 +15,6 @@ Classifier: Intended Audience :: Developers
15
15
  Classifier: Intended Audience :: System Administrators
16
16
  Classifier: License :: OSI Approved :: MIT License
17
17
  Classifier: Programming Language :: Python :: 3
18
- Classifier: Programming Language :: Python :: 3.9
19
18
  Classifier: Programming Language :: Python :: 3.10
20
19
  Classifier: Programming Language :: Python :: 3.11
21
20
  Classifier: Programming Language :: Python :: 3.12
@@ -27,17 +26,17 @@ Classifier: Environment :: Console
27
26
  Classifier: Operating System :: POSIX :: Linux
28
27
  Classifier: Operating System :: MacOS
29
28
  Classifier: Operating System :: Microsoft :: Windows
30
- Requires-Python: >=3.9
29
+ Requires-Python: >=3.10
31
30
  Description-Content-Type: text/markdown
32
31
  License-File: LICENSE
33
- Requires-Dist: paramiko>=3.0
32
+ Requires-Dist: paramiko<6,>=5.0
34
33
  Requires-Dist: click>=8.0
35
34
  Requires-Dist: cryptography>=41.0
36
35
  Requires-Dist: rich>=13.0
37
36
  Requires-Dist: PyYAML>=6.0
38
37
  Requires-Dist: aiofiles>=3.0
39
38
  Provides-Extra: async
40
- Requires-Dist: asyncssh>=2.14.0; extra == "async"
39
+ Requires-Dist: asyncssh<3,>=2.24.0; extra == "async"
41
40
  Provides-Extra: cloud
42
41
  Requires-Dist: boto3>=1.34; extra == "cloud"
43
42
  Provides-Extra: dev
@@ -46,6 +45,7 @@ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
46
45
  Requires-Dist: pytest-cov>=4.0; extra == "dev"
47
46
  Requires-Dist: ruff>=0.4; extra == "dev"
48
47
  Requires-Dist: mypy>=1.10; extra == "dev"
48
+ Requires-Dist: asyncssh<3,>=2.24.0; extra == "dev"
49
49
  Requires-Dist: keyring>=24.0; extra == "dev"
50
50
  Provides-Extra: docs
51
51
  Requires-Dist: pdoc==15.0.4; extra == "docs"
@@ -55,7 +55,7 @@ Dynamic: license-file
55
55
  <img src="https://img.shields.io/pypi/v/remote_cmd_manager?style=for-the-badge&logo=pypi&logoColor=white&label=PyPI" alt="PyPI">
56
56
  <img src="https://img.shields.io/pypi/dm/remote_cmd_manager?style=for-the-badge&logo=python&logoColor=white&label=Downloads" alt="Downloads">
57
57
  <img src="https://img.shields.io/github/stars/Vae-Scrooge/remote-cmd?style=for-the-badge&logo=github" alt="Stars">
58
- <img src="https://img.shields.io/badge/python-3.9%2B-blue?style=for-the-badge&logo=python" alt="Python">
58
+ <img src="https://img.shields.io/badge/python-3.10%2B-blue?style=for-the-badge&logo=python" alt="Python">
59
59
  <img src="https://img.shields.io/github/license/Vae-Scrooge/remote-cmd?style=for-the-badge" alt="License">
60
60
  <img src="https://img.shields.io/github/actions/workflow/status/Vae-Scrooge/remote-cmd/ci.yml?style=for-the-badge&logo=githubactions&label=CI" alt="CI">
61
61
  </p>
@@ -89,30 +89,32 @@ pip install remote_cmd_manager && remote-cmd host add web-01 192.168.1.10 ubuntu
89
89
 
90
90
  ---
91
91
 
92
- ## v2.4.0 Release Highlights
92
+ ## v2.6.0 Release Highlights
93
93
 
94
- v2.4.0 adds an opt-in retained-output cap for batch execution and cleans up logging
95
- handler resources, with default behavior and public result schemas unchanged. See the
96
- [full migration notes](./CHANGELOG.md) before upgrading automated callers.
94
+ v2.6.0 is an architecture and runtime-scalability release: the compatibility facade
95
+ moved out of `core` (eliminating the `core → service` dependency) and a process-wide
96
+ live-connection budget was added for fleet-scale resource control. Public APIs remain
97
+ backward compatible. See the [full migration notes](./CHANGELOG.md).
97
98
 
98
- ### Bounded Batch Output
99
+ ### Architecture
99
100
 
100
- - `BatchExecutor` / `AsyncBatchExecutor` accept an optional `max_output_bytes` (default `None`): `None` keeps full `stdout`/`stderr`, while a positive value caps each host's retained streams and appends a `[output truncated: N bytes omitted]` marker.
101
- - Truncation is deterministic and UTF-8 byte-boundary safe, applies to `stdout` and `stderr` independently, and never changes command success/failure or exit codes.
102
- - The cap bounds the retained `BatchResult`; the SSH client may still hold full output transiently during execution, so it is not a process-wide RSS limit.
101
+ - `HostManager` now lives in `remote_cmd.api.host_manager`; `remote_cmd.core.host_manager` remains a re-export shim, so existing imports keep working.
102
+ - Pool policy helpers moved to `remote_cmd.core.pool_policy` (shim kept at the old path); `core` no longer imports `service`.
103
+ - The async kernel's worker queue now converts unexpected per-host errors into failure results, so a single internal error can no longer stall an entire batch.
103
104
 
104
- ### Reliability
105
+ ### Global Connection Budget
105
106
 
106
- - `setup_logging` now closes previous root-logger handlers before removing them, avoiding unclosed-file resource warnings when logging is reconfigured.
107
- - Connection-pool lifetime wording in the documentation now matches the actual behavior: internal pools are created lazily per host and closed as soon as that host finishes (including its retries).
107
+ - New `ConnectionBudget` (exported from `remote_cmd`): caps **live** SSH connections process-wide across pools and executors — idle pooled connections count, and slots release on close/cleanup/`close_all`.
108
+ - One instance can be shared by both kernels via `connection_budget=` on `BatchExecutor`, `AsyncBatchExecutor`, `SyncConnectionPool`, and `AsyncConnectionPool`.
109
+ - Optional `acquire_timeout` raises the transient `BudgetTimeoutError`; the default (`None`) keeps unlimited behavior.
108
110
 
109
111
  ### Compatibility
110
112
 
111
- - Default behavior is unchanged (`max_output_bytes=None` retains full output) and the public `BatchResult` / `BatchHostResult` schemas are unchanged.
113
+ - All existing imports and constructor signatures keep working; new parameters are optional and default to previous behavior.
112
114
 
113
115
  ## Table of Contents
114
116
 
115
- - [v2.4.0 Release Highlights](#v240-release-highlights)
117
+ - [v2.6.0 Release Highlights](#v260-release-highlights)
116
118
  - [Why Remote CMD?](#why-remote-cmd)
117
119
  - [Quick Start](#quick-start)
118
120
  - [Use Cases](#use-cases)
@@ -262,6 +264,7 @@ with SSHClient(config) as client:
262
264
  | **Tag System** | Filter hosts by tag (e.g., `production`, `web`, `db`) |
263
265
  | **Batch Ops** | Run commands across any host group, synchronously or asynchronously; optional per-host retained-output cap (`max_output_bytes`) |
264
266
  | **Async Kernel** | `AsyncSSHClient` / `AsyncConnectionPool` / `AsyncBatchExecutor` via the `[async]` extra |
267
+ | **Global Connection Budget** | Optional `ConnectionBudget` caps live SSH connections process-wide across pools and executors |
265
268
  | **Task Runner** | Track and schedule long-running remote tasks with statuses (`TaskRunner`) |
266
269
  | **Connection Test** | Test all host SSH connections and report status |
267
270
  | **Secure Logging** | Structured logging that filters sensitive data (`SensitiveDataFilter`) |
@@ -350,7 +353,7 @@ Good first issues are labelled `good first issue` in the
350
353
  developed independently as a focused alternative to heavyweight tools for the
351
354
  ad-hoc SSH tasks that come up in day-to-day server work.
352
355
 
353
- - **Project health:** CI runs on every PR, Python 3.9+ is supported, and the
356
+ - **Project health:** CI runs on every PR, Python 3.10+ is supported, and the
354
357
  public API is versioned under [semantic versioning](https://semver.org/).
355
358
  - **Your code, your servers:** usage stays open under the MIT license — nothing
356
359
  is telemetry-driven or locked behind a service.
@@ -2,7 +2,7 @@
2
2
  <img src="https://img.shields.io/pypi/v/remote_cmd_manager?style=for-the-badge&logo=pypi&logoColor=white&label=PyPI" alt="PyPI">
3
3
  <img src="https://img.shields.io/pypi/dm/remote_cmd_manager?style=for-the-badge&logo=python&logoColor=white&label=Downloads" alt="Downloads">
4
4
  <img src="https://img.shields.io/github/stars/Vae-Scrooge/remote-cmd?style=for-the-badge&logo=github" alt="Stars">
5
- <img src="https://img.shields.io/badge/python-3.9%2B-blue?style=for-the-badge&logo=python" alt="Python">
5
+ <img src="https://img.shields.io/badge/python-3.10%2B-blue?style=for-the-badge&logo=python" alt="Python">
6
6
  <img src="https://img.shields.io/github/license/Vae-Scrooge/remote-cmd?style=for-the-badge" alt="License">
7
7
  <img src="https://img.shields.io/github/actions/workflow/status/Vae-Scrooge/remote-cmd/ci.yml?style=for-the-badge&logo=githubactions&label=CI" alt="CI">
8
8
  </p>
@@ -36,30 +36,32 @@ pip install remote_cmd_manager && remote-cmd host add web-01 192.168.1.10 ubuntu
36
36
 
37
37
  ---
38
38
 
39
- ## v2.4.0 Release Highlights
39
+ ## v2.6.0 Release Highlights
40
40
 
41
- v2.4.0 adds an opt-in retained-output cap for batch execution and cleans up logging
42
- handler resources, with default behavior and public result schemas unchanged. See the
43
- [full migration notes](./CHANGELOG.md) before upgrading automated callers.
41
+ v2.6.0 is an architecture and runtime-scalability release: the compatibility facade
42
+ moved out of `core` (eliminating the `core → service` dependency) and a process-wide
43
+ live-connection budget was added for fleet-scale resource control. Public APIs remain
44
+ backward compatible. See the [full migration notes](./CHANGELOG.md).
44
45
 
45
- ### Bounded Batch Output
46
+ ### Architecture
46
47
 
47
- - `BatchExecutor` / `AsyncBatchExecutor` accept an optional `max_output_bytes` (default `None`): `None` keeps full `stdout`/`stderr`, while a positive value caps each host's retained streams and appends a `[output truncated: N bytes omitted]` marker.
48
- - Truncation is deterministic and UTF-8 byte-boundary safe, applies to `stdout` and `stderr` independently, and never changes command success/failure or exit codes.
49
- - The cap bounds the retained `BatchResult`; the SSH client may still hold full output transiently during execution, so it is not a process-wide RSS limit.
48
+ - `HostManager` now lives in `remote_cmd.api.host_manager`; `remote_cmd.core.host_manager` remains a re-export shim, so existing imports keep working.
49
+ - Pool policy helpers moved to `remote_cmd.core.pool_policy` (shim kept at the old path); `core` no longer imports `service`.
50
+ - The async kernel's worker queue now converts unexpected per-host errors into failure results, so a single internal error can no longer stall an entire batch.
50
51
 
51
- ### Reliability
52
+ ### Global Connection Budget
52
53
 
53
- - `setup_logging` now closes previous root-logger handlers before removing them, avoiding unclosed-file resource warnings when logging is reconfigured.
54
- - Connection-pool lifetime wording in the documentation now matches the actual behavior: internal pools are created lazily per host and closed as soon as that host finishes (including its retries).
54
+ - New `ConnectionBudget` (exported from `remote_cmd`): caps **live** SSH connections process-wide across pools and executors — idle pooled connections count, and slots release on close/cleanup/`close_all`.
55
+ - One instance can be shared by both kernels via `connection_budget=` on `BatchExecutor`, `AsyncBatchExecutor`, `SyncConnectionPool`, and `AsyncConnectionPool`.
56
+ - Optional `acquire_timeout` raises the transient `BudgetTimeoutError`; the default (`None`) keeps unlimited behavior.
55
57
 
56
58
  ### Compatibility
57
59
 
58
- - Default behavior is unchanged (`max_output_bytes=None` retains full output) and the public `BatchResult` / `BatchHostResult` schemas are unchanged.
60
+ - All existing imports and constructor signatures keep working; new parameters are optional and default to previous behavior.
59
61
 
60
62
  ## Table of Contents
61
63
 
62
- - [v2.4.0 Release Highlights](#v240-release-highlights)
64
+ - [v2.6.0 Release Highlights](#v260-release-highlights)
63
65
  - [Why Remote CMD?](#why-remote-cmd)
64
66
  - [Quick Start](#quick-start)
65
67
  - [Use Cases](#use-cases)
@@ -209,6 +211,7 @@ with SSHClient(config) as client:
209
211
  | **Tag System** | Filter hosts by tag (e.g., `production`, `web`, `db`) |
210
212
  | **Batch Ops** | Run commands across any host group, synchronously or asynchronously; optional per-host retained-output cap (`max_output_bytes`) |
211
213
  | **Async Kernel** | `AsyncSSHClient` / `AsyncConnectionPool` / `AsyncBatchExecutor` via the `[async]` extra |
214
+ | **Global Connection Budget** | Optional `ConnectionBudget` caps live SSH connections process-wide across pools and executors |
212
215
  | **Task Runner** | Track and schedule long-running remote tasks with statuses (`TaskRunner`) |
213
216
  | **Connection Test** | Test all host SSH connections and report status |
214
217
  | **Secure Logging** | Structured logging that filters sensitive data (`SensitiveDataFilter`) |
@@ -297,7 +300,7 @@ Good first issues are labelled `good first issue` in the
297
300
  developed independently as a focused alternative to heavyweight tools for the
298
301
  ad-hoc SSH tasks that come up in day-to-day server work.
299
302
 
300
- - **Project health:** CI runs on every PR, Python 3.9+ is supported, and the
303
+ - **Project health:** CI runs on every PR, Python 3.10+ is supported, and the
301
304
  public API is versioned under [semantic versioning](https://semver.org/).
302
305
  - **Your code, your servers:** usage stays open under the MIT license — nothing
303
306
  is telemetry-driven or locked behind a service.
@@ -7,7 +7,7 @@ name = "remote_cmd_manager"
7
7
  dynamic = ["version"]
8
8
  description = "Cross-platform SSH remote server management tool (Linux/macOS/Windows) — lightweight, zero-configuration CLI and Python API for managing servers over SSH. Supports host CRUD, batch commands, file transfer, credential encryption, async execution, and more."
9
9
  readme = "README.md"
10
- requires-python = ">=3.9"
10
+ requires-python = ">=3.10"
11
11
  license = { text = "MIT" }
12
12
  authors = [
13
13
  { name = "Vae-Scrooge", email = "vaescrooge@gmail.com" },
@@ -19,7 +19,6 @@ classifiers = [
19
19
  "Intended Audience :: System Administrators",
20
20
  "License :: OSI Approved :: MIT License",
21
21
  "Programming Language :: Python :: 3",
22
- "Programming Language :: Python :: 3.9",
23
22
  "Programming Language :: Python :: 3.10",
24
23
  "Programming Language :: Python :: 3.11",
25
24
  "Programming Language :: Python :: 3.12",
@@ -33,7 +32,7 @@ classifiers = [
33
32
  "Operating System :: Microsoft :: Windows",
34
33
  ]
35
34
  dependencies = [
36
- "paramiko>=3.0",
35
+ "paramiko>=5.0,<6",
37
36
  "click>=8.0",
38
37
  "cryptography>=41.0",
39
38
  "rich>=13.0",
@@ -43,22 +42,24 @@ dependencies = [
43
42
 
44
43
  [project.optional-dependencies]
45
44
  # 异步原生执行内核:启用 BatchExecutor(use_async=True) / AsyncSSHClient / AsyncConnectionPool 时需要
46
- async = ["asyncssh>=2.14.0"]
45
+ # 下限锁定在 2.24.x:2.23.x 及更早版本存在 2026 年公开 CVE(SCP 路径穿越等)。
46
+ async = ["asyncssh>=2.24.0,<3"]
47
47
  # 云平台主机发现(Phase 4 预留)
48
48
  cloud = ["boto3>=1.34"]
49
49
  # 开发与测试
50
+ # 包含 async extra:默认测试套件会 import 异步模块,仅装 dev 无法收集。
50
51
  dev = [
51
52
  "pytest>=8.0",
52
53
  "pytest-asyncio>=0.23",
53
54
  "pytest-cov>=4.0",
54
55
  "ruff>=0.4",
55
56
  "mypy>=1.10",
57
+ "asyncssh>=2.24.0,<3",
56
58
  # 测试 keyring 凭据提供者(生产为可选依赖,测试环境固定安装)
57
59
  "keyring>=24.0",
58
60
  ]
59
- # 文档生成(pdoc>=16 需 Python>=3.10,固定到 15.x 以兼容 requires-python>=3.9)。
60
- # 精确 pin 15.0.4:pdoc HTML 输出随版本/解释器变化,docs/api 漂移门禁
61
- # (scripts/check_docs_drift.py)要求生成结果可复现。
61
+ # 文档生成。pdoc 固定到 15.0.4:docs/api 漂移门禁(scripts/check_docs_drift.py)
62
+ # 要求生成结果可复现。
62
63
  docs = ["pdoc==15.0.4"]
63
64
 
64
65
  [project.urls]
@@ -88,7 +89,7 @@ markers = [
88
89
  ]
89
90
 
90
91
  [tool.ruff]
91
- target-version = "py39"
92
+ target-version = "py310"
92
93
  line-length = 100
93
94
 
94
95
  [tool.ruff.lint]
@@ -109,6 +110,12 @@ ignore = [
109
110
  "N812",
110
111
  "N999",
111
112
  "B904",
113
+ # PEP 604 / 新式导入重写推迟到 v2.6(保持 v2.5 变更面最小)
114
+ "UP006", # dict/list 泛型重写
115
+ "UP007", # Union -> X|Y
116
+ "UP035", # typing.* 导入重写
117
+ "UP041", # IOError -> TimeoutError 别名
118
+ "UP045", # Optional -> X|None
112
119
  ]
113
120
 
114
121
  [tool.ruff.lint.per-file-ignores]
@@ -48,7 +48,7 @@ Remote CMD - SSH 远程服务器管理工具
48
48
  - 文档: 参见 docs/ 目录
49
49
 
50
50
  Author: Vae-Scrooge
51
- Version: 2.4.0(单一真相源见 remote_cmd._version)
51
+ Version: 2.6.0(单一真相源见 remote_cmd._version)
52
52
  License: MIT
53
53
  """
54
54
 
@@ -74,8 +74,9 @@ try:
74
74
  except ImportError: # pragma: no cover - 依赖 asyncssh,未安装时不导出异步符号
75
75
  _HAS_ASYNC = False
76
76
 
77
+ from remote_cmd.api.host_manager import HostManager
78
+ from remote_cmd.core.budget import ConnectionBudget
77
79
  from remote_cmd.core.host import Host
78
- from remote_cmd.core.host_manager import HostManager
79
80
  from remote_cmd.core.ssh_client import SSHClient
80
81
  from remote_cmd.core.sync_connection_pool import SyncConnectionPool
81
82
 
@@ -96,10 +97,16 @@ from remote_cmd.service import (
96
97
  HostService,
97
98
  SSHService,
98
99
  )
99
- from remote_cmd.service.batch_executor import BatchExecutor, BatchHostResult, BatchResult
100
+ from remote_cmd.service.batch_executor import (
101
+ BatchExecutor,
102
+ BatchHostResult,
103
+ BatchResult,
104
+ OutputPolicy,
105
+ )
100
106
  from remote_cmd.service.credential_provider import KeyringCredentialProvider
101
107
  from remote_cmd.service.task_runner import Task, TaskRunner, TaskStatus
102
108
  from remote_cmd.utils.crypto import CredentialEncryption
109
+ from remote_cmd.utils.exceptions import BudgetTimeoutError, PlaintextCredentialWarning
103
110
  from remote_cmd.utils.logging_utils import (
104
111
  SensitiveDataFilter,
105
112
  get_logger,
@@ -134,6 +141,10 @@ if _HAS_ASYNC:
134
141
  "BatchExecutor",
135
142
  "BatchResult",
136
143
  "BatchHostResult",
144
+ "OutputPolicy",
145
+ "PlaintextCredentialWarning",
146
+ "ConnectionBudget",
147
+ "BudgetTimeoutError",
137
148
  "SyncConnectionPool",
138
149
  "TaskRunner",
139
150
  "Task",
@@ -167,6 +178,10 @@ else:
167
178
  "BatchExecutor",
168
179
  "BatchResult",
169
180
  "BatchHostResult",
181
+ "OutputPolicy",
182
+ "PlaintextCredentialWarning",
183
+ "ConnectionBudget",
184
+ "BudgetTimeoutError",
170
185
  "SyncConnectionPool",
171
186
  "TaskRunner",
172
187
  "Task",
@@ -9,4 +9,4 @@
9
9
  - ``__version__`` 由 ``remote_cmd/__init__.py`` 导入并继续作为公共 API 导出。
10
10
  """
11
11
 
12
- __version__ = "2.4.0"
12
+ __version__ = "2.6.0"
@@ -0,0 +1,22 @@
1
+ """
2
+ API facade 包(向后兼容层)
3
+
4
+ 存放从 ``core`` 迁出的公共兼容 facade,使核心层(``core``)保持对
5
+ ``service`` 的零依赖。当前包含:
6
+
7
+ - :class:`remote_cmd.api.host_manager.HostManager`:v1.x 的 HostManager
8
+ API,内部委托给 HostService + JsonHostRepository。
9
+
10
+ 旧导入路径 ``remote_cmd.core.host_manager`` 仍然可用(re-export shim),
11
+ 但新代码应使用 ``remote_cmd.api.host_manager`` 或直接使用 HostService。
12
+
13
+ 依赖方向(v2.6 契约)::
14
+
15
+ cli / api → service → core → utils
16
+ repository ← service / cli
17
+ core 绝不 import service / cli / api 的实现
18
+ """
19
+
20
+ from remote_cmd.api.host_manager import HostManager
21
+
22
+ __all__ = ["HostManager"]
@@ -1,12 +1,18 @@
1
1
  """
2
- 主机管理模块(向后兼容层)
2
+ 主机管理模块(向后兼容 facade,canonical 位置)
3
3
 
4
- 保持原有 API 兼容,内部委托给新的 Repository + Service 层。
4
+ 保持原有 HostManager API 兼容,内部委托给新的 Repository + Service 层。
5
5
 
6
6
  新的代码应直接使用:
7
7
  - remote_cmd.core.host.Host (代替 Host)
8
8
  - remote_cmd.service.host_service.HostService (代替 HostManager)
9
9
  - remote_cmd.repository.json_host_repository.JsonHostRepository
10
+ - remote_cmd.api.host_manager.HostManager(本模块;仅在需要旧 API 时)
11
+
12
+ v2.6 架构说明:
13
+ 本模块自 ``remote_cmd.core.host_manager`` 迁至 ``remote_cmd.api``,
14
+ 使 ``core`` 层不再依赖 ``service`` 层。旧导入路径由
15
+ ``remote_cmd.core.host_manager`` 的 re-export shim 保持可用。
10
16
  """
11
17
 
12
18
  import logging
@@ -19,13 +19,14 @@ import uuid
19
19
  from typing import Any, Optional
20
20
 
21
21
  from remote_cmd.core.async_ssh_client import AsyncSSHClient
22
- from remote_cmd.core.ssh_client import ConnectionConfig
23
- from remote_cmd.service._pool_policy import (
22
+ from remote_cmd.core.budget import ConnectionBudget
23
+ from remote_cmd.core.pool_policy import (
24
24
  ConnectionMeta,
25
25
  idle_expired,
26
26
  lifetime_expired,
27
27
  should_close,
28
28
  )
29
+ from remote_cmd.core.ssh_client import ConnectionConfig
29
30
  from remote_cmd.utils.exceptions import PoolClosedError
30
31
 
31
32
  logger = logging.getLogger(__name__)
@@ -40,6 +41,9 @@ class AsyncConnectionPool:
40
41
  max_lifetime: 连接最大生命周期(秒),超过自动关闭
41
42
  idle_timeout: 空闲超时(秒),超过自动关闭
42
43
  health_check_interval: 后台清理任务周期(秒)
44
+ connection_budget: 可选的全局连接预算(v2.6)。提供时本池创建的
45
+ 每条存活连接都占用一个预算槽位(含空闲连接),连接关闭/清理/
46
+ close_all 时释放;跨多个池共享同一预算实例可获得全局连接上限
43
47
  """
44
48
 
45
49
  def __init__(
@@ -50,6 +54,7 @@ class AsyncConnectionPool:
50
54
  idle_timeout: int = 300,
51
55
  health_check_interval: int = 60,
52
56
  client_factory: Optional[Any] = None,
57
+ connection_budget: Optional[ConnectionBudget] = None,
53
58
  ) -> None:
54
59
  """
55
60
  Args:
@@ -60,6 +65,7 @@ class AsyncConnectionPool:
60
65
  health_check_interval: 后台清理任务周期(秒)
61
66
  client_factory: 客户端工厂,默认为 AsyncSSHClient;测试可注入
62
67
  mock(与 SyncConnectionPool 对齐)
68
+ connection_budget: 可选的全局连接预算(ConnectionBudget)
63
69
  """
64
70
  self.config = config
65
71
  self._max = max_connections
@@ -68,6 +74,7 @@ class AsyncConnectionPool:
68
74
  self._health_check_interval = health_check_interval
69
75
  # 客户端工厂:默认为 AsyncSSHClient;测试可注入 mock
70
76
  self._client_factory = client_factory or AsyncSSHClient
77
+ self._connection_budget = connection_budget
71
78
 
72
79
  # 容器
73
80
  self._connections: list[AsyncSSHClient] = []
@@ -190,10 +197,16 @@ class AsyncConnectionPool:
190
197
  # ------------------------------------------------------------------
191
198
  async def _create_connection(self) -> AsyncSSHClient:
192
199
  client = self._client_factory(self.config)
200
+ if self._connection_budget is not None:
201
+ await self._connection_budget.acquire_async()
193
202
  try:
194
203
  await client.connect()
195
204
  except Exception: # noqa: BLE001
196
- # 信号量由 acquire() 的 except 统一释放,此处不再释放
205
+ # 信号量由 acquire() 的 except 统一释放,此处不再释放。
206
+ # 连接预算仅在成功建连后由 _close_connection 释放;
207
+ # 建连失败立即归还预算,避免预算泄漏
208
+ if self._connection_budget is not None:
209
+ await self._connection_budget.release_async()
197
210
  self._total_failed += 1
198
211
  raise
199
212
  self._connections.append(client)
@@ -237,8 +250,14 @@ class AsyncConnectionPool:
237
250
  with contextlib.suppress(Exception):
238
251
  await conn.disconnect()
239
252
  self._meta.pop(id(conn), None)
240
- if conn in self._connections:
253
+ tracked = conn in self._connections
254
+ if tracked:
241
255
  self._connections.remove(conn)
256
+ # exactly-once:仅对仍被池追踪的连接释放预算。
257
+ # close_all 与 release 可能对同一连接重复调用 _close_connection,
258
+ # 用 tracked 守卫避免预算被超额释放(BoundedSemaphore 会抛 ValueError)
259
+ if tracked and self._connection_budget is not None:
260
+ await self._connection_budget.release_async()
242
261
 
243
262
  # ------------------------------------------------------------------
244
263
  # 后台监控