Posted on
Artificial Intelligence

AI-Based Server Provisioning Scripts

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

AI-Based Server Provisioning Scripts with Bash: From Zero to Safer Automation

Ever been paged at 02:00 because a snowflake server drifted from your docs? What if your provisioning scripts adapted to unexpected environments in seconds—while still keeping change control and safety checks? AI-assisted provisioning with Bash can turn tribal know‑how and scattered wiki notes into reproducible, reviewable scripts you can test in containers before touching prod.

This post shows how to build a small, auditable Bash pipeline that:

  • Prompts an AI model to generate idempotent provisioning scripts for multiple distros.

  • Sandboxes and validates outputs with containers and static checks.

  • Enforces guardrails and keeps a durable paper trail.

No magic, no lock‑in—just Bash, curl, jq, and the Linux tooling you already trust.


Why AI‑based provisioning is valid (and valuable)

  • Speed and coverage: AI can synthesize vendor docs into working commands quickly, including edge cases across apt/dnf/zypper and divergent service names.

  • Tribally‑held tasks become reproducible: Prompt + output + logs = an auditable trail you can review and replay.

  • Safer than ad‑hoc copy/paste: With containerized dry‑runs, shell linting, and a denylist for dangerous commands, you can keep change control intact.

  • Not a replacement for IaC: It’s a booster. Use it to draft or adapt scripts, then pin and version them. Feed learnings back into Ansible, Terraform, or your golden images.


Prerequisites (install with your distro’s package manager)

We’ll use curl, jq, git, a container engine (Podman or Docker), and shellcheck.

  • Debian/Ubuntu (apt):

    sudo apt update
    sudo apt install -y curl jq git podman docker.io shellcheck
    

    Optional: enable Docker service

    sudo systemctl enable --now docker
    
  • Fedora/RHEL/CentOS (dnf):

    sudo dnf install -y curl jq git podman shellcheck
    

    Optional Docker (Fedora: moby-engine):

    sudo dnf install -y moby-engine
    sudo systemctl enable --now docker
    

    Note: On RHEL/CentOS, Docker CE requires the Docker repo; Podman is recommended and upstream-supported.

  • openSUSE Leap/Tumbleweed (zypper):

    sudo zypper refresh
    sudo zypper install -y curl jq git podman docker shellcheck
    sudo systemctl enable --now docker
    

AI model options:

  • Local LLM via Ollama (simple local endpoint):

    curl -fsSL https://ollama.com/install.sh | sh
    # Then start the service and pull a model, e.g.:
    ollama pull llama3
    
  • Hosted API (e.g., OpenAI, other OpenAI‑compatible): set environment variables:

    export OPENAI_API_KEY="sk-..."
    export OPENAI_MODEL="gpt-4o-mini"
    

Step 1: Create a minimal AI‑driven provisioner

The script below:

  • Accepts an “intent” (what to provision).

  • Instructs the model to emit idempotent Bash that supports apt/dnf/zypper.

  • Writes the output to generated.sh for review.

Save as ai-provision.sh and make executable.

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

: "${AI_PROVIDER:=ollama}"   # ollama | openai
: "${OPENAI_MODEL:=gpt-4o-mini}"  # if AI_PROVIDER=openai
: "${OLLAMA_MODEL:=llama3}"       # if AI_PROVIDER=ollama

if ! command -v jq >/dev/null; then
  echo "jq is required. Install it with your package manager." >&2
  exit 1
fi
if ! command -v curl >/dev/null; then
  echo "curl is required. Install it with your package manager." >&2
  exit 1
fi

INTENT="${1:-}"
if [[ -z "$INTENT" ]]; then
  echo "Usage: $0 \"<provisioning intent>\"" >&2
  echo "Example: $0 \"Provision Nginx on Ubuntu/Fedora/openSUSE with firewall and idempotency\"" >&2
  exit 1
fi

SYSTEM_PROMPT=$'You are a senior Linux SRE. Output a single, self-contained, idempotent Bash script:\n\

- Start with: set -euo pipefail\n\

- Detect package manager (apt/dnf/zypper) and use non-interactive flags.\n\

- Be explicit, safe, and reversible where possible. Do NOT reboot or wipe disks.\n\

- Include a --dry-run flag that prints actions without changing the system.\n\

- Use systemd where applicable. Avoid interactive prompts.\n\

- No placeholders. Provide sane defaults. Comment key steps.\n\

- Output ONLY the Bash script, no explanations or markdown fences.'

USER_PROMPT="Intent: ${INTENT}"

generate_with_ollama() {
  curl -fsS http://localhost:11434/api/generate \
    -H "Content-Type: application/json" \
    -d "$(jq -nc --arg model "$OLLAMA_MODEL" --arg sys "$SYSTEM_PROMPT" --arg usr "$USER_PROMPT" \
          '{model:$model, prompt:($sys+"\n\n"+$usr), stream:false}')" | jq -r '.response'
}

generate_with_openai() {
  if [[ -z "${OPENAI_API_KEY:-}" ]]; then
    echo "OPENAI_API_KEY is not set" >&2; exit 1
  fi
  curl -fsS https://api.openai.com/v1/chat/completions \
    -H "Authorization: Bearer ${OPENAI_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "$(jq -nc --arg model "$OPENAI_MODEL" --arg sys "$SYSTEM_PROMPT" --arg usr "$USER_PROMPT" \
          '{model:$model,messages:[{role:"system",content:$sys},{role:"user",content:$usr}],temperature:0.2}')" \
    | jq -r '.choices[0].message.content'
}

echo "Generating provisioning script from AI provider: ${AI_PROVIDER}..."
RAW_OUT="$(case "$AI_PROVIDER" in
  ollama) generate_with_ollama ;;
  openai) generate_with_openai ;;
  *) echo "Unknown AI_PROVIDER=$AI_PROVIDER" >&2; exit 1 ;;
esac)"

# Strip accidental markdown fences if present.
CLEAN_OUT="$(printf "%s" "$RAW_OUT" | sed -E 's/^```(bash)?$//;s/^```$//;s/^```.*$//;s/```$//')"

OUT=generated.sh
printf "%s\n" "$CLEAN_OUT" > "$OUT"
chmod +x "$OUT"

echo "Wrote $OUT"
echo "Head of file:"
head -n 30 "$OUT" | sed 's/^/| /'

Run it:

./ai-provision.sh "Provision Nginx with a firewall rule (80/443), create a deploy user with SSH key, enable/enable service, support apt/dnf/zypper"

Review generated.sh before running anything.


Step 2: Sandbox and dry‑run in containers

Test on the target distro in a container first. Use Podman or Docker.

  • Ubuntu (apt):

    podman run --rm -it -v "$PWD:/work" -w /work ubuntu:22.04 bash -lc \
    "apt-get update -y && ./generated.sh --dry-run"
    
  • Fedora (dnf):

    podman run --rm -it -v "$PWD:/work" -w /work fedora:40 bash -lc \
    "dnf -y makecache && ./generated.sh --dry-run"
    
  • openSUSE (zypper):

    podman run --rm -it -v "$PWD:/work" -w /work opensuse/leap:15 bash -lc \
    "zypper --non-interactive refresh && ./generated.sh --dry-run"
    

If you prefer Docker, replace podman with docker.

Tip: Also add static checks before running:

shellcheck generated.sh
bash -n generated.sh

Step 3: Add guardrails (denylist + lint + approvals)

Create validate.sh to fail fast on obviously dangerous patterns and enforce linting:

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

FILE="${1:-generated.sh}"
[[ -f "$FILE" ]] || { echo "Missing $FILE"; exit 1; }

# Deny very risky commands by default
RISK_PATTERNS=(
  'rm -rf /\b'
  ':(){ :|:& };:'        # fork bomb
  '\bmkfs(\.| )'
  '\bdd if='
  '\bswapoff -a\b'
  '\bshutdown\b|\breboot\b'
  '\bwget .*/run.sh\b'   # curling and piping to sh without verify
  'curl .* \| .*sh'
)

for pat in "${RISK_PATTERNS[@]}"; do
  if grep -Eqi -- "$pat" "$FILE"; then
    echo "Validation failed: matched risky pattern: $pat" >&2
    exit 2
  fi
done

shellcheck "$FILE"
echo "Validation passed."

Use it:

./validate.sh generated.sh

Only after it passes validation and a human review should you proceed to apply changes on a real host.


Step 4: Apply with logs and a paper trail

Create a simple “journal” directory:

mkdir -p provisioning_journal
SHA="$(sha256sum generated.sh | awk '{print $1}')"
DATE="$(date -Is)"
cp generated.sh "provisioning_journal/${DATE}_${SHA}.sh"
printf '{"date":"%s","sha":"%s","intent":"%s","model":"%s","provider":"%s"}\n' \
  "$DATE" "$SHA" "$INTENT" "${OPENAI_MODEL:-$OLLAMA_MODEL}" "$AI_PROVIDER" \
  > "provisioning_journal/${DATE}_${SHA}.json"

Then apply:

sudo ./generated.sh --apply

Many AI outputs will implement both --dry-run and --apply; if not, add a wrapper that exports DRY_RUN=1 for simulation.


Step 5: A compact, real‑world example

Below is a hand‑curated example of what a good AI output should look like for “Install Nginx, open 80/443 with available firewall, create a deploy user, and enable the service” across apt/dnf/zypper with a dry‑run mode.

Feel free to use this as a reference when reviewing generated outputs:

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

DRY_RUN=0
[[ "${1:-}" == "--dry-run" ]] && DRY_RUN=1
[[ "${1:-}" == "--apply" ]] && DRY_RUN=0

run() { if [[ "$DRY_RUN" -eq 1 ]]; then echo "[DRY] $*"; else eval "$@"; fi }

pm=""
if command -v apt-get >/dev/null 2>&1; then
  pm="apt"
elif command -v dnf >/dev/null 2>&1; then
  pm="dnf"
elif command -v zypper >/dev/null 2>&1; then
  pm="zypper"
else
  echo "Unsupported distro (need apt, dnf, or zypper)"; exit 1
fi

# Update metadata
case "$pm" in
  apt)   run "apt-get update -y";;
  dnf)   run "dnf -y makecache";;
  zypper) run "zypper --non-interactive refresh";;
esac

# Install Nginx
case "$pm" in
  apt)   run "DEBIAN_FRONTEND=noninteractive apt-get install -y nginx ufw || true";;
  dnf)   run "dnf -y install nginx firewalld || true";;
  zypper) run "zypper --non-interactive install -y nginx firewalld || true";;
esac

# Create deploy user if missing
if ! id -u deploy >/dev/null 2>&1; then
  run "useradd -m -s /bin/bash deploy"
  run "mkdir -p /home/deploy/.ssh"
  run "chmod 700 /home/deploy/.ssh"
  # Replace with your real public key:
  AUTHKEY="ssh-ed25519 AAAA... yourkey@example"
  run "printf '%s\n' \"$AUTHKEY\" > /home/deploy/.ssh/authorized_keys"
  run "chmod 600 /home/deploy/.ssh/authorized_keys"
  run "chown -R deploy:deploy /home/deploy/.ssh"
fi

# Firewall: prefer ufw, else firewalld if present
if command -v ufw >/dev/null 2>&1; then
  run "ufw allow 80/tcp || true"
  run "ufw allow 443/tcp || true"
  # Don’t auto-enable ufw without operator approval; log message instead
  echo "ufw present. Ensure it is enabled and default policies are correct."
elif command -v firewall-cmd >/dev/null 2>&1; then
  run "systemctl enable --now firewalld"
  run "firewall-cmd --permanent --add-service=http || true"
  run "firewall-cmd --permanent --add-service=https || true"
  run "firewall-cmd --reload || true"
else
  echo "No ufw/firewalld detected; skipping firewall configuration."
fi

# Enable and start Nginx
run "systemctl enable nginx"
run "systemctl restart nginx"

echo "Done. Mode: $([[ $DRY_RUN -eq 1 ]] && echo DRY-RUN || echo APPLIED)"

Test it in containers as shown earlier, then apply on your host with:

sudo ./generated.sh --apply

Troubleshooting and tips

  • If the model outputs interactive commands (e.g., prompts), re‑prompt emphasizing non‑interactive flags and idempotency.

  • Keep a compact allowlist of acceptable packages and commands in your validate.sh for high‑risk environments.

  • Periodically pin model versions and archive prompts/outputs for auditability.

  • Feed stabilized scripts back into your IaC so the AI layer remains a drafting assistant, not the sole source of truth.


Conclusion and next steps (CTA)

AI‑assisted provisioning doesn’t mean surrendering control—it means accelerating safe automation. Start small: 1) Set up the minimal pipeline above. 2) Generate a script for a simple service. 3) Validate, sandbox, and apply with logs. 4) Iterate guardrails and fold proven scripts into your IaC.

When you’re ready, extend the pattern to databases, app runtimes, and per‑team baselines. Your 02:00 self will thank you.

Have questions or want a deep‑dive on policy enforcement and reproducibility? Reach out—or propose a scenario you’d like to see templated next.