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.
- {remote_cmd_manager-2.4.0/remote_cmd_manager.egg-info → remote_cmd_manager-2.6.0}/PKG-INFO +23 -20
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/README.md +18 -15
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/pyproject.toml +15 -8
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/__init__.py +18 -3
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/_version.py +1 -1
- remote_cmd_manager-2.6.0/remote_cmd/api/__init__.py +22 -0
- {remote_cmd_manager-2.4.0/remote_cmd/core → remote_cmd_manager-2.6.0/remote_cmd/api}/host_manager.py +8 -2
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/async_connection_pool.py +23 -4
- remote_cmd_manager-2.6.0/remote_cmd/core/budget.py +211 -0
- remote_cmd_manager-2.6.0/remote_cmd/core/host_manager.py +16 -0
- 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
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/sync_connection_pool.py +22 -4
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/json_host_repository.py +61 -2
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/sqlite_host_repository.py +43 -2
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/_host_runner.py +31 -1
- remote_cmd_manager-2.6.0/remote_cmd/service/_pool_policy.py +16 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/_types.py +33 -1
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/async_batch_executor.py +111 -47
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/batch_executor.py +38 -11
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/retry_policy.py +1 -1
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/storage_factory.py +37 -11
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/credential_guard.py +18 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/exceptions.py +38 -3
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0/remote_cmd_manager.egg-info}/PKG-INFO +23 -20
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/SOURCES.txt +6 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/requires.txt +3 -2
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_async_batch_executor.py +51 -2
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_batch_executor.py +6 -3
- remote_cmd_manager-2.6.0/tests/test_connection_budget.py +410 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_host_manager.py +14 -0
- remote_cmd_manager-2.6.0/tests/test_output_policy.py +172 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_pool_policy.py +10 -1
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_release_metadata.py +13 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_repository.py +69 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_sqlite_repository.py +49 -2
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_storage_factory.py +40 -2
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/LICENSE +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/MANIFEST.in +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/config.example.yaml +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/basic_usage.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/deploy_script.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/nginx_batch_update.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/examples/system_health_check.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/cli/__init__.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/cli/main.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/__init__.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/async_ssh_client.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/host.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/ssh_client.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/__init__.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/repository/host_repository.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/__init__.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/credential_provider.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/host_service.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/ssh_service.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/service/task_runner.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/__init__.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/config.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/crypto.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/utils/logging_utils.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/dependency_links.txt +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/entry_points.txt +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd_manager.egg-info/top_level.txt +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/requirements.txt +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/setup.cfg +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/__init__.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/conftest.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/integration/conftest.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/integration/test_ssh_connection.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/performance/__init__.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/performance/conftest.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/performance/test_benchmarks.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_async_ssh_client.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_cli.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_config.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_credential_provider.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_crypto.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_exceptions.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_host.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_host_service.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_keyring_provider.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_logging_utils.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_retry_policy.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_ssh_client.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_ssh_service.py +0 -0
- {remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/tests/test_sync_connection_pool.py +0 -0
- {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.
|
|
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.
|
|
29
|
+
Requires-Python: >=3.10
|
|
31
30
|
Description-Content-Type: text/markdown
|
|
32
31
|
License-File: LICENSE
|
|
33
|
-
Requires-Dist: paramiko
|
|
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
|
|
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.
|
|
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.
|
|
92
|
+
## v2.6.0 Release Highlights
|
|
93
93
|
|
|
94
|
-
v2.
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
###
|
|
99
|
+
### Architecture
|
|
99
100
|
|
|
100
|
-
- `
|
|
101
|
-
-
|
|
102
|
-
- The
|
|
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
|
-
###
|
|
105
|
+
### Global Connection Budget
|
|
105
106
|
|
|
106
|
-
- `
|
|
107
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
39
|
+
## v2.6.0 Release Highlights
|
|
40
40
|
|
|
41
|
-
v2.
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
###
|
|
46
|
+
### Architecture
|
|
46
47
|
|
|
47
|
-
- `
|
|
48
|
-
-
|
|
49
|
-
- The
|
|
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
|
-
###
|
|
52
|
+
### Global Connection Budget
|
|
52
53
|
|
|
53
|
-
- `
|
|
54
|
-
-
|
|
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
|
-
-
|
|
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.
|
|
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.
|
|
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.
|
|
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>=
|
|
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
|
-
|
|
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
|
-
#
|
|
60
|
-
#
|
|
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 = "
|
|
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.
|
|
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
|
|
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",
|
|
@@ -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"]
|
{remote_cmd_manager-2.4.0/remote_cmd/core → remote_cmd_manager-2.6.0/remote_cmd/api}/host_manager.py
RENAMED
|
@@ -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
|
{remote_cmd_manager-2.4.0 → remote_cmd_manager-2.6.0}/remote_cmd/core/async_connection_pool.py
RENAMED
|
@@ -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.
|
|
23
|
-
from remote_cmd.
|
|
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
|
-
|
|
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
|
# 后台监控
|