ssh-handler 1.0.9__tar.gz → 1.2.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.
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/PKG-INFO +127 -26
- ssh_handler-1.0.9/ssh_handler.egg-info/PKG-INFO → ssh_handler-1.2.0/README.md +480 -405
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/pyproject.toml +10 -7
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/__init__.py +4 -1
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/cli.py +57 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/core.py +99 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/pyqt_worker.py +25 -0
- ssh_handler-1.2.0/ssh_handler/serial_handler.py +200 -0
- ssh_handler-1.0.9/README.md → ssh_handler-1.2.0/ssh_handler.egg-info/PKG-INFO +506 -374
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/SOURCES.txt +1 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/requires.txt +4 -12
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/LICENSE +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/setup.cfg +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/__main__.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/config.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/credentials.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/exceptions.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/ftp.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/openssh/OpenSSH-ARM64.zip +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/openssh/OpenSSH-Win32.zip +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/openssh/OpenSSH-Win64.zip +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/pool.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/results.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/setup_openssh_server.ps1 +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler/winrm_bootstrap.py +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/dependency_links.txt +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/entry_points.txt +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/ssh_handler.egg-info/top_level.txt +0 -0
- {ssh_handler-1.0.9 → ssh_handler-1.2.0}/tests/test_offline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssh-handler
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Extensive SSH/SFTP/SCP/FTP handler built on Paramiko, for test automation, CLIs and PyQt5 tools.
|
|
5
5
|
Author: ssh-handler contributors
|
|
6
6
|
License-Expression: MIT
|
|
@@ -14,19 +14,14 @@ Requires-Python: >=3.8
|
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: paramiko>=3.0
|
|
17
|
-
|
|
18
|
-
Requires-Dist:
|
|
19
|
-
|
|
20
|
-
Requires-Dist:
|
|
17
|
+
Requires-Dist: scp>=0.14
|
|
18
|
+
Requires-Dist: pyserial>=3.5
|
|
19
|
+
Requires-Dist: keyring>=23.0
|
|
20
|
+
Requires-Dist: pywinrm>=0.4.3
|
|
21
21
|
Provides-Extra: gui
|
|
22
22
|
Requires-Dist: PyQt5>=5.15; extra == "gui"
|
|
23
|
-
Provides-Extra: winrm
|
|
24
|
-
Requires-Dist: pywinrm>=0.4.3; extra == "winrm"
|
|
25
23
|
Provides-Extra: all
|
|
26
|
-
Requires-Dist: keyring>=23.0; extra == "all"
|
|
27
|
-
Requires-Dist: scp>=0.14; extra == "all"
|
|
28
24
|
Requires-Dist: PyQt5>=5.15; extra == "all"
|
|
29
|
-
Requires-Dist: pywinrm>=0.4.3; extra == "all"
|
|
30
25
|
Dynamic: license-file
|
|
31
26
|
|
|
32
27
|
# ssh-handler
|
|
@@ -86,15 +81,17 @@ behind one object that:
|
|
|
86
81
|
## Install
|
|
87
82
|
|
|
88
83
|
```bash
|
|
89
|
-
pip install ssh-handler
|
|
90
|
-
|
|
91
|
-
pip install "ssh-handler[
|
|
92
|
-
pip install "ssh-handler[gui]" # + PyQt5 (the GUI worker)
|
|
93
|
-
pip install "ssh-handler[all]" # everything
|
|
84
|
+
pip install ssh-handler # everything: SSH, SFTP, SCP, FTP, serial,
|
|
85
|
+
# credential vault, WinRM bootstrap
|
|
86
|
+
pip install "ssh-handler[gui]" # also installs PyQt5 for the GUI worker
|
|
94
87
|
```
|
|
95
88
|
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
**Batteries included.** A plain `pip install ssh-handler` pulls in `paramiko`,
|
|
90
|
+
`scp`, `pyserial`, `keyring`, and `pywinrm`, so SSH, SFTP/SCP/FTP transfers,
|
|
91
|
+
serial/COM ports, confidential credential storage, and the WinRM bootstrap all
|
|
92
|
+
work out of the box. Only **PyQt5** is optional (`[gui]`), because it's a large
|
|
93
|
+
GUI toolkit you only need when building a GUI — forcing it would bloat headless
|
|
94
|
+
and CI installs.
|
|
98
95
|
|
|
99
96
|
## Quick start
|
|
100
97
|
|
|
@@ -127,6 +124,18 @@ with SSHHandler(SSHConfig(host="10.0.0.5", username="root", password="pw")) as s
|
|
|
127
124
|
- `open_shell()` — a persistent interactive `ShellSession` with `send` /
|
|
128
125
|
`read_until` (send-expect) / `read_available`.
|
|
129
126
|
|
|
127
|
+
**Continuous / streaming output (logs)**
|
|
128
|
+
- `iter_lines(cmd)` — generator yielding a never-ending command's output **line
|
|
129
|
+
by line, live** (`slog2info -w`, `tail -f`, `journalctl -f`, `dmesg -w`).
|
|
130
|
+
- `stream(cmd, on_line=, match=, save_to=, stop_on_match=, stop_event=)` — stream
|
|
131
|
+
with **live regex matching**, a per-line/per-match callback, and **tee to a
|
|
132
|
+
local file**, all built in.
|
|
133
|
+
|
|
134
|
+
**Serial / COM ports** (`SerialHandler`, included by default)
|
|
135
|
+
- `list_serial_ports()`, `open`/`close`, `write` / `write_line`.
|
|
136
|
+
- `iter_lines()` and `stream(...)` — same live streaming + match + save-to-file
|
|
137
|
+
model as SSH, for device consoles.
|
|
138
|
+
|
|
130
139
|
**File operations (SFTP) — full Paramiko parity**
|
|
131
140
|
- Transfers: `push` / `pull` (single file **or** recursive directory, with progress
|
|
132
141
|
callbacks and transfer statistics), plus `scp_push` / `scp_pull` (SCP protocol).
|
|
@@ -247,6 +256,71 @@ When a connection just fails, the error now self-diagnoses — it probes the SSH
|
|
|
247
256
|
RDP ports and tells you *why* (e.g. "Port 22 is closed but RDP (3389) is open … no
|
|
248
257
|
SSH server listening"). Call `ssh.diagnose()` for a pre-flight reachability check.
|
|
249
258
|
|
|
259
|
+
## Continuous logs & live pattern matching
|
|
260
|
+
|
|
261
|
+
Stream a long-running remote command and react to lines as they arrive — match a
|
|
262
|
+
pattern, save to a file, or stop when something appears. Works through the jump
|
|
263
|
+
host too.
|
|
264
|
+
|
|
265
|
+
```python
|
|
266
|
+
from ssh_handler import SSHHandler, SSHConfig
|
|
267
|
+
|
|
268
|
+
with SSHHandler(SSHConfig(host="10.120.1.91", username="root", password="pw",
|
|
269
|
+
jump_host=rdp_box), quiet=True) as ssh:
|
|
270
|
+
|
|
271
|
+
# (a) simplest: iterate lines live
|
|
272
|
+
for line in ssh.iter_lines("slog2info -w"):
|
|
273
|
+
print(line)
|
|
274
|
+
if "FATAL" in line:
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
# (b) full: match + tee to a local file + callback, stop on a pattern
|
|
278
|
+
result = ssh.stream(
|
|
279
|
+
"tail -f /var/log/messages",
|
|
280
|
+
on_line=print, # called for every line
|
|
281
|
+
match=r"error|fail", # regex; matching lines collected
|
|
282
|
+
on_match=lambda l: print("HIT:", l),
|
|
283
|
+
save_to="device.log", # tee every line to this local file
|
|
284
|
+
stop_on_match=False, # set True to stop at the first match
|
|
285
|
+
timeout=60, # optional overall time limit
|
|
286
|
+
)
|
|
287
|
+
print(result["lines"], "lines,", len(result["matches"]), "matched")
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
To stop a stream from another thread (e.g. a GUI Stop button), pass a
|
|
291
|
+
`threading.Event` as `stop_event=` and `.set()` it.
|
|
292
|
+
|
|
293
|
+
## Serial / COM ports
|
|
294
|
+
|
|
295
|
+
Same streaming + match + save model for device serial consoles (included by
|
|
296
|
+
default — no extra install).
|
|
297
|
+
|
|
298
|
+
> **Important — where the COM port physically is.** `pyserial` opens a *local*
|
|
299
|
+
> port, so this runs on the machine the device is **plugged into**. If the device
|
|
300
|
+
> is on your laptop, run it on your laptop. If it's on the remote/RDP machine,
|
|
301
|
+
> either run the script there, or — on Linux targets — stream the device file
|
|
302
|
+
> over SSH instead: `ssh.stream("cat /dev/ttyUSB0", match=..., save_to=...)`.
|
|
303
|
+
|
|
304
|
+
```python
|
|
305
|
+
from ssh_handler import SerialHandler, list_serial_ports
|
|
306
|
+
|
|
307
|
+
print(list_serial_ports()) # [{'device':'COM5','description':...}, ...]
|
|
308
|
+
|
|
309
|
+
with SerialHandler("COM5", baudrate=115200, quiet=True) as ser:
|
|
310
|
+
ser.write_line("version") # send a command
|
|
311
|
+
res = ser.stream(
|
|
312
|
+
on_line=print,
|
|
313
|
+
match=r"login:", # wait for the login prompt
|
|
314
|
+
stop_on_match=True,
|
|
315
|
+
save_to="serial_console.log", # tee to file
|
|
316
|
+
timeout=120,
|
|
317
|
+
)
|
|
318
|
+
print("matched:", res["matched"])
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
`write_line(..., eol="\r\n")` for consoles that need CRLF. Everything returns the
|
|
322
|
+
same `OperationResult` in safe mode and raises `SerialError` otherwise.
|
|
323
|
+
|
|
250
324
|
## Confidential credentials
|
|
251
325
|
|
|
252
326
|
| Mechanism | What it does |
|
|
@@ -306,17 +380,29 @@ Every action returns structured data, not bare strings:
|
|
|
306
380
|
## CLI reference
|
|
307
381
|
|
|
308
382
|
```bash
|
|
309
|
-
python -m ssh_handler run
|
|
310
|
-
python -m ssh_handler push
|
|
311
|
-
python -m ssh_handler pull
|
|
312
|
-
python -m ssh_handler info
|
|
383
|
+
python -m ssh_handler run --host H --user U --domain CORP uptime
|
|
384
|
+
python -m ssh_handler push --host H --user U ./build /tmp/build --recursive
|
|
385
|
+
python -m ssh_handler pull --host H --user U /var/log ./logs --recursive
|
|
386
|
+
python -m ssh_handler info --host H --user U --json
|
|
313
387
|
python -m ssh_handler store-credential --user U --domain CORP --service my_test_lab
|
|
388
|
+
|
|
389
|
+
# continuous logs over SSH, with live matching + save:
|
|
390
|
+
python -m ssh_handler stream --host H --user U --match "error|fail" \
|
|
391
|
+
--save run.log -- slog2info -w
|
|
392
|
+
|
|
393
|
+
# serial / COM ports:
|
|
394
|
+
python -m ssh_handler list-serial
|
|
395
|
+
python -m ssh_handler serial-monitor --port COM5 --baud 115200 \
|
|
396
|
+
--match "login:" --stop-on-match --save console.log
|
|
397
|
+
|
|
398
|
+
# install OpenSSH Server on THIS Windows machine (offline, self-elevates):
|
|
399
|
+
ssh-handler-setup
|
|
314
400
|
```
|
|
315
401
|
|
|
316
402
|
Password options: `--password` (hidden prompt), `--use-stored` (read from the OS
|
|
317
403
|
vault), `--key FILE` (private key). Add `--json` for machine-readable output.
|
|
318
|
-
|
|
319
|
-
|
|
404
|
+
Put `--match`/`--save` *before* the streamed command. After `pip install`, the
|
|
405
|
+
`ssh-handler` and `ssh-handler-setup` console scripts are also available.
|
|
320
406
|
|
|
321
407
|
## PyQt5 integration
|
|
322
408
|
|
|
@@ -337,8 +423,22 @@ thread.started.connect(lambda: worker.run_command("uptime"))
|
|
|
337
423
|
thread.start()
|
|
338
424
|
```
|
|
339
425
|
|
|
340
|
-
Signals: `log`, `connected`, `command_done`, `transfer_done`, `progress`,
|
|
341
|
-
`finished`. The import is
|
|
426
|
+
Signals: `log`, `connected`, `command_done`, `transfer_done`, `progress`,
|
|
427
|
+
`stream_line`, `stream_match`, `stream_done`, `error`, `finished`. The import is
|
|
428
|
+
lazy, so the rest of the package works where PyQt5 isn't installed.
|
|
429
|
+
|
|
430
|
+
**Streaming logs into the GUI** — drive `start_stream` in the worker thread and
|
|
431
|
+
wire the per-line signals to your widgets; `stop_stream()` ends it cleanly:
|
|
432
|
+
|
|
433
|
+
```python
|
|
434
|
+
worker.stream_line.connect(log_view.append) # every live line
|
|
435
|
+
worker.stream_match.connect(lambda l: alerts.append(l)) # only matching lines
|
|
436
|
+
thread.started.connect(lambda: worker.start_stream("slog2info -w",
|
|
437
|
+
match="error|fail",
|
|
438
|
+
save_to="device.log"))
|
|
439
|
+
# later, from a Stop button:
|
|
440
|
+
worker.stop_stream()
|
|
441
|
+
```
|
|
342
442
|
|
|
343
443
|
## Parallel fleet operations
|
|
344
444
|
|
|
@@ -372,8 +472,9 @@ with FTPHandler(FTPConfig(host="ftp.example.com", username="u",
|
|
|
372
472
|
ssh_handler/
|
|
373
473
|
config.py SSHConfig, FTPConfig
|
|
374
474
|
credentials.py Secret, CredentialStore, mask, prompt_password
|
|
375
|
-
core.py SSHHandler, ShellSession (SSH + SFTP + SCP + diagnose)
|
|
475
|
+
core.py SSHHandler, ShellSession (SSH + SFTP + SCP + stream + diagnose)
|
|
376
476
|
ftp.py FTPHandler (FTP / FTPS)
|
|
477
|
+
serial_handler.py SerialHandler, list_serial_ports (serial / COM ports)
|
|
377
478
|
winrm_bootstrap.py enable_openssh_via_winrm (one-time sshd enable over WinRM)
|
|
378
479
|
pool.py SSHPool (parallel multi-host)
|
|
379
480
|
cli.py argparse entry point (python -m ssh_handler / ssh-handler)
|