From 92f5d886c759aa54eab3e6060020edc01ab452bc Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 22:01:27 +0000 Subject: [PATCH 1/6] Add leave-overnight wizard to laptop_full_setup.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gather all setup choices upfront so users can answer questions once and walk away. Changes: - Setup wizard asks all questions before any installation begins: 1. Component selection (AI stack / Kiwix / both) 2. Ollama model storage (Docker volume or custom bind-mount path) 3. Kiwix ZIM storage location (detect drives with enough free space) 4. Firewall LAN subnet (auto-detected from current IP) 5. Model downloads (confirm GPU-matched models, optional DeepSeek-R1) 6. ZIM downloads (all / select / skip) - Shows a summary and asks for confirmation before doing anything - Ollama supports bind-mount to custom path (e.g. /mnt/ssd/ollama) - Kiwix ZIM dir is configurable (separate large drive) - Model pull runs unattended at end using upfront answer - ZIM downloads integrated inline (no need to run kiwix_download.sh separately) — background wget with progress log https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx --- laptop_full_setup.sh | 368 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 337 insertions(+), 31 deletions(-) diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index 703c8cb..7f47ce8 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -5,11 +5,11 @@ # Kiwix · Gitea · InvokeAI · Portainer # # Usage: -# ./laptop_full_setup.sh — new install or update +# ./laptop_full_setup.sh — interactive setup (answer questions, leave overnight) # ./laptop_full_setup.sh --force — overwrite existing config files too # -# ZIM downloads: run ./kiwix_download.sh separately (large files) -# GPU: Ollama auto-detects VRAM — works with any NVIDIA GPU +# Asks all questions upfront, then runs everything unattended. +# GPU: Ollama auto-detects VRAM — works with any NVIDIA GPU # ============================================================================= set -euo pipefail @@ -33,26 +33,56 @@ LOCAL_IP=$(ip route get 1.1.1.1 2>/dev/null | grep -oP 'src \K\S+' \ || hostname -I | awk '{print $1}') [[ -z "$LOCAL_IP" ]] && read -rp "Enter your LAN IP: " LOCAL_IP -# Models pulled on first install (adjust to your VRAM) -EMBED_MODEL="nomic-embed-text" # always needed for RAG +# Models (defaults, may be adjusted below based on VRAM) +EMBED_MODEL="nomic-embed-text" CHAT_MODEL="qwen2.5:14b" CODE_MODEL="qwen2.5-coder:7b" FAST_MODEL="qwen2.5:7b" +# ── detect GPU ──────────────────────────────────────────────────────────────── +VRAM_GB=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader,nounits 2>/dev/null \ + | head -1 | awk '{printf "%d", $1/1024}' 2>/dev/null || echo "0") +GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | head -1 || echo "None") + +if [[ "$VRAM_GB" -ge 14 ]]; then + CHAT_MODEL="qwen2.5:14b"; CODE_MODEL="qwen2.5-coder:14b" + GPU_TIER="16GB VRAM — 14B models" +elif [[ "$VRAM_GB" -ge 8 ]]; then + CHAT_MODEL="qwen2.5:14b"; CODE_MODEL="qwen2.5-coder:7b" + GPU_TIER="8GB VRAM — 14B chat, 7B code" +elif [[ "$VRAM_GB" -ge 4 ]]; then + CHAT_MODEL="qwen2.5:7b"; CODE_MODEL="qwen2.5-coder:7b" + GPU_TIER="6GB VRAM — 7B models" +elif [[ "$VRAM_GB" -gt 0 ]]; then + CHAT_MODEL="qwen2.5:7b"; CODE_MODEL="qwen2.5-coder:7b" + GPU_TIER="${VRAM_GB}GB VRAM — 7B models" +else + CHAT_MODEL="qwen2.5:7b"; CODE_MODEL="qwen2.5-coder:7b" + GPU_TIER="CPU only — 7B models (slow)" +fi + # ── new vs update ───────────────────────────────────────────────────────────── IS_UPDATE=false [[ -f "$BASE/docker-compose.yml" ]] && IS_UPDATE=true -section "Local AI Stack — $($IS_UPDATE && echo UPDATE || echo NEW INSTALL)" -info "Base : $BASE" -info "Host : $LOCAL_IP" +# ============================================================================= +# QUESTIONS UPFRONT — answer everything, then walk away +# ============================================================================= +section "Local AI Stack — Setup Wizard" +echo "" +echo -e " ${BOLD}Answer the questions below, then leave it overnight.${NC}" +echo -e " Everything will install and download while you sleep." +echo "" +info "Machine : $(hostname)" +info "LAN IP : $LOCAL_IP" +info "GPU : ${GPU_NAME} (${VRAM_GB}GB VRAM)" $IS_UPDATE && warn "Existing install found. Config files kept unless --force is passed." -# ── component menu ───────────────────────────────────────────────────────────── +# ── Q1: Components ──────────────────────────────────────────────────────────── echo "" -echo -e " ${BOLD}Select components to install:${NC}" +echo -e " ${BOLD}[1/5] What do you want to install?${NC}" echo "" -echo " 1) Full stack — AI stack + Kiwix" +echo " 1) Full stack — AI stack + Kiwix offline library" echo " 2) AI stack — Ollama · WebUI · RAG · MCP · ChromaDB · SearXNG · Gitea · InvokeAI · Portainer" echo " 3) Kiwix only — Offline Wikipedia / docs (runs standalone)" echo "" @@ -64,6 +94,132 @@ case "$MENU_CHOICE" in 3) INSTALL_AI=false; INSTALL_KIWIX=true ;; *) die "Invalid choice '$MENU_CHOICE' — run again and choose 1, 2, or 3" ;; esac + +# ── Q2: Storage for Ollama models ───────────────────────────────────────────── +OLLAMA_STORAGE="volume" # "volume" = Docker named volume, else a host path +OLLAMA_HOST_PATH="" + +if $INSTALL_AI; then + echo "" + echo -e " ${BOLD}[2/5] Where should Ollama models be stored?${NC}" + echo " (Models are large — 5-50GB each. A fast SSD or large HDD is ideal.)" + echo "" + echo " Available mount points with >20GB free:" + df -h --output=target,avail,fstype 2>/dev/null \ + | awk 'NR>1 && $2~/[0-9]/ { + val=$2; unit=substr(val,length(val)); + num=substr(val,1,length(val)-1)+0; + if ((unit=="G" && num>=20) || unit=="T") print " " $0 + }' | head -10 + echo "" + echo " 1) Docker volume (default — stored in /var/lib/docker/volumes/)" + echo " 2) Custom path (e.g. /mnt/ssd/ollama or /data/ollama)" + echo "" + read -rp " Choice [1]: " STORAGE_CHOICE + STORAGE_CHOICE="${STORAGE_CHOICE:-1}" + if [[ "$STORAGE_CHOICE" == "2" ]]; then + read -rp " Enter full path for Ollama models: " OLLAMA_HOST_PATH + OLLAMA_HOST_PATH="${OLLAMA_HOST_PATH%/}" # strip trailing slash + [[ -z "$OLLAMA_HOST_PATH" ]] && die "No path entered." + OLLAMA_STORAGE="bind" + fi +fi + +# ── Q3: Storage for Kiwix ZIMs ──────────────────────────────────────────────── +KIWIX_DIR="$BASE/kiwix" # default + +if $INSTALL_KIWIX; then + echo "" + echo -e " ${BOLD}[3/5] Where should Kiwix ZIM files be stored?${NC}" + echo " (ZIMs are large — Wikipedia alone is ~46GB. Total collection ~130GB.)" + echo "" + echo " Available mount points with >50GB free:" + df -h --output=target,avail,fstype 2>/dev/null \ + | awk 'NR>1 && $2~/[0-9]/ { + val=$2; unit=substr(val,length(val)); + num=substr(val,1,length(val)-1)+0; + if ((unit=="G" && num>=50) || unit=="T") print " " $0 + }' | head -10 + echo "" + echo " Default: $KIWIX_DIR" + read -rp " Press Enter to use default, or type a different path: " KIWIX_INPUT + if [[ -n "$KIWIX_INPUT" ]]; then + KIWIX_DIR="${KIWIX_INPUT%/}" + fi + + # ── Q4: Download ZIMs now? ───────────────────────────────────────────────── + echo "" + echo -e " ${BOLD}[4/5] Download ZIM files? (offline Wikipedia, Stack Overflow, etc.)${NC}" + echo " Downloads run in the background — safe to start now and leave overnight." + echo " Total: ~130GB (Wikipedia 46GB, Project Gutenberg 60GB, others smaller)" + echo "" + echo " 1) Yes — download all ZIMs overnight (~130GB)" + echo " 2) Select — choose which ZIMs to download" + echo " 3) No — skip for now (run ./kiwix_download.sh later)" + echo "" + read -rp " Choice [3]: " ZIM_CHOICE + ZIM_CHOICE="${ZIM_CHOICE:-3}" +else + ZIM_CHOICE="3" +fi + +# ── Q4b: Firewall (LAN subnet) ──────────────────────────────────────────────── +LAN_SUBNET="192.168.1.0/24" +if command -v ufw &>/dev/null && { [[ ! -f "$BASE/.ufw-done" ]] || $FORCE; }; then + echo "" + # Auto-detect likely subnet from current IP + AUTO_SUBNET=$(echo "$LOCAL_IP" | awk -F. '{print $1"."$2"."$3".0/24"}') + echo -e " ${BOLD}[4b] Firewall — allow LAN access to services${NC}" + read -rp " LAN subnet [${AUTO_SUBNET}]: " LAN_INPUT + LAN_SUBNET="${LAN_INPUT:-$AUTO_SUBNET}" + [[ "$LAN_SUBNET" =~ /[0-9]+$ ]] || LAN_SUBNET="${LAN_SUBNET}/24" +fi + +# ── Q5: Pull Ollama models now? ─────────────────────────────────────────────── +PULL_MODELS=false +PULL_DEEPSEEK=false + +if $INSTALL_AI; then + echo "" + echo -e " ${BOLD}[5/5] Download AI models now?${NC}" + echo " GPU detected: ${GPU_NAME} (${VRAM_GB}GB VRAM) → recommended tier: ${GPU_TIER}" + echo "" + echo " Models that will be downloaded:" + echo " nomic-embed-text (~300MB) — RAG embeddings (required)" + echo " ${FAST_MODEL} (~5GB) — fast chat" + echo " ${CHAT_MODEL} (~9GB) — smart chat" + echo " ${CODE_MODEL} (~5GB) — code assistant" + echo "" + read -rp " Download models now? (~20GB total) [Y/n]: " DO_PULL + if [[ "${DO_PULL,,}" != "n" ]]; then + PULL_MODELS=true + read -rp " Also download DeepSeek-R1:14b for reasoning? (~9GB extra) [y/N]: " DO_DS + [[ "${DO_DS,,}" == "y" ]] && PULL_DEEPSEEK=true + fi +fi + +# ── Summary ─────────────────────────────────────────────────────────────────── +echo "" +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo -e "${BOLD} Setup plan — starting now:${NC}" +echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +echo "" +$INSTALL_AI && echo " ✓ AI stack (Ollama · WebUI · RAG · MCP · Gitea · InvokeAI · Portainer)" +$INSTALL_KIWIX && echo " ✓ Kiwix offline library" +if $INSTALL_AI; then + if [[ "$OLLAMA_STORAGE" == "bind" ]]; then + echo " ✓ Ollama models → $OLLAMA_HOST_PATH" + else + echo " ✓ Ollama models → Docker volume (default)" + fi +fi +$INSTALL_KIWIX && echo " ✓ Kiwix ZIMs → $KIWIX_DIR" +$PULL_MODELS && echo " ✓ Pull models : embed + ${FAST_MODEL} + ${CHAT_MODEL} + ${CODE_MODEL}${PULL_DEEPSEEK:+ + deepseek-r1:14b}" +[[ "$ZIM_CHOICE" == "1" ]] && echo " ✓ Download all ZIMs in background (~130GB)" +[[ "$ZIM_CHOICE" == "2" ]] && echo " ✓ Select ZIMs to download (prompted after stack starts)" +echo "" +read -rp " Proceed? [Y/n]: " CONFIRM +[[ "${CONFIRM,,}" == "n" ]] && echo "Aborted." && exit 0 echo "" # ── helper: write only if missing (or --force) ──────────────────────────────── @@ -129,10 +285,14 @@ fi section "Directories" # ============================================================================= for d in papers repos workspace index searxng invokeai-data invokeai-outputs \ - kiwix gitea portainer-data logs; do + gitea portainer-data logs; do mkdir -p "$BASE/$d" done -ok "Directories ready under $BASE" +# Kiwix ZIM dir — may be on a different drive +mkdir -p "$KIWIX_DIR" +# Ollama bind-mount dir (if using custom path) +[[ "$OLLAMA_STORAGE" == "bind" ]] && mkdir -p "$OLLAMA_HOST_PATH" +ok "Directories ready" # ============================================================================= if $INSTALL_AI; then @@ -209,6 +369,15 @@ fi # ============================================================================= section "Docker Compose" # ============================================================================= +# Build ollama volume line based on storage choice +if [[ "$OLLAMA_STORAGE" == "bind" ]]; then + OLLAMA_VOLUME_LINE=" - ${OLLAMA_HOST_PATH}:/root/.ollama" + OLLAMA_VOLUMES_DECL="" +else + OLLAMA_VOLUME_LINE=" - ollama-models:/root/.ollama" + OLLAMA_VOLUMES_DECL=" ollama-models:" +fi + # Always written — it's the stack definition and safe to update cat > "$BASE/docker-compose.yml" << COMPOSE # Local AI Stack — generated $(date '+%Y-%m-%d') @@ -225,7 +394,7 @@ services: ports: - "0.0.0.0:11434:11434" volumes: - - ollama-models:/root/.ollama +${OLLAMA_VOLUME_LINE} environment: - OLLAMA_NUM_GPU=999 # use all available VRAM (auto-detects GPU size) - OLLAMA_NUM_CTX=8192 # lower to 4096 if you hit OOM @@ -353,7 +522,6 @@ services: cap_add: [CHOWN, SETGID, SETUID] # ── Kiwix — Offline Wikipedia/docs ───────────────────────────────────────── - # Download ZIMs first: ./kiwix_download.sh kiwix: image: ghcr.io/kiwix/kiwix-serve:latest container_name: kiwix @@ -361,7 +529,7 @@ services: ports: - "0.0.0.0:8181:8080" volumes: - - $BASE/kiwix:/data + - $KIWIX_DIR:/data command: "*.zim" # ── Gitea — Self-hosted Git ───────────────────────────────────────────────── @@ -419,7 +587,7 @@ services: - $BASE/portainer-data:/data volumes: - ollama-models: +${OLLAMA_VOLUMES_DECL} open-webui-data: invokeai-models: COMPOSE @@ -430,10 +598,6 @@ section "Firewall (UFW)" # ============================================================================= if command -v ufw &>/dev/null; then if [[ ! -f "$BASE/.ufw-done" ]] || $FORCE; then - read -rp " LAN subnet for firewall [192.168.1.0/24]: " LAN_SUBNET - LAN_SUBNET="${LAN_SUBNET:-192.168.1.0/24}" - [[ "$LAN_SUBNET" =~ /[0-9]+$ ]] || LAN_SUBNET="${LAN_SUBNET}/24" - for port_comment in \ "3000:Open WebUI" "11434:Ollama" "8001:RAG Server" \ "8002:MCP Server" "8000:ChromaDB" "8888:SearXNG" \ @@ -608,14 +772,153 @@ docker compose pull --quiet $COMPOSE_SERVICES docker compose up -d $COMPOSE_SERVICES ok "Stack started" -if $INSTALL_AI && ! $IS_UPDATE; then - echo "" - read -rp "Pull Ollama models now? (~20GB total, takes 10-40 min) [Y/n]: " DO_PULL - if [[ "${DO_PULL,,}" != "n" ]]; then - bash "$BASE/pull-models.sh" - else - info "Run later: bash $BASE/pull-models.sh" +# ── Pull Ollama models (answer was captured upfront) ───────────────────────── +if $INSTALL_AI && $PULL_MODELS; then + section "Pulling Ollama Models" + info "Waiting for Ollama to be ready..." + until docker exec ollama ollama list &>/dev/null; do sleep 3; done + ok "Ollama ready." + + info "Pulling embed model (required for RAG)..." + docker exec ollama ollama pull "$EMBED_MODEL" + + info "Pulling fast chat model..." + docker exec ollama ollama pull "$FAST_MODEL" + + info "Pulling smart chat model..." + docker exec ollama ollama pull "$CHAT_MODEL" + + info "Pulling code model..." + docker exec ollama ollama pull "$CODE_MODEL" + + if $PULL_DEEPSEEK; then + info "Pulling DeepSeek-R1:14b (reasoning)..." + docker exec ollama ollama pull deepseek-r1:14b fi + + ok "All models pulled." + echo "" + docker exec ollama ollama list +elif $INSTALL_AI; then + info "Skipping model pull — run later: bash $BASE/pull-models.sh" +fi + +# ── Start ZIM downloads (answer was captured upfront) ──────────────────────── +if $INSTALL_KIWIX && [[ "$ZIM_CHOICE" != "3" ]]; then + section "Starting ZIM Downloads" + LOG="$BASE/logs/kiwix-download.log" + mkdir -p "$(dirname "$LOG")" + + info "Checking mirrors for latest ZIM filenames..." + + MIRROR="https://ftp.fau.de/kiwix/zim" + MIRROR2="https://download.kiwix.org/zim" + + latest_zim() { + local base_url="$1" pattern="$2" + curl -s "$base_url/" | grep -oP "${pattern}_[0-9-]+\.zim" | sort -u | tail -1 + } + + dl_zim() { + local cat="$1" file="$2" desc="$3" mirror="${4:-$MIRROR}" + local dest="$KIWIX_DIR/$file" + if [[ -z "$file" ]]; then warn "Could not find $desc — skipping"; return; fi + if [[ -f "$dest" ]]; then ok "$desc already downloaded"; return; fi + info "Starting: $desc" + nohup wget -c "$mirror/$cat/$file" -O "$dest" >> "$LOG" 2>&1 & + echo $! >> "$KIWIX_DIR/.download_pids" + ok "Download started (PID $!) — $desc" + } + + WIKI=$(latest_zim "$MIRROR/wikipedia" "wikipedia_en_all_nopic") + SO=$(latest_zim "$MIRROR/stack_exchange" "stackoverflow.com_en_all") + ARCH=$(latest_zim "$MIRROR/other" "archlinux_en_all_maxi") + WIKT=$(latest_zim "$MIRROR/wiktionary" "wiktionary_en_all_nopic") + WIKB=$(latest_zim "$MIRROR/wikibooks" "wikibooks_en_all_nopic") + WIKS=$(latest_zim "$MIRROR/wikisource" "wikisource_en_all_nopic") + WIKV=$(latest_zim "$MIRROR/wikivoyage" "wikivoyage_en_all_nopic") + WIKUNI=$(latest_zim "$MIRROR/wikiversity" "wikiversity_en_all_nopic") + WIKNEWS=$(latest_zim "$MIRROR/wikinews" "wikinews_en_all_nopic") + WIKQ=$(latest_zim "$MIRROR/wikiquote" "wikiquote_en_all_nopic") + VIKIDIA=$(latest_zim "$MIRROR/vikidia" "vikidia_en_all_nopic") + TED=$(latest_zim "$MIRROR/ted" "ted_mul_youth") + PHET=$(latest_zim "$MIRROR/phet" "phet_en_all") + DEVDOCS=$(latest_zim "$MIRROR/devdocs" "devdocs_en_zig") + FCC=$(latest_zim "$MIRROR/freecodecamp" "freecodecamp_en_all") + IFIX=$(latest_zim "$MIRROR/ifixit" "ifixit_en_all") + LIBRE=$(latest_zim "$MIRROR/libretexts" "libretexts.org_en_workforce") + GUT=$(latest_zim "$MIRROR2/gutenberg" "gutenberg_en_all") + + if [[ "$ZIM_CHOICE" == "2" ]]; then + # Let user select which ZIMs + echo "" + echo " Select ZIMs to download (enter numbers separated by spaces):" + echo " 1) Wikipedia ~46GB ${WIKI:-NOT FOUND}" + echo " 2) Stack Overflow ~3GB ${SO:-NOT FOUND}" + echo " 3) Arch Linux Wiki ~30MB ${ARCH:-NOT FOUND}" + echo " 4) Wiktionary ~2GB ${WIKT:-NOT FOUND}" + echo " 5) Wikibooks ~500MB ${WIKB:-NOT FOUND}" + echo " 6) Wikisource ~4GB ${WIKS:-NOT FOUND}" + echo " 7) Wikivoyage ~200MB ${WIKV:-NOT FOUND}" + echo " 8) Wikiversity ~500MB ${WIKUNI:-NOT FOUND}" + echo " 9) WikiNews ~300MB ${WIKNEWS:-NOT FOUND}" + echo " 10) Wikiquote ~300MB ${WIKQ:-NOT FOUND}" + echo " 11) Vikidia (kids K-8) ~66MB ${VIKIDIA:-NOT FOUND}" + echo " 12) TED Talks ~5GB ${TED:-NOT FOUND}" + echo " 13) PhET Simulations ~500MB ${PHET:-NOT FOUND}" + echo " 14) DevDocs ~1GB ${DEVDOCS:-NOT FOUND}" + echo " 15) FreeCodeCamp ~small ${FCC:-NOT FOUND}" + echo " 16) iFixit ~2GB ${IFIX:-NOT FOUND}" + echo " 17) LibreTexts ~varies ${LIBRE:-NOT FOUND}" + echo " 18) Project Gutenberg ~60GB ${GUT:-NOT FOUND}" + echo "" + read -rp " Your choices (e.g. 1 2 3): " ZIM_PICKS + rm -f "$KIWIX_DIR/.download_pids" + for n in $ZIM_PICKS; do + case "$n" in + 1) dl_zim "wikipedia" "$WIKI" "Wikipedia" ;; + 2) dl_zim "stack_exchange" "$SO" "Stack Overflow" ;; + 3) dl_zim "other" "$ARCH" "Arch Linux Wiki" ;; + 4) dl_zim "wiktionary" "$WIKT" "Wiktionary" ;; + 5) dl_zim "wikibooks" "$WIKB" "Wikibooks" ;; + 6) dl_zim "wikisource" "$WIKS" "Wikisource" ;; + 7) dl_zim "wikivoyage" "$WIKV" "Wikivoyage" ;; + 8) dl_zim "wikiversity" "$WIKUNI" "Wikiversity" ;; + 9) dl_zim "wikinews" "$WIKNEWS" "WikiNews" ;; + 10) dl_zim "wikiquote" "$WIKQ" "Wikiquote" ;; + 11) dl_zim "vikidia" "$VIKIDIA" "Vikidia (kids K-8)" ;; + 12) dl_zim "ted" "$TED" "TED Talks" ;; + 13) dl_zim "phet" "$PHET" "PhET Simulations" ;; + 14) dl_zim "devdocs" "$DEVDOCS" "DevDocs" ;; + 15) dl_zim "freecodecamp" "$FCC" "FreeCodeCamp" ;; + 16) dl_zim "ifixit" "$IFIX" "iFixit" ;; + 17) dl_zim "libretexts" "$LIBRE" "LibreTexts" ;; + 18) dl_zim "gutenberg" "$GUT" "Project Gutenberg" "$MIRROR2" ;; + esac + done + else + # Download all + rm -f "$KIWIX_DIR/.download_pids" + dl_zim "wikipedia" "$WIKI" "Wikipedia" + dl_zim "stack_exchange" "$SO" "Stack Overflow" + dl_zim "other" "$ARCH" "Arch Linux Wiki" + dl_zim "wiktionary" "$WIKT" "Wiktionary" + dl_zim "wikibooks" "$WIKB" "Wikibooks" + dl_zim "wikisource" "$WIKS" "Wikisource" + dl_zim "wikivoyage" "$WIKV" "Wikivoyage" + dl_zim "wikiversity" "$WIKUNI" "Wikiversity" + dl_zim "wikinews" "$WIKNEWS" "WikiNews" + dl_zim "wikiquote" "$WIKQ" "Wikiquote" + dl_zim "vikidia" "$VIKIDIA" "Vikidia (kids K-8)" + dl_zim "ted" "$TED" "TED Talks" + dl_zim "phet" "$PHET" "PhET Simulations" + dl_zim "devdocs" "$DEVDOCS" "DevDocs" + dl_zim "freecodecamp" "$FCC" "FreeCodeCamp" + dl_zim "ifixit" "$IFIX" "iFixit" + dl_zim "libretexts" "$LIBRE" "LibreTexts" + dl_zim "gutenberg" "$GUT" "Project Gutenberg" "$MIRROR2" + fi + ok "ZIM downloads running in background — monitor: tail -f $LOG" fi # ============================================================================= @@ -632,7 +935,7 @@ if $INSTALL_AI; then echo -e " ${CYAN}Portainer${NC} → https://$LOCAL_IP:9443" fi if $INSTALL_KIWIX; then - echo -e " ${CYAN}Kiwix${NC} → http://$LOCAL_IP:8181 (needs ZIMs — run kiwix_download.sh)" + echo -e " ${CYAN}Kiwix${NC} → http://$LOCAL_IP:8181" fi echo "" if $INSTALL_AI; then @@ -643,8 +946,11 @@ if $INSTALL_AI; then echo -e " ${YELLOW}Drop PDFs into:${NC} $BASE/papers/" echo -e " ${YELLOW}Your workspace:${NC} $BASE/workspace/" fi -if $INSTALL_KIWIX; then - echo -e " ${YELLOW}ZIM downloads:${NC} ./kiwix_download.sh" +if $INSTALL_KIWIX && [[ "$ZIM_CHOICE" == "3" ]]; then + echo -e " ${YELLOW}ZIM downloads:${NC} ./kiwix_download.sh (not started)" +elif $INSTALL_KIWIX; then + echo -e " ${YELLOW}ZIM progress:${NC} tail -f $BASE/logs/kiwix-download.log" + echo -e " ${YELLOW}ZIM location:${NC} $KIWIX_DIR/" fi echo "" $IS_UPDATE && echo -e " ${GREEN}Update complete.${NC}" \ From 45dfa58d931485562550ae7b0e64df3e0450ede8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 22:03:49 +0000 Subject: [PATCH 2/6] Add top-level checklist menu to laptop_full_setup.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the single-choice component menu with a multi-select checklist (whiptail if available, text-based toggle fallback). Users now pick any combination of: [*] Full system setup — runs ubuntu-post-install.sh (Ubuntu apps, security hardening, backups, Docker services) [*] AI stack — Ollama, Open WebUI, RAG, MCP, Gitea, InvokeAI [*] Kiwix — Offline Wikipedia, Stack Overflow, Arch Wiki This makes laptop_full_setup.sh the single entry point — run it once, toggle what you need (like Ubuntu Server's tasksel), leave overnight. https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx --- laptop_full_setup.sh | 101 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 80 insertions(+), 21 deletions(-) diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index 7f47ce8..4d16288 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -1,14 +1,18 @@ #!/usr/bin/env bash # ============================================================================= -# Local AI Stack — Ubuntu 24.04 -# Services: Ollama · Open WebUI · RAG · MCP · ChromaDB · SearXNG -# Kiwix · Gitea · InvokeAI · Portainer +# Local AI Setup — Ubuntu 24.04 +# One script to rule them all. Select what you need, answer questions, walk away. +# +# Options (checklist at launch): +# • Full system setup — Ubuntu apps, security, backups (ubuntu-post-install.sh) +# • AI stack — Ollama · Open WebUI · RAG · MCP · ChromaDB · SearXNG +# Gitea · InvokeAI · Portainer +# • Kiwix — Offline Wikipedia, Stack Overflow, Arch Wiki, etc. # # Usage: -# ./laptop_full_setup.sh — interactive setup (answer questions, leave overnight) +# ./laptop_full_setup.sh — interactive (asks everything upfront) # ./laptop_full_setup.sh --force — overwrite existing config files too # -# Asks all questions upfront, then runs everything unattended. # GPU: Ollama auto-detects VRAM — works with any NVIDIA GPU # ============================================================================= set -euo pipefail @@ -78,22 +82,60 @@ info "LAN IP : $LOCAL_IP" info "GPU : ${GPU_NAME} (${VRAM_GB}GB VRAM)" $IS_UPDATE && warn "Existing install found. Config files kept unless --force is passed." -# ── Q1: Components ──────────────────────────────────────────────────────────── -echo "" -echo -e " ${BOLD}[1/5] What do you want to install?${NC}" -echo "" -echo " 1) Full stack — AI stack + Kiwix offline library" -echo " 2) AI stack — Ollama · WebUI · RAG · MCP · ChromaDB · SearXNG · Gitea · InvokeAI · Portainer" -echo " 3) Kiwix only — Offline Wikipedia / docs (runs standalone)" -echo "" -read -rp " Choice [1]: " MENU_CHOICE -MENU_CHOICE="${MENU_CHOICE:-1}" -case "$MENU_CHOICE" in - 1) INSTALL_AI=true; INSTALL_KIWIX=true ;; - 2) INSTALL_AI=true; INSTALL_KIWIX=false ;; - 3) INSTALL_AI=false; INSTALL_KIWIX=true ;; - *) die "Invalid choice '$MENU_CHOICE' — run again and choose 1, 2, or 3" ;; -esac +# ── Q1: What to install — checklist ────────────────────────────────────────── +INSTALL_POSTINSTALL=false +INSTALL_AI=true +INSTALL_KIWIX=true + +POSTINSTALL_SCRIPT="$SCRIPT_DIR/ubuntu-post-install.sh" +HAS_POSTINSTALL=false +[[ -f "$POSTINSTALL_SCRIPT" ]] && HAS_POSTINSTALL=true + +# Build checklist via whiptail if available, else text toggle +if command -v whiptail &>/dev/null; then + WHIP_ARGS=() + $HAS_POSTINSTALL && WHIP_ARGS+=( + "postinstall" "Full system setup — Ubuntu apps, security, backups" OFF + ) + WHIP_ARGS+=( + "aistack" "AI stack — Ollama · WebUI · RAG · MCP · Gitea · InvokeAI" ON + "kiwix" "Kiwix — Offline Wikipedia, Stack Overflow, Arch Wiki…" ON + ) + SELECTED=$(whiptail --title "Local AI Setup" \ + --checklist "SPACE = toggle TAB = move ENTER = confirm" \ + 15 72 "${#WHIP_ARGS[@]}" "${WHIP_ARGS[@]}" 3>&1 1>&2 2>&3) || die "Cancelled." + INSTALL_AI=false; INSTALL_KIWIX=false + [[ "$SELECTED" == *"postinstall"* ]] && INSTALL_POSTINSTALL=true + [[ "$SELECTED" == *"aistack"* ]] && INSTALL_AI=true + [[ "$SELECTED" == *"kiwix"* ]] && INSTALL_KIWIX=true +else + # Text-based toggle list + echo "" + echo -e " ${BOLD}[1/5] Select what to install${NC}" + echo " (Type a number to toggle on/off, then press Enter with no number to confirm)" + echo "" + SEL_POST=false; SEL_AI=true; SEL_KIWIX=true + while true; do + echo "" + $HAS_POSTINSTALL && printf " [%s] 1. Full system setup — Ubuntu apps, security, backups\n" "$($SEL_POST && echo '*' || echo ' ')" + printf " [%s] 2. AI stack — Ollama · WebUI · RAG · MCP · Gitea · InvokeAI\n" "$($SEL_AI && echo '*' || echo ' ')" + printf " [%s] 3. Kiwix — Offline Wikipedia, Stack Overflow, docs\n" "$($SEL_KIWIX && echo '*' || echo ' ')" + echo "" + read -rp " Toggle [number] or Enter to confirm: " T + case "$T" in + 1) $HAS_POSTINSTALL && { $SEL_POST && SEL_POST=false || SEL_POST=true; } ;; + 2) $SEL_AI && SEL_AI=false || SEL_AI=true ;; + 3) $SEL_KIWIX && SEL_KIWIX=false || SEL_KIWIX=true ;; + "") break ;; + esac + done + INSTALL_POSTINSTALL=$SEL_POST + INSTALL_AI=$SEL_AI + INSTALL_KIWIX=$SEL_KIWIX +fi + +$INSTALL_AI || $INSTALL_KIWIX || $INSTALL_POSTINSTALL \ + || die "Nothing selected — run again and select at least one option." # ── Q2: Storage for Ollama models ───────────────────────────────────────────── OLLAMA_STORAGE="volume" # "volume" = Docker named volume, else a host path @@ -204,6 +246,7 @@ echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━ echo -e "${BOLD} Setup plan — starting now:${NC}" echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" +$INSTALL_POSTINSTALL && echo " ✓ Full system setup (ubuntu-post-install.sh — interactive)" $INSTALL_AI && echo " ✓ AI stack (Ollama · WebUI · RAG · MCP · Gitea · InvokeAI · Portainer)" $INSTALL_KIWIX && echo " ✓ Kiwix offline library" if $INSTALL_AI; then @@ -235,6 +278,22 @@ write_if_new() { fi } +# ============================================================================= +# Run full system post-install first (sets up Docker, security, base services) +# ============================================================================= +if $INSTALL_POSTINSTALL; then + section "Full System Setup" + if [[ ! -f "$POSTINSTALL_SCRIPT" ]]; then + die "ubuntu-post-install.sh not found at $POSTINSTALL_SCRIPT" + fi + info "Launching ubuntu-post-install.sh in normal install mode..." + echo "" + bash "$POSTINSTALL_SCRIPT" + echo "" + ok "System setup complete — continuing with selected components..." + echo "" +fi + # ============================================================================= section "Prerequisites" # ============================================================================= From 796a073c0a57b765ea78560a46c5d2b8ad2b1295 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 20 Mar 2026 22:22:02 +0000 Subject: [PATCH 3/6] Add Docker/Compose always-check and SSH key import to AI stack setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Docker + Docker Compose check now runs on every invocation (not skipped on updates), installing if missing — ensures the stack can always run - Added Docker Compose plugin install as fallback if compose is missing - NVIDIA Container Toolkit check also always runs (not update-gated) - Added SSH key import wizard question: import public keys from GitHub (gh:username) and/or Launchpad (lp:username) using ssh-import-id, so the machine is accessible over SSH immediately after setup https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx --- laptop_full_setup.sh | 96 +++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 29 deletions(-) diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index 4d16288..f93fada 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -137,13 +137,31 @@ fi $INSTALL_AI || $INSTALL_KIWIX || $INSTALL_POSTINSTALL \ || die "Nothing selected — run again and select at least one option." +# ── Q1b: SSH keys from GitHub / Launchpad ───────────────────────────────────── +SSH_IMPORT_IDS=() # list of "gh:username" or "lp:username" entries + +if ! $IS_UPDATE || [[ ! -f "$HOME/.ssh/authorized_keys" ]]; then + echo "" + echo -e " ${BOLD}[SSH] Import SSH public keys? (for passwordless SSH into this machine)${NC}" + echo " Pulls your public keys from GitHub or Launchpad and adds them to" + echo " ~/.ssh/authorized_keys using ssh-import-id." + echo "" + echo " Examples: gh:yourusername lp:yourlaunchpadid" + echo " Multiple: gh:alice lp:alice" + echo "" + read -rp " Usernames (or Enter to skip): " SSH_INPUT + if [[ -n "$SSH_INPUT" ]]; then + read -ra SSH_IMPORT_IDS <<< "$SSH_INPUT" + fi +fi + # ── Q2: Storage for Ollama models ───────────────────────────────────────────── OLLAMA_STORAGE="volume" # "volume" = Docker named volume, else a host path OLLAMA_HOST_PATH="" if $INSTALL_AI; then echo "" - echo -e " ${BOLD}[2/5] Where should Ollama models be stored?${NC}" + echo -e " ${BOLD}[2/6] Where should Ollama models be stored?${NC}" echo " (Models are large — 5-50GB each. A fast SSD or large HDD is ideal.)" echo "" echo " Available mount points with >20GB free:" @@ -172,7 +190,7 @@ KIWIX_DIR="$BASE/kiwix" # default if $INSTALL_KIWIX; then echo "" - echo -e " ${BOLD}[3/5] Where should Kiwix ZIM files be stored?${NC}" + echo -e " ${BOLD}[3/6] Where should Kiwix ZIM files be stored?${NC}" echo " (ZIMs are large — Wikipedia alone is ~46GB. Total collection ~130GB.)" echo "" echo " Available mount points with >50GB free:" @@ -191,7 +209,7 @@ if $INSTALL_KIWIX; then # ── Q4: Download ZIMs now? ───────────────────────────────────────────────── echo "" - echo -e " ${BOLD}[4/5] Download ZIM files? (offline Wikipedia, Stack Overflow, etc.)${NC}" + echo -e " ${BOLD}[4/6] Download ZIM files? (offline Wikipedia, Stack Overflow, etc.)${NC}" echo " Downloads run in the background — safe to start now and leave overnight." echo " Total: ~130GB (Wikipedia 46GB, Project Gutenberg 60GB, others smaller)" echo "" @@ -211,7 +229,7 @@ if command -v ufw &>/dev/null && { [[ ! -f "$BASE/.ufw-done" ]] || $FORCE; }; th echo "" # Auto-detect likely subnet from current IP AUTO_SUBNET=$(echo "$LOCAL_IP" | awk -F. '{print $1"."$2"."$3".0/24"}') - echo -e " ${BOLD}[4b] Firewall — allow LAN access to services${NC}" + echo -e " ${BOLD}[4b/6] Firewall — allow LAN access to services${NC}" read -rp " LAN subnet [${AUTO_SUBNET}]: " LAN_INPUT LAN_SUBNET="${LAN_INPUT:-$AUTO_SUBNET}" [[ "$LAN_SUBNET" =~ /[0-9]+$ ]] || LAN_SUBNET="${LAN_SUBNET}/24" @@ -223,7 +241,7 @@ PULL_DEEPSEEK=false if $INSTALL_AI; then echo "" - echo -e " ${BOLD}[5/5] Download AI models now?${NC}" + echo -e " ${BOLD}[5/6] Download AI models now?${NC}" echo " GPU detected: ${GPU_NAME} (${VRAM_GB}GB VRAM) → recommended tier: ${GPU_TIER}" echo "" echo " Models that will be downloaded:" @@ -248,6 +266,7 @@ echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━ echo "" $INSTALL_POSTINSTALL && echo " ✓ Full system setup (ubuntu-post-install.sh — interactive)" $INSTALL_AI && echo " ✓ AI stack (Ollama · WebUI · RAG · MCP · Gitea · InvokeAI · Portainer)" +[[ "${#SSH_IMPORT_IDS[@]}" -gt 0 ]] && echo " ✓ SSH keys → ${SSH_IMPORT_IDS[*]}" $INSTALL_KIWIX && echo " ✓ Kiwix offline library" if $INSTALL_AI; then if [[ "$OLLAMA_STORAGE" == "bind" ]]; then @@ -297,21 +316,28 @@ fi # ============================================================================= section "Prerequisites" # ============================================================================= -if ! $IS_UPDATE; then - # Docker - if ! command -v docker &>/dev/null; then - info "Installing Docker..." - curl -fsSL https://get.docker.com | sh - sudo usermod -aG docker "$USER" - ok "Docker installed — log out/in or run: newgrp docker" - else - ok "Docker: $(docker --version)" - fi +# ── Docker (always check — required whether new install or update) ───────────── +if ! command -v docker &>/dev/null; then + info "Installing Docker..." + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker "$USER" + warn "Added $USER to docker group — changes take effect on next login." + warn "If docker commands fail, run: newgrp docker" +else + ok "Docker: $(docker --version | sed 's/Docker version //')" +fi - # NVIDIA Container Toolkit - if command -v nvidia-smi &>/dev/null && \ - ! dpkg -l 2>/dev/null | grep -q nvidia-container-toolkit; then +# Ensure Docker Compose plugin is present (included with modern Docker) +if ! docker compose version &>/dev/null; then + info "Installing docker-compose-plugin..." + sudo apt-get install -y docker-compose-plugin +fi +ok "Docker Compose: $(docker compose version --short 2>/dev/null || echo 'ok')" + +# ── NVIDIA Container Toolkit ─────────────────────────────────────────────────── +if command -v nvidia-smi &>/dev/null; then + if ! dpkg -l 2>/dev/null | grep -q nvidia-container-toolkit; then info "Installing NVIDIA Container Toolkit..." curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \ | sudo gpg --dearmor --yes \ @@ -324,20 +350,32 @@ if ! $IS_UPDATE; then sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker ok "NVIDIA Container Toolkit installed" - elif command -v nvidia-smi &>/dev/null; then - ok "NVIDIA Container Toolkit already present" else - warn "No NVIDIA GPU detected — CPU-only (Ollama will use CPU)" + ok "NVIDIA Container Toolkit: already present" fi - - # ripgrep (used by MCP search_code tool) - if ! command -v rg &>/dev/null; then - sudo apt-get install -y ripgrep - ok "ripgrep installed" - fi - else - ok "Skipping prereq checks (update mode)" + info "No NVIDIA GPU detected — Ollama will run on CPU" +fi + +# ── ripgrep (used by MCP search_code tool) ───────────────────────────────────── +if ! command -v rg &>/dev/null; then + sudo apt-get install -y ripgrep + ok "ripgrep installed" +fi + +# ── SSH key import from GitHub / Launchpad ───────────────────────────────────── +if [[ "${#SSH_IMPORT_IDS[@]}" -gt 0 ]]; then + section "SSH Keys" + if ! command -v ssh-import-id &>/dev/null; then + info "Installing ssh-import-id..." + sudo apt-get install -y ssh-import-id + fi + mkdir -p "$HOME/.ssh" + chmod 700 "$HOME/.ssh" + for id in "${SSH_IMPORT_IDS[@]}"; do + info "Importing SSH keys: $id" + ssh-import-id "$id" && ok "Keys imported: $id" || warn "Failed to import: $id" + done fi # ============================================================================= From 7953ba9d99caf6c68b0f8cdc2177d3c8bd252d5f Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 00:23:11 +0000 Subject: [PATCH 4/6] Add model selection wizard with speed estimates and origin preference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace fixed model defaults with an interactive wizard: Origin preference: 1) Western-only — Codestral (Mistral 🇫🇷) · Phi4 · Mistral 7B 2) Performance-first — Qwen2.5 + Qwen2.5-Coder (top benchmarks) 3) Mixed — Western chat/reasoning, Qwen for coding only 4) Custom — free-form model names Size tier table — speed estimated for the user's actual VRAM with no hard limits (Ollama already uses all available VRAM via NUM_GPU=999): ✓ fast — fully in VRAM ~ good — fits with small overhang (~reading speed) ✗ slow — partial CPU offload ✗ very slow — heavy CPU offload Recommended tier shown as suggestion based on VRAM, user can override. pull-models.sh now bakes in the chosen model names at install time. REASON_MODEL replaces hardcoded deepseek-r1:14b; skipped if empty or same as chat model. https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx --- laptop_full_setup.sh | 166 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 139 insertions(+), 27 deletions(-) diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index f93fada..dcd575a 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -235,27 +235,127 @@ if command -v ufw &>/dev/null && { [[ ! -f "$BASE/.ufw-done" ]] || $FORCE; }; th [[ "$LAN_SUBNET" =~ /[0-9]+$ ]] || LAN_SUBNET="${LAN_SUBNET}/24" fi -# ── Q5: Pull Ollama models now? ─────────────────────────────────────────────── +# ── Q5: Model selection wizard ──────────────────────────────────────────────── PULL_MODELS=false -PULL_DEEPSEEK=false +REASON_MODEL="" if $INSTALL_AI; then + + # speed estimate based on Q4 model size vs available VRAM + # no hard limits — just honest labels so the user can choose + speed_label() { + local mgb="$1" # approximate Q4 size in GB + if [[ "$VRAM_GB" -eq 0 ]]; then + printf "CPU only — very slow" + elif (( mgb <= VRAM_GB )); then + printf "✓ fast — fully in VRAM" + elif (( mgb <= VRAM_GB + 2 )); then + printf "~ good — fits with small overhang (~reading speed)" + elif (( mgb <= VRAM_GB + 8 )); then + printf "✗ slow — partial CPU offload" + else + printf "✗ very slow — heavy CPU offload" + fi + } + echo "" - echo -e " ${BOLD}[5/6] Download AI models now?${NC}" - echo " GPU detected: ${GPU_NAME} (${VRAM_GB}GB VRAM) → recommended tier: ${GPU_TIER}" + echo -e " ${BOLD}[5/6] Model selection${NC}" + echo " GPU: ${GPU_NAME:-None} (${VRAM_GB}GB VRAM)" echo "" - echo " Models that will be downloaded:" - echo " nomic-embed-text (~300MB) — RAG embeddings (required)" - echo " ${FAST_MODEL} (~5GB) — fast chat" - echo " ${CHAT_MODEL} (~9GB) — smart chat" - echo " ${CODE_MODEL} (~5GB) — code assistant" + echo " Origin preference:" + echo " 1) Western-only — Codestral (Mistral 🇫🇷) · Phi4 (Microsoft 🇺🇸) · Mistral 7B" + echo " 2) Performance-first — Qwen2.5 · Qwen2.5-Coder (Chinese, top benchmarks)" + echo " 3) Mixed — Western for chat/reasoning, Qwen for coding only" + echo " 4) Custom — enter model names manually" echo "" - read -rp " Download models now? (~20GB total) [Y/n]: " DO_PULL - if [[ "${DO_PULL,,}" != "n" ]]; then - PULL_MODELS=true - read -rp " Also download DeepSeek-R1:14b for reasoning? (~9GB extra) [y/N]: " DO_DS - [[ "${DO_DS,,}" == "y" ]] && PULL_DEEPSEEK=true + read -rp " Choice [1]: " MODEL_PREF + MODEL_PREF="${MODEL_PREF:-1}" + + # Q4 approximate sizes in GB: 7B=4, 13-15B=9, 22B=13, 32B=19, 70-72B=41 + echo "" + echo " Size tier — estimated speed on your ${VRAM_GB}GB GPU:" + echo " (No hard limits — Ollama uses all available VRAM automatically)" + echo "" + printf " %-8s %-42s %s\n" "Tier" "Models" "Speed on your system" + printf " %-8s %-42s %s\n" "--------" "------------------------------------------" "--------------------" + + case "$MODEL_PREF" in + 1) # Western + printf " %-8s %-42s %s\n" "7B" "mistral:7b + codellama:7b" "$(speed_label 4)" + printf " %-8s %-42s %s\n" "14B" "phi4:14b + starcoder2:15b" "$(speed_label 9)" + printf " %-8s %-42s %s\n" "22B" "phi4:14b + codestral:22b" "$(speed_label 13)" + printf " %-8s %-42s %s\n" "70B" "llama3.3:70b + codestral:22b" "$(speed_label 41)" + ;; + 2) # Performance + printf " %-8s %-42s %s\n" "7B" "qwen2.5:7b + qwen2.5-coder:7b" "$(speed_label 4)" + printf " %-8s %-42s %s\n" "14B" "qwen2.5:14b + qwen2.5-coder:14b" "$(speed_label 9)" + printf " %-8s %-42s %s\n" "32B" "qwen2.5:14b + qwen2.5-coder:32b" "$(speed_label 19)" + printf " %-8s %-42s %s\n" "72B" "qwen2.5:72b + qwen2.5-coder:32b" "$(speed_label 41)" + ;; + 3) # Mixed + printf " %-8s %-42s %s\n" "7B" "mistral:7b + qwen2.5-coder:7b" "$(speed_label 4)" + printf " %-8s %-42s %s\n" "14B" "phi4:14b + qwen2.5-coder:14b" "$(speed_label 9)" + printf " %-8s %-42s %s\n" "32B" "phi4:14b + qwen2.5-coder:32b" "$(speed_label 19)" + printf " %-8s %-42s %s\n" "70B" "llama3.3:70b + qwen2.5-coder:32b" "$(speed_label 41)" + ;; + esac + + # VRAM-based recommendation (soft — shown as suggestion only) + if [[ "$VRAM_GB" -ge 20 ]]; then REC_TIER="32B" + elif [[ "$VRAM_GB" -ge 12 ]]; then REC_TIER="22B" + elif [[ "$VRAM_GB" -ge 6 ]]; then REC_TIER="14B" + else REC_TIER="7B" fi + # performance pref has no 22B tier + [[ "$MODEL_PREF" == "2" && "$REC_TIER" == "22B" ]] && REC_TIER="32B" + + echo "" + + if [[ "$MODEL_PREF" == "4" ]]; then + # Custom — free-form entry + echo " Current defaults: fast=$FAST_MODEL chat=$CHAT_MODEL code=$CODE_MODEL" + echo " Press Enter on any line to keep the default shown." + echo "" + read -rp " Fast/chat model [$FAST_MODEL]: " _in; FAST_MODEL="${_in:-$FAST_MODEL}" + read -rp " Smart chat model [$CHAT_MODEL]: " _in; CHAT_MODEL="${_in:-$CHAT_MODEL}" + read -rp " Code model [$CODE_MODEL]: " _in; CODE_MODEL="${_in:-$CODE_MODEL}" + read -rp " Reasoning model (Enter to skip): " REASON_MODEL + else + read -rp " Choose tier [$REC_TIER]: " TIER_PICK + TIER_PICK="${TIER_PICK:-$REC_TIER}" + + case "${MODEL_PREF}:${TIER_PICK}" in + # Western + 1:7B) FAST_MODEL="mistral:7b"; CHAT_MODEL="mistral:7b"; CODE_MODEL="codellama:7b"; REASON_MODEL="" ;; + 1:14B) FAST_MODEL="mistral:7b"; CHAT_MODEL="phi4:14b"; CODE_MODEL="starcoder2:15b"; REASON_MODEL="phi4:14b" ;; + 1:22B) FAST_MODEL="mistral:7b"; CHAT_MODEL="phi4:14b"; CODE_MODEL="codestral:22b"; REASON_MODEL="phi4:14b" ;; + 1:70B) FAST_MODEL="mistral:7b"; CHAT_MODEL="llama3.3:70b"; CODE_MODEL="codestral:22b"; REASON_MODEL="llama3.3:70b" ;; + # Performance-first + 2:7B) FAST_MODEL="qwen2.5:7b"; CHAT_MODEL="qwen2.5:7b"; CODE_MODEL="qwen2.5-coder:7b"; REASON_MODEL="" ;; + 2:14B) FAST_MODEL="qwen2.5:7b"; CHAT_MODEL="qwen2.5:14b"; CODE_MODEL="qwen2.5-coder:14b"; REASON_MODEL="deepseek-r1:14b" ;; + 2:32B) FAST_MODEL="qwen2.5:7b"; CHAT_MODEL="qwen2.5:14b"; CODE_MODEL="qwen2.5-coder:32b"; REASON_MODEL="deepseek-r1:14b" ;; + 2:72B) FAST_MODEL="qwen2.5:7b"; CHAT_MODEL="qwen2.5:72b"; CODE_MODEL="qwen2.5-coder:32b"; REASON_MODEL="deepseek-r1:14b" ;; + # Mixed + 3:7B) FAST_MODEL="mistral:7b"; CHAT_MODEL="mistral:7b"; CODE_MODEL="qwen2.5-coder:7b"; REASON_MODEL="" ;; + 3:14B) FAST_MODEL="mistral:7b"; CHAT_MODEL="phi4:14b"; CODE_MODEL="qwen2.5-coder:14b"; REASON_MODEL="phi4:14b" ;; + 3:32B) FAST_MODEL="mistral:7b"; CHAT_MODEL="phi4:14b"; CODE_MODEL="qwen2.5-coder:32b"; REASON_MODEL="phi4:14b" ;; + 3:70B) FAST_MODEL="mistral:7b"; CHAT_MODEL="llama3.3:70b"; CODE_MODEL="qwen2.5-coder:32b"; REASON_MODEL="llama3.3:70b" ;; + *) + warn "Unrecognised tier '$TIER_PICK' — keeping detected defaults" + ;; + esac + fi + + echo "" + echo " Models selected:" + printf " %-16s %s\n" "Fast chat:" "$FAST_MODEL" + printf " %-16s %s\n" "Smart chat:" "$CHAT_MODEL" + printf " %-16s %s\n" "Code:" "$CODE_MODEL" + [[ -n "$REASON_MODEL" ]] && printf " %-16s %s\n" "Reasoning:" "$REASON_MODEL" + printf " %-16s %s\n" "Embed (RAG):" "$EMBED_MODEL" + echo "" + read -rp " Download these models now? [Y/n]: " DO_PULL + [[ "${DO_PULL,,}" != "n" ]] && PULL_MODELS=true fi # ── Summary ─────────────────────────────────────────────────────────────────── @@ -276,7 +376,7 @@ if $INSTALL_AI; then fi fi $INSTALL_KIWIX && echo " ✓ Kiwix ZIMs → $KIWIX_DIR" -$PULL_MODELS && echo " ✓ Pull models : embed + ${FAST_MODEL} + ${CHAT_MODEL} + ${CODE_MODEL}${PULL_DEEPSEEK:+ + deepseek-r1:14b}" +$PULL_MODELS && echo " ✓ Pull models : $EMBED_MODEL + $FAST_MODEL + $CHAT_MODEL + $CODE_MODEL${REASON_MODEL:+ + $REASON_MODEL}" [[ "$ZIM_CHOICE" == "1" ]] && echo " ✓ Download all ZIMs in background (~130GB)" [[ "$ZIM_CHOICE" == "2" ]] && echo " ✓ Select ZIMs to download (prompted after stack starts)" echo "" @@ -786,25 +886,37 @@ ok "status.sh" cat > "$BASE/pull-models.sh" << PULLSH #!/bin/bash +# Models chosen at install time — re-run setup to change selection +EMBED_MODEL="$EMBED_MODEL" +FAST_MODEL="$FAST_MODEL" +CHAT_MODEL="$CHAT_MODEL" +CODE_MODEL="$CODE_MODEL" +REASON_MODEL="$REASON_MODEL" + echo "Waiting for Ollama..." until docker exec ollama ollama list &>/dev/null; do sleep 3; done echo "Ollama ready." echo "" -echo "Pulling embed model (needed for RAG)..." -docker exec ollama ollama pull $EMBED_MODEL +echo "Pulling embed model (RAG — required)..." +docker exec ollama ollama pull "\$EMBED_MODEL" -echo "Pulling fast chat model (~5GB)..." -docker exec ollama ollama pull $FAST_MODEL +echo "Pulling fast chat model..." +docker exec ollama ollama pull "\$FAST_MODEL" -echo "Pulling smart chat model (~9GB)..." -docker exec ollama ollama pull $CHAT_MODEL +echo "Pulling smart chat model..." +[[ "\$CHAT_MODEL" != "\$FAST_MODEL" ]] && docker exec ollama ollama pull "\$CHAT_MODEL" -echo "Pulling code model (~5GB)..." -docker exec ollama ollama pull $CODE_MODEL +echo "Pulling code model..." +docker exec ollama ollama pull "\$CODE_MODEL" + +if [[ -n "\$REASON_MODEL" && "\$REASON_MODEL" != "\$CHAT_MODEL" ]]; then + echo "Pulling reasoning model..." + docker exec ollama ollama pull "\$REASON_MODEL" +fi echo "" -echo "Done. Models:" +echo "Done. Installed models:" docker exec ollama ollama list PULLSH chmod +x "$BASE/pull-models.sh" @@ -888,9 +1000,9 @@ if $INSTALL_AI && $PULL_MODELS; then info "Pulling code model..." docker exec ollama ollama pull "$CODE_MODEL" - if $PULL_DEEPSEEK; then - info "Pulling DeepSeek-R1:14b (reasoning)..." - docker exec ollama ollama pull deepseek-r1:14b + if [[ -n "$REASON_MODEL" ]]; then + info "Pulling reasoning model ($REASON_MODEL)..." + docker exec ollama ollama pull "$REASON_MODEL" fi ok "All models pulled." From 7e52271cb69edc4106ca3181fc14fb94b892f7a4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 00:33:14 +0000 Subject: [PATCH 5/6] Replace component menu with per-service whiptail checklist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Top-level menu now just: Full system setup / AI stack - AI stack opens a second whiptail checklist (same style as ubuntu-post-install.sh) where each service is individually toggled: Ollama (always on, core) Open WebUI · RAG+ChromaDB · MCP Server · SearXNG Gitea · InvokeAI · Portainer · Kiwix - Kiwix moved from top-level into the AI stack service list - Existing containers auto-detected and pre-ticked ON - MCP dependency on RAG enforced automatically with a warning - COMPOSE_SERVICES, Done URLs, and summary all driven by per-service SVC_* flags so only selected services are started/shown - Text toggle fallback for systems without whiptail https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx --- laptop_full_setup.sh | 168 +++++++++++++++++++++++++++++++------------ 1 file changed, 122 insertions(+), 46 deletions(-) diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index dcd575a..ec5cca3 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -82,61 +82,122 @@ info "LAN IP : $LOCAL_IP" info "GPU : ${GPU_NAME} (${VRAM_GB}GB VRAM)" $IS_UPDATE && warn "Existing install found. Config files kept unless --force is passed." -# ── Q1: What to install — checklist ────────────────────────────────────────── +# ── Q1: Top-level — what to run ─────────────────────────────────────────────── INSTALL_POSTINSTALL=false INSTALL_AI=true -INSTALL_KIWIX=true + +# Per-service flags (all default ON — toggled off in Q1a) +SVC_WEBUI=true; SVC_RAG=true; SVC_MCP=true; SVC_SEARXNG=true +SVC_GITEA=true; SVC_INVOKEAI=true; SVC_PORTAINER=true; SVC_KIWIX=true POSTINSTALL_SCRIPT="$SCRIPT_DIR/ubuntu-post-install.sh" HAS_POSTINSTALL=false [[ -f "$POSTINSTALL_SCRIPT" ]] && HAS_POSTINSTALL=true -# Build checklist via whiptail if available, else text toggle if command -v whiptail &>/dev/null; then WHIP_ARGS=() $HAS_POSTINSTALL && WHIP_ARGS+=( - "postinstall" "Full system setup — Ubuntu apps, security, backups" OFF - ) - WHIP_ARGS+=( - "aistack" "AI stack — Ollama · WebUI · RAG · MCP · Gitea · InvokeAI" ON - "kiwix" "Kiwix — Offline Wikipedia, Stack Overflow, Arch Wiki…" ON + "postinstall" "Full system setup — Ubuntu apps, security, backups" OFF ) + WHIP_ARGS+=( "aistack" "AI stack — select services on next screen" ON ) SELECTED=$(whiptail --title "Local AI Setup" \ --checklist "SPACE = toggle TAB = move ENTER = confirm" \ - 15 72 "${#WHIP_ARGS[@]}" "${WHIP_ARGS[@]}" 3>&1 1>&2 2>&3) || die "Cancelled." - INSTALL_AI=false; INSTALL_KIWIX=false + 12 68 "${#WHIP_ARGS[@]}" "${WHIP_ARGS[@]}" 3>&1 1>&2 2>&3) || die "Cancelled." + INSTALL_AI=false [[ "$SELECTED" == *"postinstall"* ]] && INSTALL_POSTINSTALL=true [[ "$SELECTED" == *"aistack"* ]] && INSTALL_AI=true - [[ "$SELECTED" == *"kiwix"* ]] && INSTALL_KIWIX=true else - # Text-based toggle list echo "" - echo -e " ${BOLD}[1/5] Select what to install${NC}" - echo " (Type a number to toggle on/off, then press Enter with no number to confirm)" - echo "" - SEL_POST=false; SEL_AI=true; SEL_KIWIX=true + echo -e " ${BOLD}[1] Select what to run${NC}" + echo " (Type a number to toggle, Enter to confirm)" + SEL_POST=false; SEL_AI=true while true; do echo "" - $HAS_POSTINSTALL && printf " [%s] 1. Full system setup — Ubuntu apps, security, backups\n" "$($SEL_POST && echo '*' || echo ' ')" - printf " [%s] 2. AI stack — Ollama · WebUI · RAG · MCP · Gitea · InvokeAI\n" "$($SEL_AI && echo '*' || echo ' ')" - printf " [%s] 3. Kiwix — Offline Wikipedia, Stack Overflow, docs\n" "$($SEL_KIWIX && echo '*' || echo ' ')" + $HAS_POSTINSTALL && printf " [%s] 1. Full system setup — Ubuntu apps, security, backups\n" "$($SEL_POST && echo '*' || echo ' ')" + printf " [%s] 2. AI stack — select services on next screen\n" "$($SEL_AI && echo '*' || echo ' ')" echo "" read -rp " Toggle [number] or Enter to confirm: " T case "$T" in 1) $HAS_POSTINSTALL && { $SEL_POST && SEL_POST=false || SEL_POST=true; } ;; - 2) $SEL_AI && SEL_AI=false || SEL_AI=true ;; - 3) $SEL_KIWIX && SEL_KIWIX=false || SEL_KIWIX=true ;; + 2) $SEL_AI && SEL_AI=false || SEL_AI=true ;; "") break ;; esac done INSTALL_POSTINSTALL=$SEL_POST INSTALL_AI=$SEL_AI - INSTALL_KIWIX=$SEL_KIWIX fi -$INSTALL_AI || $INSTALL_KIWIX || $INSTALL_POSTINSTALL \ +$INSTALL_AI || $INSTALL_POSTINSTALL \ || die "Nothing selected — run again and select at least one option." +# ── Q1a: AI stack service selection ─────────────────────────────────────────── +if $INSTALL_AI; then + # detect which containers already exist (mark them ON) + existing_svc() { + docker ps -a --format '{{.Names}}' 2>/dev/null | grep -q "^$1$" && echo ON || echo OFF + } + + if command -v whiptail &>/dev/null; then + SELECTED_SVCS=$(whiptail --title "AI Stack — Select Services" \ + --checklist "Ollama always installed (core). SPACE = toggle ENTER = confirm" \ + 20 72 9 \ + "open-webui" "Open WebUI — Chat interface (like ChatGPT, uses Ollama)" "$(existing_svc open-webui)" \ + "rag" "RAG + ChromaDB— Code & document search (needed by MCP)" "$(existing_svc rag-server)" \ + "mcp" "MCP Server — Claude Code tools (bash, files, git, search)" "$(existing_svc mcp-server)" \ + "searxng" "SearXNG — Private local web search engine" "$(existing_svc searxng)" \ + "gitea" "Gitea — Self-hosted Git server" "$(existing_svc gitea)" \ + "invokeai" "InvokeAI — Image generation (Stable Diffusion)" "$(existing_svc invokeai)" \ + "portainer" "Portainer — Docker management web UI" "$(existing_svc portainer)" \ + "kiwix" "Kiwix — Offline Wikipedia, Stack Overflow, Arch Wiki" "$(existing_svc kiwix)" \ + 3>&1 1>&2 2>&3) || die "Cancelled." + + SVC_WEBUI=false; [[ "$SELECTED_SVCS" == *'"open-webui"'* ]] && SVC_WEBUI=true + SVC_RAG=false; [[ "$SELECTED_SVCS" == *'"rag"'* ]] && SVC_RAG=true + SVC_MCP=false; [[ "$SELECTED_SVCS" == *'"mcp"'* ]] && SVC_MCP=true + SVC_SEARXNG=false; [[ "$SELECTED_SVCS" == *'"searxng"'* ]] && SVC_SEARXNG=true + SVC_GITEA=false; [[ "$SELECTED_SVCS" == *'"gitea"'* ]] && SVC_GITEA=true + SVC_INVOKEAI=false; [[ "$SELECTED_SVCS" == *'"invokeai"'* ]] && SVC_INVOKEAI=true + SVC_PORTAINER=false;[[ "$SELECTED_SVCS" == *'"portainer"'* ]] && SVC_PORTAINER=true + SVC_KIWIX=false; [[ "$SELECTED_SVCS" == *'"kiwix"'* ]] && SVC_KIWIX=true + else + # Text toggle fallback + echo "" + echo -e " ${BOLD}[1a] AI Stack — select services to install${NC}" + echo " Ollama always included (required core). Type number to toggle, Enter to confirm." + _W=$SVC_WEBUI; _R=$SVC_RAG; _M=$SVC_MCP; _S=$SVC_SEARXNG + _G=$SVC_GITEA; _I=$SVC_INVOKEAI; _P=$SVC_PORTAINER; _K=$SVC_KIWIX + while true; do + echo "" + printf " [%s] 1. Open WebUI — Chat interface\n" "$($_W && echo '*' || echo ' ')" + printf " [%s] 2. RAG+ChromaDB — Code & document search\n" "$($_R && echo '*' || echo ' ')" + printf " [%s] 3. MCP Server — Claude Code tools (needs RAG)\n" "$($_M && echo '*' || echo ' ')" + printf " [%s] 4. SearXNG — Private web search\n" "$($_S && echo '*' || echo ' ')" + printf " [%s] 5. Gitea — Self-hosted Git\n" "$($_G && echo '*' || echo ' ')" + printf " [%s] 6. InvokeAI — Image generation\n" "$($_I && echo '*' || echo ' ')" + printf " [%s] 7. Portainer — Docker management UI\n" "$($_P && echo '*' || echo ' ')" + printf " [%s] 8. Kiwix — Offline Wikipedia, Stack Overflow\n" "$($_K && echo '*' || echo ' ')" + echo "" + read -rp " Toggle [number] or Enter to confirm: " T + case "$T" in + 1) $_W && _W=false || _W=true ;; + 2) $_R && _R=false || _R=true ;; + 3) $_M && _M=false || _M=true ;; + 4) $_S && _S=false || _S=true ;; + 5) $_G && _G=false || _G=true ;; + 6) $_I && _I=false || _I=true ;; + 7) $_P && _P=false || _P=true ;; + 8) $_K && _K=false || _K=true ;; + "") break ;; + esac + done + SVC_WEBUI=$_W; SVC_RAG=$_R; SVC_MCP=$_M; SVC_SEARXNG=$_S + SVC_GITEA=$_G; SVC_INVOKEAI=$_I; SVC_PORTAINER=$_P; SVC_KIWIX=$_K + fi + + # Enforce dependencies: MCP needs RAG + $SVC_MCP && ! $SVC_RAG && { warn "MCP Server requires RAG — enabling RAG+ChromaDB"; SVC_RAG=true; } +fi + # ── Q1b: SSH keys from GitHub / Launchpad ───────────────────────────────────── SSH_IMPORT_IDS=() # list of "gh:username" or "lp:username" entries @@ -188,7 +249,7 @@ fi # ── Q3: Storage for Kiwix ZIMs ──────────────────────────────────────────────── KIWIX_DIR="$BASE/kiwix" # default -if $INSTALL_KIWIX; then +if $SVC_KIWIX; then echo "" echo -e " ${BOLD}[3/6] Where should Kiwix ZIM files be stored?${NC}" echo " (ZIMs are large — Wikipedia alone is ~46GB. Total collection ~130GB.)" @@ -365,9 +426,19 @@ echo -e "${BOLD} Setup plan — starting now:${NC}" echo -e "${BOLD}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo "" $INSTALL_POSTINSTALL && echo " ✓ Full system setup (ubuntu-post-install.sh — interactive)" -$INSTALL_AI && echo " ✓ AI stack (Ollama · WebUI · RAG · MCP · Gitea · InvokeAI · Portainer)" [[ "${#SSH_IMPORT_IDS[@]}" -gt 0 ]] && echo " ✓ SSH keys → ${SSH_IMPORT_IDS[*]}" -$INSTALL_KIWIX && echo " ✓ Kiwix offline library" +if $INSTALL_AI; then + _svcs="Ollama" + $SVC_WEBUI && _svcs+=" · Open WebUI" + $SVC_RAG && _svcs+=" · RAG+ChromaDB" + $SVC_MCP && _svcs+=" · MCP" + $SVC_SEARXNG && _svcs+=" · SearXNG" + $SVC_GITEA && _svcs+=" · Gitea" + $SVC_INVOKEAI && _svcs+=" · InvokeAI" + $SVC_PORTAINER && _svcs+=" · Portainer" + $SVC_KIWIX && _svcs+=" · Kiwix" + echo " ✓ AI stack → $_svcs" +fi if $INSTALL_AI; then if [[ "$OLLAMA_STORAGE" == "bind" ]]; then echo " ✓ Ollama models → $OLLAMA_HOST_PATH" @@ -375,7 +446,7 @@ if $INSTALL_AI; then echo " ✓ Ollama models → Docker volume (default)" fi fi -$INSTALL_KIWIX && echo " ✓ Kiwix ZIMs → $KIWIX_DIR" +$SVC_KIWIX && echo " ✓ Kiwix ZIMs → $KIWIX_DIR" $PULL_MODELS && echo " ✓ Pull models : $EMBED_MODEL + $FAST_MODEL + $CHAT_MODEL + $CODE_MODEL${REASON_MODEL:+ + $REASON_MODEL}" [[ "$ZIM_CHOICE" == "1" ]] && echo " ✓ Download all ZIMs in background (~130GB)" [[ "$ZIM_CHOICE" == "2" ]] && echo " ✓ Select ZIMs to download (prompted after stack starts)" @@ -972,8 +1043,17 @@ cd "$BASE" info "Pulling images (this takes a few minutes on first run)..." COMPOSE_SERVICES="" -$INSTALL_AI && COMPOSE_SERVICES+=" ollama open-webui chromadb rag-server mcp-server searxng gitea invokeai portainer" -$INSTALL_KIWIX && COMPOSE_SERVICES+=" kiwix" +if $INSTALL_AI; then + COMPOSE_SERVICES="ollama" + $SVC_WEBUI && COMPOSE_SERVICES+=" open-webui" + $SVC_RAG && COMPOSE_SERVICES+=" chromadb rag-server" + $SVC_MCP && COMPOSE_SERVICES+=" mcp-server" + $SVC_SEARXNG && COMPOSE_SERVICES+=" searxng" + $SVC_GITEA && COMPOSE_SERVICES+=" gitea" + $SVC_INVOKEAI && COMPOSE_SERVICES+=" invokeai" + $SVC_PORTAINER && COMPOSE_SERVICES+=" portainer" + $SVC_KIWIX && COMPOSE_SERVICES+=" kiwix" +fi # shellcheck disable=SC2086 docker compose pull --quiet $COMPOSE_SERVICES @@ -1013,7 +1093,7 @@ elif $INSTALL_AI; then fi # ── Start ZIM downloads (answer was captured upfront) ──────────────────────── -if $INSTALL_KIWIX && [[ "$ZIM_CHOICE" != "3" ]]; then +if $SVC_KIWIX && [[ "$ZIM_CHOICE" != "3" ]]; then section "Starting ZIM Downloads" LOG="$BASE/logs/kiwix-download.log" mkdir -p "$(dirname "$LOG")" @@ -1135,29 +1215,25 @@ echo "" echo -e "${GREEN}${BOLD}━━━ Done! ━━━${NC}" echo "" if $INSTALL_AI; then - echo -e " ${CYAN}Open WebUI${NC} → http://$LOCAL_IP:3000" - echo -e " ${CYAN}InvokeAI${NC} → http://$LOCAL_IP:9090" - echo -e " ${CYAN}SearXNG${NC} → http://$LOCAL_IP:8888" - echo -e " ${CYAN}Gitea${NC} → http://$LOCAL_IP:3001" - echo -e " ${CYAN}RAG Health${NC} → http://$LOCAL_IP:8001/health" - echo -e " ${CYAN}MCP SSE${NC} → http://$LOCAL_IP:8002/sse" - echo -e " ${CYAN}Portainer${NC} → https://$LOCAL_IP:9443" -fi -if $INSTALL_KIWIX; then - echo -e " ${CYAN}Kiwix${NC} → http://$LOCAL_IP:8181" + $SVC_WEBUI && echo -e " ${CYAN}Open WebUI${NC} → http://$LOCAL_IP:3000" + $SVC_INVOKEAI && echo -e " ${CYAN}InvokeAI${NC} → http://$LOCAL_IP:9090" + $SVC_SEARXNG && echo -e " ${CYAN}SearXNG${NC} → http://$LOCAL_IP:8888" + $SVC_GITEA && echo -e " ${CYAN}Gitea${NC} → http://$LOCAL_IP:3001" + $SVC_RAG && echo -e " ${CYAN}RAG Health${NC} → http://$LOCAL_IP:8001/health" + $SVC_MCP && echo -e " ${CYAN}MCP SSE${NC} → http://$LOCAL_IP:8002/sse" + $SVC_PORTAINER && echo -e " ${CYAN}Portainer${NC} → https://$LOCAL_IP:9443" + $SVC_KIWIX && echo -e " ${CYAN}Kiwix${NC} → http://$LOCAL_IP:8181" fi echo "" if $INSTALL_AI; then - echo -e " ${YELLOW}Add MCP to Claude Code:${NC}" - echo " claude mcp add local http://$LOCAL_IP:8002/sse" - echo "" + $SVC_MCP && { echo -e " ${YELLOW}Add MCP to Claude Code:${NC}"; echo " claude mcp add local http://$LOCAL_IP:8002/sse"; echo ""; } echo -e " ${YELLOW}Add API tokens to:${NC} $BASE/.env" - echo -e " ${YELLOW}Drop PDFs into:${NC} $BASE/papers/" + $SVC_RAG && echo -e " ${YELLOW}Drop PDFs into:${NC} $BASE/papers/" echo -e " ${YELLOW}Your workspace:${NC} $BASE/workspace/" fi -if $INSTALL_KIWIX && [[ "$ZIM_CHOICE" == "3" ]]; then +if $SVC_KIWIX && [[ "$ZIM_CHOICE" == "3" ]]; then echo -e " ${YELLOW}ZIM downloads:${NC} ./kiwix_download.sh (not started)" -elif $INSTALL_KIWIX; then +elif $SVC_KIWIX; then echo -e " ${YELLOW}ZIM progress:${NC} tail -f $BASE/logs/kiwix-download.log" echo -e " ${YELLOW}ZIM location:${NC} $KIWIX_DIR/" fi From 47e91baf692ac19777b7097d5cbc29c0114649b7 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 21 Mar 2026 01:48:22 +0000 Subject: [PATCH 6/6] Fix start.sh and Done output to only show URLs for selected services Build URL/path lines as variables before the heredoc so only installed services appear in start.sh and the final Done summary. https://claude.ai/code/session_012gDnantBmFTWZGCiKyjazx --- laptop_full_setup.sh | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/laptop_full_setup.sh b/laptop_full_setup.sh index ec5cca3..55e037f 100755 --- a/laptop_full_setup.sh +++ b/laptop_full_setup.sh @@ -890,6 +890,23 @@ fi section "Helper Scripts" # ============================================================================= +# Build URL list for start.sh (only include selected services) +_START_URLS="" +$SVC_WEBUI && _START_URLS+=$'echo " Open WebUI → http://'"$LOCAL_IP"$':3000"\n' +$SVC_INVOKEAI && _START_URLS+=$'echo " InvokeAI → http://'"$LOCAL_IP"$':9090"\n' +$SVC_SEARXNG && _START_URLS+=$'echo " SearXNG → http://'"$LOCAL_IP"$':8888"\n' +$SVC_GITEA && _START_URLS+=$'echo " Gitea → http://'"$LOCAL_IP"$':3001"\n' +$SVC_RAG && _START_URLS+=$'echo " RAG Health → http://'"$LOCAL_IP"$':8001/health"\n' +$SVC_MCP && _START_URLS+=$'echo " MCP SSE → http://'"$LOCAL_IP"$':8002/sse"\n' +$SVC_PORTAINER && _START_URLS+=$'echo " Portainer → https://'"$LOCAL_IP"$':9443"\n' +$SVC_KIWIX && _START_URLS+=$'echo " Kiwix → http://'"$LOCAL_IP"$':8181"\n' +_START_MCP="" +$SVC_MCP && _START_MCP=$'echo ""\necho " Claude Code MCP:"\necho " claude mcp add local http://'"$LOCAL_IP"$':8002/sse"\n' +_START_PDFS="" +$SVC_RAG && _START_PDFS=$'echo " Drop PDFs → '"$BASE"$'/papers/"\n' +_START_IMGS="" +$SVC_INVOKEAI && _START_IMGS=$'echo " Images out → '"$BASE"$'/invokeai-outputs/"\n' + cat > "$BASE/start.sh" << STARTSH #!/bin/bash cd "$BASE" @@ -897,21 +914,9 @@ echo "Pulling latest images..." docker compose pull --quiet docker compose up -d echo "" -echo " Open WebUI → http://$LOCAL_IP:3000" -echo " InvokeAI → http://$LOCAL_IP:9090" -echo " SearXNG → http://$LOCAL_IP:8888" -echo " Kiwix → http://$LOCAL_IP:8181" -echo " Gitea → http://$LOCAL_IP:3001" -echo " RAG Health → http://$LOCAL_IP:8001/health" -echo " MCP SSE → http://$LOCAL_IP:8002/sse" -echo " Portainer → https://$LOCAL_IP:9443" -echo "" +$_START_URLS echo " Workspace → $BASE/workspace/" -echo " Drop PDFs → $BASE/papers/" -echo " Images out → $BASE/invokeai-outputs/" -echo "" -echo " Claude Code MCP:" -echo " claude mcp add local http://$LOCAL_IP:8002/sse" +$_START_PDFS$_START_IMGS$_START_MCP STARTSH chmod +x "$BASE/start.sh" ok "start.sh"