{"id":50430457,"url":"https://github.com/pacnpal/ssh-wrappers","last_synced_at":"2026-05-31T14:01:07.789Z","repository":{"id":353802043,"uuid":"1220943102","full_name":"pacnpal/ssh-wrappers","owner":"pacnpal","description":"Eleven tiny POSIX shell wrappers for ssh and ssh-copy-id — fix MaxAuthTries, force password auth, pin one key, agent-forward, keepalive, multiplex (ControlMaster), push keys, force a TTY, compress, debug -vvv. sshh lists what's installed. One-liner curl install for zsh/bash/ksh on macOS and Linux.","archived":false,"fork":false,"pushed_at":"2026-05-18T16:53:50.000Z","size":1014,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-18T17:29:14.360Z","etag":null,"topics":["bash","cli","command-line-tool","developer-tools","dotfiles","keepalive","linux","macos","multiplexing","openssh","password-authentication","posix","shell","ssh","ssh-agent","ssh-client","ssh-copy-id","ssh-keys","ssh-tools","zsh"],"latest_commit_sha":null,"homepage":"https://pacnpal.github.io/ssh-wrappers/","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pacnpal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-25T14:46:03.000Z","updated_at":"2026-05-18T16:53:55.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/pacnpal/ssh-wrappers","commit_stats":null,"previous_names":["pacnpal/ssh-wrappers"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pacnpal/ssh-wrappers","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fssh-wrappers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fssh-wrappers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fssh-wrappers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fssh-wrappers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pacnpal","download_url":"https://codeload.github.com/pacnpal/ssh-wrappers/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fssh-wrappers/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33733754,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-31T02:00:06.040Z","response_time":95,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","cli","command-line-tool","developer-tools","dotfiles","keepalive","linux","macos","multiplexing","openssh","password-authentication","posix","shell","ssh","ssh-agent","ssh-client","ssh-copy-id","ssh-keys","ssh-tools","zsh"],"created_at":"2026-05-31T14:01:06.720Z","updated_at":"2026-05-31T14:01:07.781Z","avatar_url":"https://github.com/pacnpal.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/social-card.svg\" alt=\"ssh-wrappers — small POSIX shell wrappers around ssh\" width=\"720\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003essh-wrappers\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/pacnpal/ssh-wrappers/actions/workflows/shellcheck.yml\"\u003e\u003cimg alt=\"shellcheck\" src=\"https://github.com/pacnpal/ssh-wrappers/actions/workflows/shellcheck.yml/badge.svg\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"license: MIT\" src=\"https://img.shields.io/badge/license-MIT-blue.svg\"\u003e\u003c/a\u003e\n  \u003cimg alt=\"shell: POSIX\" src=\"https://img.shields.io/badge/shell-POSIX-success\"\u003e\n  \u003cimg alt=\"platforms\" src=\"https://img.shields.io/badge/platforms-macOS%20%7C%20Linux-lightgrey\"\u003e\n  \u003ca href=\"https://github.com/pacnpal/ssh-wrappers/stargazers\"\u003e\u003cimg alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/pacnpal/ssh-wrappers?style=flat\u0026logo=github\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/pacnpal/ssh-wrappers/commits/master\"\u003e\u003cimg alt=\"GitHub last commit\" src=\"https://img.shields.io/github/last-commit/pacnpal/ssh-wrappers/master\"\u003e\u003c/a\u003e\n  \u003cimg alt=\"views\" src=\"https://visitor-badge.laobi.icu/badge?page_id=pacnpal.ssh-wrappers\"\u003e\n\u003c/p\u003e\n\nEleven small POSIX shell wrappers around `ssh` and `ssh-copy-id` that fix the most common day-to-day OpenSSH annoyances — `Too many authentication failures` from agents with too many keys, `client_loop: send disconnect: Broken pipe` on idle sessions, `sudo: a terminal is required to read the password`, `ssh-copy-id` exiting before the password prompt, host-key prompts on ephemeral cloud VMs, reconnect latency on bastion hops, and the rest of the well-known papercuts. Each wrapper is a one-line tweak of options, packaged behind a name short enough that you'll actually use it. Plus a built-in `sshh` to remind you which is which.\n\nWorks on macOS, Linux, and any POSIX shell — `zsh`, `bash`, `ksh`, with a fish snippet for copy-paste.\n\nHomepage: \u003chttps://pacnpal.github.io/ssh-wrappers/\u003e\n\n## The wrappers\n\n| Wrapper | Purpose | Mnemonic |\n|---------|---------|----------|\n| [`sshp`](docs/sshp.md) | Force **p**assword authentication (disable pubkey for one connection) | **p**assword |\n| [`sshi`](docs/sshi.md) | Use only explicitly configured **i**dentities (`IdentitiesOnly=yes`) | **i**dentities |\n| [`ssha`](docs/ssha.md) | Forward your local ssh-**a**gent to the remote host (`-A`) | **a**gent |\n| [`sshcp`](docs/sshcp.md) | **C**o**p**y a key with `ssh-copy-id` without pubkey auth (skip the `MaxAuthTries` burn) | **c**o**p**y |\n| [`sshq`](docs/sshq.md) | **Q**uiet/quick: skip host key prompts, don't pollute `known_hosts` | **q**uick |\n| [`sshk`](docs/sshk.md) | **K**eepalive: don't drop on idle (`ServerAlive*`) | **k**eepalive |\n| [`sshm`](docs/sshm.md) | **M**ultiplex: instant subsequent connections (`ControlMaster`) | **m**ultiplex |\n| [`ssht`](docs/ssht.md) | Force a pseudo-**t**erminal (`-t`) — for `sudo`, `htop`, `vim` over ssh | **t**ty |\n| [`sshc`](docs/sshc.md) | **C**ompression (`-C`) — wins on slow links and text-heavy streams | **c**ompression |\n| [`sshv`](docs/sshv.md) | **V**erbose debug (`-vvv`) — see exactly what `ssh` is trying | **v**erbose |\n| [`sshh`](docs/sshh.md) | **H**elp — list installed wrappers, what they do, how to use | **h**elp |\n\n## Why?\n\n`ssh` is configurable to a fault. Most of its sharp edges have a fix that's a single `-o option=value` away — but it's the kind of fix you have to remember exists, type correctly, and know when to apply.\n\nThese wrappers turn each fix into a one-character mnemonic:\n\n- Agent has too many keys, server says `Too many authentication failures` → `sshi`.\n- Need to type a password but `ssh` keeps offering keys instead → `sshp`.\n- Pushing your key to a fresh server but `ssh-copy-id` fails before the password prompt → `sshcp`.\n- Connection dropped while you got coffee → `sshk`.\n- Spinning up the same host's connection 50 times in a deploy script → `sshm`.\n- Run `sudo` over ssh, get `sudo: a terminal is required` → `ssht`.\n- Cloud VM with a fresh host key, don't want to litter `known_hosts` → `sshq`.\n- Need agent forwarding for `git pull` on a bastion → `ssha`.\n- Streaming logs over a slow link → `sshc`.\n- Connection failing for an unknown reason → `sshv`.\n- Forgot which wrapper does what → `sshh`.\n\n## Install\n\n### One-liner (everything)\n\n```sh\ncurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh\n```\n\n### Selective install\n\nPass wrapper names as positional arguments to install only those:\n\n```sh\n# Just the auth helpers\ncurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- sshp sshi ssha\n\n# Just connection management\ncurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- sshk sshm\n\n# Single wrapper\ncurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- ssht\n```\n\nList the available names with `--list`:\n\n```sh\ncurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- --list\n```\n\n### What the installer does\n\n- auto-detects your shell from `$SHELL` (zsh, bash, ksh)\n- writes the selected functions to the matching rc file (`~/.zshrc`, `~/.bash_profile` on macOS bash, `~/.bashrc` elsewhere, `~/.kshrc`, …) inside a marked block\n- is idempotent — re-running does nothing if the managed block is already there\n- **refuses** to silently shadow wrappers you've already defined yourself (use `--force` to install anyway)\n- supports `--uninstall` to cleanly remove the managed block (and only the managed block)\n\n### Override the target file\n\n```sh\nSSH_WRAPPERS_RC=~/.zprofile sh install.sh\n```\n\n### Replace an existing install with a different selection\n\n```sh\ncurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- --force sshp sshi sshk sshm\n```\n\n`--force` removes the existing managed block and writes the new selection.\n\n### Uninstall\n\n```sh\ncurl -fsSL https://pacnpal.github.io/ssh-wrappers/install.sh | sh -s -- --uninstall\n```\n\nRemoves only the marked block; anything else in your rc file is untouched.\n\n### Fish\n\nNot covered by the installer (different function syntax). Run the installer once to see the snippet you can paste into `~/.config/fish/config.fish`, or copy the bodies straight from the per-wrapper docs in [`docs/`](docs/).\n\n### Manual\n\nSkip the installer entirely — three ways:\n\n- Copy the function definition you want from the per-wrapper doc in [`docs/`](docs/) into your rc file (each `docs/sshX.md` shows the function at the top).\n- Source a standalone file directly: `. wrappers/sshX.sh` defines just that one wrapper in your current shell. Files under [`wrappers/`](wrappers/) are regenerated copies of what `install.sh` would write — useful for cherry-picking into your dotfiles.\n- Open `install.sh` and read the `emit_fn()` block — it's the source of truth for every wrapper body.\n\n## Usage\n\nEvery wrapper accepts the same arguments as `ssh`:\n\n```sh\nsshp user@host                                  # password instead of keys\nsshi -i ~/.ssh/work_ed25519 user@host           # only this key\nssha bastion                                    # forward agent\nsshcp user@new-host                             # push key, skip pubkey auth\nsshq ec2-user@10.0.0.42                         # ephemeral cloud VM\nsshk prod-bastion 'tail -f /var/log/syslog'     # long idle session\nsshm work-bastion                               # then re-run; instant\nssht user@host sudo systemctl restart nginx     # sudo over ssh\nsshc remote-builder 'tail -f build.log'         # compression\nsshv user@host                                  # debug auth failure\nsshh                                            # show all wrappers + install status\nsshh sshm                                       # detail for one wrapper\n```\n\nRead the per-wrapper docs for what each option actually does, security tradeoffs, and `~/.ssh/config` equivalents:\n\n- [sshp](docs/sshp.md) · [sshi](docs/sshi.md) · [ssha](docs/ssha.md) · [sshcp](docs/sshcp.md) — auth\n- [sshq](docs/sshq.md) — trust\n- [sshk](docs/sshk.md) · [sshm](docs/sshm.md) — connection lifetime\n- [ssht](docs/ssht.md) · [sshc](docs/sshc.md) — I/O\n- [sshv](docs/sshv.md) — debugging\n- [sshh](docs/sshh.md) — help / introspection\n\n## Requirements\n\n- POSIX shell (`/bin/sh`) for the installer\n- OpenSSH client (any version from the past decade — every option used here has been stable for years)\n- Interactive shell of zsh, bash, or ksh for the wrappers (fish has its own snippet — see install)\n\nNo build step, no runtime dependencies beyond what comes with your OS.\n\n## Troubleshooting\n\n**`Too many authentication failures`** — your agent has more keys than the server's `MaxAuthTries` (default 6). Use [`sshi`](docs/sshi.md) to offer only one specific key, or [`sshp`](docs/sshp.md) to skip pubkey entirely.\n\n**`Permission denied (publickey)` with `sshp`** — the server has `PasswordAuthentication no`. No client-side wrapper can fix this; the server must allow password auth.\n\n**`ssh-copy-id` exits with `Too many authentication failures` before asking for a password** — your agent's loaded keys are exhausting `MaxAuthTries` before the password prompt. Use [`sshcp`](docs/sshcp.md) instead of plain `ssh-copy-id`.\n\n**`sudo: a terminal is required` over ssh** — pass the command through [`ssht`](docs/ssht.md) instead of plain `ssh`.\n\n**`client_loop: send disconnect: Broken pipe` after idle** — use [`sshk`](docs/sshk.md), or set `ServerAliveInterval 30` for `Host *` in `~/.ssh/config`.\n\n**`sshp`/`sshi`/etc. \"command not found\" after install** — open a fresh shell, or `source ~/.zshrc`. Shell functions only exist in interactive shells that have sourced your rc file. Check `type sshp` in the new shell.\n\n**Function overridden by an alias or another script in `PATH`** — shell functions take precedence over executables in interactive shells, but not in non-interactive scripts. `type sshp` reveals what's actually being invoked.\n\n**Wrapper conflicts with one I already defined** — the installer refuses to silently shadow you. Either remove your existing definition, install only the wrappers that don't conflict, or pass `--force` to overwrite.\n\n**Pages URL returns 404 or stale content** — the GitHub Pages CDN caches for ~10 min. Use the commit-pinned raw URL or `https://cdn.jsdelivr.net/gh/pacnpal/ssh-wrappers/install.sh` to bust the cache once.\n\n## Development\n\nLint the installer locally:\n\n```sh\nshellcheck --shell=sh install.sh scripts/sync-wrappers.sh\n```\n\nCI runs the same on every push to `master` — see the badge above.\n\nAfter editing `install.sh`'s `emit_fn()` block (function bodies live there), regenerate the standalone copies in `wrappers/`:\n\n```sh\nsh scripts/sync-wrappers.sh\n```\n\nSee [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) for the full checklist when adding a new wrapper — every file/count/metadata block that needs to stay in sync.\n\nProject layout:\n\n```\n.\n├── README.md                 # this file\n├── LICENSE\n├── CHANGELOG.md              # release history; edit the [Unreleased] section\n├── install.sh                # the installer (source of truth for function bodies)\n├── index.html                # GitHub Pages landing page\n├── wrappers/                 # standalone copies of each wrapper, one .sh per\n│   ├── README.md             # what this dir is and how it's regenerated\n│   └── ssh{p,i,a,cp,q,k,m,t,c,v,h}.sh\n├── docs/                     # per-wrapper docs + CONTRIBUTING\n│   ├── CONTRIBUTING.md       # how to add a wrapper\n│   └── ssh{p,i,a,cp,q,k,m,t,c,v,h}.md\n├── scripts/\n│   └── sync-wrappers.sh      # regenerate wrappers/ from install.sh\n├── assets/\n│   ├── logo.svg              # the mark\n│   ├── logo.png              # rendered 512x512\n│   ├── social-card.svg       # 1280x640 OG image\n│   └── social-card.png       # rendered\n└── .github/workflows/\n    └── shellcheck.yml        # CI\n```\n\n## Contributing\n\nSee [`docs/CONTRIBUTING.md`](docs/CONTRIBUTING.md) for what to touch when adding a new wrapper. The short version: edit `install.sh`'s `emit_fn()` plus `_sshh_data`, add `docs/sshX.md`, update the count in `README.md` / `docs/sshh.md` / `index.html` / `assets/social-card.svg`, re-render the social card PNG, and run `sh scripts/sync-wrappers.sh`.\n\n## License\n\n[MIT](LICENSE) © pacnpal\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpacnpal%2Fssh-wrappers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpacnpal%2Fssh-wrappers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpacnpal%2Fssh-wrappers/lists"}