Add configure-storage.sh — interactive drive/folder setup for ZIM and models
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
This commit is contained in:
Executable
+239
@@ -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"
|
||||
Reference in New Issue
Block a user