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 for key_file and known_hosts to absolute paths
  • Validates authentication: Ensures either password or key_file is provided
  • Checks required fields: Verifies host, user, and cmd are 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: true to verify the remote host against known hosts
  • Custom known hosts: Use known_hosts: "~/.ssh/known_hosts" (defaults to ~/.ssh/known_hosts and /etc/ssh/ssh_known_hosts if not specified)
  • Lenient mode: Omit strict_host_check or set to false to 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:

  1. Opens the SSH connection via ssh.Dial using the configured host and port
  2. Starts a new session and applies environment variables from the env map
  3. Optionally prepends a cd command if workdir is specified
  4. Launches the command via session.Start
  5. Captures stdout and stderr through separate pipes concurrently

Timeout Handling and Signal Management

The plugin respects the user-defined timeout parameter via Go contexts:

  • Graceful termination: On timeout, sends SIGTERM to the remote process
  • Force kill: Falls back to SIGKILL if the process doesn't terminate gracefully
  • Context cancellation: Uses Go's context.WithTimeout for precise deadline management

Return Value Structure

The method returns a Result struct containing:

  • Original request: The Req that was executed
  • Response data: Exit code (res.code), stdout (res.stdout), stderr (res.stderr)
  • Runtime metrics: Execution duration (RT)
  • Status: High-level status code (0 for success, 1 for 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.go and provides a structured way to execute remote commands through the Req and Res structs.
  • 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.Execute with 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →