- 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 shellcheckOptional: enable Docker service
sudo systemctl enable --now dockerFedora/RHEL/CentOS (dnf):
sudo dnf install -y curl jq git podman shellcheckOptional Docker (Fedora: moby-engine):
sudo dnf install -y moby-engine sudo systemctl enable --now dockerNote: 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 llama3Hosted 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.