Files
local-ai/configure-storage.sh
T
Claude 5bfedf2cee consolidate everything into the repo folder; add compose management comments
All scripts now default BASE to their own directory (SCRIPT_DIR) instead
of ~/docker/ai-stack, so after a git clone the user never needs to change
folders — docker-compose.yml, .env, settings, and data dirs all live next
to the scripts.

configure-searxng-safesearch.sh and configure-storage.sh do the same for
standalone use (still honoured when called with BASE= from a parent script).

docker-compose.yml (generated by both setup scripts) gains a comment block
at the top listing the everyday docker compose commands:
  up -d / down / restart / stop / logs -f / pull / ps
so the file itself is the reference for managing containers.

.gitignore added to exclude generated files (docker-compose.yml, .env,
requirements.txt, helper scripts) and data directories (workspace/, kiwix/,
searxng/, gitea/, etc.) from git tracking.

https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx
2026-03-21 15:38:20 +00:00

240 lines
9.2 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# configure-storage.sh — interactively assign drives for ZIM and model storage
# then patches docker-compose.yml in place
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; BOLD='\033[1m'; NC='\033[0m'
info() { echo -e "${CYAN}[..]${NC} $*"; }
ok() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[!!]${NC} $*"; }
die() { echo -e "${RED}[ERR]${NC} $*"; exit 1; }
BASE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
COMPOSE="$BASE/docker-compose.yml"
[[ -f "$COMPOSE" ]] || die "docker-compose.yml not found at $BASE — run local-ai-setup.sh first"
# ── collect drives ─────────────────────────────────────────────────────────────
echo -e "\n${BOLD}━━━ Detected Drives ━━━${NC}\n"
# build array of mountpoints excluding tiny/system ones
mapfile -t MOUNTS < <(
df -h --output=target,size,avail,pcent 2>/dev/null \
| tail -n +2 \
| grep -v -E '^\s*(/$|/boot|/sys|/proc|/dev|/run|/snap|/var/lib/docker|/etc|tmpfs)' \
| awk '$3 ~ /[0-9]/ {print}' \
| sort -u
)
if [[ ${#MOUNTS[@]} -eq 0 ]]; then
warn "No additional mounted drives found beyond system disk."
warn "Mount your drives first (e.g. /mnt/storage), then re-run this script."
echo ""
echo "Quick mount example:"
echo " sudo mkdir -p /mnt/storage"
echo " sudo mount /dev/sdb1 /mnt/storage"
echo " # To make permanent, add to /etc/fstab"
exit 0
fi
# display numbered list
echo -e " ${BOLD}# Mount Point Size Free Used${NC}"
echo " ─────────────────────────────────────────────────────"
IDX=0
declare -a MOUNT_PATHS
for line in "${MOUNTS[@]}"; do
mp=$(echo "$line" | awk '{print $1}')
sz=$(echo "$line" | awk '{print $2}')
av=$(echo "$line" | awk '{print $3}')
pc=$(echo "$line" | awk '{print $4}')
printf " ${CYAN}%-3d${NC} %-30s %-7s %-7s %s\n" "$IDX" "$mp" "$sz" "$av" "$pc"
MOUNT_PATHS[$IDX]="$mp"
(( IDX++ ))
done
echo ""
pick_drive() {
local purpose="$1"
local varname="$2"
local skip_ok="${3:-true}"
echo -e "${BOLD}$purpose storage${NC}"
if $skip_ok; then
read -rp " Enter drive number (or Enter to skip): " choice
else
read -rp " Enter drive number: " choice
fi
if [[ -z "$choice" ]]; then
eval "$varname=''"
info "Skipping $purpose storage."
elif [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -lt "$IDX" ]]; then
eval "$varname='${MOUNT_PATHS[$choice]}'"
ok "Selected: ${MOUNT_PATHS[$choice]}"
else
warn "Invalid choice — skipping $purpose."
eval "$varname=''"
fi
echo ""
}
# ── ZIM storage ────────────────────────────────────────────────────────────────
echo -e "${BOLD}━━━ Kiwix ZIM Storage ━━━${NC}\n"
echo " ZIM files can be large (Wikipedia alone is ~100GB)."
echo " Pick a drive with plenty of space."
echo ""
pick_drive "ZIM" ZIM_DRIVE
ZIM_PATH=""
if [[ -n "$ZIM_DRIVE" ]]; then
read -rp " Folder name on that drive [zims]: " ZIM_FOLDER
ZIM_FOLDER="${ZIM_FOLDER:-zims}"
ZIM_PATH="$ZIM_DRIVE/$ZIM_FOLDER"
mkdir -p "$ZIM_PATH"
ok "ZIM path: $ZIM_PATH"
echo ""
fi
# ── Model storage ──────────────────────────────────────────────────────────────
echo -e "${BOLD}━━━ Ollama Model Storage ━━━${NC}\n"
echo " Models range from 4GB (7B) to 30GB+ (70B)."
echo " Pick a fast drive (SSD preferred) with 50200GB free."
echo ""
pick_drive "Model" MODEL_DRIVE
MODEL_PATH=""
if [[ -n "$MODEL_DRIVE" ]]; then
read -rp " Folder name on that drive [ollama-models]: " MODEL_FOLDER
MODEL_FOLDER="${MODEL_FOLDER:-ollama-models}"
MODEL_PATH="$MODEL_DRIVE/$MODEL_FOLDER"
mkdir -p "$MODEL_PATH"
ok "Model path: $MODEL_PATH"
echo ""
fi
# ── bail if nothing selected ───────────────────────────────────────────────────
if [[ -z "$ZIM_PATH" && -z "$MODEL_PATH" ]]; then
info "Nothing selected — no changes made."
exit 0
fi
# ── summary + confirm ──────────────────────────────────────────────────────────
echo -e "${BOLD}━━━ Planned Changes ━━━${NC}\n"
[[ -n "$ZIM_PATH" ]] && echo " Kiwix ZIMs → $ZIM_PATH"
[[ -n "$MODEL_PATH" ]] && echo " Ollama models → $MODEL_PATH"
echo ""
read -rp "Apply these changes to docker-compose.yml? [Y/n]: " CONFIRM
[[ "${CONFIRM,,}" == "n" ]] && { info "Aborted — no changes made."; exit 0; }
# ── backup ────────────────────────────────────────────────────────────────────
cp "$COMPOSE" "$COMPOSE.bak"
info "Backed up to docker-compose.yml.bak"
# ── patch kiwix volumes ────────────────────────────────────────────────────────
if [[ -n "$ZIM_PATH" ]]; then
# Replace the kiwix volumes line to add extra ZIM mounts
# Current: volumes: [$BASE/kiwix:/data]
# New: volumes:
# - $BASE/kiwix:/data
# - /mnt/drive/zims:/data/external
python3 - "$COMPOSE" "$BASE" "$ZIM_PATH" << 'PY'
import sys, re
compose_file = sys.argv[1]
base = sys.argv[2]
zim_path = sys.argv[3]
text = open(compose_file).read()
# Find the kiwix service volumes line (single-line array style)
old = f" volumes: [{base}/kiwix:/data]"
new = (
f" volumes:\n"
f" - {base}/kiwix:/data\n"
f" - {zim_path}:/data/external"
)
if old in text:
text = text.replace(old, new)
open(compose_file, "w").write(text)
print(f"[OK] Kiwix volumes updated")
else:
# Already multi-line — append the new mount if not already there
marker = "container_name: kiwix"
if marker in text and zim_path not in text:
# find the volumes block under kiwix and append
lines = text.splitlines()
in_kiwix = False
in_vols = False
insert_after = -1
for i, line in enumerate(lines):
if marker in line:
in_kiwix = True
if in_kiwix and "volumes:" in line:
in_vols = True
if in_vols and line.strip().startswith("- ") and ":/data" in line:
insert_after = i
if in_vols and insert_after > 0 and not line.strip().startswith("- "):
break
if insert_after > 0:
lines.insert(insert_after + 1, f" - {zim_path}:/data/external")
open(compose_file, "w").write("\n".join(lines) + "\n")
print(f"[OK] Kiwix extra ZIM volume appended")
else:
print(f"[!!] Could not patch kiwix volumes — edit manually: add - {zim_path}:/data/external")
else:
print(f"[!!] Kiwix volumes line not found in expected format — edit manually")
PY
fi
# ── patch ollama volumes ───────────────────────────────────────────────────────
if [[ -n "$MODEL_PATH" ]]; then
python3 - "$COMPOSE" "$MODEL_PATH" << 'PY'
import sys
compose_file = sys.argv[1]
model_path = sys.argv[2]
text = open(compose_file).read()
# Replace Docker-managed volume with host path
old = " volumes: [ollama-models:/root/.ollama]"
new = f" volumes: [{model_path}:/root/.ollama]"
if old in text:
text = text.replace(old, new)
# Remove ollama-models from the top-level volumes section if present
text = text.replace(" ollama-models:\n", "")
open(compose_file, "w").write(text)
print(f"[OK] Ollama volume → {model_path}")
else:
print(f"[!!] ollama volumes line not in expected format — edit manually:")
print(f" change ollama-models:/root/.ollama to {model_path}:/root/.ollama")
PY
fi
# ── restart if running ─────────────────────────────────────────────────────────
echo ""
if docker compose -f "$COMPOSE" ps --quiet 2>/dev/null | grep -q .; then
read -rp "Stack is running — restart now to apply changes? [Y/n]: " RESTART
if [[ "${RESTART,,}" != "n" ]]; then
cd "$BASE"
docker compose down
docker compose up -d
ok "Stack restarted"
else
warn "Remember to restart: cd $BASE && docker compose down && docker compose up -d"
fi
else
info "Stack not running — changes will apply on next start."
fi
echo ""
echo -e "${GREEN}${BOLD}Done!${NC}"
[[ -n "$ZIM_PATH" ]] && echo " Drop .zim files into: $ZIM_PATH"
[[ -n "$MODEL_PATH" ]] && echo " Ollama models stored in: $MODEL_PATH"
echo ""
echo " Undo: cp $COMPOSE.bak $COMPOSE"