Metadata-Version: 2.4
Name: ssherd
Version: 0.1.2
Summary: Batch SSH CLI for server fleets: one file, zero dependencies, powered by your OpenSSH client
License: MIT License
        
        Copyright (c) 2026 trdthg
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Classifier: Environment :: Console
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: System :: Systems Administration
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# ssherd

**Run commands on many servers at once — one Python file, zero dependencies, powered by your OpenSSH client.**

[![CI](https://github.com/trdthg/ssherd/actions/workflows/ci.yml/badge.svg)](https://github.com/trdthg/ssherd/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/trdthg/ssherd/blob/master/LICENSE)
![Python](https://img.shields.io/badge/python-3.9%2B-blue)

[English](https://github.com/trdthg/ssherd#readme) | [简体中文](https://github.com/trdthg/ssherd/blob/master/README.zh-CN.md)

`ssherd` is a small batch-SSH CLI for server fleets. Your `~/.ssh/config` (or a project-local `.ssh/config`) *is* the inventory: ssherd reads the `Host` aliases, fans out commands in parallel, and lets the real `ssh` client handle everything it already does best — addresses, jump hosts (`ProxyJump`), users, keys, host keys.

Works on Linux, macOS, and Windows (Git-Bash / PowerShell).

![ssherd running whoami on 16 servers at once, grouping identical output into one block](https://raw.githubusercontent.com/trdthg/ssherd/master/demo.gif)

## Features

- **Single file, zero dependencies** — copy `ssherd.py` anywhere, needs only Python 3.9+ and `ssh`
- **SSH config as inventory** — selectors by name, comma list, or range (`web-01..web-16`, `all`)
- **Parallel execution** with a live per-device progress bar
- **Smart grouped output** — identical results collapse into one block, outliers shown as compact diffs (`--diff`)
- **Jump hosts / keys / users** — all resolved natively by OpenSSH, never re-implemented
- **Password auth support** via OpenSSH's official `SSH_ASKPASS_REQUIRE=force` (no sshpass, never touches your tty)
- **Sudo fan-out** — `sudo -S` wrapping with one local password prompt
- **`add-user`** — generates plain, auditable bash commands by default (`--exec` to run them)

## Installation

```bash
pipx install ssherd    # isolated venv + global command (recommended)
pip install ssherd     # or plain pip — works on any OS
```

Requirements: Python 3.9+ and the OpenSSH client (`ssh`) you already have.

Prefer no installer at all? It's one self-contained file — grab it, read it,
hack it:

```bash
curl -fsSL https://raw.githubusercontent.com/trdthg/ssherd/master/ssherd.py \
  -o ~/.local/bin/ssherd && chmod +x ~/.local/bin/ssherd
```

Uninstall: `pipx uninstall ssherd` or `pip uninstall ssherd`.

## Quick start

```bash
# the inventory is just your ssh config Host aliases
python -m ssherd list

# run in parallel across a range
python -m ssherd run web-01..web-16 -- whoami

# comma lists and single hosts work too
python -m ssherd run web-01,web-05,db-01 -- hostname

# sudo: prompts once, wraps remotely with sudo -S
python -m ssherd run --sudo db-01..db-32 -- apt update

# scriptable: pass passwords inline (note: lands in shell history)
python -m ssherd run --sudo=SUDOPASS db-01 -- systemctl restart nginx

# group identical outputs, show only the differences
python -m ssherd run all --diff -- 'cat /etc/os-release'
```

## Inventory

Devices are the `Host` entries in your SSH config (`Host *` wildcards are skipped):

```
Host web-01
    HostName 192.0.2.10
    User deploy
    # ProxyJump jumpbox   ← jump hosts just work, ssh handles them
```

A `.ssh/config` in the current directory takes precedence over `~/.ssh/config`,
so a fleet per project is one folder away. Put `.ssh/` in `.gitignore` and real
IPs never get committed. Selectors accept aliases, comma lists, ranges
(`web-01..web-16`), and `all` / `*`.

## Creating users

`add-user` prints the plain bash commands (useradd / usermod / chpasswd) for you
to audit and paste, or to wrap in your own loop. Add `--exec` to fan them out
through the same parallel channel:

```bash
python -m ssherd add-user web-01..web-16 deploy
python -m ssherd add-user web-01..web-16 deploy --sudo --password 'TempPass-123' --exec
```

## Why not Ansible / pssh / fabric?

They are great; this is for the other 90% of ops work: one-off fleet commands
where you refuse to install anything, write a playbook, or learn a DSL.
ssherd delegates *all* connection semantics to OpenSSH, so anything your
`ssh` can reach, ssherd can reach — including bastion hosts.

## Tests

```bash
python -m unittest discover -v
```

## License

[MIT](https://github.com/trdthg/ssherd/blob/master/LICENSE)
