English | 简体中文
sshc is a small SSH helper CLI for managing hosts, running remote commands,
transferring files, executing local scripts on remote hosts, and keeping per-host execution logs.
It is intended for lightweight deployment, troubleshooting, and day-to-day remote operations where a full automation platform would be too heavy.
- Manage SSH hosts in
~/.config/sshc/sshc.config.json- Encrypt saved passwords before writing to disk
- Manage shared credential profiles with
auth - Inspect and edit local settings with
cfg - Read simple host entries from
~/.ssh/config - Verify SSH host keys with
known_hostsby default - Run remote commands by saved host name, IP, or unique partial match
- Execute local shell scripts on remote hosts
- Connect through a single jump host with host config or
--jump - Run commands or scripts across multiple hosts with
batch-run/brun - Set remote working directory, timeout, environment variables, sudo, and sudo user
- Upload files with
uploadand download files withdownloadover SFTP- Verify single-file transfers with SHA256
- Keep per-host JSONL run logs under
~/.config/sshc/logs/ - Open an interactive remote PTY with
login/connect
- Recommended Install by eget:
eget install inhere/sshc - Install by Golang:
go install github.com/inhere/sshc/cmd/sshc@latest - Download the archive for your platform from GitHub Releases, extract it, and put the
sshcbinary on yourPATH.
git clone https://github.com/inhere/sshc.git
cd sshc
go build -o sshc ./cmd/sshc
# Windows
go build -o sshc.exe ./cmd/sshcsshc add --ip 192.168.1.10 --name devhost -u root -p password
sshc list
sshc run devhost -- uptime
sshc auth add dev-root -u root -p --remark "shared root login"
sshc host add --ip 192.168.1.10 --name devhost --auth dev-root # use auth refer
sshc host add --ip 10.0.0.8 --name inner-db --auth dev-root --jump bastion
sshc run devhost --script ./deploy.sh
sshc run inner-db --jump bastion -- hostname
sshc batch-run --hosts devhost,web-2 -- uptime
sshc scp -l ./dist -r /opt/app/dist devhost
sshc download -r /var/log/my-app/app.log -l tmp/logs/ devhost --sha256
sshc log devhost --tail 20sshc add Add or update a host
sshc list|ls List saved hosts
sshc cfg|config Manage config
sshc auth|cred Manage credential profiles
sshc host|hosts Manage hosts
sshc run|exec Run a remote command
sshc batch-run|brun Run a command or script on multiple hosts
sshc login Open an interactive SSH shell
sshc scp|upload Upload files or directories
sshc download|dl Download files or directories
sshc log Show or search run logs
Run command-specific help for full options:
sshc <command> --helpsshc add --ip 192.168.1.10 -u root -p password
sshc add --ip 192.168.1.10 --name devhost -u root -p password --port 22
sshc add --ip 192.168.1.10 --name devhost -u root --key ~/.ssh/id_rsa
sshc add --ip 192.168.1.10 --name devhost --auth dev-root
sshc add --ip 10.0.0.8 --name inner-db --auth dev-root --jump bastion
sshc add -I
sshc add --from-clipboardsshc add -I prompts for host fields interactively and hides password input.
--from-clipboard accepts either key=value/key: value lines or one CSV line:
ip=192.168.1.10
user=root
password=password
name=devhost
port=22
192.168.1.10,root,password,devhost,22
Use auth profiles when multiple hosts share the same user, password, or key:
sshc auth add dev-root -u root -p --remark "shared root login"
sshc auth add deploy-key -u deploy --key ~/.ssh/id_ed25519
sshc auth list
sshc auth show dev-root
sshc auth rm old-profile --yessshc auth add -p prompts for a hidden password. It intentionally does not
accept -p secret or --password secret.
Attach a profile to a host:
sshc host add --ip 192.168.1.10 --name devhost --auth dev-root
sshc host add --ip 10.0.0.8 --name inner-db --auth dev-root --jump bastionsshc host add --ip 192.168.1.10 --name devhost --auth dev-root
sshc host add --ip 10.0.0.8 --name inner-db --auth dev-root --jump bastion
sshc host list --group testing --show-ip
sshc host list --match devhost
sshc host show devhost
sshc host rm devhost --yes
sshc host rename old-name new-nameTop-level add, list, and ls remain available for quick daily use.
sshc list
sshc ls
sshc list --show-ipsshc list shows the host name, group, address, authentication type, and remark.
IPv4 addresses are masked by default, for example 10.*.*.8. Use --show-ip
when you need the full address.
Hosts from ~/.ssh/config are also listed when they have HostName, User, and
IdentityFile.
sshc run 192.168.1.10 -- uptime
sshc run devhost -- docker ps
sshc run devhost --cwd /opt/app -- python -m app
sshc run devhost --timeout 30s --kill-after 5s -- systemctl status nginx
sshc run devhost -e APP_ENV=prod -e DEBUG=1 -- printenv APP_ENV
sshc run devhost --efile ./remote.env -- envRemote commands must be placed after --.
Environment files support comments, blank lines, plain KEY=value, and
export KEY=value lines:
APP_ENV=prod
export DEBUG=1
NAME="hello world"
Set jump on the target host when it normally needs a bastion:
sshc host add --ip 1.2.3.4 --name bastion --auth dev-root
sshc host add --ip 10.0.0.8 --name inner-db --auth dev-root --jump bastionEquivalent config:
{
"hosts": [
{
"name": "bastion",
"ip": "1.2.3.4",
"auth_ref": "dev-root"
},
{
"name": "inner-db",
"ip": "10.0.0.8",
"auth_ref": "dev-root",
"jump": "bastion"
}
]
}Then use the target host as usual:
sshc run inner-db -- hostname
sshc login inner-db
sshc scp -l app.jar -r /tmp/app.jar inner-db
sshc download -r /var/log/app.log -l tmp/logs inner-dbUse --jump to override the configured jump host for one command:
sshc run inner-db --jump bastion -- hostname
sshc login inner-db --jump bastion
sshc scp -l app.jar -r /tmp/app.jar inner-db --jump bastion
sshc download -r /var/log/app.log -l tmp/logs inner-db --jump bastionThe initial implementation supports one jump host. Nested jump hosts,
ProxyCommand, and PVE/LXC/vhost-specific execution are not supported yet.
Host key checking still happens on the local machine for both the jump host and
the target host.
sshc run devhost --script ./deploy.sh
sshc run devhost --script ./deploy.sh --cwd /opt/app
sshc run devhost --script ./deploy.sh --remote-script-dir /opt/app/tmp
sshc run devhost --script ./deploy.sh --keep-remote-scriptUse --script for multiline shell, here-doc, source or virtualenv activation,
or commands that require heavy quoting.
Script mode uploads the local file to /tmp by default and runs it with bash.
Use --remote-script-dir when /tmp has restrictive mount options, permissions,
or cleanup policies.
sshc batch-run --hosts devhost,web-2 -- uptime
sshc brun --hosts devhost,web-2 -- hostname
sshc batch-run --group testing --parallel 5 --script ./deploy.sh
sshc batch-run --hosts-file hosts.txt -- hostname
sshc batch-run --hosts-file ips.txt --auth dev-root --script ./init.sh--hosts accepts a comma-separated list. --hosts-file reads one host target per
line and ignores blank lines and full-line comments. Saved hosts are resolved
first; unresolved IP or hostname targets can be used with shared auth options
such as --auth, -u, --key, or -p.
Use --parallel to limit concurrency. With --fail-fast, sshc stops starting
new hosts after the first failure and waits for already running hosts to finish.
sshc run devhost --sudo -- apt-get update
sshc run devhost --sudo-user app --cwd /opt/app -- whoami
sshc run devhost --script ./deploy.sh --sudo-user app --remote-script-dir /opt/app/tmp--sudo and --sudo-user require passwordless sudo, or an SSH user that already
has the required privileges.
sshc scp -l ./local-file.txt -r /tmp/remote-file.txt devhost
sshc scp -l ./local-file.txt -r /tmp/remote-file.txt devhost --sha256
sshc scp -l ./local-dir -r /tmp/remote-dir devhost
sshc scp -l ./dist -r /opt/app/dist devhost --remove-dir
sshc scp -l "./dist/*.jar" -r /opt/app/lib devhost
sshc scp -l ./a.jar -l ./b.jar -r /opt/app/lib/ devhost
sshc scp --map ./config/app.yml=/etc/app/app.yml --map ./scripts/deploy.sh=/opt/app/deploy.sh devhostUse repeatable -l/--local when multiple local paths should go into one remote
directory. Use repeatable --map local=remote when each local path needs an
explicit remote destination.
sshc download -r /tmp/remote-file.txt -l ./local-file.txt devhost
sshc download -r /tmp/remote-file.txt -l ./local-file.txt devhost --sha256
sshc download -r /var/log/my-app/app.log -l tmp/logs/ devhost --sha256
sshc dl -r /tmp/remote-dir -l ./local-dir devhostExisting local directories receive the remote base name. Local paths ending with
/ or \ are also treated as directories.
sshc log
sshc log devhost
sshc log devhost --match uptime
sshc log devhost --tail 50
sshc log devhost -m error --tail 50
sshc log --id 20260704-173012-a1b2c3
sshc log --id 20260704-173012-a1b2c3 --tail 80
sshc log --id 20260704-173012-a1b2c3 --lines 120,180
sshc log devhost --lines 20,80Every run writes one JSON log line to ~/.config/sshc/logs/<host>.log by
default. Each run has a task_id. Short output is kept inline in JSONL; larger
output is stored under ~/.config/sshc/logs/yyyyMMdd/<task_id>.out.log and can
be opened with sshc log --id <task_id>.
Interactive login sessions only record connection metadata.
sshc login devhost
sshc connect devhost
sshc login --term xterm-256color devhostlogin and connect open an interactive remote PTY. The terminal type defaults
to the local TERM value and falls back to xterm-256color.
Most commands accept a saved host name or IP. sshc resolves exact matches
first, then a unique partial match across host name, IP, remark, and group.
For example, if a saved host has name testing-web, group testing, and remark
gpu runner, these can match when the result is unique:
sshc run "testing web" -- hostname
sshc run "testing gpu" -- uptimeIf multiple hosts match, sshc returns the candidate list instead of guessing.
Default config file:
~/.config/sshc/sshc.config.json
Example config:
{
"version": 1,
"logs_path": "logs",
"defaults": {
"user": "root",
"port": 22,
"connect_timeout": "20s",
"remote_script_dir": "/tmp",
"host_key_check": "known_hosts",
"known_hosts_path": "~/.ssh/known_hosts"
},
"auth_profiles": [
{
"name": "dev-root",
"user": "root",
"password_enc": "v1:...",
"remark": "shared root login"
}
],
"hosts": [
{
"name": "devhost",
"ip": "192.168.1.10",
"auth_ref": "dev-root",
"group": "testing"
},
{
"name": "inner-db",
"ip": "10.0.0.8",
"auth_ref": "dev-root",
"jump": "devhost",
"group": "testing"
}
]
}logs_path can be absolute, start with ~, or be relative to
~/.config/sshc.
Default run log directory:
~/.config/sshc/logs/<host>.log
Use another config file:
SSHC_CONFIG=/path/to/sshc.config.json sshc listSaved hosts override entries loaded from ~/.ssh/config when the name or IP is
the same.
For compatibility, ~/.config/sshc/hosts.json is still read when the new default
config file does not exist.
Config helpers:
sshc cfg path
sshc cfg show
sshc cfg show --raw
sshc cfg get logs_path
sshc cfg set logs_path ./runtime/logs
sshc cfg unset logs_path
sshc cfg doctorcfg show masks passwords and encrypted password values. cfg show --raw
prints the config file as stored on disk and is intended for local debugging.
- Saved passwords are encrypted before being written to
sshc.config.json. - The local encryption key is stored at
~/.config/sshc/key; keep both files private. - Legacy plaintext
passwordfields are still readable for compatibility. - Prefer SSH keys over passwords when possible.
- If both password and
--keyare provided, key authentication is tried first. - SSH host keys are checked against
~/.ssh/known_hostsby default. - If a host is not trusted yet, connect once with
ssh devhostor add its key toknown_hostsbefore usingsshc. - Set
host_key_checktoinsecureonly when you explicitly want to skip host key verification. - With
--script --sudo-user, the uploaded temporary script is readable by local remote users so the target sudo user can execute it. For sensitive scripts, use--remote-script-dirwith a restricted remote directory. loginopens an interactive PTY. Session logs record connection metadata only, not typed commands or terminal output.
go test ./...
go build -o tmp/sshc ./cmd/sshcOn Windows:
go test ./...
go build -o tmp\sshc.exe ./cmd/sshcRelease builds are driven by tags:
git tag v0.1.0
git push origin v0.1.0MIT