remote-cmd-manager 1.2.0__tar.gz → 1.2.2__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 (71) hide show
  1. {remote_cmd_manager-1.2.0/remote_cmd_manager.egg-info → remote_cmd_manager-1.2.2}/PKG-INFO +94 -20
  2. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/README.md +93 -19
  3. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/pyproject.toml +1 -1
  4. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/__init__.py +5 -2
  5. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/cli/main.py +27 -35
  6. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/core/async_connection_pool.py +26 -8
  7. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/core/async_ssh_client.py +30 -13
  8. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/core/host.py +4 -1
  9. remote_cmd_manager-1.2.2/remote_cmd/core/host_manager.py +165 -0
  10. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/core/ssh_client.py +8 -3
  11. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/core/sync_connection_pool.py +16 -6
  12. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/repository/json_host_repository.py +7 -1
  13. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/repository/sqlite_host_repository.py +23 -4
  14. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/async_batch_executor.py +44 -13
  15. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/batch_executor.py +18 -10
  16. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/credential_provider.py +22 -3
  17. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/host_service.py +25 -7
  18. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/storage_factory.py +3 -3
  19. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/utils/crypto.py +20 -5
  20. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2/remote_cmd_manager.egg-info}/PKG-INFO +94 -20
  21. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd_manager.egg-info/SOURCES.txt +2 -0
  22. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_async_batch_executor.py +4 -0
  23. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_batch_executor.py +28 -0
  24. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_cli.py +54 -0
  25. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_credential_provider.py +37 -0
  26. remote_cmd_manager-1.2.2/tests/test_host_manager.py +132 -0
  27. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_sqlite_repository.py +37 -0
  28. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/LICENSE +0 -0
  29. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/MANIFEST.in +0 -0
  30. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/config.example.yaml +0 -0
  31. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/examples/basic_usage.py +0 -0
  32. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/examples/deploy_script.py +0 -0
  33. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/examples/nginx_batch_update.py +0 -0
  34. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/examples/system_health_check.py +0 -0
  35. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/cli/__init__.py +0 -0
  36. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/core/__init__.py +0 -0
  37. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/repository/__init__.py +0 -0
  38. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/repository/host_repository.py +0 -0
  39. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/__init__.py +0 -0
  40. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/ssh_service.py +0 -0
  41. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/service/task_runner.py +0 -0
  42. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/utils/__init__.py +0 -0
  43. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/utils/config.py +0 -0
  44. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/utils/exceptions.py +0 -0
  45. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd/utils/logging_utils.py +0 -0
  46. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd_manager.egg-info/dependency_links.txt +0 -0
  47. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd_manager.egg-info/entry_points.txt +0 -0
  48. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd_manager.egg-info/requires.txt +0 -0
  49. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/remote_cmd_manager.egg-info/top_level.txt +0 -0
  50. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/requirements.txt +0 -0
  51. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/setup.cfg +0 -0
  52. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/__init__.py +0 -0
  53. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/conftest.py +0 -0
  54. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/integration/conftest.py +0 -0
  55. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/integration/test_ssh_connection.py +0 -0
  56. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/performance/__init__.py +0 -0
  57. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/performance/conftest.py +0 -0
  58. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/performance/test_benchmarks.py +0 -0
  59. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_async_ssh_client.py +0 -0
  60. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_config.py +0 -0
  61. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_crypto.py +0 -0
  62. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_host.py +0 -0
  63. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_host_service.py +0 -0
  64. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_keyring_provider.py +0 -0
  65. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_logging_utils.py +0 -0
  66. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_repository.py +0 -0
  67. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_ssh_client.py +0 -0
  68. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_ssh_service.py +0 -0
  69. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_storage_factory.py +0 -0
  70. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/tests/test_sync_connection_pool.py +0 -0
  71. {remote_cmd_manager-1.2.0 → remote_cmd_manager-1.2.2}/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: 1.2.0
3
+ Version: 1.2.2
4
4
  Summary: SSH 远程服务器管理工具 / Python SSH remote server management tool
5
5
  Author-email: Vae-Scrooge <vaescrooge@gmail.com>
6
6
  License: MIT
@@ -52,18 +52,23 @@ Dynamic: license-file
52
52
  <img src="https://img.shields.io/badge/python-3.9%2B-blue?style=for-the-badge&logo=python" alt="Python">
53
53
  <img src="https://img.shields.io/github/license/Vae-Scrooge/remote-cmd?style=for-the-badge" alt="License">
54
54
  <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">
55
- <img src="https://img.shields.io/badge/code%20style-black-black?style=for-the-badge" alt="Code Style">
56
55
  </p>
57
56
 
58
57
  <h1 align="center">Remote CMD — SSH Server Management<br><small>Without the Overhead</small></h1>
59
58
 
59
+ <p align="center">
60
+ <img src="https://img.shields.io/badge/English-blue?style=flat-square" alt="English"> ·
61
+ <a href="./README.zh-CN.md"><img src="https://img.shields.io/badge/中文-gray?style=flat-square" alt="中文"></a>
62
+ </p>
63
+
60
64
  <p align="center">
61
65
  <b><code>pip install remote_cmd_manager</code></b> &nbsp;·&nbsp;
62
66
  <a href="#quick-start">Quick Start</a> &nbsp;·&nbsp;
63
67
  <a href="#use-cases">Use Cases</a> &nbsp;·&nbsp;
68
+ <a href="#cli-reference">CLI Reference</a> &nbsp;·&nbsp;
64
69
  <a href="#python-api">Python API</a> &nbsp;·&nbsp;
65
- <a href="./docs">Docs</a> &nbsp;·&nbsp;
66
- <a href="./CONTRIBUTING.md">Contributing</a>
70
+ <a href="#documentation">Documentation</a> &nbsp;·&nbsp;
71
+ <a href="#contributing">Contributing</a>
67
72
  </p>
68
73
 
69
74
  <p align="center">
@@ -83,6 +88,23 @@ pip install remote_cmd_manager && remote-cmd host add web-01 192.168.1.10 ubuntu
83
88
 
84
89
  ---
85
90
 
91
+ ## Table of Contents
92
+
93
+ - [Why Remote CMD?](#why-remote-cmd)
94
+ - [Quick Start](#quick-start)
95
+ - [Use Cases](#use-cases)
96
+ - [CLI Reference](#cli-reference)
97
+ - [Python API](#python-api)
98
+ - [Features](#features)
99
+ - [Installation](#installation)
100
+ - [Documentation](#documentation)
101
+ - [Project Status](#project-status)
102
+ - [Maintainership](#maintainership)
103
+ - [Contributing](#contributing)
104
+ - [License](#license)
105
+
106
+ ---
107
+
86
108
  ## Why Remote CMD?
87
109
 
88
110
  | Feature | `remote-cmd` | `ssh` + shell | Ansible | Fabric |
@@ -91,7 +113,7 @@ pip install remote_cmd_manager && remote-cmd host add web-01 192.168.1.10 ubuntu
91
113
  | Batch commands across hosts | ✅ `batch-run` | ❌ Write a loop | ✅ Playbook | ✅ |
92
114
  | File transfer (upload/download) | ✅ Built-in | ✅ scp | ✅ copy module | ✅ |
93
115
  | Python API | ✅ `from remote_cmd import ...` | ❌ | ❌ YAML-only | ✅ |
94
- | Zero setup | ✅ `pip install → go` | ❌ Config SSH | ❌ `ansible.cfg` | ❌ |
116
+ | Zero setup | ✅ `pip install → go` | ❌ Configure SSH | ❌ `ansible.cfg` | ❌ |
95
117
  | Learning curve | **Low** | Low | **High** | Medium |
96
118
 
97
119
  **Use `remote-cmd` when** you need a CLI that works immediately for ad-hoc SSH tasks. **Use Ansible when** you need full configuration management and idempotent playbooks.
@@ -118,7 +140,8 @@ remote-cmd batch-run -t production "df -h /"
118
140
 
119
141
  ## Use Cases
120
142
 
121
- ### 🖥️ System Admin — Check disk across 20 servers in one command
143
+ ### 🖥️ System Administrators — Check disk across 20 servers in one command
144
+
122
145
  ```bash
123
146
  remote-cmd batch-run -t production "df -h / | tail -1"
124
147
  # Output:
@@ -127,10 +150,11 @@ remote-cmd batch-run -t production "df -h / | tail -1"
127
150
  # ✗ db-01 → Connection refused
128
151
  ```
129
152
 
130
- ### 🚀 Deploy — Pull code and restart service
153
+ ### 🚀 Deploy — Pull code and restart a service
154
+
131
155
  ```python
132
156
  from remote_cmd.service.host_service import HostService
133
- from remote_cmd.repository.json_host_repository import JsonHostRepository
157
+ from remote_cmd.repository import JsonHostRepository
134
158
 
135
159
  service = HostService(repository=JsonHostRepository("hosts.json"))
136
160
  for host in service.list_hosts(tag="staging"):
@@ -141,19 +165,38 @@ for host in service.list_hosts(tag="staging"):
141
165
  ```
142
166
 
143
167
  ### 🔥 Incident Response — Check logs across all servers
168
+
144
169
  ```bash
145
170
  remote-cmd batch-run -t web "journalctl -xe -n 50 | grep -i error"
146
171
  ```
147
172
 
148
173
  ### 🔧 Config Update — Upload and reload nginx across tagged hosts
174
+
149
175
  ```bash
150
- # Upload new config
151
- scp nginx.conf user@server:/tmp/nginx.conf # or use the upload command
176
+ # Upload new config, reload across web servers
152
177
  remote-cmd run web-01 "sudo cp /tmp/nginx.conf /etc/nginx/nginx.conf && sudo nginx -t && sudo systemctl reload nginx"
153
178
  ```
154
179
 
155
180
  ---
156
181
 
182
+ ## CLI Reference
183
+
184
+ All operations are available from the terminal:
185
+
186
+ | Command | Description |
187
+ |---|---|
188
+ | `remote-cmd host add <name> <host> <user>` | Register a server (`-k/--key`, `-p/--port`, `-t/--tag`, repeatable) |
189
+ | `remote-cmd host list [-t TAG]` | List hosts, optionally filtered by tag |
190
+ | `remote-cmd host show <name>` | Show one host's details |
191
+ | `remote-cmd host test <name>` | Test connectivity to a host |
192
+ | `remote-cmd host remove <name>` | Remove a host |
193
+ | `remote-cmd run <name> "<cmd>"` | Run a command on one host |
194
+ | `remote-cmd upload <name> <local> <remote>` | Upload a file via SFTP |
195
+ | `remote-cmd download <name> <remote> <local>` | Download a file via SFTP |
196
+ | `remote-cmd batch-run -t <tag> "<cmd>"` | Run across all hosts in a tag (`-C` concurrency, `-T` timeout, `--async`, `--show-failures`) |
197
+
198
+ ---
199
+
157
200
  ## Python API
158
201
 
159
202
  Use Remote CMD inside your own scripts and automation:
@@ -187,13 +230,18 @@ with SSHClient(config) as client:
187
230
 
188
231
  | Category | Details |
189
232
  |---|---|
190
- | **SSH Auth** | Password + key file + ssh-agent |
233
+ | **SSH Auth** | Password + key file + ssh-agent, with pluggable credential providers |
234
+ | **Credential Chain** | Source passwords from environment, keyring, or arbitrary providers, in priority order |
235
+ | **Credential Encryption** | AES-encrypt secrets at rest (`CredentialEncryption`) |
191
236
  | **Commands** | Single, multi-line, sudo with password |
192
- | **File Transfer** | Upload/download via SFTP |
193
- | **Host Management** | CRUD with JSON/YAML persistence |
237
+ | **File Transfer** | Upload/download via SFTP (`remote-cmd upload/download`) |
238
+ | **Host Management** | CRUD with pluggable JSON or **SQLite** persistence |
194
239
  | **Tag System** | Filter hosts by tag (e.g., `production`, `web`, `db`) |
195
- | **Batch Ops** | Run commands across any host group |
240
+ | **Batch Ops** | Run commands across any host group, synchronously or asynchronously |
241
+ | **Async Kernel** | `AsyncSSHClient` / `AsyncConnectionPool` / `AsyncBatchExecutor` via the `[async]` extra |
242
+ | **Task Runner** | Track and schedule long-running remote tasks with statuses (`TaskRunner`) |
196
243
  | **Connection Test** | Ping all hosts and report status |
244
+ | **Secure Logging** | Structured logging that filters sensitive data (`SensitiveDataFilter`) |
197
245
  | **Type Safety** | Full type annotations + mypy strict |
198
246
 
199
247
  ---
@@ -201,10 +249,10 @@ with SSHClient(config) as client:
201
249
  ## Installation
202
250
 
203
251
  ```bash
204
- # From PyPI (recommended) — 同步 API 与 CLI
252
+ # From PyPI (recommended) — keeps API and CLI in sync
205
253
  pip install remote_cmd_manager
206
254
 
207
- # With async native support (AsyncSSHClient / AsyncConnectionPool / AsyncBatchExecutor)
255
+ # With native async support (AsyncSSHClient / AsyncConnectionPool / AsyncBatchExecutor)
208
256
  pip install "remote_cmd_manager[async]"
209
257
 
210
258
  # From source
@@ -216,13 +264,13 @@ pip install -e ".[dev]"
216
264
  The `[async]` extra installs `asyncssh` and enables the native async execution
217
265
  kernel: `AsyncSSHClient`, `AsyncConnectionPool` and `AsyncBatchExecutor`
218
266
  (also available via `BatchExecutor(use_async=True)`). Without it, `import remote_cmd`
219
- still works — async symbols are simply not exported.
267
+ still works — the async symbols are simply not exported.
220
268
 
221
269
  ---
222
270
 
223
271
  ## Documentation
224
272
 
225
- 📚 **[完整文档中心](./docs/README.md)** — 教程、API 参考、架构设计、故障排查
273
+ 📚 **[Full Documentation Center](./docs/README.md)** — tutorials, API reference, architecture, and troubleshooting
226
274
 
227
275
  | Document | Contents |
228
276
  |---|---|
@@ -230,11 +278,15 @@ still works — async symbols are simply not exported.
230
278
  | [API Docs (auto-generated)](./docs/api/remote_cmd.html) | Complete API reference generated by pdoc |
231
279
  | [Quickstart Tutorial](./docs/tutorial-quickstart.md) | Step-by-step walkthrough |
232
280
  | [Advanced Tutorial](./docs/tutorial-advanced.md) | Batch ops, error handling, production patterns |
233
- | [Development Guide](./docs/DEVELOPMENT.md) | Setup dev environment, contributing |
281
+ | [Architecture](./docs/architecture.md) | System architecture and design decisions |
282
+ | [Development Guide](./docs/DEVELOPMENT.md) | Set up the dev environment, contributing |
234
283
  | [Troubleshooting](./docs/TROUBLESHOOTING.md) | Common issues and solutions |
235
284
  | [Changelog](./CHANGELOG.md) | Release history |
236
285
  | [Mobile Remote Guide](./MOBILE-REMOTE-GUIDE.md) | Manage servers from your phone |
237
286
 
287
+ > **Note:** The documentation center and tutorials are maintained in **Chinese**. See
288
+ > [README.zh-CN.md](./README.zh-CN.md) for the Chinese version of this page.
289
+
238
290
  ---
239
291
 
240
292
  ## Project Status
@@ -243,8 +295,30 @@ still works — async symbols are simply not exported.
243
295
 
244
296
  **Roadmap:**
245
297
  - [x] Async SSH operations (parallel execution) — v1.1.0
298
+ - [x] Pluggable storage backends (JSON + SQLite) — v1.2.x
299
+ - [x] Chainable credential providers + at-rest encryption — v1.2.x
246
300
  - [ ] Configuration profiles (AWS, GCP, custom)
247
301
  - [ ] Output formatting (JSON, table)
302
+ - [ ] Templated command recipes
303
+
304
+ Good first issues are labelled `good first issue` in the
305
+ [issue tracker](https://github.com/Vae-Scrooge/remote-cmd/issues) — contributions welcome.
306
+
307
+ ---
308
+
309
+ ## Maintainership
310
+
311
+ **Remote CMD is an actively maintained open-source project.** It is designed and
312
+ developed independently as a focused alternative to heavyweight tools for the
313
+ ad-hoc SSH tasks that come up in day-to-day server work.
314
+
315
+ - **Project health:** CI runs on every PR, Python 3.9+ is supported, and the
316
+ public API is versioned under [semantic versioning](https://semver.org/).
317
+ - **Your code, your servers:** usage stays open under the MIT license — nothing
318
+ is telemetry-driven or locked behind a service.
319
+ - **Why open source?** The tooling around ad-hoc SSH administration was either
320
+ too heavy (Ansible) or too bare (raw shell loops). Remote CMD exists so that
321
+ a single command can cover the common 90% of remote admin.
248
322
 
249
323
  ---
250
324
 
@@ -258,7 +332,7 @@ Before contributing, please read our [Code of Conduct](./CODE_OF_CONDUCT.md).
258
332
 
259
333
  ## License
260
334
 
261
- MIT © [Vae-Scrooge](https://github.com/Vae-Scrooge)
335
+ MIT © [Vae-Scrooge](https://github.com/Vae-Scrooge/remote-cmd)
262
336
 
263
337
  ---
264
338
 
@@ -5,18 +5,23 @@
5
5
  <img src="https://img.shields.io/badge/python-3.9%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
- <img src="https://img.shields.io/badge/code%20style-black-black?style=for-the-badge" alt="Code Style">
9
8
  </p>
10
9
 
11
10
  <h1 align="center">Remote CMD — SSH Server Management<br><small>Without the Overhead</small></h1>
12
11
 
12
+ <p align="center">
13
+ <img src="https://img.shields.io/badge/English-blue?style=flat-square" alt="English"> ·
14
+ <a href="./README.zh-CN.md"><img src="https://img.shields.io/badge/中文-gray?style=flat-square" alt="中文"></a>
15
+ </p>
16
+
13
17
  <p align="center">
14
18
  <b><code>pip install remote_cmd_manager</code></b> &nbsp;·&nbsp;
15
19
  <a href="#quick-start">Quick Start</a> &nbsp;·&nbsp;
16
20
  <a href="#use-cases">Use Cases</a> &nbsp;·&nbsp;
21
+ <a href="#cli-reference">CLI Reference</a> &nbsp;·&nbsp;
17
22
  <a href="#python-api">Python API</a> &nbsp;·&nbsp;
18
- <a href="./docs">Docs</a> &nbsp;·&nbsp;
19
- <a href="./CONTRIBUTING.md">Contributing</a>
23
+ <a href="#documentation">Documentation</a> &nbsp;·&nbsp;
24
+ <a href="#contributing">Contributing</a>
20
25
  </p>
21
26
 
22
27
  <p align="center">
@@ -36,6 +41,23 @@ pip install remote_cmd_manager && remote-cmd host add web-01 192.168.1.10 ubuntu
36
41
 
37
42
  ---
38
43
 
44
+ ## Table of Contents
45
+
46
+ - [Why Remote CMD?](#why-remote-cmd)
47
+ - [Quick Start](#quick-start)
48
+ - [Use Cases](#use-cases)
49
+ - [CLI Reference](#cli-reference)
50
+ - [Python API](#python-api)
51
+ - [Features](#features)
52
+ - [Installation](#installation)
53
+ - [Documentation](#documentation)
54
+ - [Project Status](#project-status)
55
+ - [Maintainership](#maintainership)
56
+ - [Contributing](#contributing)
57
+ - [License](#license)
58
+
59
+ ---
60
+
39
61
  ## Why Remote CMD?
40
62
 
41
63
  | Feature | `remote-cmd` | `ssh` + shell | Ansible | Fabric |
@@ -44,7 +66,7 @@ pip install remote_cmd_manager && remote-cmd host add web-01 192.168.1.10 ubuntu
44
66
  | Batch commands across hosts | ✅ `batch-run` | ❌ Write a loop | ✅ Playbook | ✅ |
45
67
  | File transfer (upload/download) | ✅ Built-in | ✅ scp | ✅ copy module | ✅ |
46
68
  | Python API | ✅ `from remote_cmd import ...` | ❌ | ❌ YAML-only | ✅ |
47
- | Zero setup | ✅ `pip install → go` | ❌ Config SSH | ❌ `ansible.cfg` | ❌ |
69
+ | Zero setup | ✅ `pip install → go` | ❌ Configure SSH | ❌ `ansible.cfg` | ❌ |
48
70
  | Learning curve | **Low** | Low | **High** | Medium |
49
71
 
50
72
  **Use `remote-cmd` when** you need a CLI that works immediately for ad-hoc SSH tasks. **Use Ansible when** you need full configuration management and idempotent playbooks.
@@ -71,7 +93,8 @@ remote-cmd batch-run -t production "df -h /"
71
93
 
72
94
  ## Use Cases
73
95
 
74
- ### 🖥️ System Admin — Check disk across 20 servers in one command
96
+ ### 🖥️ System Administrators — Check disk across 20 servers in one command
97
+
75
98
  ```bash
76
99
  remote-cmd batch-run -t production "df -h / | tail -1"
77
100
  # Output:
@@ -80,10 +103,11 @@ remote-cmd batch-run -t production "df -h / | tail -1"
80
103
  # ✗ db-01 → Connection refused
81
104
  ```
82
105
 
83
- ### 🚀 Deploy — Pull code and restart service
106
+ ### 🚀 Deploy — Pull code and restart a service
107
+
84
108
  ```python
85
109
  from remote_cmd.service.host_service import HostService
86
- from remote_cmd.repository.json_host_repository import JsonHostRepository
110
+ from remote_cmd.repository import JsonHostRepository
87
111
 
88
112
  service = HostService(repository=JsonHostRepository("hosts.json"))
89
113
  for host in service.list_hosts(tag="staging"):
@@ -94,19 +118,38 @@ for host in service.list_hosts(tag="staging"):
94
118
  ```
95
119
 
96
120
  ### 🔥 Incident Response — Check logs across all servers
121
+
97
122
  ```bash
98
123
  remote-cmd batch-run -t web "journalctl -xe -n 50 | grep -i error"
99
124
  ```
100
125
 
101
126
  ### 🔧 Config Update — Upload and reload nginx across tagged hosts
127
+
102
128
  ```bash
103
- # Upload new config
104
- scp nginx.conf user@server:/tmp/nginx.conf # or use the upload command
129
+ # Upload new config, reload across web servers
105
130
  remote-cmd run web-01 "sudo cp /tmp/nginx.conf /etc/nginx/nginx.conf && sudo nginx -t && sudo systemctl reload nginx"
106
131
  ```
107
132
 
108
133
  ---
109
134
 
135
+ ## CLI Reference
136
+
137
+ All operations are available from the terminal:
138
+
139
+ | Command | Description |
140
+ |---|---|
141
+ | `remote-cmd host add <name> <host> <user>` | Register a server (`-k/--key`, `-p/--port`, `-t/--tag`, repeatable) |
142
+ | `remote-cmd host list [-t TAG]` | List hosts, optionally filtered by tag |
143
+ | `remote-cmd host show <name>` | Show one host's details |
144
+ | `remote-cmd host test <name>` | Test connectivity to a host |
145
+ | `remote-cmd host remove <name>` | Remove a host |
146
+ | `remote-cmd run <name> "<cmd>"` | Run a command on one host |
147
+ | `remote-cmd upload <name> <local> <remote>` | Upload a file via SFTP |
148
+ | `remote-cmd download <name> <remote> <local>` | Download a file via SFTP |
149
+ | `remote-cmd batch-run -t <tag> "<cmd>"` | Run across all hosts in a tag (`-C` concurrency, `-T` timeout, `--async`, `--show-failures`) |
150
+
151
+ ---
152
+
110
153
  ## Python API
111
154
 
112
155
  Use Remote CMD inside your own scripts and automation:
@@ -140,13 +183,18 @@ with SSHClient(config) as client:
140
183
 
141
184
  | Category | Details |
142
185
  |---|---|
143
- | **SSH Auth** | Password + key file + ssh-agent |
186
+ | **SSH Auth** | Password + key file + ssh-agent, with pluggable credential providers |
187
+ | **Credential Chain** | Source passwords from environment, keyring, or arbitrary providers, in priority order |
188
+ | **Credential Encryption** | AES-encrypt secrets at rest (`CredentialEncryption`) |
144
189
  | **Commands** | Single, multi-line, sudo with password |
145
- | **File Transfer** | Upload/download via SFTP |
146
- | **Host Management** | CRUD with JSON/YAML persistence |
190
+ | **File Transfer** | Upload/download via SFTP (`remote-cmd upload/download`) |
191
+ | **Host Management** | CRUD with pluggable JSON or **SQLite** persistence |
147
192
  | **Tag System** | Filter hosts by tag (e.g., `production`, `web`, `db`) |
148
- | **Batch Ops** | Run commands across any host group |
193
+ | **Batch Ops** | Run commands across any host group, synchronously or asynchronously |
194
+ | **Async Kernel** | `AsyncSSHClient` / `AsyncConnectionPool` / `AsyncBatchExecutor` via the `[async]` extra |
195
+ | **Task Runner** | Track and schedule long-running remote tasks with statuses (`TaskRunner`) |
149
196
  | **Connection Test** | Ping all hosts and report status |
197
+ | **Secure Logging** | Structured logging that filters sensitive data (`SensitiveDataFilter`) |
150
198
  | **Type Safety** | Full type annotations + mypy strict |
151
199
 
152
200
  ---
@@ -154,10 +202,10 @@ with SSHClient(config) as client:
154
202
  ## Installation
155
203
 
156
204
  ```bash
157
- # From PyPI (recommended) — 同步 API 与 CLI
205
+ # From PyPI (recommended) — keeps API and CLI in sync
158
206
  pip install remote_cmd_manager
159
207
 
160
- # With async native support (AsyncSSHClient / AsyncConnectionPool / AsyncBatchExecutor)
208
+ # With native async support (AsyncSSHClient / AsyncConnectionPool / AsyncBatchExecutor)
161
209
  pip install "remote_cmd_manager[async]"
162
210
 
163
211
  # From source
@@ -169,13 +217,13 @@ pip install -e ".[dev]"
169
217
  The `[async]` extra installs `asyncssh` and enables the native async execution
170
218
  kernel: `AsyncSSHClient`, `AsyncConnectionPool` and `AsyncBatchExecutor`
171
219
  (also available via `BatchExecutor(use_async=True)`). Without it, `import remote_cmd`
172
- still works — async symbols are simply not exported.
220
+ still works — the async symbols are simply not exported.
173
221
 
174
222
  ---
175
223
 
176
224
  ## Documentation
177
225
 
178
- 📚 **[完整文档中心](./docs/README.md)** — 教程、API 参考、架构设计、故障排查
226
+ 📚 **[Full Documentation Center](./docs/README.md)** — tutorials, API reference, architecture, and troubleshooting
179
227
 
180
228
  | Document | Contents |
181
229
  |---|---|
@@ -183,11 +231,15 @@ still works — async symbols are simply not exported.
183
231
  | [API Docs (auto-generated)](./docs/api/remote_cmd.html) | Complete API reference generated by pdoc |
184
232
  | [Quickstart Tutorial](./docs/tutorial-quickstart.md) | Step-by-step walkthrough |
185
233
  | [Advanced Tutorial](./docs/tutorial-advanced.md) | Batch ops, error handling, production patterns |
186
- | [Development Guide](./docs/DEVELOPMENT.md) | Setup dev environment, contributing |
234
+ | [Architecture](./docs/architecture.md) | System architecture and design decisions |
235
+ | [Development Guide](./docs/DEVELOPMENT.md) | Set up the dev environment, contributing |
187
236
  | [Troubleshooting](./docs/TROUBLESHOOTING.md) | Common issues and solutions |
188
237
  | [Changelog](./CHANGELOG.md) | Release history |
189
238
  | [Mobile Remote Guide](./MOBILE-REMOTE-GUIDE.md) | Manage servers from your phone |
190
239
 
240
+ > **Note:** The documentation center and tutorials are maintained in **Chinese**. See
241
+ > [README.zh-CN.md](./README.zh-CN.md) for the Chinese version of this page.
242
+
191
243
  ---
192
244
 
193
245
  ## Project Status
@@ -196,8 +248,30 @@ still works — async symbols are simply not exported.
196
248
 
197
249
  **Roadmap:**
198
250
  - [x] Async SSH operations (parallel execution) — v1.1.0
251
+ - [x] Pluggable storage backends (JSON + SQLite) — v1.2.x
252
+ - [x] Chainable credential providers + at-rest encryption — v1.2.x
199
253
  - [ ] Configuration profiles (AWS, GCP, custom)
200
254
  - [ ] Output formatting (JSON, table)
255
+ - [ ] Templated command recipes
256
+
257
+ Good first issues are labelled `good first issue` in the
258
+ [issue tracker](https://github.com/Vae-Scrooge/remote-cmd/issues) — contributions welcome.
259
+
260
+ ---
261
+
262
+ ## Maintainership
263
+
264
+ **Remote CMD is an actively maintained open-source project.** It is designed and
265
+ developed independently as a focused alternative to heavyweight tools for the
266
+ ad-hoc SSH tasks that come up in day-to-day server work.
267
+
268
+ - **Project health:** CI runs on every PR, Python 3.9+ is supported, and the
269
+ public API is versioned under [semantic versioning](https://semver.org/).
270
+ - **Your code, your servers:** usage stays open under the MIT license — nothing
271
+ is telemetry-driven or locked behind a service.
272
+ - **Why open source?** The tooling around ad-hoc SSH administration was either
273
+ too heavy (Ansible) or too bare (raw shell loops). Remote CMD exists so that
274
+ a single command can cover the common 90% of remote admin.
201
275
 
202
276
  ---
203
277
 
@@ -211,7 +285,7 @@ Before contributing, please read our [Code of Conduct](./CODE_OF_CONDUCT.md).
211
285
 
212
286
  ## License
213
287
 
214
- MIT © [Vae-Scrooge](https://github.com/Vae-Scrooge)
288
+ MIT © [Vae-Scrooge](https://github.com/Vae-Scrooge/remote-cmd)
215
289
 
216
290
  ---
217
291
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "remote_cmd_manager"
7
- version = "1.2.0"
7
+ version = "1.2.2"
8
8
  description = "SSH 远程服务器管理工具 / Python SSH remote server management tool"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -48,11 +48,11 @@ Remote CMD - SSH 远程服务器管理工具
48
48
  - 文档: 参见 docs/ 目录
49
49
 
50
50
  Author: Vae-Scrooge
51
- Version: 1.0.0
51
+ Version: 1.2.2
52
52
  License: MIT
53
53
  """
54
54
 
55
- __version__ = "1.2.0"
55
+ __version__ = "1.2.2"
56
56
  __author__ = "Vae-Scrooge"
57
57
  __email__ = "vae-scrooge@example.com"
58
58
  __license__ = "MIT"
@@ -73,6 +73,7 @@ except ImportError: # pragma: no cover - 依赖 asyncssh,未安装时不导
73
73
  _HAS_ASYNC = False
74
74
 
75
75
  from remote_cmd.core.host import Host
76
+ from remote_cmd.core.host_manager import HostManager
76
77
  from remote_cmd.core.ssh_client import SSHClient
77
78
  from remote_cmd.core.sync_connection_pool import SyncConnectionPool
78
79
 
@@ -113,6 +114,7 @@ if _HAS_ASYNC:
113
114
  "AsyncConnectionPool",
114
115
  "AsyncBatchExecutor",
115
116
  "Host",
117
+ "HostManager",
116
118
  # 新架构导出
117
119
  "HostRepository",
118
120
  "JsonHostRepository",
@@ -145,6 +147,7 @@ else:
145
147
  # 原有导出(向后兼容,不含异步符号)
146
148
  "SSHClient",
147
149
  "Host",
150
+ "HostManager",
148
151
  # 新架构导出
149
152
  "HostRepository",
150
153
  "JsonHostRepository",
@@ -62,9 +62,17 @@ def _build_service(config_file: str, storage_backend: Optional[str] = None) -> H
62
62
  @click.group()
63
63
  @click.version_option(version=__version__, prog_name="remote-cmd")
64
64
  @click.option("--config", "-c", type=click.Path(), help="Path to config file")
65
+ @click.option(
66
+ "--hosts-file",
67
+ "hosts_file_override",
68
+ type=click.Path(),
69
+ help="Override hosts storage file (e.g. hosts.db for SQLite). "
70
+ "Takes precedence over the config's hosts_file; backend is inferred "
71
+ "from the extension (.json/.db/.sqlite) unless storage_backend is set.",
72
+ )
65
73
  @click.option("--verbose", "-v", is_flag=True, help="Enable verbose output mode")
66
74
  @click.pass_context
67
- def cli(ctx, config: Optional[str], verbose: bool):
75
+ def cli(ctx, config: Optional[str], hosts_file_override: Optional[str], verbose: bool):
68
76
  """
69
77
  Remote CMD - SSH remote server management tool
70
78
 
@@ -90,12 +98,15 @@ def cli(ctx, config: Optional[str], verbose: bool):
90
98
  ctx.obj["config"] = load_config(config_path)
91
99
  ctx.obj["verbose"] = verbose
92
100
 
93
- hosts_file = ctx.obj["config"].get("hosts_file", "hosts.json")
101
+ # CLI --hosts-file 优先于配置文件中的 hosts_file;storage_backend 仍由配置提供
102
+ # (扩展名推断覆盖 .json/.db/.sqlite 常见场景)
103
+ hosts_file = hosts_file_override or ctx.obj["config"].get("hosts_file", "hosts.json")
94
104
  storage_backend = ctx.obj["config"].get("storage_backend")
95
105
  ctx.obj["service"] = _build_service(hosts_file, storage_backend)
96
106
 
97
107
  if verbose:
98
108
  click.echo(f"Using config file: {config_path}")
109
+ click.echo(f"Using hosts file: {hosts_file}")
99
110
 
100
111
 
101
112
  @cli.group()
@@ -120,16 +131,6 @@ def host():
120
131
  @click.argument("hostname", required=True)
121
132
  @click.argument("username", required=True)
122
133
  @click.option("--port", "-p", default=22, help="SSH port (default: 22)")
123
- @click.option(
124
- "--password",
125
- "-P",
126
- default=None,
127
- help=(
128
- "Login password (INSECURE: visible in shell history and process list). "
129
- "Prefer the REMOTE_CMD_PASSWORD environment variable, or omit this "
130
- "option to be prompted interactively."
131
- ),
132
- )
133
134
  @click.option("--key", "-k", help="Path to SSH private key file")
134
135
  @click.option("--tag", "-t", multiple=True, help="Host tag (may be repeated)")
135
136
  @click.option("--description", "-d", default="", help="Host description")
@@ -140,7 +141,6 @@ def host_add(
140
141
  hostname: str,
141
142
  username: str,
142
143
  port: int,
143
- password: Optional[str],
144
144
  key: Optional[str],
145
145
  tag: tuple,
146
146
  description: str,
@@ -151,15 +151,20 @@ def host_add(
151
151
  NAME: host name (unique identifier)
152
152
  HOSTNAME: host address (IP or domain)
153
153
  USERNAME: SSH login username
154
+
155
+ Password input (in order of security):
156
+
157
+ 1. REMOTE_CMD_PASSWORD environment variable (safest: not in shell
158
+ history or process list).
159
+ 2. Interactive prompt via getpass (no echo, not recorded).
160
+
161
+ The insecure ``--password`` option has been removed to prevent passwords
162
+ from leaking via shell history and ``ps``/``/proc``. Use one of the above.
154
163
  """
155
164
  service: HostService = ctx.obj["service"]
156
165
 
157
166
  # Resolve password: priority REMOTE_CMD_PASSWORD env var > interactive
158
- # getpass > --password argument.
159
- # Security order: env var is safest (not in command line/history); getpass
160
- # is interactive without echo and is not recorded.
161
- # --password appears in shell history and process list, so it is the lowest
162
- # priority and triggers a warning.
167
+ # getpass. Both are secure (not visible in command line/history).
163
168
  env_password = os.environ.get("REMOTE_CMD_PASSWORD")
164
169
  resolved_password: Optional[str] = None
165
170
 
@@ -173,22 +178,6 @@ def host_add(
173
178
  click.echo(click.style("\n✗ cancelled", fg="red"), err=True)
174
179
  ctx.exit(1)
175
180
 
176
- if password and not env_password:
177
- # Only warn when the user explicitly used --password (not env var)
178
- click.echo(
179
- click.style(
180
- "⚠ Warning: passing the password via --password is insecure "
181
- "(visible in shell history and process list). "
182
- "Use the REMOTE_CMD_PASSWORD environment variable or "
183
- "interactive input instead.",
184
- fg="yellow",
185
- ),
186
- err=True,
187
- )
188
- # --password as the last-resort fallback
189
- if not resolved_password:
190
- resolved_password = password
191
-
192
181
  host = Host(
193
182
  name=name,
194
183
  hostname=hostname,
@@ -279,6 +268,7 @@ def host_show(ctx, name: str):
279
268
  if host.key_filename:
280
269
  # Sanitize key path: show only the filename, not full path
281
270
  from pathlib import Path
271
+
282
272
  key_name = Path(host.key_filename).name
283
273
  click.echo(f" Key file: {key_name}")
284
274
  tags_str = ", ".join(host.tags) if host.tags else "-"
@@ -435,7 +425,9 @@ def batch_run(
435
425
  use_async=use_async,
436
426
  )
437
427
 
438
- click.echo(f"Batch running on {len(host_names)} hosts, command='{command}', concurrency={concurrency}")
428
+ click.echo(
429
+ f"Batch running on {len(host_names)} hosts, command='{command}', concurrency={concurrency}"
430
+ )
439
431
  click.echo()
440
432
 
441
433
  bar: Any