- Posted on
- • Artificial Intelligence
Artificial Intelligence NAS Automation
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Artificial Intelligence NAS Automation: Smarter Bash Workflows for Your Home Lab
If your NAS keeps growing faster than your free evenings, you’re not alone. Sorting, tagging, deduplicating, and monitoring terabytes of files by hand is a grind. The good news: you can bolt AI onto your existing Linux + Bash toolkit to automate the repetitive parts—without giving up control or going full vendor lock-in.
This guide shows you how to add practical AI to your NAS using simple, composable shell scripts and CLI tools. You’ll get real-time file labeling, smarter deduplication, predictive drive health alerts, and a safe “ChatOps” wrapper to turn natural language into auditable shell commands.
What you’ll need:
Comfort editing Bash scripts and systemd units
A Linux NAS or server with Docker/Podman available (local is fine)
Willingness to test on a scratch dataset before touching production
Why AI for NAS Ops?
Scale: Manual file triage and organization doesn’t scale past a few hundred gigs. AI helps classify and describe new files on arrival.
Precision: Traditional deduplication is binary (hash or no hash). AI-powered similarity (like perceptual hashing) can spot near-dupes.
Proactivity: SMART data looks like alphabet soup. Basic anomaly detection can warn you about drive drift before failure.
Safety and speed: Natural-language interfaces are great, but only if they’re constrained. A safe wrapper gives you speed without foot-guns.
Prerequisites and installation
We’ll use common CLI tools and run a local LLM via container for privacy and offline use. Install the following:
Packages:
jq (JSON parsing)
inotify-tools (file system events)
smartmontools (drive health)
fdupes (exact dedup)
ExifTool (metadata)
Poppler tools (pdftotext)
FFmpeg (media metadata)
Python 3 + pip
rclone (optional sync/backup)
Docker or Podman (to run a local LLM, e.g., Ollama)
Python libraries (for perceptual hashing):
pillow
imagehash
Install commands:
APT (Debian/Ubuntu):
sudo apt update
sudo apt install -y jq inotify-tools smartmontools fdupes libimage-exiftool-perl poppler-utils ffmpeg python3 python3-pip rclone docker.io podman
python3 -m pip install --user pillow imagehash
# If using Docker:
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
# Re-login for docker group to take effect
DNF (Fedora/RHEL/CentOS Stream):
sudo dnf install -y jq inotify-tools smartmontools fdupes perl-Image-ExifTool poppler-utils ffmpeg python3 python3-pip rclone moby-engine podman
python3 -m pip install --user pillow imagehash
# If using Docker (moby-engine):
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
# Re-login for docker group to take effect
Note: On older Fedora/RHEL where ffmpeg isn’t in the default repos, enable RPM Fusion (see rpmfusion.org) before installing ffmpeg.
ZYPPER (openSUSE Leap/Tumbleweed):
sudo zypper refresh
sudo zypper install -y jq inotify-tools smartmontools fdupes perl-Image-ExifTool poppler-tools ffmpeg python3 python3-pip rclone docker podman
python3 -m pip install --user pillow imagehash
# If using Docker:
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
# Re-login for docker group to take effect
Note: If ffmpeg is missing, enable the Packman repository (see the openSUSE wiki) and install ffmpeg from there.
Run a local LLM (Ollama) via container:
# With Docker
docker run -d --name ollama -p 11434:11434 --restart unless-stopped ollama/ollama:latest
# Pull a model (e.g., llama3.1)
docker exec -it ollama ollama pull llama3.1
OR with Podman:
podman run -d --name ollama -p 11434:11434 --restart=always docker.io/ollama/ollama:latest
podman exec -it ollama ollama pull llama3.1
1) Real-time AI file intake: auto-tagging and summaries
Goal: As new files land on your NAS, extract useful text/metadata and ask a local LLM to produce tags and a short summary. Save results in a sidecar JSON (safe and reversible).
Script: watch a directory with inotifywait, extract content, call Ollama, and store metadata.
#!/usr/bin/env bash
# ai-intake.sh: real-time tagging for new files
WATCH_DIR="${1:-/srv/data/incoming}"
META_SUFFIX=".meta.json"
OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434/api/generate}"
MODEL="${MODEL:-llama3.1}"
mime_of() { file -b --mime-type -- "$1"; }
extract_payload() {
local f="$1" mime; mime="$(mime_of "$f")"
case "$mime" in
application/pdf)
pdftotext -nopgbrk "$f" - | head -c 8000
;;
text/*)
head -c 8000 -- "$f"
;;
image/*)
exiftool -j "$f" | jq -c '.[0]'
;;
video/*|audio/*)
ffprobe -v quiet -print_format json -show_format -show_streams "$f" | jq -c
;;
*)
echo "{\"name\":\"$(basename "$f")\",\"size\":$(stat -c%s "$f")}"
;;
esac
}
generate_meta() {
local f="$1" content prompt
content="$(extract_payload "$f" | tr -d '\000')"
prompt=$'You are a file librarian. Given the content/metadata below, produce concise JSON:\n{"tags": [up to 8 short lowercase tags], "summary": "one sentence", "type": "kind of file"}\nContent:\n'"$content"
curl -s "$OLLAMA_URL" \
-H "Content-Type: application/json" \
-d @- <<EOF | jq -r '.response' | jq .
{"model":"'"$MODEL"'","prompt":"'"${prompt//\"/\\\"}"'","stream":false}
EOF
}
mkdir -p "$WATCH_DIR"
echo "Watching: $WATCH_DIR"
inotifywait -m -e close_write,create --format '%w%f' "$WATCH_DIR" | while read -r f; do
[ -f "$f" ] || continue
out="${f}${META_SUFFIX}"
echo "Processing $f ..."
meta="$(generate_meta "$f" || echo '{}')"
echo "$meta" | jq -c '{
file: "'"$f"'", timestamp: now | todate, meta: .
}' > "${out}.tmp" && mv "${out}.tmp" "$out"
echo "Saved $out"
done
Start it with:
bash ai-intake.sh /path/to/incomingOutput:
/path/to/incoming/filename.ext.meta.jsonwith tags/summary/typeTip: write tags to extended attributes or embed XMP later if desired.
Security note: The model only sees extracted text/metadata. PDFs are truncated for performance. Tweak limits for your hardware.
2) Smarter deduplication: exact and “near-duplicate” images
First pass: exact duplicates with fdupes.
# List duplicate sets
fdupes -r /srv/data/library > /tmp/dupes.txt
# Optionally interactively delete
# fdupes -rdN /srv/data/library
Near-duplicates (images): Use perceptual hashing to find similar images (resized, minor edits).
Python helper phash_dupes.py:
#!/usr/bin/env python3
import sys, json, pathlib
from PIL import Image
import imagehash
root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")
thresh = int(sys.argv[2]) if len(sys.argv) > 2 else 5
db = {}
for p in root.rglob("*"):
if not p.is_file(): continue
if p.suffix.lower() not in [".jpg",".jpeg",".png",".webp",".bmp",".tiff"]: continue
try:
h = imagehash.phash(Image.open(p))
db[str(p)] = str(h)
except Exception as e:
pass
# group similar by Hamming distance
paths = list(db.keys())
groups = []
visited = set()
for i,p in enumerate(paths):
if p in visited: continue
g = [p]; visited.add(p)
for q in paths[i+1:]:
if q in visited: continue
d = imagehash.hex_to_hash(db[p]) - imagehash.hex_to_hash(db[q])
if d <= thresh:
g.append(q); visited.add(q)
if len(g) > 1:
groups.append(g)
print(json.dumps(groups, indent=2))
Run and quarantine:
python3 phash_dupes.py /srv/data/photos 6 > /tmp/near_dupes.json
jq -r '.[] | .[1:][]' /tmp/near_dupes.json | while read -r f; do
q="/srv/data/quarantine/$(date +%F)"; mkdir -p "$q"
echo "Quarantining near-duplicate: $f"
mv -n -- "$f" "$q"/ 2>/dev/null || true
done
Adjust threshold (smaller = stricter). Manually review the quarantine folder before deletion.
Combine with
rmlintif you prefer its reporting for exact/broken/empty files.
3) Predictive drive health: lightweight anomaly alerts from SMART
Pull SMART attributes daily, compute simple anomalies (z-scores) against your drive’s history, and alert via syslog (or email hook).
Collector smart-collect.sh:
#!/usr/bin/env bash
OUT="/var/log/nas_smart.csv"
DRIVES=(${DRIVES:-/dev/sd[a-z]})
[ -f "$OUT" ] || echo "ts,dev,raw_read_error_rate,realloc,pending,udma_crc,temp" > "$OUT"
ts="$(date -Is)"
for d in "${DRIVES[@]}"; do
smartctl -A "$d" | awk -v ts="$ts" -v dev="$d" '
$2=="Raw_Read_Error_Rate"{r=$10}
$2=="Reallocated_Sector_Ct"{re=$10}
$2=="Current_Pending_Sector"{p=$10}
$2=="UDMA_CRC_Error_Count"{c=$10}
$1=="194"||$2=="Temperature_Celsius"{t=$10}
END{printf "%s,%s,%s,%s,%s,%s,%s\n", ts, dev, r+0, re+0, p+0, c+0, t+0}
' >> "$OUT"
done
Anomaly check smart-anomaly.py (no extra dependencies):
#!/usr/bin/env python3
import csv, sys, statistics, time
path = sys.argv[1] if len(sys.argv)>1 else "/var/log/nas_smart.csv"
recent_by_dev = {}
with open(path) as f:
r = csv.DictReader(f)
for row in r:
recent_by_dev.setdefault(row["dev"], []).append(row)
def z(x, xs):
xs = [float(v) for v in xs]
if len(xs)<10: return 0.0
mu = statistics.mean(xs); sd = statistics.pstdev(xs) or 1.0
return (float(x)-mu)/sd
alerts=[]
for dev, rows in recent_by_dev.items():
rows = rows[-200:] # last 200 samples
keys = ["realloc","pending","udma_crc","temp"]
latest = rows[-1]
for k in keys:
series = [r[k] for r in rows[:-1]]
zz = z(latest[k], series)
if zz > 3.0: # spike
alerts.append(f"{dev} {k} z={zz:.1f} latest={latest[k]}")
if alerts:
for a in alerts:
print(f"SMART anomaly: {a}")
sys.exit(1)
else:
print("SMART OK")
Systemd timers (root):
/etc/systemd/system/nas-smart-collect.service
[Unit]
Description=NAS SMART collector
[Service]
Type=oneshot
Environment="DRIVES=/dev/sda /dev/sdb /dev/sdc"
ExecStart=/usr/local/sbin/smart-collect.sh
/etc/systemd/system/nas-smart-collect.timer
[Unit]
Description=Run SMART collector hourly
[Timer]
OnCalendar=hourly
Persistent=true
[Install]
WantedBy=timers.target
/etc/systemd/system/nas-smart-anomaly.service
[Unit]
Description=NAS SMART anomaly check
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/smart-anomaly.py /var/log/nas_smart.csv
StandardOutput=journal
StandardError=journal
/etc/systemd/system/nas-smart-anomaly.timer
[Unit]
Description=Run SMART anomaly check daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Enable:
sudo install -m 755 smart-collect.sh /usr/local/sbin/
sudo install -m 755 smart-anomaly.py /usr/local/sbin/
sudo systemctl daemon-reload
sudo systemctl enable --now nas-smart-collect.timer nas-smart-anomaly.timer
Hook up notifications:
- Journal tail to email with an MTA or use a small script that sends webhook/Discord/Slack when exit code ≠ 0.
4) Safe ChatOps: natural language to shell, with guardrails
Let an LLM propose commands, but only execute after you confirm—and only from a whitelist of safe verbs/paths.
nasctl.sh:
#!/usr/bin/env bash
set -euo pipefail
PROMPT="${*:-"Summarize disk usage under /srv/data as a single safe command"}"
OLLAMA_URL="${OLLAMA_URL:-http://localhost:11434/api/generate}"
MODEL="${MODEL:-llama3.1}"
read -r -d '' SYS <<'SYS'
You are a command builder for a Linux NAS. Output ONLY JSON like:
{"cmd": "...", "reason": "..."}
Rules:
- Use ONLY these tools: ls, du, df, find, grep, sed, awk, fdupes, rclone, rsync, tar, touch, mkdir, mv, cp, rm
- Limit paths to /srv/data or /srv/backup
- Prefer read-only (no rm) unless user explicitly says delete
- One-liners; no pipes to curl|sh; no network except rclone if asked
SYS
payload=$(jq -nc --arg sys "$SYS" --arg prompt "$PROMPT" --arg model "$MODEL" \
'{model:$model, prompt:($sys+"\nUser: "+$prompt+"\nAssistant:"), stream:false}')
resp=$(curl -s "$OLLAMA_URL" -H "Content-Type: application/json" -d "$payload" \
| jq -r '.response' | tail -n1)
cmd=$(echo "$resp" | jq -r '.cmd // empty')
reason=$(echo "$resp" | jq -r '.reason // "n/a"')
if [ -z "$cmd" ]; then echo "No command returned."; exit 1; fi
echo "Proposed: $cmd"
echo "Reason: $reason"
read -rp "Execute? [y/N] " yn
if [[ "$yn" =~ ^[Yy]$ ]]; then
eval "$cmd"
else
echo "Aborted."
fi
Examples:
./nasctl.sh "find duplicate files under /srv/data and list totals per folder"
./nasctl.sh "archive /srv/data/projects to /srv/backup/projects.tar.gz without overwriting"
You stay in control: nothing runs without your explicit confirmation.
Putting it together
Intake pipeline labels and summarizes new files automatically, making search and curation painless.
Dedup pass reclaims space by removing exact dupes and quarantining near-dupes for review.
SMART watcher nudges you about drift so you can migrate a drive before it dies.
ChatOps lets you move faster with safety rails when you can’t remember the perfect
findincantation.
Optional: add rclone to sync tagged/curated content to cloud or offsite storage; encrypt with rclone crypt.
Example nightly sync:
rclone sync /srv/data remote:offsite --fast-list --transfers=8 --checkers=8 --immutable --log-file=/var/log/rclone-sync.log
Conclusion and next steps
AI doesn’t replace your NAS skills—it augments them. Start small: 1) Spin up the local LLM container. 2) Run the intake watcher on a test folder. 3) Trial the dedup scripts on copies. 4) Enable SMART timers and watch the logs for a week.
When you’re happy, roll it out to your real dataset. Consider adding:
XMP writing for permanent, searchable metadata
A small web UI to browse tags and summaries
A Git repo for your scripts and systemd units (infrastructure as code)
If you found this useful, subscribe and share what you automated—real-world tweaks and pitfalls help everyone level up their homelabs.