Push a local script to an SSH host — picked from your ~/.ssh/config — and run it there as root, with a confirmation step and clean, colored output.
You keep small provisioning / maintenance scripts on your machine and want to run one on a server without copying it across by hand every time. ssh-deploy reads your SSH config, lets you fuzzy-pick the host (or pass --target), shows exactly what it will do, then scps the script to /tmp, runs it with sudo bash over a TTY, removes it, and exits with the script's own exit code.
- Host picker from
~/.ssh/config— fuzzy (fzf) with a livessh -Gpreview, or a numbered-menu fallback when fzf isn't installed. - Uses your existing SSH setup — keys, jump hosts, YubiKey/FIDO, ports — nothing to reconfigure. Manual
user@hostalways available. - Safe by default — a deploy plan +
y/Nconfirm (skip with-y), and a--dry-runthat prints the exact commands without touching anything. - Readable output — colors that auto-disable when piped, on
--no-color, or whenNO_COLORis set. - Honest exit codes — returns the remote script's real exit status, not
rm's. - Portable — one self-contained Bash script (3.2+, no GNU-only tools); only
ssh/scprequired,fzfoptional. Runs on Linux, macOS, *BSD, and WSL.
Runs anywhere with bash 3.2+ and OpenSSH — Linux, macOS, *BSD, and Windows via WSL or Git-Bash. It's written to the lowest common denominator (no bash-4 features, no GNU-only tools), so the single file behaves the same across all of them; fzf is optional.
The target host just needs /tmp, bash, mktemp, and sudo for your SSH user — i.e. any Unix-like server. Deploying to a native Windows host isn't supported (the sudo bash model assumes a POSIX target).
bash3.2+, plussshandscp(OpenSSH).- Optional:
fzffor the fuzzy picker (brew install fzf/apt install fzf). - The target host needs
bash,mktemp, andsudofor your SSH user — you'll be prompted for the password (a TTY is allocated for it).
brew install BrainInBlack/tap/ssh-deployDrop the single script anywhere on your PATH:
curl -fsSL https://raw.githubusercontent.com/BrainInBlack/ssh-deploy/main/ssh-deploy -o ~/.local/bin/ssh-deploy
chmod +x ~/.local/bin/ssh-deployor clone and symlink:
git clone https://github.com/BrainInBlack/ssh-deploy.git
ln -s "$PWD/ssh-deploy/ssh-deploy" ~/.local/bin/ssh-deploy(Make sure ~/.local/bin is on your PATH.)
ssh-deploy [options] <payload>
ARGUMENTS
payload local script to run as root on the target (required)
OPTIONS
-t, --target HOST deploy to this ssh alias / user@host (skip the picker)
-y, --yes don't ask for confirmation
-n, --dry-run show what would happen; don't copy or run
-c, --config FILE ssh config to read (default: ~/.ssh/config)
--no-color disable colored output
-h, --help show this help
-V, --version show version
EXAMPLES
ssh-deploy setup.sh # pick a host (fzf/menu), then deploy
ssh-deploy -t web01 setup.sh # straight to a known host
ssh-deploy -n -t web01 setup.sh # dry-run: show the plan, change nothing
Full reference: man ssh-deploy (installed by Homebrew; documents exit status, files, and environment too).
- Parse
~/.ssh/config(or--config FILE) forHostaliases (wildcard/pattern entries are skipped;Includefiles aren't followed). The config is optional — with no~/.ssh/configand no--config, the picker is skipped and you're prompted for a target (or pass--target). When a config is present it's also used for the actualscp/sshconnection. - Pick one (fzf preview shows the resolved
ssh -Ghostname/user/port/key) or pass--target; manualuser@hostis always an option. - Print the deploy plan and ask to confirm (
-yto skip,-nto only preview). - Open one shared SSH connection (so the password / key-touch happens once),
mktempa private file under/tmpon the target, andscpthe payload into it — no predictable name, no symlink/clobber race. ssh -t <target> "sudo bash <tmpfile>; rm -f <tmpfile>"— the TTY lets thesudopassword (and any key touch) work, and the temp copy is always removed.- Exit with the remote script's exit code.
- It runs your script as root on the remote host — review what you deploy.
- It authenticates using whatever
~/.ssh/configspecifies for that host. The tool never handles, copies, or stores keys. - The payload is copied to a private
mktempfile under/tmpon the target (atomic create, mode 600 — no predictable path or symlink race) and removed after the run, even on failure.
MIT © BrainInBlack — see LICENSE.