From b2946a1c24830071de39d5c9f23166072d143d0d Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 19:22:24 +0000 Subject: [PATCH] =?UTF-8?q?Add=20configure-storage.sh=20=E2=80=94=20intera?= =?UTF-8?q?ctive=20drive/folder=20setup=20for=20ZIM=20and=20models?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scans mounted drives, shows size/free space, prompts user to assign drives for Kiwix ZIM files and Ollama model storage, then patches docker-compose.yml in place with a backup. https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx --- configure-storage.sh | 239 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100755 configure-storage.sh diff --git a/configure-storage.sh b/configure-storage.sh new file mode 100755 index 0000000..667c245 --- /dev/null +++ b/configure-storage.sh @@ -0,0 +1,239 @@ +#!/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="$HOME/docker/ai-stack" +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 50–200GB 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"