Go SDK¶
win-sshpass is not only a command-line tool but also a reusable Go library (package sshpass). You can embed SSH/SFTP/Shell capabilities into your own application.
Installation¶
Quick Start¶
package main
import (
"log"
sshpass "github.com/chuccp/win-sshpass"
)
func main() {
cfg := sshpass.NewConfig()
cfg.Host = "example.com"
cfg.User = "root"
cfg.Password = "secret"
// NewClient dials and returns a ready-to-use client.
client, err := sshpass.NewClient(cfg, sshpass.WithSignalHandler())
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Execute a command (output streams to os.Stdout/os.Stderr by default).
if err := client.Exec("uname -a"); err != nil {
log.Fatal(err)
}
}
Client Core Methods¶
NewClient¶
Establishes an SSH connection and returns a client:
cfg: Connection configuration (*sshpass.Config)opts: Optional functional options (...sshpass.Option)- If
cfg.Timeout > 0, an operation timer is armed that closes the connection when the deadline elapses
Exec¶
Executes a single remote command:
- Output streams are configured via
WithStdout/WithStderr(default:os.Stdout/os.Stderr) - Input stream is configured via
WithStdin(default:os.Stdin)
ExecCapture¶
Executes a command and captures the output (non-streaming), suitable for programmatic processing:
stdout, stderr, exitCode, err := client.ExecCapture("whoami")
if exitCode == 0 && err == nil {
fmt.Println("Output:", stdout)
} else {
fmt.Println("Error:", stderr)
}
exitCode: 0 on success, non-zero for remote exit status, -1 for session/connection errorserr: non-nil only on session creation or connection-level failures; nil for normal exits (including non-zero exit codes)
Shell¶
Starts an interactive shell:
- Auto-detects terminal, supports PTY and raw mode
- Supports dynamic terminal resizing
- Supports rz/sz file transfer (requires
WithFileSelectorconfiguration)
SFTP¶
Opens an SFTP sub-channel:
sftp, err := client.SFTP()
if err != nil {
log.Fatal(err)
}
defer sftp.Close()
// Upload file
err = sftp.Upload("./local.txt", "/tmp/remote.txt")
// Download file
err = sftp.Download("/tmp/remote.txt", "./local.txt")
// Access underlying *sftp.Client (advanced usage)
rawClient := sftp.SFTP()
Close¶
Closes the SSH connection:
- Idempotent — multiple calls return the same error
- Automatically stops timer and signal handler
TimedOut¶
Check if the failure was due to timeout:
Port Forwarding¶
Create TCP tunnels through the SSH connection. Both local and remote forwarding return a *Forwarder that runs in the background.
LocalForward¶
Listens on a local address and forwards each connection to a remote target through SSH:
// Forward localhost:8080 → db.internal:3306 via the SSH server
fwd, err := client.LocalForward("127.0.0.1:8080", "db.internal:3306")
if err != nil {
log.Fatal(err)
}
defer fwd.Close()
// Forwarder runs in the background until Close() is called
RemoteForward¶
Asks the SSH server to listen on a remote address and forwards connections back to a local target:
// Expose localhost:8080 on the remote server's port 9090
fwd, err := client.RemoteForward("0.0.0.0:9090", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
defer fwd.Close()
Forwarder Lifecycle¶
Close()— stops the tunnel and releases the listener (safe to call multiple times)Wait()— blocks until the forwarder stops (byClose()or listener error)- Both methods are goroutine-safe
Port Forwarding & Connection Sharing
LocalForward and RemoteForward share the Client's SSH connection. Closing the Client also terminates all forwarders.
Configuration (Config)¶
cfg := sshpass.NewConfig()
cfg.Host = "example.com"
cfg.User = "root"
cfg.Password = "secret"
cfg.Port = "22"
cfg.KeyPath = "~/.ssh/id_ed25519"
cfg.StrictHostKey = true
cfg.Timeout = 30 // Operation timeout (seconds), 0 = no limit
cfg.ConnectTimeout = 10 // TCP connection timeout (seconds)
cfg.Retries = 3 // Connection retry count
Load from Config File¶
Load Config or Password File¶
cfg, pass, err := sshpass.LoadConfigOrPasswordFile("file.txt", "", false)
if cfg != nil {
// It's a config file
} else {
// It's a password file; pass contains the password
}
Key Generation¶
Generate SSH key pairs programmatically:
// Generate Ed25519 key pair (recommended)
pair, err := sshpass.GenerateKeyPair(sshpass.KeyEd25519, "user@host")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Private key:\n%s\n", pair.PrivateKey)
fmt.Printf("Public key:\n%s\n", pair.PublicKey)
// Generate RSA key pair (minimum 2048 bits)
pair, err = sshpass.GenerateRSAKeyPair(4096, "user@host")
// Save key pair to files
err = sshpass.SaveKeyPair(pair, "~/.ssh/mykey")
// Creates: ~/.ssh/mykey (private, 0600) and ~/.ssh/mykey.pub (public)
// Get default key path
path := sshpass.DefaultKeyPath(sshpass.KeyEd25519) // ~/.ssh/id_ed25519
// Deploy public key to remote server (requires an existing client connection)
err = sshpass.DeployPublicKey(client, pair.PublicKey)
Functional Options¶
Configure Client behavior through functional options:
I/O Streams¶
// Redirect input/output
client, err := sshpass.NewClient(cfg,
sshpass.WithStdin(myReader),
sshpass.WithStdout(myWriter),
sshpass.WithStderr(myWriter),
)
Progress Callback¶
client, err := sshpass.NewClient(cfg,
sshpass.WithProgress(func(desc string, sent, total int64) {
fmt.Printf("\r%s %d/%d bytes", desc, sent, total)
}),
)
desc: Transfer description (e.g., "Uploading file.txt")sent: Bytes transferred so fartotal: Total file size- The SDK performs no rendering — callers display progress however they like
File Selector¶
type myFileSelector struct{}
func (s myFileSelector) OpenFile() (string, error) {
// Implement file open dialog
return "/path/to/file", nil
}
func (s myFileSelector) SaveFile(defaultName string) (string, error) {
// Implement file save dialog
return "/path/to/save", nil
}
client, err := sshpass.NewClient(cfg,
sshpass.WithFileSelector(myFileSelector{}),
)
Used for rz/sz shell transfer file selection. The SDK ships no default implementation.
Signal Handler¶
Registers a Ctrl+C handler that closes the connection. Off by default so the library never interferes with host signal handling.
Breakpoint Resume¶
Enables breakpoint-resume for SFTP transfers — interrupted uploads/downloads continue from where they left off.
SSH Agent Forwarding¶
Enables ssh-agent forwarding — the remote server can use the local ssh-agent for further SSH connections (e.g., git clone from a jump host). Requires a running local ssh-agent with keys loaded.
SSH Agent Authentication¶
Set UseAgent on the config to authenticate using the local ssh-agent:
cfg := sshpass.NewConfig()
cfg.Host = "example.com"
cfg.User = "root"
cfg.UseAgent = true // Auto-detect and use local ssh-agent
When UseAgent is true and no password or key path is configured, win-sshpass automatically connects to the local ssh-agent for authentication. This is the default behavior in the CLI when neither -p nor -i is specified.
Proxy Configuration¶
cfg := sshpass.NewConfig()
cfg.Host = "example.com"
cfg.User = "root"
cfg.Password = "secret"
cfg.ProxyURL = "socks5://user:pass@127.0.0.1:1080" // or http://, https://, socks4://
When ProxyURL is set, the SSH connection is tunneled through the specified proxy. Supported protocols: SOCKS5 (with optional auth), SOCKS4, SOCKS4A, HTTP CONNECT, HTTPS CONNECT.
Low-Level API¶
Dial¶
Create an SSH client connection directly:
Returns *ssh.Client for scenarios requiring more low-level control.
Argument Parsing¶
// Parse SSH arguments
config, cmd := sshpass.ParseSSHArgs([]string{"ssh", "user@host", "ls"})
// Parse SCP arguments
config, args := sshpass.ParseSCPArgs([]string{"scp", "file.txt", "user@host:/tmp/"})
// Parse Rsync arguments
config, args := sshpass.ParseRsyncArgs([]string{"rsync", "-avz", "./", "user@host:/backup/"})
// Detect command type
cmdType := sshpass.DetectCommandType(args)
Utility Functions¶
// Run SCP transfer
err := sshpass.RunSCP(client, args)
// Run Rsync transfer
err := sshpass.RunRsync(client, args)
// Clean remote path (handle Git Bash path conversion)
path, err := sshpass.CleanRemotePath("//tmp/file.txt")
// Parse user@host:path format
user, host, path := sshpass.ParseUserHostPath("user@host:/tmp/file.txt")
// Split paths (comma or space separated)
paths, err := sshpass.SplitPaths("a.txt,b.txt,c.txt", "local")
// Extract exit code from error
code, ok := sshpass.ExitCodeFromError(err)
Complete Examples¶
Batch Command Execution¶
package main
import (
"fmt"
"log"
sshpass "github.com/chuccp/win-sshpass"
)
func main() {
hosts := []string{"192.168.1.101", "192.168.1.102", "192.168.1.103"}
for _, host := range hosts {
cfg := sshpass.NewConfig()
cfg.Host = host
cfg.User = "root"
cfg.Password = "secret"
client, err := sshpass.NewClient(cfg)
if err != nil {
log.Printf("[%s] Connection failed: %v", host, err)
continue
}
fmt.Printf("[%s] Executing command...\n", host)
if err := client.Exec("uptime"); err != nil {
log.Printf("[%s] Command failed: %v", host, err)
}
client.Close()
}
}
File Upload with Progress¶
package main
import (
"fmt"
"log"
sshpass "github.com/chuccp/win-sshpass"
)
func main() {
cfg := sshpass.NewConfig()
cfg.Host = "example.com"
cfg.User = "root"
cfg.Password = "secret"
client, err := sshpass.NewClient(cfg,
sshpass.WithProgress(func(desc string, sent, total int64) {
pct := sent * 100 / total
fmt.Printf("\r%s %d%%", desc, pct)
}),
)
if err != nil {
log.Fatal(err)
}
defer client.Close()
sftp, err := client.SFTP()
if err != nil {
log.Fatal(err)
}
defer sftp.Close()
if err := sftp.Upload("./large-file.zip", "/tmp/large-file.zip"); err != nil {
log.Fatal(err)
}
fmt.Println("\nUpload complete!")
}
Next Steps¶
- Best Practices - Security and efficiency tips