- Posted on
- • Artificial Intelligence
Artificial Intelligence Configuration Drift Detection
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Bash-first AI: Detecting Configuration Drift Before It Bites You
Ever sworn “nobody touched that server” right before a 2 a.m. outage? Configuration drift is the silent culprit: tiny, untracked changes that compound into downtime, security incidents, and compliance gaps. The good news: you don’t need a data center’s worth of tooling to get ahead of it. With a few reliable Linux tools and a small dose of AI, you can baseline, watch, and flag risky drift from the command line.
This guide shows you how to:
Capture reproducible baselines of critical system state
Automate drift snapshots with Bash
Score drift with an anomaly detector (Isolation Forest)
Get practical alerts and human-friendly summaries
All commands and code are Bash-first, with minimal Python for the AI bit.
Why this matters (and why AI helps)
Not all changes are bad. Patching day looks different from a quiet Sunday. AI helps you learn “normal” change patterns from your own hosts and elevates the weird ones.
Compliance and audits love repeatable evidence. Baselines plus versioned, machine-readable diffs reduce audit time and blast radius.
Security signal over noise. Most drift is benign; AI-based scoring highlights unusual service enables, package churn, or configuration spikes that deserve human eyes.
What you’ll build
A snapshot script that captures:
- /etc file checksums
- Enabled services
- Installed packages
- Listening ports
- Firewall rules
A feature extractor that compares snapshots and produces metrics
A tiny Python script that flags anomalies using Isolation Forest
Optional automation via cron or inotify
Prerequisites and installation
We’ll use standard CLI tools plus Python’s scientific stack.
Install required packages:
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y git jq inotify-tools python3 python3-pip
# Optional if you want cron scheduling:
sudo apt install -y cron
- Fedora/RHEL/CentOS (dnf):
sudo dnf install -y git jq inotify-tools python3 python3-pip
# Optional cron (enable/start service):
sudo dnf install -y cronie
sudo systemctl enable --now crond
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y git jq inotify-tools python3 python3-pip
# Optional cron:
sudo zypper install -y cron
Install Python packages (user scope):
python3 -m pip install --user --upgrade pip
python3 -m pip install --user scikit-learn pandas numpy
Make sure ~/.local/bin is in your PATH if needed:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
. ~/.bashrc
1) Build a clean baseline (repeatable snapshots)
Create a directory for your snapshots and a Bash script to capture system state.
Create /usr/local/bin/ai-drift-snapshot.sh:
#!/usr/bin/env bash
set -euo pipefail
SNAPDIR="${SNAPDIR:-/var/lib/ai-drift/snapshots}"
TS="$(date -u +%Y%m%dT%H%M%SZ)"
CUR="$SNAPDIR/$TS"
sudo mkdir -p "$CUR"
sudo chown "$(id -u)":"$(id -g)" "$CUR"
pkg_list() {
if command -v dpkg-query >/dev/null 2>&1; then
dpkg-query -W -f='${Package}\t${Version}\n' | sort
elif command -v rpm >/dev/null 2>&1; then
rpm -qa --qf '%{NAME}\t%{EPOCHNUM}:%{VERSION}-%{RELEASE}.%{ARCH}\n' | sort
else
echo -e "unknown-pkgmgr\t0"
fi
}
echo "# snapshot: $TS" > "$CUR/meta.txt"
uname -a >> "$CUR/meta.txt" || true
cat /etc/os-release >> "$CUR/meta.txt" 2>/dev/null || true
# Package inventory
pkg_list > "$CUR/packages.tsv"
# Systemd unit files (enabled/disabled/etc.)
if command -v systemctl >/dev/null 2>&1; then
systemctl list-unit-files --type=service --no-legend --no-pager > "$CUR/systemd-unit-files.txt" || true
fi
# Checksums of /etc (skip some private/cert dirs to reduce noise)
sudo find /etc -xdev -type f \
-not -path '/etc/ssl/private/*' \
-not -path '/etc/pki/*' \
-not -path '/etc/ssh/ssh_host_*' \
-exec sha256sum {} + | sort > "$CUR/etc.sha256"
# Root crontab (if any)
crontab -l > "$CUR/crontab.root" 2>/dev/null || true
# Listening sockets
ss -tulpen > "$CUR/ports.txt" 2>/dev/null || true
# Firewall rules (iptables/nft)
iptables-save > "$CUR/iptables.save" 2>/dev/null || true
nft list ruleset > "$CUR/nft.txt" 2>/dev/null || true
# Disk overview (helps spot sudden growth)
df -hT > "$CUR/df.txt" 2>/dev/null || true
# Maintain a symlink to "latest"
ln -sfn "$CUR" "$SNAPDIR/latest"
echo "Snapshot saved to: $CUR"
Make it executable and run it:
sudo install -m 0755 ai-drift-snapshot.sh /usr/local/bin/ai-drift-snapshot.sh
sudo /usr/local/bin/ai-drift-snapshot.sh
Commit snapshots to a local Git repo if you want time travel and off-host backups:
sudo bash -c '
set -e
SNAPDIR=/var/lib/ai-drift/snapshots
cd "$SNAPDIR"
git init -q || true
git add .
git commit -m "baseline $(date -u +%F)" || true
'
2) Automate capture (cron and/or inotify)
Option A: periodic snapshots with cron (e.g., every 6 hours):
( crontab -l 2>/dev/null; echo "0 */6 * * * sudo /usr/local/bin/ai-drift-snapshot.sh >> /var/log/ai-drift.log 2>&1" ) | crontab -
Option B: watch /etc for live changes and snapshot after bursts:
#!/usr/bin/env bash
# /usr/local/bin/ai-drift-watch.sh
set -euo pipefail
DEBOUNCE=60
LAST=0
inotifywait -m -r -e modify,create,delete,move --format '%T %w%f %e' --timefmt '%F %T' /etc | while read -r line; do
NOW=$(date +%s)
if (( NOW - LAST > DEBOUNCE )); then
echo "Change in /etc detected: $line"
sudo /usr/local/bin/ai-drift-snapshot.sh
LAST="$NOW"
fi
done
Run it as a service if desired, or in a tmux/screen session:
sudo install -m 0755 ai-drift-watch.sh /usr/local/bin/ai-drift-watch.sh
/usr/local/bin/ai-drift-watch.sh >> /var/log/ai-drift.log 2>&1 &
3) Extract features and score drift with AI
We’ll convert snapshot diffs into simple numeric features, then let an Isolation Forest learn what “normal” looks like for your host.
Create /usr/local/bin/ai-drift-features.sh:
#!/usr/bin/env bash
set -euo pipefail
SNAPDIR="${SNAPDIR:-/var/lib/ai-drift/snapshots}"
OUTCSV="${OUTCSV:-$SNAPDIR/features.csv}"
# Find previous and current snapshots
mapfile -t snaps < <(find "$SNAPDIR" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' 2>/dev/null | sort)
if (( ${#snaps[@]} < 2 )); then
echo "Need at least two snapshots to compute features." >&2
exit 1
fi
CUR="${snaps[-1]}"
PREV="${snaps[-2]}"
p="$SNAPDIR/$PREV"
c="$SNAPDIR/$CUR"
# etc checksum diff lines (content changes + additions/removals)
etc_diff=$(diff -U 0 "$p/etc.sha256" "$c/etc.sha256" 2>/dev/null | grep -E '^[+-][0-9a-f]' | wc -l || echo 0)
# Enabled services churn
enabled_prev=$(awk '$2=="enabled"{print $1}' "$p/systemd-unit-files.txt" 2>/dev/null | sort -u || true)
enabled_curr=$(awk '$2=="enabled"{print $1}' "$c/systemd-unit-files.txt" 2>/dev/null | sort -u || true)
tmpd="$(mktemp -d)"
printf '%s\n' $enabled_prev > "$tmpd/p.txt"
printf '%s\n' $enabled_curr > "$tmpd/c.txt"
new_services=$(comm -13 "$tmpd/p.txt" "$tmpd/c.txt" | wc -l || echo 0)
removed_services=$(comm -23 "$tmpd/p.txt" "$tmpd/c.txt" | wc -l || echo 0)
# Package adds/removes (names only)
cut -f1 "$p/packages.tsv" | sort > "$tmpd/pkgs_p.txt"
cut -f1 "$c/packages.tsv" | sort > "$tmpd/pkgs_c.txt"
new_pkgs=$(comm -13 "$tmpd/pkgs_p.txt" "$tmpd/pkgs_c.txt" | wc -l || echo 0)
removed_pkgs=$(comm -23 "$tmpd/pkgs_p.txt" "$tmpd/pkgs_c.txt" | wc -l || echo 0)
# Listening sockets count
ports_prev=$(grep -E 'LISTEN|UNCONN' "$p/ports.txt" 2>/dev/null | wc -l || echo 0)
ports_curr=$(grep -E 'LISTEN|UNCONN' "$c/ports.txt" 2>/dev/null | wc -l || echo 0)
ports_delta=$(( ports_curr - ports_prev ))
# Time features
ts="$CUR"
hour=$(date -u -d "${ts:0:8} ${ts:9:2}:${ts:11:2}:${ts:13:2}Z" +%H 2>/dev/null || date -u +%H)
# Write header if missing
if [ ! -f "$OUTCSV" ]; then
echo "timestamp,etc_diff,new_services,removed_services,new_pkgs,removed_pkgs,ports_delta,hour" > "$OUTCSV"
fi
echo "$CUR,$etc_diff,$new_services,$removed_services,$new_pkgs,$removed_pkgs,$ports_delta,$hour" >> "$OUTCSV"
rm -rf "$tmpd"
echo "Features appended for $CUR -> $OUTCSV"
Create /usr/local/bin/ai-drift-detect.py:
#!/usr/bin/env python3
import sys
import json
import pandas as pd
from sklearn.ensemble import IsolationForest
if len(sys.argv) < 2:
print("Usage: ai-drift-detect.py /var/lib/ai-drift/snapshots/features.csv")
sys.exit(1)
csv_path = sys.argv[1]
df = pd.read_csv(csv_path)
if len(df) < 10:
# Not enough history; fallback to rule-of-thumb
latest = df.iloc[-1]
rough = (latest['etc_diff'] > 10) or (latest['new_services'] > 0) or (latest['new_pkgs'] > 5)
out = {
"timestamp": latest['timestamp'],
"anomaly": bool(rough),
"reason": "heuristic" if rough else "insufficient_history",
"etc_diff": int(latest['etc_diff']),
"new_services": int(latest['new_services']),
"removed_services": int(latest['removed_services']),
"new_pkgs": int(latest['new_pkgs']),
"removed_pkgs": int(latest['removed_pkgs']),
"ports_delta": int(latest['ports_delta']),
"hour": int(latest['hour'])
}
print(json.dumps(out, indent=2))
sys.exit(0)
# Train on all but last row
train = df.iloc[:-1, 1:] # exclude timestamp
test = df.iloc[-1:, 1:]
model = IsolationForest(
n_estimators=200,
contamination=0.1,
random_state=42
)
model.fit(train)
pred = model.predict(test)[0] # 1 normal, -1 anomaly
score = model.decision_function(test)[0] # higher is more normal
latest = df.iloc[-1]
out = {
"timestamp": latest['timestamp'],
"anomaly": True if pred == -1 else False,
"score": float(score),
"etc_diff": int(latest['etc_diff']),
"new_services": int(latest['new_services']),
"removed_services": int(latest['removed_services']),
"new_pkgs": int(latest['new_pkgs']),
"removed_pkgs": int(latest['removed_pkgs']),
"ports_delta": int(latest['ports_delta']),
"hour": int(latest['hour'])
}
print(json.dumps(out, indent=2))
Install and wire it together:
sudo install -m 0755 ai-drift-features.sh /usr/local/bin/ai-drift-features.sh
sudo install -m 0755 ai-drift-detect.py /usr/local/bin/ai-drift-detect.py
Usage after at least two snapshots:
sudo /usr/local/bin/ai-drift-snapshot.sh
sudo /usr/local/bin/ai-drift-snapshot.sh
/usr/local/bin/ai-drift-features.sh
/usr/local/bin/ai-drift-detect.py /var/lib/ai-drift/snapshots/features.csv
Automate post-snapshot processing by appending this to your cron entry:
0 */6 * * * sudo /usr/local/bin/ai-drift-snapshot.sh && /usr/local/bin/ai-drift-features.sh && /usr/local/bin/ai-drift-detect.py /var/lib/ai-drift/snapshots/features.csv >> /var/log/ai-drift.log 2>&1
4) Real-world example: the “harmless” tweak
Scenario:
A teammate “temporarily” enables a custom systemd service and opens a new port.
A minor hotfix also tweaks multiple /etc files.
Your detector output:
{
"timestamp": "20250117T031500Z",
"anomaly": true,
"score": -0.12,
"etc_diff": 27,
"new_services": 1,
"removed_services": 0,
"new_pkgs": 0,
"removed_pkgs": 0,
"ports_delta": 1,
"hour": 03
}
Interpretation: unusual config churn outside change windows, a newly enabled service, and a new listening socket. That’s worth investigation before it becomes a 2 a.m. page.
Production tips and hardening
Whitelist expected change windows: add a simple guard in ai-drift-detect.py to relax scores during maintenance windows.
Git-remote your snapshots: push to a central, append-only repo for tamper-evidence.
Tune features: consider adding kernel params (sysctl -a), sudoers diffs, container images, or specific high-signal files (e.g., sshd_config).
Reduce noise: exclude cert rotations and known ephemeral files from /etc checksuming.
Permissions: snapshots may contain sensitive file paths. Restrict SNAPDIR to root and carefully manage backups.
Systemd timers: prefer systemd timers over cron where possible for better visibility and logs.
Conclusion and next steps
Configuration drift isn’t going away—but with a Bash-first workflow and a tiny AI model, you can turn chaos into signal. Start by:
1) Deploying the snapshot script on one staging host today
2) Running for a week to build history
3) Reviewing flagged anomalies and tuning exclusions
4) Rolling out to more hosts and centralizing snapshots
If you found this useful, try extending the feature set or wiring alerts into your chat/issue system. Your future self—sleeping through the night—will thank you.