How to Run Remote Commands Using SSH with Probe's ssh Plugin: A Complete Technical Guide
To run remote commands via SSH in Probe, define a workflow step with uses: ssh and provide the host, user, cmd, and either a password or key_file in the with block; the plugin handles connection, execution, timeout management, and returns structured output including exit code, stdout, and stderr.
Probe treats every built-in action as a plugin that receives a map of parameters, builds the appropriate client, executes the work, and returns a structured result. The SSH plugin, implemented in ssh/client.go, provides a self-contained, reproducible way to execute commands on remote servers within your Probe workflows.
Understanding the SSH Plugin Architecture
The SSH plugin follows a clear request-response pattern defined in ssh/client.go. Every workflow invocation maps YAML parameters to Go structs, validates the input, establishes an SSH connection, and captures the command output.
Parameter Definition with Req and Res Structs
The plugin defines its input contract through the Req struct (lines 26-38) and output through the Res struct (lines 42-46):
type Req struct {
Host string `map:"host" validate:"required"`
Port int `map:"port"`
User string `map:"user" validate:"required"`
Cmd string `map:"cmd" validate:"required"`
Password string `map:"password"`
KeyFile string `map:"key_file"`
KeyPassphrase string `map:"key_passphrase"`
Timeout string `map:"timeout"`
Workdir string `map:"workdir"`
Env map[string]string `map:"env"`
StrictHostCheck bool `map:"strict_host_check"`
KnownHosts string `map:"known_hosts"`
cb *Callback
}
type Res struct {
Code int `map:"code"`
Stdout string `map:"stdout"`
Stderr string `map:"stderr"`
}
The map struct tags allow Probe to automatically populate these fields from your workflow YAML's with block, while the validate:"required" tags enforce that host, user, and cmd must be present.
Parsing, Validation, and Defaults
The parseParams function (lines 86-115) normalizes your workflow input before execution:
- Port defaults to 22 if not specified
- Timeout defaults to 30s if not specified
- Expands tilde (
~) paths forkey_fileandknown_hoststo absolute paths - Validates authentication: Ensures either
passwordorkey_fileis provided - Checks required fields: Verifies
host,user, andcmdare present
This validation layer prevents connection failures by catching configuration errors before establishing the SSH session.
Configuring SSH Authentication
The createSSHConfig function (lines 91-77 in ssh/client.go) builds the ssh.ClientConfig with multiple authentication methods and host key verification strategies.
Password-Based Authentication
Provide the password parameter in your workflow:
uses: ssh
with:
host: "prod.example.com"
user: "deploy"
password: "${{ secrets.SSH_PASSWORD }}"
cmd: "whoami"
Public Key Authentication with Passphrase Support
For key-based auth, specify key_file and optionally key_passphrase:
uses: ssh
with:
host: "prod.example.com"
user: "deploy"
key_file: "~/.ssh/deploy_key"
key_passphrase: "${{ secrets.KEY_PASS }}"
cmd: "git pull origin main"
The plugin handles encrypted private keys by decrypting them with the provided passphrase before use.
Host Key Verification
Control security strictness with strict_host_check and known_hosts:
- Strict mode: Set
strict_host_check: trueto verify the remote host against known hosts - Custom known hosts: Use
known_hosts: "~/.ssh/known_hosts"(defaults to~/.ssh/known_hostsand/etc/ssh/ssh_known_hostsif not specified) - Lenient mode: Omit
strict_host_checkor set tofalseto skip verification (not recommended for production)
Executing Remote Commands
The Req.Do() method (lines 79-52) orchestrates the actual command execution with robust timeout and signal handling.
Connection and Session Management
When Do() executes, it performs the following sequence:
- Opens the SSH connection via
ssh.Dialusing the configuredhostandport - Starts a new session and applies environment variables from the
envmap - Optionally prepends a
cdcommand ifworkdiris specified - Launches the command via
session.Start - Captures
stdoutandstderrthrough separate pipes concurrently
Timeout Handling and Signal Management
The plugin respects the user-defined timeout parameter via Go contexts:
- Graceful termination: On timeout, sends
SIGTERMto the remote process - Force kill: Falls back to
SIGKILLif the process doesn't terminate gracefully - Context cancellation: Uses Go's
context.WithTimeoutfor precise deadline management
Return Value Structure
The method returns a Result struct containing:
- Original request: The
Reqthat was executed - Response data: Exit code (
res.code), stdout (res.stdout), stderr (res.stderr) - Runtime metrics: Execution duration (
RT) - Status: High-level status code (
0for success,1for failure)
Writing SSH Workflows in YAML
Define remote command execution in your Probe workflow files using the ssh action. The with block maps 1-to-1 to the fields of the Req struct.
Complete Workflow Example
name: Deploy Application
jobs:
- name: SSH Deploy
steps:
- name: Run remote script
uses: ssh
with:
host: "prod.example.com"
port: 22
user: "deploy"
key_file: "~/.ssh/deploy_key"
cmd: |
cd /opt/myapp
git pull origin main
systemctl restart myapp
timeout: "2m"
workdir: "/opt/myapp"
env:
DEPLOY_ENV: production
GIT_SSH_COMMAND: "ssh -o StrictHostKeyChecking=yes"
strict_host_check: true
known_hosts: "~/.ssh/known_hosts"
test: res.code == 0 && !contains(res.stderr, "error")
Key parameters explained:
- host (required): Target server hostname or IP address
- user (required): SSH username for authentication
- cmd (required): The command string to execute remotely
- timeout: Maximum execution time (e.g.,
30s,5m,1h) - workdir: Remote directory to change into before executing the command
- env: Map of environment variables to set in the remote session
Using the SSH Plugin Programmatically
For Go applications using Probe as a library, import the SSH plugin and invoke it through the generic Execute function (lines 80-31 in ssh/client.go).
Basic Programmatic Usage
package main
import (
"fmt"
"github.com/linyows/probe"
_ "github.com/linyows/probe/actions/ssh" // registers the ssh plugin
)
func main() {
workflow := map[string]any{
"name": "Deploy",
"jobs": []any{
map[string]any{
"name": "ssh-deploy",
"steps": []any{
map[string]any{
"name": "Run remote",
"uses": "ssh",
"with": map[string]any{
"host": "prod.example.com",
"user": "deploy",
"key_file": "~/.ssh/deploy_key",
"cmd": `cd /opt/myapp && git pull origin main`,
"timeout": "120s",
"env": map[string]string{
"DEPLOY_ENV": "production",
},
},
"test": "res.code == 0",
},
},
},
},
}
out, err := probe.Execute(workflow)
if err != nil {
panic(err)
}
fmt.Printf("Result: %+v\n", out)
}
The probe.Execute function parses the map, detects the ssh action, converts the parameters to a Req struct, calls Do(), and returns the Result as a generic map[string]any.
Implementing Callback Hooks
The SSH plugin supports Before and After callbacks for logging or side effects via WithBefore and WithAfter:
import "github.com/linyows/probe/ssh"
// Register a callback before connection
ssh.WithBefore(func(host string, port int, user string, cmd string) {
fmt.Printf("[SSH] Connecting to %s@%s:%d → %s\n", user, host, port, cmd)
})
// Register a callback after execution
ssh.WithAfter(func(r *ssh.Result) {
fmt.Printf("[SSH] Completed: exit=%d elapsed=%s\n", r.Res.Code, r.RT)
})
These hooks allow you to inject custom telemetry, audit logging, or debugging without modifying the core plugin implementation in ssh/client.go.
Summary
- The SSH plugin in Probe is implemented in
ssh/client.goand provides a structured way to execute remote commands through theReqandResstructs. - Authentication flexibility supports both password-based and public-key authentication (including encrypted keys with passphrases), configured through the workflow YAML or Go code.
- Robust execution handling includes configurable timeouts with SIGTERM/SIGKILL signal management, concurrent stdout/stderr capture, and working directory/environment variable support.
- Integration options include declarative YAML workflows for CI/CD pipelines or programmatic Go integration using
probe.Executewith optional callback hooks for observability.
Frequently Asked Questions
How do I handle SSH host key verification in Probe?
Set strict_host_check: true in your workflow and provide a known_hosts file path (defaults to ~/.ssh/known_hosts). According to the source code in ssh/client.go, the plugin reads this file to verify the remote server's identity before establishing the connection, falling back to system defaults at /etc/ssh/ssh_known_hosts if not specified.
What happens if my remote command exceeds the timeout?
The plugin uses Go's context-based timeout management. When the timeout expires, it first sends SIGTERM to the remote process; if the process doesn't exit gracefully, it escalates to SIGKILL. The Res struct will contain the exit code and any output captured before termination.
Can I use environment variables in my remote commands?
Yes. The Req struct includes an Env field of type map[string]string that accepts arbitrary environment variables. In YAML, define them under the env key in the with block. The plugin sets these variables in the remote session before executing your command, making them available to scripts and applications run via the cmd parameter.
How do I authenticate with an encrypted SSH private key?
Provide both the key_file path and the key_passphrase in your workflow configuration. The createSSHConfig function decrypts the private key using the passphrase before authentication, supporting standard OpenSSH encrypted key formats without requiring you to decrypt keys on disk.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →