Metadata-Version: 2.4
Name: turbossh
Version: 0.2.0
Summary: TurboSSH — SSH / Serial / SFTP / FTP terminal & automation toolkit for automotive & embedded (API, CLI, and a MobaXterm-style PyQt5 GUI).
Author: ssh-handler contributors
License-Expression: MIT
Keywords: ssh,sftp,scp,ftp,paramiko,automation,pyqt5,testing
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Topic :: System :: Networking
Classifier: Topic :: Software Development :: Testing
Classifier: Intended Audience :: Developers
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: paramiko>=3.0
Requires-Dist: scp>=0.14
Requires-Dist: pyserial>=3.5
Requires-Dist: keyring>=23.0
Requires-Dist: pywinrm>=0.4.3
Requires-Dist: pyte>=0.8.1
Provides-Extra: gui
Requires-Dist: PyQt5>=5.15; extra == "gui"
Provides-Extra: all
Requires-Dist: PyQt5>=5.15; extra == "all"
Dynamic: license-file

# TurboSSH

[![PyPI](https://img.shields.io/pypi/v/turbossh.svg)](https://pypi.org/project/turbossh/)
[![Python](https://img.shields.io/pypi/pyversions/turbossh.svg)](https://pypi.org/project/turbossh/)

**TurboSSH** is an SSH / Serial / SFTP / SCP / FTP / RDP terminal and automation
toolkit built on [Paramiko](https://www.paramiko.org/) — for automotive &
embedded work, test automation, and everyday remote ops. It ships three ways in
one install:

1. **Python API** — `import turbossh` (or `ssh_handler`) for scripts & test frameworks.
2. **CLI** — `turbossh …`, fully argument-driven.
3. **MobaXterm-style GUI** — `turbossh-gui`, a tabbed multi-session terminal with a
   true **VT100 emulator** (htop/vim/top work), an **SFTP browser**, **serial**
   consoles, and **RDP** launch. Ships as a **prebuilt Windows exe** (PyQt5 baked
   in — no PyQt5 install needed).

```bash
pip install turbossh
turbossh-gui
```

---

## Table of contents
- [Install](#install)
- [The GUI](#the-gui-mobaxterm-style)
- [Quick start (API)](#quick-start-api)
- [Connecting — local & via RDP jump](#connecting--local--via-rdp-jump-host)
- [Running commands](#running-commands)
- [Continuous logs (slog2info / journalctl / tail -f)](#continuous-logs)
- [File transfer — SFTP / SCP / FTP](#file-transfer--sftp--scp--ftp)
- [Serial / COM ports](#serial--com-ports)
- [Automotive / legacy devices](#automotive--legacy-devices)
- [Confidential credentials](#confidential-credentials)
- [Enable SSH on a Windows/RDP box (offline)](#enable-ssh-on-a-windowsrdp-box-offline)
- [CLI reference](#cli-reference)
- [Result objects & error handling](#result-objects--error-handling)
- [Embed in your own PyQt5 app](#embed-in-your-own-pyqt5-app)
- [Building the exe](#building-the-exe)
- [API map](#api-map)

## Install

```bash
pip install turbossh           # API + CLI + prebuilt Windows GUI exe
pip install "turbossh[gui]"    # also installs PyQt5 to run the GUI from source
```

Batteries included: `paramiko`, `scp`, `pyserial`, `keyring`, `pywinrm`, and
`pyte` (VT100) are pulled in automatically. Only PyQt5 is optional — the shipped
GUI **exe** already contains it, so `turbossh-gui` works without installing PyQt5
(handy on Windows ARM64, where PyQt5 has no wheel).

## The GUI (MobaXterm-style)

```bash
turbossh-gui
```

- **Session manager sidebar** — saved SSH / Serial / RDP sessions. Passwords are
  kept in the OS credential vault, never in the JSON. New / Edit / Delete / Connect.
- **Tabbed sessions** — many connections open at once, each a closable tab.
- **True VT100 terminal** (pyte) — full-screen apps like **htop, vim, top, less,
  nano** render correctly, with colors, a block cursor, and live PTY resize.
- **Quick buttons** — one-click `slog2info -w`, `journalctl -f`, `dmesg -w`,
  `tail -f`, plus **Ctrl-C** and **Clear**.
- **Split SFTP browser** under each SSH terminal — navigate, upload, download,
  mkdir, rename, delete (transfers on a separate channel; UI never blocks).
- **Serial console** — open a COM port and type/read live.
- **RDP session** — launches native Remote Desktop (`mstsc`) with credentials
  pre-seeded.
- **Keyboard shortcuts** — Ctrl+T/N new session, Ctrl+W close tab, Ctrl+Enter
  connect, F1 docs; in the terminal: Ctrl+Shift+C/V copy-paste, Ctrl-C/D/Z,
  arrows, Tab-completion (server-side), function keys.
- **Crash-proof** — background threads for all I/O; a startup failure shows a
  native popup and writes `~/.ssh-handler/crash.log`; runtime errors are logged,
  never fatal.

## Quick start (API)

```python
from turbossh import SSHHandler, SSHConfig

with SSHHandler(SSHConfig(host="10.0.0.5", username="root", password="pw")) as ssh:
    print(ssh.run("uname -a").text)            # clean single-line output
    ssh.run("systemctl restart nginx", check=True)
    ssh.push("local.txt", "/tmp/remote.txt")
    ssh.pull("/etc/nginx", "./backup", recursive=True)
```

## Connecting — local & via RDP jump host

The same methods work everywhere; only the config changes.

```python
# direct
cfg = SSHConfig(host="10.0.0.5", username="root", password="pw",
                host_key_policy="ignore")          # ignore = lab/reimaged devices

# through an RDP/bastion machine (laptop -> jump -> target)
jump = SSHConfig(host="10.232.9.22", domain="CORP", username="user", password="pw")
cfg  = SSHConfig(host="10.120.1.91", username="root", password="pw",
                 jump_host=jump, host_key_policy="ignore")

with SSHHandler(cfg, quiet=True) as ssh:
    print(ssh.run("hostname").text)
```

| `host_key_policy` | Behaviour |
|---|---|
| `"auto"` (default) | add unknown keys, **reject changed** keys |
| `"ignore"` | accept any key incl. changed — for reimaged/DHCP lab devices |
| `"reject"` | strict; only keys already in known_hosts |

## Running commands

```python
r = ssh.run("ls -la", timeout=30, check=False)
print(r.text)        # stdout, stripped     print(r.stdout)  # raw
print(r.exit_code, r.ok, r.duration)
ssh.run_many(["a", "b", "c"], stop_on_error=True)
ssh.sudo("systemctl restart app", password="pw")
```

## Continuous logs

```python
# stream live, match a pattern, tee to a file (ANSI auto-cleaned)
ssh.stream("slog2info -w", on_line=print,
           match=r"error|fail", save_to="device.log", timeout=120)

# generator form
for line in ssh.iter_lines("journalctl -f"):
    print(line)
```
`match` is a regex; `stop_on_match=True` stops at the first hit (send/expect).

## File transfer — SFTP / SCP / FTP

```python
ssh.push("fw.bin", "/tmp/fw.bin")                 # SFTP upload
ssh.pull("/var/log/messages", "messages.log")     # SFTP download
ssh.push("./build", "/tmp/build", recursive=True) # folder, with progress callback
ssh.scp_push("img.tar", "/tmp/img.tar")           # SCP protocol

from turbossh import FTPHandler, FTPConfig
with FTPHandler(FTPConfig(host="ftp.x", username="u", password="p", use_tls=True)) as ftp:
    ftp.push("a.txt", "a.txt"); ftp.pull("b.txt", "b.txt")
```
Via RDP: identical — transfers ride the `jump_host` tunnel automatically. Remote
FS helpers: `listdir, stat, exists, isdir, mkdir, makedirs, rename, remove, chmod,
read_text, write_text, walk`.

## Serial / COM ports

```python
# local port (device plugged into THIS machine)
from turbossh import SerialHandler, list_serial_ports
print(list_serial_ports())
with SerialHandler("COM5", baudrate=115200) as ser:
    ser.write_line("version")
    ser.stream(on_line=print, match=r"login:", save_to="com5.log")

# port on a REMOTE machine, over SSH/jump (COM = Windows, /dev/tty* = Linux)
with SSHHandler(cfg, quiet=True) as ssh:
    ssh.serial_write("COM5", "version", baudrate=115200)
    ssh.serial_stream("COM5", baudrate=115200, on_line=print,
                      match=r"login:", save_to="com5.log")
```

## Automotive / legacy devices

Old ECUs / embedded SSH servers often use crypto modern Paramiko drops. Re-enable it:

```python
cfg = SSHConfig(host="10.0.0.9", username="root", password="pw",
                enable_legacy_algorithms=True)     # old KEX/ciphers/host-keys
# fine-grained: disabled_algorithms={"pubkeys": ["rsa-sha2-512"]}
```
In the GUI, tick **"Enable legacy algorithms"** in the session dialog.

## Confidential credentials

| Mechanism | What it does |
|---|---|
| `Secret` | wraps a password; logs/reprs show `********`; only `.reveal()` exposes it |
| `mask()` | redacts secrets from any string (applied to all logging automatically) |
| `CredentialStore` | stores/reads passwords in the OS vault via `keyring` — no plaintext |
| `prompt_password()` | hidden terminal input |

## Enable SSH on a Windows/RDP box (offline)

```powershell
turbossh-setup            # self-elevates, installs OpenSSH Server from a bundled
                          # ZIP (ARM64/Win64/Win32), starts sshd, opens firewall,
                          # generates + fixes host keys. No internet/Windows Update.
```
Or auto-enable a remote box over WinRM: `SSHConfig(auto_bootstrap_via_winrm=True)`.

## CLI reference

```bash
turbossh run    --host H --user U [--domain CORP] [--use-stored] uname -a
turbossh push   --host H --user U ./build /tmp/build --recursive
turbossh pull   --host H --user U /var/log ./logs --recursive
turbossh info   --host H --user U --json
turbossh store-credential --user U --domain CORP --service my_lab
turbossh-gui              # launch the GUI
turbossh-setup            # install OpenSSH Server on this machine (offline)
turbossh-shortcut         # create a Desktop shortcut to the GUI
turbossh-docs             # open the docs
```
Password options: `--password` (hidden prompt), `--use-stored` (OS vault), `--key FILE`.

## Result objects & error handling

- `CommandResult` — `.exit_code`, `.stdout`, `.text`, `.stderr`, `.duration`, `.ok`
- `TransferResult` — `.size_bytes`, `.duration`, `.human_speed`, `.files`
- `OperationResult` — safe-mode wrapper: `bool(res)`, `.value`, `.error`, `.unwrap()`

**Raise mode** (default): typed exceptions — `SSHConnectionError`,
`SSHAuthenticationError`, `SSHTimeoutError`, `SSHCommandError`, `SSHTransferError`,
`FTPError`, `SerialError`, `WinRMError`, `CredentialError` (all subclass `SSHError`).
Auto-retry on connect + auto-reconnect on drop are built in, and a failed connect
self-diagnoses (probes SSH/RDP ports and explains why).

**Safe mode** (`SSHHandler(cfg, safe=True)`): every call returns an
`OperationResult` instead of raising — ideal for GUIs/long-running tools.

## Embed in your own PyQt5 app

`ssh_handler.pyqt_worker.SSHWorker` is a `QObject` (safe mode) you move to a
`QThread` and drive via signals (`log`, `connected`, `command_done`,
`transfer_done`, `progress`, `error`, `finished`).

## Building the exe

```bash
pip install "turbossh[gui]" pyinstaller
python scripts/build_exe.py            # dist/turbossh-gui/...
python scripts/build_exe.py --onefile  # single .exe
```

## API map

```
turbossh/                 import shim -> re-exports ssh_handler
ssh_handler/
  config.py        SSHConfig (legacy algos, jump, host-key policy…), FTPConfig
  core.py          SSHHandler  (SSH + SFTP + SCP + stream + serial + diagnose)
  serial_handler.py  SerialHandler, list_serial_ports
  ftp.py           FTPHandler (FTP/FTPS)
  winrm_bootstrap.py  enable_openssh_via_winrm
  pool.py          SSHPool (parallel multi-host)
  credentials.py   Secret, CredentialStore, mask, prompt_password
  results.py       CommandResult, TransferResult, ShellResult, OperationResult
  cli.py           CLI + launchers (gui/docs/setup/shortcut/rdp)
  pyqt_worker.py   SSHWorker (embed in your own GUI)
  bin/turbossh-gui.exe         prebuilt GUI (PyQt5 baked in)
  gui/             MobaXterm-style app
    vt100.py       true VT100 terminal (pyte) — htop/vim render correctly
    terminal.py    reader thread
    sftp_browser.py  remote file browser (threaded transfers)
    session_dialog.py / sessions.py   session manager + saved profiles
    session_widgets.py   SSH terminal + SFTP split, serial console
    main_window.py · app.py · theme.py · log_panel.py
```

## License

MIT
