A tiny SSH/SFTP CLI for remote deploy and training workflows — a single static
Go binary that replaces sshpass + scp + ad-hoc shell scripts.
Credentials are read from the environment only, never from argv, so your
password never lands in shell history or a process listing. Authenticates with
an SSH key or a password. Host keys are verified against
~/.ssh/known_hosts with trust-on-first-use (TOFU).
inspect— one-round-trip inventory of the remote (Python, GPU, CPU, disk, memory).run— execute a command and stream stdout/stderr; optional PTY for nicer progress bars (tqdm, etc.), or--detachto start a job that outlives the session.upload— copy a file or directory over SFTP, with sensible default excludes (.venv,__pycache__,.git,runs/, …).wait— block until a marker string appears in a remote log, then tail it — handy for bridging "remote job finished" to a local notification.
Requires Go 1.26+.
git clone https://github.com/debpalash/sshbox.git
cd sshbox
go build -o sshbox .This produces a standalone sshbox binary you can drop on your PATH.
sshbox inspect
sshbox run <command...>
sshbox run --pty <command...> # allocate a PTY (nicer tqdm output)
sshbox run --detach [--log=PATH] <command...>
# start a job that outlives the session
# (default log: ~/sshbox-run.log)
sshbox upload <local> <remote> [--exclude=pat,pat]
sshbox wait <remote-log> [--marker=STR] [--poll=SEC] [--timeout=SEC]
# block until marker appears in log
# default marker: "Training complete"
| Variable | Description | Default |
|---|---|---|
SSHBOX_HOST |
remote hostname / IP | (required) |
SSHBOX_USER |
remote username | (required) |
SSHBOX_KEY |
private key path | first ~/.ssh/id_* |
SSHBOX_KEY_PASS |
passphrase for an encrypted key | (none) |
SSHBOX_PASS |
remote password (env only) | (none) |
SSHBOX_PORT |
SSH port | 22 |
SSHBOX_REMOTE_DIR |
default remote workdir | ~/box-vision |
One of SSHBOX_KEY or SSHBOX_PASS is required. With neither set, sshbox
looks for ~/.ssh/id_ed25519, id_ecdsa, then id_rsa, so a normal SSH setup
needs no configuration. When both a key and a password are available the key
is tried first — it is the stronger credential, and quietly falling back to a
password you happen to have set is not what anyone means by "I gave you a key".
export SSHBOX_HOST=192.0.2.10
export SSHBOX_USER=ubuntu
export SSHBOX_KEY=~/.ssh/id_ed25519 # or: export SSHBOX_PASS='your-password'
sshbox inspect
sshbox upload ./project '~/project'
sshbox run --pty 'cd ~/project && python train.py'
# …or start it detached and come back to it:
sshbox run --detach --log='~/project/train.log' 'cd ~/project && python train.py'
sshbox wait '~/project/train.log' --marker='Training complete' --poll=30An SSH session stays open until every process holding its stdout/stderr has exited. A backgrounded job inherits those descriptors from the shell, so
sshbox run 'python train.py &' # blocks for the whole runwaits for the job anyway — & returns the shell, not the channel. --detach
redirects all three descriptors, wraps the command in setsid, and closes the
session without waiting, so the job keeps running and you get your prompt back.
wait is how you pick the job back up.
- The password is read only from
SSHBOX_PASS; it is never accepted as a command-line argument. - Host keys are pinned in
~/.ssh/known_hosts. The first connection to a new host is trusted and recorded; a later key change for a known host is rejected (basic MITM protection). - Prefer SSH keys over passwords, especially on untrusted networks. Set
SSHBOX_KEY; password auth remains for hosts that offer nothing else. - An encrypted key needs
SSHBOX_KEY_PASS. sshbox says so by name rather than failing with the library's opaque parse error.
MIT © 2026 debpalash