Posted on
Artificial Intelligence

Artificial Intelligence for SSH Automation

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence for SSH Automation: Safer, Faster Ops from Your Bash Prompt

You’re on-call. It’s 2 AM. A fleet-wide issue pops up: a service is down or disks are filling. You know exactly what to do—if only you could draft the right command quickly and run it across dozens of hosts without fat-fingering anything. That’s where AI for SSH automation shines: it turns your intent into safe, reviewable commands, runs them at scale, and summarizes the results so you can move on.

In this article, you’ll:

  • See why AI + SSH is a practical combo right now.

  • Install the minimal tooling you need.

  • Build a tiny, auditable Bash workflow that:

    • Converts natural language into safe commands,
    • Executes them across many hosts,
    • Summarizes logs with AI.
  • Get real-world examples and guardrails.

Why this matters

  • Reduce cognitive load: Offload “what’s the exact flag for X again?” to the model, while you stay in control.

  • Scale with confidence: Vet a single suggested command, then fan it out across many hosts with built-in logging.

  • Faster post-ops: Summarize long outputs and spot anomalies without manual sift-and-compare.

This isn’t “let AI run your infra.” It’s “let AI draft, you decide, then automate safely.”


What we’ll build

A small Bash script that:

  • Calls an AI model to generate a single, safe Bash command for a task you describe.

  • Asks for your confirmation, then runs the command locally or over SSH on a list of hosts.

  • Captures outputs to logs and lets AI summarize the results.

No heavy frameworks required. Just ssh, curl, jq, and optionally pssh or parallel.


Install prerequisites

The following packages are used:

  • OpenSSH client (ssh, scp)

  • curl for API calls

  • jq for JSON parsing

  • parallel or pssh for multi-host execution (either works; we’ll try pssh first, then fall back to parallel)

Install with your package manager:

  • Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y openssh-client curl jq parallel pssh
  • Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y openssh-clients curl jq parallel pssh
  • openSUSE/SLE (zypper):
sudo zypper refresh
sudo zypper install -y openssh curl jq parallel pssh

Confirm SSH key-based auth is set up for your fleet (recommended over passwords). For example:

ssh-keygen -t ed25519 -C "$USER@$(hostname -s)"
ssh-copy-id user@host1

Step 1 — A tiny Bash “AI-to-command” helper

This function asks an AI model to produce a single, non-destructive Bash command from your natural-language prompt, then sanitizes the output. You stay in the loop to confirm before anything runs.

Create ai-ssh.sh:

#!/usr/bin/env bash
set -euo pipefail

# Configuration: set your API key and model. Works with OpenAI-compatible endpoints.
: "${OPENAI_BASE_URL:=https://api.openai.com/v1}"
: "${OPENAI_MODEL:=gpt-4o-mini}"    # Use a capable, inexpensive model
: "${OPENAI_API_KEY?Set OPENAI_API_KEY in your environment}"

LOG_DIR="${LOG_DIR:-logs}"
mkdir -p "$LOG_DIR"

denylist_regex='(^|\s)(rm\s+-rf|mkfs|dd\s+if=|:(){:|:&};:|shutdown\s+-h|reboot|poweroff|chown\s+-R\s+/\s|chmod\s+-R\s+/\s|>\/dev\/sda|>\/dev\/nvme|wipefs)(\s|$)'
system_prompt_cmd=$'You are a Bash command generator for experienced Linux admins.\n\
Output ONLY a single safe, non-interactive, idempotent command suitable for running via SSH on mixed Linux distros.\n\
Prefer read-only checks unless explicitly asked to change state. NEVER include explanations or code fences.\n\
Avoid destructive operations (rm -rf, mkfs, dd to disks, etc.). Use POSIX tools when possible.\n\
If package managers are involved, test config first (e.g., nginx -t && sudo systemctl reload nginx).\n\
Do not assume sudo is passwordless; prefer read-only checks or guarded changes.'

post_chat() {
  local prompt="$1"
  local payload
  payload="$(jq -n \
    --arg model "$OPENAI_MODEL" \
    --arg sys "$system_prompt_cmd" \
    --arg user "$prompt" \
    '{model:$model, temperature:0.1,
      messages:[{role:"system",content:$sys},{role:"user",content:$user}]}')"

  curl -sS \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -H "Content-Type: application/json" \
    "$OPENAI_BASE_URL/chat/completions" \
    -d "$payload" \
  | jq -r '.choices[0].message.content'
}

strip_fences() {
  # Remove accidental Markdown fences/backticks if present
  sed -E 's/^```.*$//g; s/^`(.*)`$/\1/g'
}

ai2cmd() {
  local ask="$*"
  local raw resp cmd
  resp="$(post_chat "$ask" | strip_fences)"
  # Take first line only, in case an LLM misbehaves
  cmd="$(printf "%s\n" "$resp" | head -n1 | tr -d '\r')"

  # Basic denylist safety
  if [[ "$cmd" =~ $denylist_regex ]]; then
    echo "Refusing potentially destructive command:"
    echo "  $cmd" >&2
    return 2
  fi

  # No empty outputs
  if [[ -z "${cmd// }" ]]; then
    echo "No command produced. Try being more specific." >&2
    return 1
  fi

  printf "%s\n" "$cmd"
}

confirm() {
  local cmd="$1"
  echo "Suggested command:"
  echo "  $cmd"
  read -rp "Proceed? [y/N] " ans
  [[ "${ans:-N}" =~ ^[Yy]$ ]]
}

run_local() {
  local cmd="$1"
  local ts=$(date +%Y%m%d-%H%M%S)
  echo "Running locally..."
  bash -lc "$cmd" 2>&1 | tee "$LOG_DIR/local-$ts.log"
}

run_remote() {
  local hosts_file="$1"
  local cmd="$2"
  local ts=$(date +%Y%m%d-%H%M%S)
  local logfile="$LOG_DIR/remote-$ts.log"

  if command -v pssh >/dev/null 2>&1; then
    echo "Using pssh across hosts in $hosts_file ..."
    # -i prints output as it arrives, -p controls parallelism, -t 0 no timeout
    pssh -h "$hosts_file" -i -p 30 -t 0 -O "StrictHostKeyChecking=no" "$cmd" \
      | tee "$logfile"
  else
    echo "pssh not found; using GNU parallel + ssh ..."
    parallel --citation >/dev/null 2>&1 || true
    # Simple case: commands without nested quotes run well.
    parallel -a "$hosts_file" -j 30 --tag \
      ssh -o BatchMode=yes -o StrictHostKeyChecking=no {} -- "$cmd" \
      | tee "$logfile"
  fi

  echo "Aggregate log: $logfile"
}

summarize_log() {
  local file="$1"
  local sys=$'You summarize multi-host command outputs for Linux ops.\n\
Produce:\n- High-level outcome\n- Notable anomalies (host -> note)\n- Counts/metrics if obvious\n- Next actions.\nKeep it concise.'
  local text
  # Limit to ~200KB to avoid huge payloads; adjust as needed
  text="$(head -c 200000 "$file")"
  local payload
  payload="$(jq -n \
    --arg model "$OPENAI_MODEL" \
    --arg sys "$sys" \
    --arg user "$text" \
    '{model:$model, temperature:0.2,
      messages:[{role:"system",content:$sys},{role:"user",content:$user}]}')"

  echo "AI summary:"
  curl -sS \
    -H "Authorization: Bearer $OPENAI_API_KEY" \
    -H "Content-Type: application/json" \
    "$OPENAI_BASE_URL/chat/completions" \
    -d "$payload" \
  | jq -r '.choices[0].message.content'
}

usage() {
  cat <<EOF
Usage:
  $(basename "$0") "natural language prompt"             # suggest + run locally
  $(basename "$0") -H hosts.txt "natural language prompt" # suggest + run on hosts
  $(basename "$0") -S logs/remote-YYYYmmdd-HHMMSS.log     # summarize a log file

Environment:
  OPENAI_API_KEY   (required)
  OPENAI_BASE_URL  (default: https://api.openai.com/v1)
  OPENAI_MODEL     (default: gpt-4o-mini)
EOF
}

main() {
  if [[ $# -eq 0 ]]; then usage; exit 1; fi

  local hosts="" summarize=""
  while getopts ":H:S:" opt; do
    case "$opt" in
      H) hosts="$OPTARG" ;;
      S) summarize="$OPTARG" ;;
      *) usage; exit 1 ;;
    esac
  done
  shift $((OPTIND-1))

  if [[ -n "$summarize" ]]; then
    [[ -f "$summarize" ]] || { echo "No such file: $summarize" >&2; exit 1; }
    summarize_log "$summarize"
    exit 0
  fi

  local prompt="${*:-}"
  [[ -n "$prompt" ]] || { usage; exit 1; }

  local cmd
  cmd="$(ai2cmd "$prompt")" || exit $?

  if confirm "$cmd"; then
    if [[ -n "$hosts" ]]; then
      [[ -f "$hosts" ]] || { echo "No such hosts file: $hosts" >&2; exit 1; }
      run_remote "$hosts" "$cmd"
    else
      run_local "$cmd"
    fi
  else
    echo "Cancelled."
  fi
}

main "$@"

Make it executable:

chmod +x ai-ssh.sh

Export your API key and, if needed, a custom base URL or model:

export OPENAI_API_KEY="sk-..."
# export OPENAI_BASE_URL="https://api.yourprovider.example/v1"
# export OPENAI_MODEL="gpt-4o"   # or another Chat Completions-capable model

Step 2 — Run commands across many hosts

Prepare a simple hosts file (one host per line, optionally with user):

cat > hosts.txt <<'EOF'
web-01
web-02
db-01
user@batch-01
EOF

Now ask for a command and run it fleet-wide:

./ai-ssh.sh -H hosts.txt "Show top 10 largest directories under /var without crossing filesystem boundaries"

Example suggestion (you’ll be asked to confirm):

sudo du -x /var 2>/dev/null | sort -h | tail -n 10

The script will:

  • Execute in parallel across your hosts,

  • Stream tagged outputs to the terminal,

  • Save an aggregate log in logs/remote-YYYYmmdd-HHMMSS.log.

Tip: Adjust parallelism with -p (pssh) or -j (parallel) if you need to go slower/faster.


Step 3 — Summarize results and spot anomalies

After a big run, let the model summarize:

./ai-ssh.sh -S logs/remote-20260101-153012.log

You’ll get a concise digest, e.g.:

  • Outcome: Most hosts show moderate usage; web-02 has unusually large /var/log/nginx.

  • Anomalies: db-01 missing du permissions under /var/lib/postgresql.

  • Next actions: Rotate/compress nginx logs on web-02; check permissions or run with sudo on db-01.

This is especially helpful when outputs are long and heterogeneous.


Step 4 — Real-world examples you can try

  • Check a service health non-destructively:

    • Prompt: “Test nginx config and show status without reloading.”
    • Likely command: nginx -t && systemctl status --no-pager nginx || true
  • Fleet-wide package version check:

    • Prompt: “Print OpenSSL version string.”
    • Likely command: openssl version 2>/dev/null || command -v openssl || echo "openssl not found"
  • Disk pressure triage:

    • Prompt: “List files larger than 500MB under /var/log, newest first.”
    • Likely command: find /var/log -type f -size +500M -printf '%T@ %p\n' | sort -nr | head -n 20 | awk '{ $1=""; sub(/^ /,""); print }'
  • Safe service reload (guarded):

    • Prompt: “If nginx config passes, reload it; otherwise print the error.”
    • Likely command: sudo sh -c 'nginx -t && systemctl reload nginx || (echo "nginx -t failed"; exit 1)'

Remember: you confirm every suggestion before it runs.


Step 5 — Guardrails and good practices

  • Start read-only: Ask for checks first; only then request changes.

  • Keep humans in the loop: Always confirm, especially for multi-host runs.

  • Denylist and allowlist: Expand denylist_regex; optionally add an allowlist of verbs you permit in production.

  • Log everything: Persist outputs in logs/ for audit and incident reviews.

  • Use SSH keys and bastions: Prefer key-based auth, ProxyJump, BatchMode=yes. Avoid plaintext passwords and sshpass.

  • Dry-run flags: When applicable (e.g., rsync --dry-run), have the model include them first.

  • Least privilege: If you need sudo, scope it to the minimum and consider password prompts carefully.


Troubleshooting

  • Model outputs multiple lines or explanations:

    • The script takes the first line only. Re-run with a more specific prompt (e.g., “Output a single command, no explanations”).
  • Quoting issues on complex commands:

    • Ask for simpler commands, or wrap logic in a small script you deploy, then run that script via SSH.
  • parallel citation prompt:

    • The script calls parallel --citation silently. You can also run it once manually to acknowledge.

Conclusion and next steps

You just built a compact, auditable AI workflow for SSH operations:

  • Natural language to safe commands,

  • Human confirmation,

  • Parallel execution,

  • AI summaries for fast insight.

Next steps:

  • Extend the denylist and add an allowlist suited to your environment.

  • Integrate with your inventory and SSH config (e.g., ~/.ssh/config, bastion via ProxyJump).

  • Add per-team prompts (networking, DB, web) to steer the model toward your standards.

  • Hook the logs into your SIEM or ticketing for full auditability.

If this sped up your workflow, consider wrapping it into your team’s “ops toolbox” repo and sharing a few approved prompts to get everyone moving faster—safely.