- 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)curlfor API callsjqfor JSON parsingparallelorpsshfor multi-host execution (either works; we’ll trypsshfirst, then fall back toparallel)
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
dupermissions 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 andsshpass.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.
parallelcitation prompt:- The script calls
parallel --citationsilently. You can also run it once manually to acknowledge.
- The script calls
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 viaProxyJump).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.