Tom_Neverwinter

koboldcpp android install

May 16th, 2025 (edited)
46
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 100.21 KB | None | 0 0
  1. #!/data/data/com.termux/files/usr/bin/env bash
  2. #
  3. # KoboldCpp Termux Interactive Installer & Manager
  4. # Version 3.1 (with Model Cache Management enhancements)
  5. #
  6. # --- Usage & Requirements ---
  7. #
  8. # Usage:
  9. # bash koboldcpp_manager.sh
  10. #
  11. # Requirements:
  12. # - Termux environment on Android (v0.118+ recommended)
  13. # - Bash shell
  14. # - Internet connection for downloading KoboldCpp and models
  15. # - Termux:API package (optional, but recommended for wake lock functionality)
  16. # Install with: pkg install termux-api
  17. #
  18. # For more advanced KoboldCpp setup and options, refer to the official documentation:
  19. # https://github.com/LostRuins/koboldcpp
  20. #
  21.  
  22. # --- Prevent Sourcing ---
  23. if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
  24. echo "This script is meant to be executed, not sourced." >&2
  25. if (return 0 2>/dev/null); then return 1; else exit 1; fi
  26. fi
  27.  
  28. # --- Script Configuration ---
  29. set -euo pipefail # Exit on error, undefined variable, and pipe failure.
  30.  
  31. # --- Early Initializations ---
  32. # Global flag to ensure log directory creation is attempted only once.
  33. LOG_FILE_DIR_INIT_ATTEMPTED=false
  34.  
  35. # Function to ensure the log directory exists
  36. _ensure_log_dir_exists_once() {
  37. if [ "$LOG_FILE_DIR_INIT_ATTEMPTED" = true ]; then
  38. return
  39. fi
  40. local current_log_file_path="$HOME/koboldcpp_install.log"
  41. local log_dir
  42. log_dir=$(dirname "$current_log_file_path")
  43.  
  44. if ! mkdir -p "$log_dir"; then
  45. echo "CRITICAL ERROR: Could not create log directory '$log_dir'. Logging will fail. Aborting." >&2
  46. exit 1
  47. fi
  48. LOG_FILE_DIR_INIT_ATTEMPTED=true
  49. }
  50. _ensure_log_dir_exists_once # Call immediately
  51.  
  52. # --- Global Readonly Variables ---
  53. readonly KOBOLDCPP_DIR="$HOME/koboldcpp"
  54. readonly MODEL_DIR="$KOBOLDCPP_DIR/models" # This is your model cache directory
  55. readonly EXPECTED_MODEL_DIR_PATH="$HOME/koboldcpp/models"
  56. readonly DOWNLOAD_DIR="$HOME/storage/downloads"
  57. readonly BACKUP_DIR="$HOME/koboldcpp_backup"
  58. readonly LOG_FILE="$HOME/koboldcpp_install.log"
  59. readonly REPO_URL="https://github.com/LostRuins/koboldcpp.git"
  60. readonly DEFAULT_BRANCH="main"
  61. readonly CONFIG_FILE="$HOME/.koboldcpp_config"
  62. readonly VERSION="3.1"
  63.  
  64. # Colors for prettier output
  65. readonly RED='\033[0;31m'
  66. readonly GREEN='\033[0;32m'
  67. readonly YELLOW='\033[0;33m'
  68. readonly BLUE='\033[0;34m'
  69. readonly CYAN='\033[0;36m'
  70. readonly BOLD='\033[1m'
  71. readonly NC='\033[0m' # No Color
  72.  
  73. # --- Portable File Size Command Initialization ---
  74. DU_BYTES_CMD=""
  75. DU_BYTES_TYPE="unknown" # 'du_b', 'du_bytes', 'stat', or 'unknown'
  76.  
  77. _initialize_du_commands() {
  78. local test_file_path="/proc/self/exe" # Generally reliable link
  79. local temp_file_created=false
  80.  
  81. if [ ! -r "$test_file_path" ] || [ ! -f "$test_file_path" ]; then
  82. test_file_path=$(mktemp)
  83. if [ -z "$test_file_path" ] || [ ! -f "$test_file_path" ]; then
  84. if typeset -f warn > /dev/null; then warn "mktemp failed for 'du' test."; else echo "Warning: mktemp failed for 'du' test." >&2; fi
  85. DU_BYTES_TYPE="unknown"
  86. return
  87. fi
  88. temp_file_created=true
  89. trap '_cleanup_temp_file "$test_file_path"; trap - EXIT' EXIT
  90. fi
  91.  
  92. if du -b "$test_file_path" >/dev/null 2>&1; then
  93. DU_BYTES_CMD="du -b"
  94. DU_BYTES_TYPE="du_b"
  95. elif du --bytes "$test_file_path" >/dev/null 2>&1; then
  96. DU_BYTES_CMD="du --bytes"
  97. DU_BYTES_TYPE="du_bytes"
  98. elif stat -c %s "$test_file_path" >/dev/null 2>&1; then
  99. DU_BYTES_CMD="stat -c %s"
  100. DU_BYTES_TYPE="stat"
  101. else
  102. DU_BYTES_TYPE="unknown"
  103. if typeset -f warn > /dev/null; then warn "No reliable command for exact byte counts found."; else echo "Warning: No reliable command for exact byte counts found." >&2; fi
  104. fi
  105.  
  106. if [ "$temp_file_created" = true ]; then
  107. _cleanup_temp_file "$test_file_path"
  108. trap - EXIT # Clear the specific trap if we cleaned up
  109. fi
  110. }
  111.  
  112. _cleanup_temp_file() {
  113. if [ -n "${1:-}" ] && [ -f "$1" ]; then
  114. rm -f "$1"
  115. fi
  116. }
  117. _initialize_du_commands # Initialize once
  118.  
  119. # --- Utility functions ---
  120. log() {
  121. echo -e "${GREEN}→${NC} $1"
  122. echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1" >> "$LOG_FILE"
  123. }
  124.  
  125. error() {
  126. echo -e "${RED}❌ ERROR:${NC} $1" >&2
  127. echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE"
  128. return 1
  129. }
  130.  
  131. success() {
  132. echo -e "${GREEN}✅${NC} $1"
  133. echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE"
  134. }
  135.  
  136. warn() {
  137. echo -e "${YELLOW}⚠️ WARNING:${NC} $1"
  138. echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1" >> "$LOG_FILE"
  139. }
  140.  
  141. info() {
  142. echo -e "${BLUE}ℹ️ INFO:${NC} $1"
  143. }
  144.  
  145. # Core pause logic.
  146. _pause_script_execution() {
  147. local clear_behavior="${1:-clear}" # Default to "clear"
  148. echo
  149. echo -n "Press Enter to continue..."
  150. read -r
  151. if [ "$clear_behavior" = "clear" ]; then
  152. clear
  153. fi
  154. }
  155.  
  156. # Pauses script and keeps screen content.
  157. wait_for_keypress_keep_screen() {
  158. _pause_script_execution "keep"
  159. }
  160.  
  161. # Pauses script and clears screen (default for progressing through menus/steps).
  162. press_enter() {
  163. _pause_script_execution "clear"
  164. }
  165.  
  166. clear_screen() {
  167. clear
  168. }
  169.  
  170. # Function to save user configurations
  171. save_config() {
  172. local key="$1"
  173. local value="$2"
  174.  
  175. # Create config file if it doesn't exist
  176. touch "$CONFIG_FILE"
  177.  
  178. # Check if key already exists, update if it does, add if it doesn't
  179. if grep -q "^$key=" "$CONFIG_FILE"; then
  180. sed -i "s/^$key=.*/$key=$value/" "$CONFIG_FILE"
  181. else
  182. echo "$key=$value" >> "$CONFIG_FILE"
  183. fi
  184. }
  185.  
  186. # Function to load user configurations
  187. load_config() {
  188. local key="$1"
  189. local default="$2"
  190.  
  191. if [ -f "$CONFIG_FILE" ] && grep -q "^$key=" "$CONFIG_FILE"; then
  192. grep "^$key=" "$CONFIG_FILE" | cut -d= -f2
  193. else
  194. echo "$default"
  195. fi
  196. }
  197.  
  198. get_file_size_bytes() {
  199. local filepath="${1:-}"
  200. if [ -z "$filepath" ] || [ ! -e "$filepath" ]; then
  201. echo "0"
  202. return
  203. fi
  204.  
  205. local size_output=""
  206. case "$DU_BYTES_TYPE" in
  207. "du_b") size_output=$(du -b "$filepath" 2>/dev/null | cut -f1) ;;
  208. "du_bytes") size_output=$(du --bytes "$filepath" 2>/dev/null | cut -f1) ;;
  209. "stat") size_output=$(stat -c %s "$filepath" 2>/dev/null) ;;
  210. *)
  211. local size_k
  212. size_k=$(du -k "$filepath" 2>/dev/null | cut -f1)
  213. if [[ "$size_k" =~ ^[0-9]+$ ]]; then
  214. size_output=$((size_k * 1024))
  215. else
  216. size_output="0"
  217. fi
  218. ;;
  219. esac
  220. echo "${size_output:-0}" # Ensure output, default to 0 if empty
  221. }
  222.  
  223. get_file_size_human() {
  224. local filepath="${1:-}"
  225. if [ -z "$filepath" ] || [ ! -e "$filepath" ]; then
  226. echo "N/A"
  227. return
  228. fi
  229. du -h "$filepath" 2>/dev/null | cut -f1 || echo "N/A"
  230. }
  231.  
  232. # Check if a model is corrupted (basic checks)
  233. # Returns 0 for likely OK, 1 for likely corrupted.
  234. check_model_integrity() {
  235. local model_path="$1"
  236. local model_name=$(basename "$model_path")
  237.  
  238. # Simple size check - empty or very small files are likely corrupted
  239. local size_bytes=$(get_file_size_bytes "$model_path")
  240. if [ "$size_bytes" -lt 10240 ]; then # Less than 10KB
  241. warn "Model $model_name appears to be too small ($(get_file_size_human "$model_path")). It may be corrupted."
  242. return 1
  243. fi
  244.  
  245. # Check file header for GGUF signature (basic check, requires hexdump)
  246. if command -v hexdump >/dev/null 2>&1; then
  247. # Read the first 8 bytes and check for the GGUF magic number (0x46554747)
  248. # This is "GGUF" in ASCII, followed by version number (typically 0x00000003 or 0x00000002)
  249. # We check for 47 47 55 46 (GGUF) followed by any 4 bytes.
  250. local header=$(hexdump -n 8 -e '1/1 "%02x"' "$model_path" 2>/dev/null)
  251. if [[ "$header" != "47475546"* ]]; then
  252. warn "Model $model_name does not start with a valid GGUF magic number. It may be corrupted or not a GGUF file."
  253. return 1
  254. fi
  255. else
  256. # If hexdump is not available, we can only do the size check
  257. if [ "$size_bytes" -lt 1000000 ]; then # Arbitrary larger size threshold if no hexdump
  258. warn "Could not verify GGUF header (hexdump not found). Model $model_name is relatively small ($(get_file_size_human "$model_path")) and might be incomplete or corrupted."
  259. return 1
  260. fi
  261. fi
  262.  
  263. # If basic checks pass
  264. return 0
  265. }
  266.  
  267. # Validates critical directory paths against expected values and common pitfalls.
  268. _validate_critical_directory_path() {
  269. local dir_to_check="${1:-}"
  270. local expected_path="${2:-}"
  271. local operation_name="${3:-operation}" # e.g., "backup", "restore"
  272.  
  273. if [ -z "$dir_to_check" ]; then
  274. error "Safety check failed for $operation_name: Directory path is empty."
  275. return 1
  276. fi
  277. if [ "$dir_to_check" = "/" ] || [ "$dir_to_check" = "$HOME" ]; then
  278. error "Safety check failed for $operation_name: Directory path ('$dir_to_check') is a root or home directory."
  279. return 1
  280. fi
  281. # Disabled exact path check for more flexibility, rely on starts-with and critical path checks
  282. # if [ -n "$expected_path" ] && [ "$dir_to_check" != "$expected_path" ]; then
  283. # error "Safety check failed for $operation_name: Directory path ('$dir_to_check') does not match expected path ('$expected_path')."
  284. # return 1
  285. # fi
  286. # Add check to prevent operations outside HOME
  287. if [[ ! "$dir_to_check" == "$HOME"* ]]; then
  288. error "Safety check failed for $operation_name: Directory path ('$dir_to_check') is outside the home directory."
  289. return 1
  290. fi
  291.  
  292. return 0
  293. }
  294.  
  295. # Function to check available storage space
  296. check_available_space() {
  297. local required_space="$1" # in bytes
  298. local directory="$2"
  299.  
  300. if [ ! -d "$directory" ]; then
  301. warn "Target directory '$directory' does not exist. Cannot check available space."
  302. return 0 # Assume enough space if dir doesn't exist yet (mkdir -p will create)
  303. fi
  304.  
  305. # Get available space in the target directory
  306. local available_space=0
  307. if command -v df >/dev/null 2>&1; then
  308. available_space=$(df -B1 "$directory" | awk 'NR==2 {print $4}')
  309. else
  310. # If df is not available, assume there's enough space
  311. warn "df command not found. Cannot reliably check available storage space."
  312. return 0
  313. fi
  314.  
  315. if [ -z "$available_space" ] || [ "$available_space" -lt "$required_space" ]; then
  316. local required_human=$(numfmt --to=iec-i --suffix=B "$required_space" 2>/dev/null || echo "$required_space bytes")
  317. local available_human=$(numfmt --to=iec-i --suffix=B "$available_space" 2>/dev/null || echo "$available_space bytes")
  318. warn "Not enough storage space in '$directory'."
  319. warn "Required: $required_human, Available: $available_human"
  320. return 1
  321. fi
  322.  
  323. return 0
  324. }
  325.  
  326. # Find device CPU info
  327. get_device_info() {
  328. local device_info=""
  329.  
  330. # Get CPU model
  331. if [ -f "/proc/cpuinfo" ]; then
  332. local cpu_model=$(grep "model name\|Processor" /proc/cpuinfo | head -n1 | cut -d: -f2 | xargs)
  333. if [ -n "$cpu_model" ]; then
  334. device_info="CPU: $cpu_model"
  335. fi
  336. fi
  337.  
  338. # Get total RAM
  339. if [ -f "/proc/meminfo" ]; then
  340. local total_ram=$(grep "MemTotal" /proc/meminfo | awk '{print $2}')
  341. if [ -n "$total_ram" ]; then
  342. local ram_gb=$(awk -v ram="$total_ram" 'BEGIN {printf "%.2f", ram/1024/1024}')
  343. device_info="$device_info, RAM: ${ram_gb}GB"
  344. fi
  345. fi
  346.  
  347. # Get Android version if available
  348. if command -v getprop >/dev/null 2>&1; then
  349. local android_ver=$(getprop ro.build.version.release)
  350. if [ -n "$android_ver" ]; then
  351. device_info="$device_info, Android: $android_ver"
  352. fi
  353. fi
  354.  
  355. echo "$device_info"
  356. }
  357.  
  358. # --- Prerequisite Checks ---
  359. check_install() {
  360. local py_script_exists=false
  361. local binary_exists=false
  362. if [ -f "$KOBOLDCPP_DIR/koboldcpp.py" ]; then
  363. py_script_exists=true
  364. fi
  365. if [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]; then
  366. binary_exists=true
  367. fi
  368.  
  369. if [ ! -d "$KOBOLDCPP_DIR" ] || { [ "$py_script_exists" = false ] && [ "$binary_exists" = false ]; }; then
  370. echo -e "${YELLOW}KoboldCpp is not installed or key components are missing.${NC}"
  371. echo
  372. echo -n "Would you like to install/reinstall it now? [Y/n]: "
  373. read -r INSTALL_CHOICE
  374. if [[ "${INSTALL_CHOICE:-Y}" =~ ^[Nn]$ ]]; then # Default to Y
  375. error "KoboldCpp installation is required to continue."
  376. wait_for_keypress_keep_screen
  377. return 1
  378. else
  379. install_all
  380. return $?
  381. fi
  382. fi
  383. return 0
  384. }
  385.  
  386. check_termux_storage() {
  387. if [ ! -d "$DOWNLOAD_DIR" ]; then
  388. log "Setting up Termux storage access..."
  389. if command -v termux-setup-storage &>/dev/null; then
  390. termux-setup-storage
  391. else
  392. error "termux-setup-storage command not found. Please ensure Termux:API is installed and configured."
  393. info "You might need to run: pkg install termux-api && termux-setup-storage"
  394. wait_for_keypress_keep_screen
  395. return 1
  396. fi
  397.  
  398. for i in {1..10}; do
  399. if [ -d "$DOWNLOAD_DIR" ]; then
  400. break
  401. fi
  402. log "Waiting for storage access... ($i/10)"
  403. sleep 1
  404. done
  405.  
  406. if [ ! -d "$DOWNLOAD_DIR" ]; then
  407. error "Storage access not granted or Downloads directory '$DOWNLOAD_DIR' not found."
  408. info "Please ensure you granted storage permission and the directory exists."
  409. wait_for_keypress_keep_screen
  410. return 1
  411. fi
  412. fi
  413. return 0
  414. }
  415.  
  416. # Check internet connectivity
  417. check_internet() {
  418. log "Checking internet connectivity..."
  419. if ping -c 1 8.8.8.8 >/dev/null 2>&1 || ping -c 1 google.com >/dev/null 2>&1; then
  420. return 0
  421. else
  422. error "No internet connection detected. Some functions may not work properly."
  423. return 1
  424. fi
  425. }
  426.  
  427. # --- Core Functionality ---
  428.  
  429. install_all() {
  430. clear_screen
  431. echo -e "${BOLD}${CYAN}=== Installing KoboldCpp ===${NC}"
  432. echo
  433. log "Starting KoboldCpp installation..."
  434.  
  435. if command -v termux-wake-lock &>/dev/null; then
  436. log "Acquiring wake lock to prevent sleep..."
  437. termux-wake-lock
  438. else
  439. warn "termux-api not installed or termux-wake-lock not available. Consider 'pkg install termux-api'."
  440. fi
  441.  
  442. check_termux_storage || { return 1; }
  443.  
  444. # Check internet connection before proceeding
  445. check_internet
  446.  
  447. log "Updating package lists..."
  448. pkg update -y || { error "Failed to update package lists"; wait_for_keypress_keep_screen; return 1; }
  449. log "Upgrading installed packages..."
  450. pkg upgrade -y || { warn "Failed to upgrade packages. Continuing, but this might lead to issues."; }
  451.  
  452. log "Installing dependencies: openssl wget git python build-essential clang cmake libjpeg-turbo..."
  453. pkg install -y openssl wget git python build-essential clang cmake libjpeg-turbo || { error "Failed to install core packages"; wait_for_keypress_keep_screen; return 1; }
  454.  
  455. if ! command -v termux-wake-lock &>/dev/null; then
  456. log "Attempting to install termux-api for better system integration..."
  457. pkg install -y termux-api || warn "Could not install termux-api, continuing anyway."
  458. fi
  459.  
  460. if [ -d "$KOBOLDCPP_DIR/.git" ]; then
  461. log "KoboldCpp repository already exists, updating..."
  462. cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
  463. git fetch --all --prune || { error "Failed to fetch updates"; wait_for_keypress_keep_screen; return 1; }
  464. git reset --hard "origin/$DEFAULT_BRANCH" || { error "Failed to update to latest version of branch '$DEFAULT_BRANCH'"; wait_for_keypress_keep_screen; return 1; }
  465. else
  466. log "Cloning KoboldCpp repository (branch: $DEFAULT_BRANCH)..."
  467. git clone --depth 1 --branch "$DEFAULT_BRANCH" "$REPO_URL" "$KOBOLDCPP_DIR" || { error "Failed to clone repository"; wait_for_keypress_keep_screen; return 1; }
  468. cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR' after cloning"; wait_for_keypress_keep_screen; return 1; }
  469. fi
  470.  
  471. mkdir -p "$MODEL_DIR" || { error "Failed to create models directory: '$MODEL_DIR'"; wait_for_keypress_keep_screen; return 1; }
  472.  
  473. # Get system information for optimized build
  474. local device_info=$(get_device_info)
  475. log "Building for: $device_info"
  476.  
  477. log "Building KoboldCpp with portable settings for Android..."
  478. export LLAMA_PORTABLE=1
  479. export KOBOLDCPP_PLATFORM="android"
  480.  
  481. log "Cleaning previous build files (if any)..."
  482. make clean || warn "Failed to clean build files, continuing anyway."
  483.  
  484. log "Building with optimizations for your device..."
  485. make -j$(nproc) || {
  486. warn "Build failed with default optimizations, trying with safer settings (LLAMA_NO_ACCELERATE=1)..."
  487. make clean
  488. make -j$(nproc) LLAMA_NO_ACCELERATE=1 || {
  489. warn "Build failed with first fallback, trying with fewer threads..."
  490. make clean
  491. make LLAMA_NO_ACCELERATE=1 || {
  492. error "Build failed with all fallback options. Please check the error messages above."
  493. wait_for_keypress_keep_screen
  494. return 1
  495. }
  496. }
  497. }
  498.  
  499. local py_script_exists=false
  500. local binary_exists=false
  501. if [ -f "$KOBOLDCPP_DIR/koboldcpp.py" ]; then
  502. py_script_exists=true
  503. fi
  504. if [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]; then
  505. binary_exists=true
  506. fi
  507.  
  508. if [ "$py_script_exists" = false ] && [ "$binary_exists" = false ]; then
  509. error "Build verification failed. Neither koboldcpp.py nor a compiled binary (e.g., koboldcpp-android-arm64 or koboldcpp) was found."
  510. info "Please check the build logs in '$LOG_FILE' for errors."
  511. wait_for_keypress_keep_screen
  512. return 1
  513. fi
  514.  
  515. # Save build information to help with future builds
  516. if [ "$binary_exists" = true ]; then
  517. # local build_success="1" # This variable was declared but not used. Removed.
  518. save_config "last_successful_build" "$(date +%Y%m%d)"
  519. # Save the last used build option (0 for default, 1 for LLAMA_NO_ACCELERATE=1)
  520. # Check if LLAMA_NO_ACCELERATE is set to 1 in the environment or last command
  521. if [[ "${build_command:-}" == *"LLAMA_NO_ACCELERATE=1"* ]]; then
  522. save_config "build_options" "1"
  523. else
  524. save_config "build_options" "0"
  525. fi
  526. fi
  527.  
  528. success "KoboldCpp installation complete!"
  529.  
  530. if command -v termux-wake-unlock &>/dev/null; then
  531. log "Releasing wake lock..."
  532. termux-wake-unlock
  533. fi
  534.  
  535. press_enter
  536. return 0
  537. }
  538.  
  539. add_model() {
  540. clear_screen
  541. echo -e "${BOLD}${CYAN}=== Add Model from Downloads ===${NC}"
  542. echo
  543. log "This copies a .gguf model file from your Termux Downloads directory ($DOWNLOAD_DIR) to the KoboldCpp models directory ($MODEL_DIR)."
  544. echo
  545.  
  546. check_install || { return 1; }
  547. check_termux_storage || { return 1; }
  548.  
  549. # Search for models recursively in Downloads
  550. echo -e "${YELLOW}Searching for .gguf files in Downloads directory...${NC}"
  551.  
  552. local FILES=()
  553. local SEARCH_DEPTH=3 # Search up to 3 levels deep
  554.  
  555. # Use a safer find command with -print0 and read -d $'\0'
  556. while IFS= read -r -d $'\0' file; do
  557. # Filter out files directly in DOWNLOAD_DIR if they don't end with .gguf - already done by -name "*.gguf"
  558. # Additional check to prevent adding files already in MODEL_DIR (unlikely if copying, but safe)
  559. # if [[ "$(dirname "$file")" == "$MODEL_DIR" ]]; then
  560. # continue # Skip files already in the target directory
  561. # fi
  562. FILES+=("$file")
  563. done < <(find "$DOWNLOAD_DIR" -maxdepth "$SEARCH_DEPTH" -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
  564.  
  565.  
  566. if [ ${#FILES[@]} -eq 0 ]; then
  567. error "No .gguf files found in your Downloads directory (searched $SEARCH_DEPTH levels deep)."
  568. echo "Please download a model (ending with .gguf) to that directory first."
  569. press_enter
  570. return 1
  571. fi
  572.  
  573. echo -e "${YELLOW}Found models:${NC}"
  574. for i in "${!FILES[@]}"; do
  575. local FILE="${FILES[$i]}"
  576. local FILENAME
  577. FILENAME=$(basename "$FILE")
  578. local REL_PATH=${FILE#"$DOWNLOAD_DIR/"}
  579. local SIZE
  580. SIZE=$(get_file_size_human "$FILE")
  581. echo "[$i] $FILENAME ($SIZE)"
  582. echo " Location: $REL_PATH"
  583. done
  584.  
  585. echo
  586. echo -n "Enter number of the model to add (or 'q' to return to menu): "
  587. read -r CHOICE
  588.  
  589. if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then # Handle empty CHOICE
  590. return 0
  591. fi
  592.  
  593. if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#FILES[@]}" ]; then
  594. error "Invalid selection."
  595. wait_for_keypress_keep_screen
  596. return 1
  597. fi
  598.  
  599. local SRC="${FILES[$CHOICE]}"
  600. local FILENAME
  601. FILENAME=$(basename "$SRC")
  602. local DEST="$MODEL_DIR/$FILENAME"
  603.  
  604. if [ -f "$DEST" ]; then
  605. echo -n "File '$FILENAME' already exists in models directory. Overwrite? [y/N]: "
  606. read -r CONFIRM
  607. if [[ ! "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default to N
  608. info "Operation cancelled."
  609. press_enter
  610. return 0
  611. fi
  612. fi
  613.  
  614. # Check for available space before copying
  615. local file_size=$(get_file_size_bytes "$SRC")
  616. check_available_space "$file_size" "$MODEL_DIR" || {
  617. error "Not enough space to copy the model. Please free up some storage."
  618. wait_for_keypress_keep_screen
  619. return 1
  620. }
  621.  
  622. log "Copying '$FILENAME' to models directory..."
  623. mkdir -p "$MODEL_DIR"
  624. if cp "$SRC" "$DEST"; then
  625. success "Model '$FILENAME' copied to '$MODEL_DIR'."
  626.  
  627. # Verify the copied model integrity
  628. log "Verifying integrity of copied model..."
  629. if check_model_integrity "$DEST"; then
  630. success "Integrity check passed for $FILENAME."
  631. else
  632. warn "Integrity check failed for $FILENAME. The copied model may be corrupted."
  633. warn "Consider deleting it and adding it again, or re-downloading the original file."
  634. wait_for_keypress_keep_screen
  635. fi
  636. else
  637. error "Failed to copy model file '$FILENAME'. Check permissions and storage space."
  638. wait_for_keypress_keep_screen
  639. return 1
  640. fi
  641.  
  642.  
  643. press_enter
  644. return 0
  645. }
  646.  
  647. download_model() {
  648. clear_screen
  649. echo -e "${BOLD}${CYAN}=== Download Model ===${NC}"
  650. echo
  651. log "This downloads a .gguf model file directly to the KoboldCpp models directory ($MODEL_DIR)."
  652. echo
  653.  
  654. check_install || { return 1; }
  655. check_internet || {
  656. error "Internet connection is required to download models."
  657. wait_for_keypress_keep_screen
  658. return 1
  659. }
  660.  
  661. # Show recently used URLs if they exist
  662. local recent_urls=()
  663. local config_urls=$(grep "^recent_url_" "$CONFIG_FILE" 2>/dev/null | cut -d= -f2)
  664. if [ -n "$config_urls" ]; then
  665. echo -e "${YELLOW}Recently used URLs:${NC}"
  666. local url_index=0
  667. while IFS= read -r url; do
  668. recent_urls+=("$url")
  669. echo "[$url_index] $url"
  670. ((url_index++))
  671. done <<< "$config_urls"
  672. echo "[n] Enter a new URL"
  673. echo
  674. echo -n "Select an option: "
  675. read -r URL_CHOICE
  676.  
  677. local URL=""
  678. if [[ "$URL_CHOICE" =~ ^[0-9]+$ ]] && [ "$URL_CHOICE" -ge 0 ] && [ "$URL_CHOICE" -lt "${#recent_urls[@]}" ]; then
  679. URL="${recent_urls[$URL_CHOICE]}"
  680. info "Selected recent URL: $URL"
  681. else
  682. echo "Enter a direct download URL for the model file (must be a .gguf file):"
  683. echo -n "> "
  684. read -r URL
  685. # Add new valid URL to recent list
  686. if [ -n "$URL" ] && [[ "$URL" == *.gguf* ]]; then
  687. save_config "recent_url_${#recent_urls[@]}" "$URL"
  688. fi
  689. fi
  690. else
  691. echo "Enter a direct download URL for the model file (must be a .gguf file):"
  692. echo -n "> "
  693. read -r URL
  694. # Add new valid URL to recent list
  695. if [ -n "$URL" ] && [[ "$URL" == *.gguf* ]]; then
  696. save_config "recent_url_0" "$URL"
  697. fi
  698. fi
  699.  
  700.  
  701. if [ -z "${URL:-}" ]; then
  702. info "Operation cancelled (empty URL)."
  703. press_enter
  704. return 0
  705. fi
  706.  
  707. local FILENAME
  708. FILENAME=$(basename "${URL:-}") # Handle potentially empty URL
  709. FILENAME="${FILENAME%%\?*}" # Remove query string parameters
  710.  
  711. if [ -z "$FILENAME" ] || [[ ! "$FILENAME" == *.gguf ]]; then
  712. warn "Couldn't determine a valid .gguf filename from the URL ('$FILENAME')."
  713. echo -n "Please enter a filename for this model (should end with .gguf): "
  714. read -r CUSTOM_FILENAME
  715.  
  716. if [ -z "${CUSTOM_FILENAME:-}" ]; then
  717. info "Operation cancelled (empty filename)."
  718. press_enter
  719. return 0
  720. fi
  721. FILENAME="$CUSTOM_FILENAME"
  722. fi
  723.  
  724. if [[ ! "$FILENAME" == *.gguf ]]; then
  725. FILENAME="${FILENAME}.gguf"
  726. info "Added .gguf extension. Filename will be: $FILENAME"
  727. fi
  728.  
  729. local DEST="$MODEL_DIR/$FILENAME"
  730.  
  731. if [ -f "$DEST" ]; then
  732. echo -n "File '$FILENAME' already exists in models directory. Overwrite? [y/N]: "
  733. read -r CONFIRM
  734. if [[ ! "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default N
  735. info "Operation cancelled."
  736. press_enter
  737. return 0
  738. fi
  739. fi
  740.  
  741. # Check file size before downloading if possible
  742. local file_size=0
  743. if command -v curl >/dev/null 2>&1; then
  744. log "Checking file size before download..."
  745. # Use -s silent, -I head request, -L follow redirects, grep Content-Length, awk to get value, tr to remove carriage return
  746. local size_output=$(curl -sIL "$URL" 2>/dev/null | grep -i Content-Length | tail -n 1 | awk '{print $2}' | tr -d '\r')
  747. if [ -n "$size_output" ] && [[ "$size_output" =~ ^[0-9]+$ ]]; then
  748. file_size="$size_output"
  749. fi
  750.  
  751. if [ "$file_size" -gt 0 ]; then
  752. local size_human=$(numfmt --to=iec-i --suffix=B "$file_size" 2>/dev/null || echo "$file_size bytes")
  753. info "Estimated model size: $size_human"
  754.  
  755. # Check available space
  756. check_available_space "$file_size" "$MODEL_DIR" || {
  757. error "Not enough space to download the model. Please free up some storage."
  758. wait_for_keypress_keep_screen
  759. return 1
  760. }
  761. else
  762. warn "Could not reliably determine model size. Proceeding anyway, but ensure sufficient space is available."
  763. # As a fallback, check if there's at least 5GB free (a common minimum for larger models)
  764. check_available_space 5368709120 "$MODEL_DIR" || { # 5GB in bytes
  765. warn "Less than 5GB free space detected and could not get exact file size. Download might fail due to lack of space."
  766. echo -n "Continue download anyway? [y/N]: "
  767. read -r FORCE_DOWNLOAD
  768. if [[ ! "${FORCE_DOWNLOAD:-N}" =~ ^[Yy]$ ]]; then
  769. info "Download cancelled due to potential space issues."
  770. press_enter
  771. return 1
  772. fi
  773. }
  774. fi
  775. else
  776. warn "curl command not found. Cannot check file size before downloading. Proceeding anyway, but ensure sufficient space is available."
  777. # As a fallback, check if there's at least 5GB free (a common minimum for larger models)
  778. check_available_space 5368709120 "$MODEL_DIR" || { # 5GB in bytes
  779. warn "Less than 5GB free space detected and could not get exact file size. Download might fail due to lack of space."
  780. echo -n "Continue download anyway? [y/N]: "
  781. read -r FORCE_DOWNLOAD
  782. if [[ ! "${FORCE_DOWNLOAD:-N}" =~ ^[Yy]$ ]]; then
  783. info "Download cancelled due to potential space issues."
  784. press_enter
  785. return 1
  786. fi
  787. }
  788. fi
  789.  
  790. log "Downloading model from: $URL"
  791. log "This may take a while depending on file size and your connection."
  792. log "Model will be saved to: $DEST"
  793.  
  794. if command -v termux-wake-lock &>/dev/null; then
  795. termux-wake-lock
  796. fi
  797.  
  798. mkdir -p "$MODEL_DIR"
  799.  
  800. # Use curl with progress bar if available, otherwise fall back to wget
  801. if command -v curl >/dev/null 2>&1; then
  802. log "Downloading with curl..."
  803. if ! curl -L --progress-bar "$URL" -o "$DEST"; then
  804. error "Download failed. Please check the URL and your internet connection."
  805. # Clean up potentially incomplete file
  806. rm -f "$DEST" 2>/dev/null
  807. if command -v termux-wake-unlock &>/dev/null; then
  808. termux-wake-unlock
  809. fi
  810. wait_for_keypress_keep_screen
  811. return 1
  812. fi
  813. elif command -v wget >/dev/null 2>&1; then # Added explicit check for wget
  814. log "Downloading with wget..."
  815. if ! wget -c --show-progress "$URL" -O "$DEST"; then
  816. error "Download failed. Please check the URL and your internet connection."
  817. # Clean up potentially incomplete file (wget often cleans up .part files itself, but clean the target too)
  818. rm -f "$DEST" "$DEST.part" 2>/dev/null
  819. if command -v termux-wake-unlock &>/dev/null; then
  820. termux-wake-unlock
  821. fi
  822. wait_for_keypress_keep_screen
  823. return 1
  824. fi
  825. else
  826. error "Neither 'curl' nor 'wget' found. Cannot download the file."
  827. if command -v termux-wake-unlock &>/dev/null; then
  828. termux-wake-unlock
  829. fi
  830. wait_for_keypress_keep_screen
  831. return 1
  832. fi
  833.  
  834.  
  835. if command -v termux-wake-unlock &>/dev/null; then
  836. termux-wake-unlock
  837. fi
  838.  
  839. if [ -f "$DEST" ] && [ -s "$DEST" ]; then
  840. local downloaded_size=$(get_file_size_human "$DEST")
  841. success "Model '$FILENAME' ($downloaded_size) downloaded successfully to '$DEST'"
  842.  
  843. # Verify the downloaded model integrity
  844. log "Verifying integrity of downloaded model..."
  845. if check_model_integrity "$DEST"; then
  846. success "Integrity check passed for $FILENAME."
  847. else
  848. warn "Integrity check failed for $FILENAME. The downloaded model may be corrupted."
  849. warn "Consider deleting it and downloading it again."
  850. wait_for_keypress_keep_screen # Pause to show integrity warning
  851. fi
  852.  
  853. # Show notification if available
  854. if command -v termux-notification &>/dev/null; then
  855. termux-notification --title "Download Complete" --content "Model $FILENAME has been downloaded successfully."
  856. fi
  857. else
  858. error "Download failed or resulted in an empty file: File not found or empty after download completion."
  859. rm -f "$DEST" 2>/dev/null
  860. wait_for_keypress_keep_screen
  861. fi
  862.  
  863. press_enter
  864. return 0
  865. }
  866.  
  867. delete_model() {
  868. clear_screen
  869. echo -e "${BOLD}${CYAN}=== Delete Model ===${NC}"
  870. echo
  871. log "This will permanently delete a model file from your KoboldCpp models directory ($MODEL_DIR)."
  872. echo
  873.  
  874. check_install || { return 1; }
  875.  
  876. echo -e "${YELLOW}Available models in $MODEL_DIR:${NC}"
  877.  
  878. local MODELS=()
  879. while IFS= read -r -d '' file; do # Use '' for null delimiter, portable
  880. MODELS+=("$file")
  881. done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
  882.  
  883. if [ ${#MODELS[@]} -eq 0 ]; then
  884. error "No models found in '$MODEL_DIR'."
  885. wait_for_keypress_keep_screen
  886. return 1
  887. fi
  888.  
  889. for i in "${!MODELS[@]}"; do
  890. local MODEL="${MODELS[$i]}"
  891. local FILENAME
  892. FILENAME=$(basename "$MODEL")
  893. local SIZE
  894. SIZE=$(get_file_size_human "$MODEL")
  895. echo "[$i] $FILENAME ($SIZE)"
  896. done
  897.  
  898. echo
  899. echo -n "Enter number of the model to delete (or 'q' to return to menu): "
  900. read -r CHOICE
  901.  
  902. if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
  903. return 0
  904. fi
  905.  
  906. if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#MODELS[@]}" ]; then
  907. error "Invalid selection."
  908. wait_for_keypress_keep_screen
  909. return 1
  910. fi
  911.  
  912. local TARGET="${MODELS[$CHOICE]}"
  913. local FILENAME
  914. FILENAME=$(basename "$TARGET")
  915. local SIZE
  916. SIZE=$(get_file_size_human "$TARGET")
  917.  
  918. echo -e "${RED}WARNING: This action is irreversible!${NC}"
  919. echo -n "Are you sure you want to delete '$FILENAME' ($SIZE)? [y/N]: "
  920. read -r CONFIRM
  921.  
  922. if [[ "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default N
  923. log "Deleting model file: $TARGET"
  924. if rm "$TARGET"; then
  925. success "Deleted model '$FILENAME' ($SIZE freed)."
  926. else
  927. error "Failed to delete '$FILENAME'. Check permissions."
  928. wait_for_keypress_keep_screen
  929. return 1
  930. fi
  931. else
  932. info "Deletion cancelled."
  933. fi
  934.  
  935. press_enter
  936. return 0
  937. }
  938.  
  939. list_models() {
  940. clear_screen
  941. echo -e "${BOLD}${CYAN}=== Available Models ===${NC}"
  942. echo
  943. log "Listing .gguf models in the cache directory: $MODEL_DIR"
  944. echo
  945.  
  946. check_install || { return 1; }
  947. mkdir -p "$MODEL_DIR"
  948.  
  949. local MODELS=()
  950. while IFS= read -r -d '' file; do # Use '' for null delimiter, portable
  951. MODELS+=("$file")
  952. done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
  953.  
  954. if [ ${#MODELS[@]} -eq 0 ]; then
  955. echo "No models found in '$MODEL_DIR'."
  956. echo
  957. echo "You can add models using the main menu options:"
  958. echo "2. Add model from Downloads"
  959. echo "3. Download model from URL"
  960. press_enter
  961. return 0
  962. fi
  963.  
  964. echo -e "${YELLOW}Size\tStatus\tModel${NC}"
  965.  
  966. local TOTAL_SIZE_BYTES=0
  967. local CORRUPT_COUNT=0
  968.  
  969. for MODEL_PATH in "${MODELS[@]}"; do
  970. local SIZE_BYTES
  971. SIZE_BYTES=$(get_file_size_bytes "$MODEL_PATH")
  972. TOTAL_SIZE_BYTES=$((TOTAL_SIZE_BYTES + SIZE_BYTES))
  973. local HUMAN_SIZE
  974. HUMAN_SIZE=$(get_file_size_human "$MODEL_PATH")
  975. local FILENAME
  976. FILENAME=$(basename "$MODEL_PATH")
  977.  
  978. # Check if model might be corrupted
  979. local status="✓ OK"
  980. local status_color="$GREEN"
  981. if ! check_model_integrity "$MODEL_PATH" >/dev/null 2>&1; then # Check silently for listing
  982. status="⚠️ Issues"
  983. status_color="$YELLOW"
  984. ((CORRUPT_COUNT++))
  985. fi
  986.  
  987. # Use printf for consistent formatting
  988. printf "%s\t${status_color}%s${NC}\t%s\n" "$HUMAN_SIZE" "$status" "$FILENAME"
  989. done
  990.  
  991. local HUMAN_TOTAL_SIZE="0B"
  992. if [ "$TOTAL_SIZE_BYTES" -gt 0 ]; then
  993. if command -v numfmt &>/dev/null; then
  994. HUMAN_TOTAL_SIZE=$(numfmt --to=iec-i --suffix=B "$TOTAL_SIZE_BYTES")
  995. else
  996. if [ "$TOTAL_SIZE_BYTES" -ge 1073741824 ]; then
  997. HUMAN_TOTAL_SIZE=$(awk -v ts="$TOTAL_SIZE_BYTES" 'BEGIN { printf "%.2fGiB", ts/1073741824 }')
  998. elif [ "$TOTAL_SIZE_BYTES" -ge 1048576 ]; then
  999. HUMAN_TOTAL_SIZE=$(awk -v ts="$TOTAL_SIZE_BYTES" 'BEGIN { printf "%.2fMiB", ts/1048576 }')
  1000. elif [ "$TOTAL_SIZE_BYTES" -ge 1024 ]; then
  1001. HUMAN_TOTAL_SIZE=$(awk -v ts="$TOTAL_SIZE_BYTES" 'BEGIN { printf "%.2fKiB", ts/1024 }')
  1002. else
  1003. HUMAN_TOTAL_SIZE="${TOTAL_SIZE_BYTES}B"
  1004. fi
  1005. fi
  1006. fi
  1007.  
  1008. echo -e "${YELLOW}------------------------------------${NC}"
  1009. echo -e "${GREEN}Total Models:\t${#MODELS[@]}${NC}"
  1010. echo -e "${GREEN}Total Size:\t$HUMAN_TOTAL_SIZE${NC}"
  1011. if [ "$CORRUPT_COUNT" -gt 0 ]; then
  1012. echo -e "${YELLOW}Models with Potential Issues:\t$CORRUPT_COUNT${NC}"
  1013. fi
  1014. echo
  1015. echo "Legend: ✓ OK = Basic checks passed, ⚠️ Issues = Potential corruption or not a GGUF file"
  1016.  
  1017. press_enter
  1018. return 0
  1019. }
  1020.  
  1021. # New function to interactively verify a specific model's integrity
  1022. verify_model_integrity_interactive() {
  1023. clear_screen
  1024. echo -e "${BOLD}${CYAN}=== Verify Model Integrity ===${NC}"
  1025. echo
  1026. log "This will perform basic checks on a selected model file to detect potential corruption."
  1027. echo
  1028.  
  1029. check_install || { return 1; }
  1030. mkdir -p "$MODEL_DIR" # Ensure directory exists before checking
  1031.  
  1032. local MODELS=()
  1033. while IFS= read -r -d '' file; do
  1034. MODELS+=("$file")
  1035. done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
  1036.  
  1037. if [ ${#MODELS[@]} -eq 0 ]; then
  1038. error "No models found in '$MODEL_DIR' to verify."
  1039. wait_for_keypress_keep_screen
  1040. return 1
  1041. fi
  1042.  
  1043. echo -e "${YELLOW}Available models in $MODEL_DIR:${NC}"
  1044. for i in "${!MODELS[@]}"; do
  1045. local MODEL="${MODELS[$i]}"
  1046. local FILENAME=$(basename "$MODEL")
  1047. local SIZE=$(get_file_size_human "$MODEL")
  1048. echo "[$i] $FILENAME ($SIZE)"
  1049. done
  1050.  
  1051. echo
  1052. echo -n "Enter number of the model to verify (or 'q' to return to menu): "
  1053. read -r CHOICE
  1054.  
  1055. if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
  1056. return 0
  1057. fi
  1058.  
  1059. if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#MODELS[@]}" ]; then
  1060. error "Invalid selection."
  1061. wait_for_keypress_keep_screen
  1062. return 1
  1063. fi
  1064.  
  1065. local TARGET_MODEL_PATH="${MODELS[$CHOICE]}"
  1066. local TARGET_MODEL_FILENAME=$(basename "$TARGET_MODEL_PATH")
  1067.  
  1068. log "Verifying integrity of '$TARGET_MODEL_FILENAME'..."
  1069. if check_model_integrity "$TARGET_MODEL_PATH"; then
  1070. success "Model '$TARGET_MODEL_FILENAME' appears to be intact based on basic checks."
  1071. else
  1072. error "Model '$TARGET_MODEL_FILENAME' failed integrity checks. It may be corrupted or incomplete."
  1073. info "This model might not load or run correctly."
  1074. fi
  1075.  
  1076. press_enter
  1077. return 0
  1078. }
  1079.  
  1080.  
  1081. run_model() {
  1082. clear_screen
  1083. echo -e "${BOLD}${CYAN}=== Run KoboldCpp ===${NC}"
  1084. echo
  1085. log "Starting the KoboldCpp server with a selected model from the cache directory ($MODEL_DIR)."
  1086. echo
  1087.  
  1088. check_install || { return 1; }
  1089. mkdir -p "$MODEL_DIR"
  1090.  
  1091. echo -e "${YELLOW}Available models:${NC}"
  1092.  
  1093. local MODELS=()
  1094. while IFS= read -r -d '' file; do # Use '' for null delimiter, portable
  1095. MODELS+=("$file")
  1096. done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
  1097.  
  1098. if [ ${#MODELS[@]} -eq 0 ]; then
  1099. error "No models found in '$MODEL_DIR'."
  1100. echo
  1101. echo "You need to add models first. Return to the main menu and select:"
  1102. echo "2. Add model from Downloads"
  1103. echo "3. Download model from URL"
  1104. wait_for_keypress_keep_screen
  1105. return 1
  1106. fi
  1107.  
  1108. # Sort models by last used time if available
  1109. local last_used_model=""
  1110. if [ -f "$CONFIG_FILE" ]; then
  1111. last_used_model=$(grep "^last_used_model=" "$CONFIG_FILE" | cut -d= -f2)
  1112.  
  1113. # Ensure last_used_model still exists
  1114. if [ -n "$last_used_model" ] && [ ! -f "$last_used_model" ]; then
  1115. warn "Last used model '$last_used_model' not found. Resetting last used model setting."
  1116. last_used_model=""
  1117. # Consider removing the invalid config entry
  1118. # sed -i '/^last_used_model=/d' "$CONFIG_FILE"
  1119. fi
  1120.  
  1121. if [ -n "$last_used_model" ]; then
  1122. for i in "${!MODELS[@]}"; do
  1123. if [ "${MODELS[$i]}" = "$last_used_model" ]; then
  1124. # Move last used model to the top
  1125. local temp=${MODELS[0]}
  1126. MODELS[0]=${MODELS[$i]}
  1127. MODELS[$i]=$temp
  1128. break
  1129. fi
  1130. done
  1131. fi
  1132. fi
  1133.  
  1134. for i in "${!MODELS[@]}"; do
  1135. local MODEL="${MODELS[$i]}"
  1136. local FILENAME
  1137. FILENAME=$(basename "$MODEL")
  1138. local SIZE
  1139. SIZE=$(get_file_size_human "$MODEL")
  1140.  
  1141. # Mark the last used model
  1142. local marker=""
  1143. if [ "$MODEL" = "$last_used_model" ]; then
  1144. marker=" (last used)"
  1145. fi
  1146.  
  1147. echo "[$i] $FILENAME ($SIZE)$marker"
  1148. done
  1149.  
  1150. echo
  1151. echo -n "Enter number of the model to run (or 'q' to return to menu): "
  1152. read -r CHOICE
  1153.  
  1154. if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
  1155. return 0
  1156. fi
  1157.  
  1158. if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#MODELS[@]}" ]; then
  1159. error "Invalid selection."
  1160. wait_for_keypress_keep_screen
  1161. return 1
  1162. fi
  1163.  
  1164. local TARGET_MODEL_PATH="${MODELS[$CHOICE]}"
  1165. local TARGET_MODEL_FILENAME=$(basename "$TARGET_MODEL_PATH")
  1166. local TARGET_MODEL_SIZE=$(get_file_size_human "$TARGET_MODEL_PATH")
  1167.  
  1168. # Save last used model
  1169. save_config "last_used_model" "$TARGET_MODEL_PATH"
  1170.  
  1171. # Check model integrity before running
  1172. if ! check_model_integrity "$TARGET_MODEL_PATH"; then
  1173. echo -e "${RED}WARNING: This model may have integrity issues.${NC}"
  1174. echo -n "Do you still want to continue? [y/N]: "
  1175. read -r INTEGRITY_CONFIRM
  1176. if [[ ! "${INTEGRITY_CONFIRM:-N}" =~ ^[Yy]$ ]]; then
  1177. info "Operation cancelled."
  1178. press_enter
  1179. return 0
  1180. fi
  1181. fi
  1182.  
  1183. clear_screen
  1184. echo -e "${BOLD}${CYAN}=== Run Configuration: $TARGET_MODEL_FILENAME ===${NC}"
  1185. echo
  1186. echo "Model: $TARGET_MODEL_FILENAME ($TARGET_MODEL_SIZE)"
  1187. echo
  1188. info "You can customize parameters like threads, context size, and port."
  1189. info "For optimal performance, consider GPU acceleration if your device/build supports CLBlast (--useclblast 0 0)."
  1190. echo
  1191.  
  1192. # Load saved configuration if exists
  1193. local saved_config=false
  1194. local saved_threads=""
  1195. local saved_context=""
  1196. local saved_port=""
  1197. local saved_extra=""
  1198.  
  1199. local model_config_key="model_${TARGET_MODEL_FILENAME//[^a-zA-Z0-9_]/_}" # Sanitize filename for config key
  1200.  
  1201. if [ -f "$CONFIG_FILE" ]; then
  1202. saved_threads=$(grep "^${model_config_key}_threads=" "$CONFIG_FILE" | cut -d= -f2)
  1203. saved_context=$(grep "^${model_config_key}_context=" "$CONFIG_FILE" | cut -d= -f2)
  1204. saved_port=$(grep "^${model_config_key}_port=" "$CONFIG_FILE" | cut -d= -f2)
  1205. saved_extra=$(grep "^${model_config_key}_extra=" "$CONFIG_FILE" | cut -d= -f2)
  1206.  
  1207. if [ -n "$saved_threads" ] && [ -n "$saved_context" ] && [ -n "$saved_port" ]; then
  1208. saved_config=true
  1209. echo "Found saved configuration for this model:"
  1210. echo "- Threads: ${YELLOW}$saved_threads${NC}"
  1211. echo "- Context: ${YELLOW}$saved_context${NC}"
  1212. echo "- Port: ${YELLOW}$saved_port${NC}"
  1213. if [ -n "$saved_extra" ]; then
  1214. echo "- Extra options: ${YELLOW}$saved_extra${NC}"
  1215. fi
  1216. echo
  1217. fi
  1218. fi
  1219.  
  1220. echo "Select run configuration:"
  1221. echo "[1] Quick start (recommended settings for this device)"
  1222. if [ "$saved_config" = true ]; then
  1223. echo "[2] Use saved configuration for this model"
  1224. echo "[3] Custom settings"
  1225. else
  1226. echo "[2] Custom settings"
  1227. fi
  1228. echo "[q] Return to main menu"
  1229. echo
  1230. echo -n "Your choice: "
  1231. read -r CONFIG_CHOICE
  1232.  
  1233. local ARGS_STRING=""
  1234. local PORT_NUMBER="5001"
  1235. local NPROC_COUNT=4
  1236. if command -v nproc &>/dev/null; then
  1237. NPROC_COUNT=$(nproc)
  1238. if [ "$NPROC_COUNT" -eq 0 ]; then NPROC_COUNT=1; fi # Avoid 0 threads
  1239. fi
  1240. local SUGGESTED_THREADS="$NPROC_COUNT"
  1241. if [ "$NPROC_COUNT" -gt 8 ]; then # Suggest fewer threads on high-core count devices for balance
  1242. SUGGESTED_THREADS=$((NPROC_COUNT / 2))
  1243. if [ "$SUGGESTED_THREADS" -lt 4 ]; then SUGGESTED_THREADS=4; fi
  1244. fi
  1245.  
  1246.  
  1247. local current_choice="${CONFIG_CHOICE:-1}" # Default to 1 (Quick Start)
  1248. if [ "$saved_config" = false ] && [ "$current_choice" = "3" ]; then
  1249. # If saved_config is false, option 3 means custom, but the menu showed [2] Custom. Adjust choice.
  1250. current_choice="2"
  1251. fi
  1252.  
  1253.  
  1254. case "$current_choice" in
  1255. 1)
  1256. ARGS_STRING="--threads ${SUGGESTED_THREADS} --contextsize 2048 --port $PORT_NUMBER"
  1257. info "Using Quick Start with $SUGGESTED_THREADS threads, context 2048, port $PORT_NUMBER."
  1258. ;;
  1259. 2)
  1260. if [ "$saved_config" = true ]; then
  1261. # Use saved configuration (only if option 2 was explicitly chosen AND saved config exists)
  1262. ARGS_STRING="--threads $saved_threads --contextsize $saved_context --port $saved_port"
  1263. if [ -n "$saved_extra" ]; then
  1264. ARGS_STRING="$ARGS_STRING $saved_extra"
  1265. fi
  1266. info "Using saved configuration with $saved_threads threads, context $saved_context, port $saved_port."
  1267. else
  1268. # Saved config doesn't exist, this was the "Custom settings" option
  1269. # Fall through to custom settings block
  1270. CONFIG_CHOICE="custom" # Internal flag
  1271. fi
  1272. ;;
  1273. 3) # This case is only reached if saved_config is true AND user chose 3
  1274. CONFIG_CHOICE="custom" # Internal flag
  1275. ;;
  1276. q|Q)
  1277. return 0
  1278. ;;
  1279. *)
  1280. info "Invalid configuration choice, using Quick Start defaults."
  1281. ARGS_STRING="--threads ${SUGGESTED_THREADS} --contextsize 2048 --port $PORT_NUMBER"
  1282. ;;
  1283. esac
  1284.  
  1285. # Custom settings block (executed if CONFIG_CHOICE was "custom")
  1286. if [ "${CONFIG_CHOICE:-}" = "custom" ]; then
  1287. clear_screen
  1288. echo -e "${BOLD}${CYAN}=== Custom Settings for $TARGET_MODEL_FILENAME ===${NC}"
  1289. echo
  1290.  
  1291. echo -n "Number of CPU threads (suggested: $SUGGESTED_THREADS, max detected: $NPROC_COUNT): "
  1292. read -r THREADS_INPUT
  1293. local THREADS=${THREADS_INPUT:-$SUGGESTED_THREADS}
  1294. # Add validation for threads input
  1295. if ! [[ "$THREADS" =~ ^[0-9]+$ ]] || [ "$THREADS" -le 0 ]; then
  1296. warn "Invalid thread count '$THREADS_INPUT', using suggested count: $SUGGESTED_THREADS."
  1297. THREADS="$SUGGESTED_THREADS"
  1298. fi
  1299. ARGS_STRING="--threads $THREADS"
  1300.  
  1301. # Get model size to suggest appropriate context size
  1302. local MODEL_SIZE_BYTES=$(get_file_size_bytes "$TARGET_MODEL_PATH")
  1303. local SUGGESTED_CONTEXT=2048
  1304. if [ "$MODEL_SIZE_BYTES" -gt 10737418240 ]; then # >10GB models (10 * 1024^3)
  1305. SUGGESTED_CONTEXT=4096
  1306. elif [ "$MODEL_SIZE_BYTES" -lt 5368709120 ]; then # <5GB models (5 * 1024^3)
  1307. SUGGESTED_CONTEXT=8192
  1308. fi
  1309.  
  1310. echo -n "Context size (e.g., 2048, 4096, 8192; suggested: $SUGGESTED_CONTEXT): "
  1311. read -r CONTEXT_INPUT
  1312. local CONTEXT=${CONTEXT_INPUT:-$SUGGESTED_CONTEXT}
  1313. # Add validation for context size input
  1314. if ! [[ "$CONTEXT" =~ ^[0-9]+$ ]] || [ "$CONTEXT" -le 0 ]; then
  1315. warn "Invalid context size '$CONTEXT_INPUT', using suggested size: $SUGGESTED_CONTEXT."
  1316. CONTEXT="$SUGGESTED_CONTEXT"
  1317. fi
  1318.  
  1319. ARGS_STRING="$ARGS_STRING --contextsize $CONTEXT"
  1320.  
  1321. echo -n "Server port (default: 5001): "
  1322. read -r PORT_INPUT
  1323. PORT_NUMBER=${PORT_INPUT:-5001}
  1324. # Add validation for port number
  1325. if ! [[ "$PORT_NUMBER" =~ ^[0-9]+$ ]] || [ "$PORT_NUMBER" -lt 1024 ] || [ "$PORT_NUMBER" -gt 65535 ]; then
  1326. warn "Invalid port number '$PORT_INPUT', using default port: 5001."
  1327. PORT_NUMBER="5001"
  1328. fi
  1329.  
  1330. ARGS_STRING="$ARGS_STRING --port $PORT_NUMBER"
  1331.  
  1332. echo
  1333. echo "Additional options:"
  1334. echo "[1] No additional options - continue with settings above"
  1335. echo "[2] Enable GPU acceleration (CLBlast - if supported by your device/build)"
  1336. echo "[3] Use low VRAM mode (slower, uses less memory, implies --nostream)"
  1337. echo "[4] Enable Mmapped IO (faster loading but more memory usage)"
  1338. echo "[5] Set custom LORA files (if you have them)"
  1339. echo "[6] Custom extra arguments (enter raw arguments)"
  1340. echo
  1341. echo -n "Your choice for additional options [1]: "
  1342. read -r EXTRA_OPTIONS_CHOICE
  1343.  
  1344. local EXTRA_ARGS_VAL=""
  1345. case "${EXTRA_OPTIONS_CHOICE:-1}" in # Default to 1
  1346. 2)
  1347. EXTRA_ARGS_VAL="--useclblast 0 0"
  1348. info "GPU acceleration (CLBlast) enabled. Ensure your KoboldCpp is built with CLBlast support."
  1349. ;;
  1350. 3)
  1351. EXTRA_ARGS_VAL="--lowvram --nostream"
  1352. info "Low VRAM mode enabled."
  1353. ;;
  1354. 4)
  1355. EXTRA_ARGS_VAL="--mmap"
  1356. info "Memory-mapped IO enabled."
  1357. ;;
  1358. 5)
  1359. echo
  1360. echo "Enter full path to LORA file:"
  1361. read -r LORA_PATH
  1362. if [ -n "$LORA_PATH" ] && [ -f "$LORA_PATH" ]; then
  1363. echo "Enter LORA scale factor (default: 1.0):"
  1364. read -r LORA_SCALE
  1365. LORA_SCALE=${LORA_SCALE:-1.0}
  1366. EXTRA_ARGS_VAL="--lora \"$LORA_PATH:$LORA_SCALE\"" # Quote path in case of spaces
  1367. info "LORA '$LORA_PATH' will be loaded with scale $LORA_SCALE."
  1368. else
  1369. warn "LORA file not found or path empty. No LORA will be loaded."
  1370. fi
  1371. ;;
  1372. 6)
  1373. echo
  1374. echo "Enter custom additional arguments (e.g., --gpulayers 10 --unbantokens):"
  1375. read -r RAW_EXTRA_ARGS
  1376. EXTRA_ARGS_VAL="$RAW_EXTRA_ARGS"
  1377. info "Using custom extra arguments: $RAW_EXTRA_ARGS"
  1378. ;;
  1379. *) # Includes 1 and any other input
  1380. info "No additional special options selected."
  1381. ;;
  1382. esac
  1383.  
  1384. if [ -n "$EXTRA_ARGS_VAL" ]; then
  1385. ARGS_STRING="$ARGS_STRING $EXTRA_ARGS_VAL"
  1386. fi
  1387.  
  1388. # Save this configuration for future use
  1389. echo -n "Save this configuration for future use with this model? [Y/n]: "
  1390. read -r SAVE_CONFIG_CHOICE
  1391. if [[ ! "${SAVE_CONFIG_CHOICE:-Y}" =~ ^[Nn]$ ]]; then # Default Y
  1392. save_config "${model_config_key}_threads" "$THREADS"
  1393. save_config "${model_config_key}_context" "$CONTEXT"
  1394. save_config "${model_config_key}_port" "$PORT_NUMBER"
  1395. save_config "${model_config_key}_extra" "$EXTRA_ARGS_VAL"
  1396. success "Configuration saved for future use."
  1397. fi
  1398. fi # End Custom settings block
  1399.  
  1400.  
  1401. clear_screen
  1402. log "Starting KoboldCpp with model: $TARGET_MODEL_FILENAME ($TARGET_MODEL_SIZE)"
  1403. if [ -n "$ARGS_STRING" ]; then
  1404. log "Run arguments: $ARGS_STRING"
  1405. fi
  1406.  
  1407. # Show available network interfaces
  1408. local DEVICE_IP_MSG=""
  1409. if command -v ip &>/dev/null; then
  1410. local interface_ip
  1411. interface_ip=$(ip -o -4 addr show scope global | awk '!/ lo / && $4 !~ /^169\.254\./ {print $4}' | cut -d/ -f1 | head -n1)
  1412. if [ -n "$interface_ip" ]; then
  1413. DEVICE_IP_MSG=" (or http://$interface_ip:$PORT_NUMBER from another device on the same network)"
  1414. fi
  1415. fi
  1416. log "Server will be available at: http://localhost:$PORT_NUMBER${DEVICE_IP_MSG}"
  1417. log "Press Ctrl+C in this terminal to stop the server."
  1418. echo
  1419.  
  1420. cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
  1421.  
  1422. local KOBOLDCPP_BASE_CMD=""
  1423. if [ -f "./koboldcpp-android-arm64" ] && [ -x "./koboldcpp-android-arm64" ]; then
  1424. KOBOLDCPP_BASE_CMD="./koboldcpp-android-arm64"
  1425. elif [ -f "./koboldcpp" ] && [ -x "./koboldcpp" ]; then
  1426. KOBOLDCPP_BASE_CMD="./koboldcpp"
  1427. elif [ -f "./koboldcpp.py" ]; then
  1428. KOBOLDCPP_BASE_CMD="python koboldcpp.py"
  1429. else
  1430. error "Could not find a runnable KoboldCpp executable (e.g., koboldcpp-android-arm64, koboldcpp) or koboldcpp.py script in $KOBOLDCPP_DIR."
  1431. info "Please ensure KoboldCpp is installed and built correctly."
  1432. wait_for_keypress_keep_screen
  1433. return 1
  1434. fi
  1435.  
  1436. local cmd_array=()
  1437. if [[ "$KOBOLDCPP_BASE_CMD" == "python koboldcpp.py" ]]; then
  1438. cmd_array+=("python" "koboldcpp.py")
  1439. else
  1440. cmd_array+=("$KOBOLDCPP_BASE_CMD")
  1441. fi
  1442. cmd_array+=("--model" "$TARGET_MODEL_PATH")
  1443.  
  1444. local args_temp_array=()
  1445. # Safely split ARGS_STRING into arguments, handling quotes
  1446. # This is tricky in pure bash without eval/set -f, but read -r -a is better than simple split
  1447. # Using a temporary array and disabling/enabling globbing around read -a
  1448. set -f # Disable globbing
  1449. read -r -a args_temp_array <<< "$ARGS_STRING"
  1450. set +f # Re-enable globbing
  1451.  
  1452. if [ ${#args_temp_array[@]} -gt 0 ]; then
  1453. cmd_array+=("${args_temp_array[@]}")
  1454. fi
  1455.  
  1456. info "Executing: ${cmd_array[*]}"
  1457.  
  1458. if command -v termux-wake-lock &>/dev/null; then
  1459. termux-wake-lock
  1460. fi
  1461.  
  1462. # Start time measurement
  1463. local start_time=$(date +%s)
  1464.  
  1465. # Execute the command array
  1466. if ! "${cmd_array[@]}"; then
  1467. local exit_code=$?
  1468. local end_time=$(date +%s)
  1469. local runtime=$((end_time - start_time))
  1470. local hours=$((runtime / 3600))
  1471. local minutes=$(( (runtime % 3600) / 60 ))
  1472. local seconds=$((runtime % 60))
  1473.  
  1474. warn "KoboldCpp server exited with code $exit_code. Runtime: ${hours}h ${minutes}m ${seconds}s"
  1475. echo "Check the console output above for error messages."
  1476. # Save partial runtime statistics on error
  1477. save_config "last_runtime_hours" "$hours"
  1478. save_config "last_runtime_minutes" "$minutes"
  1479. save_config "last_runtime_seconds" "$seconds"
  1480. save_config "last_runtime_model" "$TARGET_MODEL_FILENAME (exit $exit_code)"
  1481.  
  1482. if command -v termux-wake-unlock &>/dev/null; then
  1483. termux-wake-unlock
  1484. fi
  1485. wait_for_keypress_keep_screen
  1486. return 1
  1487. fi
  1488.  
  1489. # End time measurement (only if command finished without error)
  1490. local end_time=$(date +%s)
  1491. local runtime=$((end_time - start_time))
  1492. local hours=$((runtime / 3600))
  1493. local minutes=$(( (runtime % 3600) / 60 ))
  1494. local seconds=$((runtime % 60))
  1495.  
  1496. # Save runtime statistics
  1497. save_config "last_runtime_hours" "$hours"
  1498. save_config "last_runtime_minutes" "$minutes"
  1499. save_config "last_runtime_seconds" "$seconds"
  1500. save_config "last_runtime_model" "$TARGET_MODEL_FILENAME"
  1501.  
  1502. if command -v termux-wake-unlock &>/dev/null; then
  1503. log "Releasing wake lock..."
  1504. termux-wake-unlock
  1505. fi
  1506.  
  1507. echo
  1508. info "KoboldCpp server stopped normally. Runtime: ${hours}h ${minutes}m ${seconds}s"
  1509.  
  1510. press_enter
  1511. return 0
  1512. }
  1513.  
  1514. rebuild() {
  1515. clear_screen
  1516. echo -e "${BOLD}${CYAN}=== Rebuild KoboldCpp ===${NC}"
  1517. echo
  1518. log "Rebuilding KoboldCpp from source in $KOBOLDCPP_DIR..."
  1519.  
  1520. # Check if KoboldCpp directory exists and is a git repo
  1521. if [ ! -d "$KOBOLDCPP_DIR/.git" ]; then
  1522. error "KoboldCpp directory '$KOBOLDCPP_DIR' is not a git repository. Cannot rebuild."
  1523. info "Please use the 'Install / Update / Rebuild' option from the main menu to install first."
  1524. wait_for_keypress_keep_screen
  1525. return 1
  1526. fi
  1527.  
  1528. cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
  1529.  
  1530. if command -v termux-wake-lock &>/dev/null; then
  1531. termux-wake-lock
  1532. fi
  1533.  
  1534. log "Cleaning build files..."
  1535. make clean || warn "Failed to clean build files, attempting rebuild anyway."
  1536.  
  1537. # Get system information for optimized build
  1538. local device_info=$(get_device_info)
  1539. log "Building for: $device_info"
  1540.  
  1541. # Try to use previously successful build options
  1542. local build_options=$(load_config "build_options" "0") # Default to 0 (default build)
  1543.  
  1544. export LLAMA_PORTABLE=1
  1545. export KOBOLDCPP_PLATFORM="android"
  1546.  
  1547. local build_command="make -j$(nproc)"
  1548. if [ "$build_options" = "1" ]; then
  1549. log "Using previously successful build options (LLAMA_NO_ACCELERATE=1)..."
  1550. build_command="make -j$(nproc) LLAMA_NO_ACCELERATE=1"
  1551. fi
  1552.  
  1553. log "Starting build with command: $build_command"
  1554. # Use eval to execute the build_command string
  1555. if eval "$build_command"; then
  1556. # If the build was successful, save the options used
  1557. if [[ "$build_command" == *"LLAMA_NO_ACCELERATE=1"* ]]; then
  1558. save_config "build_options" "1"
  1559. else
  1560. save_config "build_options" "0"
  1561. fi
  1562. success "KoboldCpp rebuilt successfully."
  1563. else
  1564. local build_exit_code=$?
  1565. error "Rebuild failed with command: $build_command (Exit code $build_exit_code)."
  1566. # If default build failed, try safer settings regardless of previous config
  1567. if [ "$build_options" != "1" ]; then
  1568. warn "Trying rebuild with safer settings (LLAMA_NO_ACCELERATE=1)..."
  1569. make clean # Clean before retrying
  1570. if make LLAMA_NO_ACCELERATE=1; then
  1571. success "KoboldCpp rebuilt successfully with LLAMA_NO_ACCELERATE=1."
  1572. save_config "build_options" "1" # Save the successful option
  1573. else
  1574. error "Rebuild failed even with safer settings. Please check error messages."
  1575. wait_for_keypress_keep_screen
  1576. return 1
  1577. fi
  1578. else
  1579. # Safer settings were already tried or previously saved, and it failed again
  1580. error "Rebuild failed with saved safer settings. Please check error messages."
  1581. wait_for_keypress_keep_screen
  1582. return 1
  1583. fi
  1584. fi
  1585.  
  1586.  
  1587. if command -v termux-wake-unlock &>/dev/null; then
  1588. termux-wake-unlock
  1589. fi
  1590.  
  1591. press_enter
  1592. return 0
  1593. }
  1594.  
  1595. update_koboldcpp() {
  1596. clear_screen
  1597. echo -e "${BOLD}${CYAN}=== Update KoboldCpp ===${NC}"
  1598. echo
  1599. log "Updating the KoboldCpp source code from the repository."
  1600.  
  1601. # Check if KoboldCpp directory exists and is a git repo
  1602. if [ ! -d "$KOBOLDCPP_DIR/.git" ]; then
  1603. error "KoboldCpp directory '$KOBOLDCPP_DIR' is not a git repository. Cannot update."
  1604. info "Please use the 'Install / Update / Rebuild' option from the main menu to install first."
  1605. wait_for_keypress_keep_screen
  1606. return 1
  1607. fi
  1608.  
  1609. check_install || { return 1; }
  1610. check_internet || {
  1611. warn "Internet connection is required to update KoboldCpp."
  1612. echo -n "Do you want to continue anyway? [y/N]: "
  1613. read -r CONTINUE
  1614. if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
  1615. press_enter
  1616. return 0
  1617. fi
  1618. }
  1619.  
  1620. log "Checking for KoboldCpp updates..."
  1621. cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
  1622.  
  1623. # Check for updates before proceeding
  1624. # Use git fetch --dry-run or similar for a cleaner check without fetching
  1625. # git fetch origin "$DEFAULT_BRANCH" --quiet --dry-run # --dry-run isn't always supported or clear
  1626. # Alternative: fetch and compare
  1627. if ! git fetch origin "$DEFAULT_BRANCH" --quiet; then
  1628. warn "Failed to fetch updates from origin. Cannot determine if updates are available."
  1629. echo -n "Attempt update anyway? [y/N]: "
  1630. read -r ATTEMPT_UPDATE
  1631. if [[ ! "${ATTEMPT_UPDATE:-N}" =~ ^[Yy]$ ]]; then
  1632. press_enter
  1633. return 0
  1634. fi
  1635. # If user wants to attempt, proceed with reset below which might fail or succeed with old data
  1636. local updates_available=false # Assume no updates to skip the log output below
  1637. else
  1638. local LOCAL=$(git rev-parse HEAD)
  1639. local REMOTE=$(git rev-parse origin/"$DEFAULT_BRANCH")
  1640.  
  1641. if [ "$LOCAL" = "$REMOTE" ]; then
  1642. info "KoboldCpp is already up to date (on branch '$DEFAULT_BRANCH')."
  1643. echo -n "Force update/rebuild anyway? [y/N]: "
  1644. read -r FORCE_UPDATE
  1645. if [[ ! "${FORCE_UPDATE:-N}" =~ ^[Yy]$ ]]; then
  1646. press_enter
  1647. return 0
  1648. fi
  1649. local updates_available=false # No actual updates
  1650. else
  1651. echo -e "${YELLOW}Updates available:${NC}"
  1652. git log --oneline --graph --decorate HEAD..origin/"$DEFAULT_BRANCH" | head -n 10
  1653. if [ "$(git log --oneline HEAD..origin/"$DEFAULT_BRANCH" 2>/dev/null | wc -l)" -gt 10 ]; then
  1654. echo "... and more changes"
  1655. fi
  1656. echo
  1657. local updates_available=true
  1658. fi
  1659. fi
  1660.  
  1661.  
  1662. echo -n "This will fetch the latest version of KoboldCpp from '$DEFAULT_BRANCH' branch and rebuild. Any local changes not committed might be lost (they will be stashed). Continue? [Y/n]: "
  1663. read -r CONFIRM
  1664. if [[ "${CONFIRM:-Y}" =~ ^[Nn]$ ]]; then # Default Y
  1665. info "Update cancelled."
  1666. press_enter
  1667. return 0
  1668. fi
  1669.  
  1670. local stashed_changes=false
  1671. local stash_pop_failed=false
  1672. # Check for uncommitted changes that are tracked or untracked
  1673. if ! git diff-index --quiet HEAD -- || git ls-files --others --exclude-standard | grep .; then
  1674. log "Stashing local changes..."
  1675. if git stash push -u -m "Auto-stash by KCPP Manager script $(date)"; then
  1676. stashed_changes=true
  1677. info "Local changes stashed."
  1678. else
  1679. warn "Failed to stash local changes. If you have modifications, they might be overwritten or cause conflicts."
  1680. fi
  1681. else
  1682. info "No local changes to stash."
  1683. fi
  1684.  
  1685. log "Fetching updates from origin..."
  1686. # Re-fetch in case --quiet fetch missed something or was skipped earlier
  1687. if ! git fetch --all --prune; then
  1688. error "Failed to fetch updates from repository."
  1689. # Attempt to reapply stash before exiting on fetch failure
  1690. if [ "$stashed_changes" = true ]; then
  1691. info "Attempting to reapply stashed changes after fetch failure..."
  1692. if git stash pop; then
  1693. success "Successfully reapplied stashed local changes."
  1694. else
  1695. warn "Could not automatically reapply stashed local changes after fetch failure. Conflicts may have occurred."
  1696. info "Your changes are still available in the stash. Use 'git stash list' to see them and 'git stash apply stash@{0}' (or similar) to reapply manually."
  1697. stash_pop_failed=true
  1698. fi
  1699. fi
  1700. if [ "$stash_pop_failed" = true ]; then wait_for_keypress_keep_screen; fi
  1701. return 1
  1702. fi
  1703.  
  1704. log "Resetting to latest version of '$DEFAULT_BRANCH'..."
  1705. if ! git reset --hard "origin/$DEFAULT_BRANCH"; then
  1706. error "Failed to update to latest version of branch '$DEFAULT_BRANCH'. Your local repository might be in an inconsistent state."
  1707. # Attempt to reapply stash before exiting on reset failure
  1708. if [ "$stashed_changes" = true ]; then
  1709. info "Attempting to reapply stashed changes after reset failure..."
  1710. if git stash pop; then
  1711. success "Successfully reapplied stashed local changes."
  1712. else
  1713. warn "Could not automatically reapply stashed local changes after reset failure. Conflicts may have occurred."
  1714. info "Your changes are still available in the stash. Use 'git stash list' to see them and 'git stash apply stash@{0}' (or similar) to reapply manually."
  1715. stash_pop_failed=true
  1716. fi
  1717. fi
  1718. if [ "$stash_pop_failed" = true ]; then wait_for_keypress_keep_screen; fi
  1719. return 1
  1720. fi
  1721.  
  1722. log "Rebuilding KoboldCpp after update..."
  1723. if ! rebuild; then
  1724. # rebuild() calls wait_for_keypress_keep_screen on its own error
  1725. # Stash reapplying is handled below after rebuild attempt
  1726. local rebuild_failed=true
  1727. else
  1728. local rebuild_failed=false
  1729. fi
  1730.  
  1731.  
  1732. if [ "$stashed_changes" = true ]; then
  1733. info "Attempting to reapply stashed local changes..."
  1734. if git stash pop; then
  1735. success "Successfully reapplied stashed local changes."
  1736. else
  1737. stash_pop_failed=true
  1738. warn "Could not automatically reapply stashed local changes. Conflicts may have occurred."
  1739. info "Your changes are still available in the stash. Use 'git stash list' to see them and 'git stash apply stash@{0}' (or similar) to reapply manually."
  1740. fi
  1741. fi
  1742.  
  1743. if [ "$rebuild_failed" = true ]; then
  1744. error "KoboldCpp update completed (source fetched and reset), but rebuild failed."
  1745. wait_for_keypress_keep_screen # Keep rebuild error message visible
  1746. return 1 # Indicate failure
  1747. elif [ "$updates_available" = true ]; then
  1748. success "KoboldCpp updated to the latest version from '$DEFAULT_BRANCH' and rebuilt."
  1749. else
  1750. info "KoboldCpp source code was already up-to-date."
  1751. success "Rebuild completed."
  1752. fi
  1753.  
  1754.  
  1755. if [ "$stash_pop_failed" = true ]; then
  1756. wait_for_keypress_keep_screen # Keep complex stash message visible
  1757. else
  1758. press_enter
  1759. fi
  1760. return 0
  1761. }
  1762.  
  1763. backup_models() {
  1764. clear_screen
  1765. echo -e "${BOLD}${CYAN}=== Backup Models ===${NC}"
  1766. echo
  1767. log "Creating a compressed archive of all .gguf models in '$MODEL_DIR'."
  1768. echo
  1769.  
  1770. check_install || { return 1; }
  1771.  
  1772. _validate_critical_directory_path "$MODEL_DIR" "$EXPECTED_MODEL_DIR_PATH" "backup" || {
  1773. wait_for_keypress_keep_screen; return 1;
  1774. }
  1775.  
  1776. local MODELS_EXIST_COUNT
  1777. MODELS_EXIST_COUNT=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | awk 'END{print NR}' RS='\0')
  1778. if [ "${MODELS_EXIST_COUNT:-0}" -eq 0 ]; then
  1779. warn "No models found in '$MODEL_DIR' to backup."
  1780. press_enter
  1781. return 0
  1782. fi
  1783.  
  1784. mkdir -p "$BACKUP_DIR" || { error "Failed to create backup directory: '$BACKUP_DIR'"; wait_for_keypress_keep_screen; return 1; }
  1785. local BACKUP_FILE="$BACKUP_DIR/koboldcpp_models_$(date +%Y%m%d_%H%M%S).tar.gz"
  1786.  
  1787. # Estimate backup size
  1788. local TOTAL_MODELS_SIZE_BYTES=0
  1789. while IFS= read -r -d '' model_file; do
  1790. local file_size=$(get_file_size_bytes "$model_file")
  1791. TOTAL_MODELS_SIZE_BYTES=$((TOTAL_MODELS_SIZE_BYTES + file_size))
  1792. done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null)
  1793.  
  1794. if [ "$TOTAL_MODELS_SIZE_BYTES" -eq 0 ]; then
  1795. warn "Found .gguf files, but their total size is zero. Skipping backup."
  1796. press_enter
  1797. return 0
  1798. fi
  1799.  
  1800. # Add 5% overhead for tar/gzip, minimum 1MB
  1801. local ESTIMATED_BACKUP_SIZE=$((TOTAL_MODELS_SIZE_BYTES * 105 / 100))
  1802. if [ "$ESTIMATED_BACKUP_SIZE" -lt 1048576 ] && [ "$TOTAL_MODELS_SIZE_BYTES" -gt 0 ]; then
  1803. ESTIMATED_BACKUP_SIZE=1048576 # Minimum 1MB estimate if models exist
  1804. fi
  1805.  
  1806.  
  1807. # Check available space
  1808. if ! check_available_space "$ESTIMATED_BACKUP_SIZE" "$BACKUP_DIR"; then
  1809. error "Not enough space for backup. Please free up some storage or backup to an external device."
  1810. wait_for_keypress_keep_screen
  1811. return 1
  1812. fi
  1813.  
  1814. log "Backing up models from '$MODEL_DIR' to '$BACKUP_FILE'..."
  1815. log "This may take a while for large models."
  1816.  
  1817. if command -v termux-wake-lock &>/dev/null; then
  1818. termux-wake-lock
  1819. fi
  1820.  
  1821. # Use tar with progress bar if 'pv' is installed
  1822. if command -v pv &>/dev/null; then
  1823. log "Using 'pv' for progress indication."
  1824. if ! tar -c -C "$(dirname "$MODEL_DIR")" "$(basename "$MODEL_DIR")" 2>/dev/null | pv -s "$TOTAL_MODELS_SIZE_BYTES" -p -e -r -b -W | gzip > "$BACKUP_FILE"; then
  1825. local tar_gzip_exit=$?
  1826. error "Backup failed (tar/gzip with pv exited with code $tar_gzip_exit). Check permissions and available space."
  1827. rm -f "$BACKUP_FILE" # Clean up failed backup file
  1828. if command -v termux-wake-unlock &>/dev/null; then
  1829. termux-wake-unlock
  1830. fi
  1831. wait_for_keypress_keep_screen
  1832. return 1
  1833. fi
  1834. else
  1835. log "Using 'tar' and 'gzip' without progress indicator (pv not found)."
  1836. if ! tar -czf "$BACKUP_FILE" -C "$(dirname "$MODEL_DIR")" "$(basename "$MODEL_DIR")"; then
  1837. local tar_gzip_exit=$?
  1838. error "Backup failed (tar/gzip exited with code $tar_gzip_exit). Check permissions and available space."
  1839. rm -f "$BACKUP_FILE" # Clean up failed backup file
  1840. if command -v termux-wake-unlock &>/dev/null; then
  1841. termux-wake-unlock
  1842. fi
  1843. wait_for_keypress_keep_screen
  1844. return 1
  1845. fi
  1846. fi
  1847.  
  1848. if command -v termux-wake-unlock &>/dev/null; then
  1849. termux-wake-unlock
  1850. fi
  1851.  
  1852. if [ -f "$BACKUP_FILE" ]; then
  1853. success "Models backed up successfully to '$BACKUP_FILE'"
  1854. info "Backup size: $(get_file_size_human "$BACKUP_FILE")"
  1855.  
  1856. # Ask if user wants to share/copy the backup elsewhere
  1857. echo
  1858. echo "What would you like to do with the backup file?"
  1859. echo "[1] Keep the backup in the default location ($BACKUP_DIR)"
  1860. echo "[2] Share the backup file via Android share menu"
  1861. echo "[3] Copy the backup to external storage"
  1862. echo "[4] Move the backup to external storage (removes from default location)"
  1863. echo
  1864. echo -n "Your choice [1]: "
  1865. read -r BACKUP_ACTION
  1866.  
  1867. case "${BACKUP_ACTION:-1}" in
  1868. 2)
  1869. if command -v termux-share &>/dev/null; then
  1870. log "Sharing backup file '$BACKUP_FILE'..."
  1871. termux-share "$BACKUP_FILE" || warn "Failed to share file using termux-share."
  1872. else
  1873. warn "termux-api not installed. Cannot share the backup file. Install with 'pkg install termux-api'."
  1874. fi
  1875. ;;
  1876. 3|4)
  1877. local action="Copy"
  1878. if [ "${BACKUP_ACTION:-}" = "4" ]; then action="Move"; fi
  1879. echo
  1880. echo "Enter destination directory (e.g., /sdcard/Download or $DOWNLOAD_DIR):"
  1881. read -r DEST_DIR
  1882. # Basic validation
  1883. if [ -z "$DEST_DIR" ]; then
  1884. error "Destination directory cannot be empty. Operation cancelled."
  1885. elif [ ! -d "$DEST_DIR" ]; then
  1886. error "Destination directory '$DEST_DIR' does not exist. Operation cancelled."
  1887. elif [ ! -w "$DEST_DIR" ]; then
  1888. error "Destination directory '$DEST_DIR' is not writable. Check permissions. Operation cancelled."
  1889. else
  1890. log "${action}ing '$BACKUP_FILE' to '$DEST_DIR/'..."
  1891. if [ "$action" = "Copy" ]; then
  1892. if cp "$BACKUP_FILE" "$DEST_DIR/"; then
  1893. success "Backup copied to $DEST_DIR/$(basename "$BACKUP_FILE")"
  1894. else
  1895. error "Failed to copy backup to $DEST_DIR. Check permissions or space."
  1896. fi
  1897. else # Move
  1898. if mv "$BACKUP_FILE" "$DEST_DIR/"; then
  1899. success "Backup moved to $DEST_DIR/$(basename "$BACKUP_FILE")"
  1900. else
  1901. error "Failed to move backup to $DEST_DIR. Check permissions or space."
  1902. warn "Original backup might still be in '$BACKUP_DIR'."
  1903. fi
  1904. fi
  1905. fi
  1906. ;;
  1907. *)
  1908. info "Backup kept in default location: $BACKUP_FILE"
  1909. ;;
  1910. esac
  1911. else
  1912. error "Backup file '$BACKUP_FILE' was not created successfully."
  1913. fi
  1914.  
  1915.  
  1916. press_enter
  1917. return 0
  1918. }
  1919.  
  1920. restore_models() {
  1921. clear_screen
  1922. echo -e "${BOLD}${CYAN}=== Restore Models ===${NC}"
  1923. echo
  1924. log "This will extract models from a backup archive (.tar.gz) into your KoboldCpp models directory ($MODEL_DIR)."
  1925. echo -e "${RED}WARNING: This will REPLACE your current models in '$MODEL_DIR'.${NC}"
  1926. echo
  1927.  
  1928. check_install || { return 1; }
  1929.  
  1930. _validate_critical_directory_path "$MODEL_DIR" "$EXPECTED_MODEL_DIR_PATH" "restore" || {
  1931. wait_for_keypress_keep_screen; return 1;
  1932. }
  1933.  
  1934. # Check for backups in the default location
  1935. if [ ! -d "$BACKUP_DIR" ]; then
  1936. mkdir -p "$BACKUP_DIR"
  1937. info "Backup directory '$BACKUP_DIR' created, but no backups found."
  1938. echo
  1939. echo "Options:"
  1940. echo "[1] Import a backup file from elsewhere into the backup directory"
  1941. echo "[2] Return to menu"
  1942. echo
  1943. echo -n "Your choice: "
  1944. read -r IMPORT_CHOICE
  1945.  
  1946. if [ "$IMPORT_CHOICE" = "1" ]; then
  1947. echo
  1948. echo "Enter full path to the backup file (.tar.gz) you want to import:"
  1949. read -r IMPORT_FILE
  1950. if [ -f "$IMPORT_FILE" ] && [[ "$IMPORT_FILE" == *.tar.gz ]]; then
  1951. log "Importing '$IMPORT_FILE' to '$BACKUP_DIR/'..."
  1952. if cp "$IMPORT_FILE" "$BACKUP_DIR/"; then
  1953. success "Backup file imported to $BACKUP_DIR/$(basename "$IMPORT_FILE")"
  1954. else
  1955. error "Failed to import backup file '$IMPORT_FILE'. Check permissions or space."
  1956. wait_for_keypress_keep_screen
  1957. return 1
  1958. fi
  1959. else
  1960. error "File doesn't exist, isn't a .tar.gz file, or path was empty."
  1961. wait_for_keypress_keep_screen
  1962. return 1
  1963. fi
  1964. else
  1965. return 0 # User chose to return
  1966. fi
  1967. fi # End of backup directory check and import option
  1968.  
  1969.  
  1970. echo -e "${YELLOW}Available backups in $BACKUP_DIR:${NC}"
  1971. local BACKUPS=()
  1972. # Sort by modification time, newest first
  1973. while IFS= read -r -d '' file; do
  1974. BACKUPS+=("$file")
  1975. done < <(find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -type f -print0 2>/dev/null | sort -rz)
  1976.  
  1977. if [ ${#BACKUPS[@]} -eq 0 ]; then
  1978. error "No backup archives (.tar.gz) found in '$BACKUP_DIR'."
  1979. wait_for_keypress_keep_screen
  1980. return 1
  1981. fi
  1982.  
  1983. for i in "${!BACKUPS[@]}"; do
  1984. local BACKUP_ARCHIVE="${BACKUPS[$i]}"
  1985. local SIZE
  1986. SIZE=$(get_file_size_human "$BACKUP_ARCHIVE")
  1987. local DATE_MODIFIED
  1988. DATE_MODIFIED=$(date -r "$BACKUP_ARCHIVE" "+%Y-%m-%d %H:%M:%S")
  1989. echo "[$i] $(basename "$BACKUP_ARCHIVE") ($SIZE) - $DATE_MODIFIED"
  1990. done
  1991.  
  1992. echo
  1993. echo -n "Enter number of the backup to restore (or 'q' to return to menu): "
  1994. read -r CHOICE
  1995.  
  1996. if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
  1997. return 0
  1998. fi
  1999.  
  2000. if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#BACKUPS[@]}" ]; then
  2001. error "Invalid selection."
  2002. wait_for_keypress_keep_screen
  2003. return 1
  2004. fi
  2005.  
  2006. local BACKUP_TO_RESTORE="${BACKUPS[$CHOICE]}"
  2007. log "Selected backup for restore: $(basename "$BACKUP_TO_RESTORE")"
  2008.  
  2009. # Preview backup contents
  2010. echo
  2011. echo -e "${YELLOW}Contents of the backup (first 10 .gguf files):${NC}"
  2012. local backup_contents=$(tar -tzf "$BACKUP_TO_RESTORE" 2>/dev/null | grep -E '\.gguf$' | head -n 10)
  2013. if [ -n "$backup_contents" ]; then
  2014. echo "$backup_contents"
  2015. local total_items=$(tar -tzf "$BACKUP_TO_RESTORE" 2>/dev/null | grep -E '\.gguf$' | wc -l)
  2016. if [ "$total_items" -gt 10 ]; then
  2017. echo "... and $((total_items - 10)) more .gguf items"
  2018. fi
  2019. else
  2020. warn "Could not list contents or no .gguf files found in the backup archive."
  2021. fi
  2022. echo
  2023.  
  2024. # Check if backup has expected directory structure ('models/')
  2025. if ! tar -tzf "$BACKUP_TO_RESTORE" 2>/dev/null | grep -q "^models/.*\.gguf$"; then
  2026. warn "This backup may not have the expected directory structure ('models/*.gguf')."
  2027. info "It might extract files directly into the parent directory of '$MODEL_DIR'."
  2028. echo -n "Continue anyway? [y/N]: "
  2029. read -r CONTINUE_RESTORE
  2030. if [[ ! "${CONTINUE_RESTORE:-N}" =~ ^[Yy]$ ]]; then
  2031. info "Restore operation cancelled."
  2032. press_enter
  2033. return 0
  2034. fi
  2035. fi
  2036.  
  2037. # Check available space for restore
  2038. local BACKUP_SIZE=$(get_file_size_bytes "$BACKUP_TO_RESTORE")
  2039. # Estimate uncompressed size (gzip compression ratio varies, estimate 2x as a minimum)
  2040. local ESTIMATED_RESTORE_SIZE=$((BACKUP_SIZE * 3)) # Use 3x for a slightly safer estimate
  2041. if [ "$ESTIMATED_RESTORE_SIZE" -lt 1048576 ]; then ESTIMATED_RESTORE_SIZE=1048576; fi # Minimum 1MB estimate
  2042.  
  2043. if ! check_available_space "$ESTIMATED_RESTORE_SIZE" "$(dirname "$MODEL_DIR")"; then
  2044. error "Not enough space to restore the backup in '$(dirname "$MODEL_DIR")'. Required estimate: $(numfmt --to=iec-i --suffix=B "$ESTIMATED_RESTORE_SIZE" 2>/dev/null || echo "$ESTIMATED_RESTORE_SIZE bytes"). Please free up some storage."
  2045. wait_for_keypress_keep_screen
  2046. return 1
  2047. fi
  2048.  
  2049. echo -e "${RED}WARNING: This will DELETE all existing files in '$MODEL_DIR' before restoring!${NC}"
  2050. echo -n "Are you absolutely sure you want to restore from this backup? [y/N]: "
  2051. read -r CONFIRM
  2052. if [[ ! "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default N
  2053. info "Restore operation cancelled."
  2054. press_enter
  2055. return 0
  2056. fi
  2057.  
  2058. local PRE_RESTORE_BACKUP_DIR="" # Initialize as empty
  2059. local current_models_count=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f 2>/dev/null | wc -l)
  2060. if [ -d "$MODEL_DIR" ] && [ "$current_models_count" -gt 0 ]; then
  2061. log "Backing up current models to a temporary location before restoring..."
  2062. PRE_RESTORE_BACKUP_DIR="$BACKUP_DIR/pre_restore_$(date +%Y%m%d_%H%M%S)_$$" # Add PID for uniqueness
  2063. if mkdir -p "$PRE_RESTORE_BACKUP_DIR"; then
  2064. if cp -a "$MODEL_DIR/." "$PRE_RESTORE_BACKUP_DIR/"; then
  2065. info "Current models backed up to '$PRE_RESTORE_BACKUP_DIR'"
  2066. else
  2067. warn "Failed to backup current models to '$PRE_RESTORE_BACKUP_DIR'. Continuing with restore, but current models are NOT backed up by this operation."
  2068. PRE_RESTORE_BACKUP_DIR="" # Clear path if backup failed
  2069. fi
  2070. else
  2071. warn "Could not create directory '$PRE_RESTORE_BACKUP_DIR' for pre-restore backup. Skipping this step."
  2072. PRE_RESTORE_BACKUP_DIR="" # Clear path if directory creation failed
  2073. fi
  2074. else
  2075. info "No existing models in '$MODEL_DIR' to back up before restoring."
  2076. fi
  2077.  
  2078.  
  2079. log "Clearing existing models in '$MODEL_DIR'..."
  2080. # Use find -delete for safer recursive deletion within the specific directory
  2081. if [ -d "$MODEL_DIR" ]; then
  2082. if find "$MODEL_DIR" -mindepth 1 -delete; then
  2083. log "Existing models cleared from '$MODEL_DIR'."
  2084. else
  2085. error "Failed to clear existing models from '$MODEL_DIR'. Check permissions."
  2086. _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore pre-restore backup
  2087. return 1
  2088. fi
  2089. else
  2090. mkdir -p "$MODEL_DIR" || { error "Failed to create model directory: '$MODEL_DIR'"; wait_for_keypress_keep_screen; return 1; }
  2091. fi
  2092.  
  2093.  
  2094. log "Extracting backup: $(basename "$BACKUP_TO_RESTORE") to '$(dirname "$MODEL_DIR")'..."
  2095. log "This may take a while for large backups."
  2096. # Ensure the parent directory exists for extraction
  2097. mkdir -p "$(dirname "$MODEL_DIR")" || { error "Failed to create parent directory for models: '$(dirname "$MODEL_DIR")'"; wait_for_keypress_keep_screen; return 1; }
  2098.  
  2099. if command -v termux-wake-lock &>/dev/null; then
  2100. termux-wake-lock
  2101. fi
  2102.  
  2103. # Extract with progress if available
  2104. if command -v pv &>/dev/null; then
  2105. log "Using 'pv' for progress indication during extraction."
  2106. if ! pv "$BACKUP_TO_RESTORE" | tar -xzf - -C "$(dirname "$MODEL_DIR")"; then
  2107. local extract_exit=$?
  2108. error "Restore failed during extraction (Exit code $extract_exit). The backup might be corrupted or there might be permission/space issues."
  2109. if command -v termux-wake-unlock &>/dev/null; then
  2110. termux-wake-unlock
  2111. fi
  2112. _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore the pre-restore backup
  2113. return 1
  2114. fi
  2115. else
  2116. log "Extracting with 'tar' without progress indicator (pv not found)."
  2117. if ! tar -xzf "$BACKUP_TO_RESTORE" -C "$(dirname "$MODEL_DIR")"; then
  2118. local extract_exit=$?
  2119. error "Restore failed during extraction (Exit code $extract_exit). The backup might be corrupted or there might be permission/space issues."
  2120. if command -v termux-wake-unlock &>/dev/null; then
  2121. termux-wake-unlock
  2122. fi
  2123. _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore the pre-restore backup
  2124. return 1
  2125. fi
  2126. fi
  2127.  
  2128. if command -v termux-wake-unlock &>/dev/null; then
  2129. termux-wake-unlock
  2130. fi
  2131.  
  2132. # Verify restored models integrity
  2133. log "Verifying restored models integrity..."
  2134. local corrupt_models=0
  2135. local restored_models_count=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f 2>/dev/null | wc -l)
  2136.  
  2137. if [ "$restored_models_count" -eq 0 ]; then
  2138. warn "No .gguf files found in '$MODEL_DIR' after extraction. Restore may have failed or the backup was empty/incorrect."
  2139. _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore pre-restore backup
  2140. return 1
  2141. fi
  2142.  
  2143. while IFS= read -r -d '' model_file; do
  2144. if ! check_model_integrity "$model_file" >/dev/null 2>&1; then # Check silently
  2145. warn "Model $(basename "$model_file") may have integrity issues after restoring."
  2146. ((corrupt_models++))
  2147. fi
  2148. done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null)
  2149.  
  2150. if [ "$corrupt_models" -gt 0 ]; then
  2151. warn "$corrupt_models models out of $restored_models_count may have integrity issues after restoring. Consider verifying them individually."
  2152. wait_for_keypress_keep_screen # Pause to show warnings
  2153. fi
  2154.  
  2155. success "Models restored successfully from $(basename "$BACKUP_TO_RESTORE"). Total restored models: $restored_models_count."
  2156.  
  2157. # Clean up the temporary pre-restore backup if it exists and restore was successful
  2158. if [ -n "$PRE_RESTORE_BACKUP_DIR" ] && [ -d "$PRE_RESTORE_BACKUP_DIR" ]; then
  2159. log "Cleaning up temporary pre-restore backup at '$PRE_RESTORE_BACKUP_DIR'..."
  2160. rm -rf "$PRE_RESTORE_BACKUP_DIR" || warn "Failed to remove temporary pre-restore backup."
  2161. fi
  2162.  
  2163. press_enter
  2164. return 0
  2165. }
  2166.  
  2167. # Helper function for restore failures
  2168. _handle_restore_failure() {
  2169. local pre_restore_dir="$1"
  2170. local model_dir="$2"
  2171.  
  2172. error "Restore operation failed."
  2173. info "Attempting to restore the pre-restore backup if it exists and contains files..."
  2174.  
  2175. if [ -n "$pre_restore_dir" ] && [ -d "$pre_restore_dir" ] && [ "$(ls -A "$pre_restore_dir" 2>/dev/null)" ]; then
  2176. info "Clearing potentially inconsistent state in '$model_dir'..."
  2177. find "$model_dir" -mindepth 1 -delete || warn "Failed to clear '$model_dir' before attempting pre-restore recovery."
  2178. mkdir -p "$model_dir" || warn "Failed to recreate '$model_dir'."
  2179.  
  2180. info "Copying models back from pre-restore backup '$pre_restore_dir'..."
  2181. if cp -a "$pre_restore_dir/." "$model_dir/"; then
  2182. success "Successfully restored models from the pre-restore backup at '$pre_restore_dir'."
  2183. info "You may want to verify the integrity of these models."
  2184. # Clean up the temporary backup after successful recovery
  2185. log "Cleaning up temporary pre-restore backup..."
  2186. rm -rf "$pre_restore_dir" || warn "Failed to remove temporary pre-restore backup."
  2187. else
  2188. warn "Failed to restore models from the pre-restore backup."
  2189. warn "Model directory '$model_dir' might be in an inconsistent state."
  2190. warn "Your original models might still be in '$pre_restore_dir'."
  2191. fi
  2192. else
  2193. warn "No valid pre-restore backup found or it was empty. Model directory '$model_dir' might be empty or in an inconsistent state."
  2194. fi
  2195. wait_for_keypress_keep_screen # Ensure the failure message and recovery status are seen
  2196. }
  2197.  
  2198.  
  2199. # Function to clean up unnecessary or temporary files
  2200. cleanup() {
  2201. clear_screen
  2202. echo -e "${BOLD}${CYAN}=== Clean Up Files ===${NC}"
  2203. echo
  2204. log "This utility helps free up disk space by removing temporary files, old backups, and build artifacts."
  2205. echo
  2206.  
  2207. log "Searching for files to clean up..."
  2208.  
  2209. local SPACE_FREED=0
  2210. local TEMP_SPACE_SAVED=0
  2211.  
  2212. # Clean up build artifacts in KoboldCpp directory
  2213. if [ -d "$KOBOLDCPP_DIR" ]; then
  2214. cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
  2215.  
  2216. # Clean build directory if it exists
  2217. if [ -d "build" ]; then
  2218. local BUILD_SIZE_KB=$(du -sk "build" 2>/dev/null | cut -f1)
  2219. if [ -n "$BUILD_SIZE_KB" ] && [ "$BUILD_SIZE_KB" -gt 0 ]; then
  2220. local BUILD_SIZE_BYTES=$((BUILD_SIZE_KB * 1024))
  2221. echo -n "Clean build artifacts in '$KOBOLDCPP_DIR/build' ($(numfmt --to=iec-i --suffix=B "$BUILD_SIZE_BYTES" 2>/dev/null || echo "$BUILD_SIZE_BYTES bytes"))? [Y/n]: "
  2222. read -r CLEAN_BUILD
  2223. if [[ ! "${CLEAN_BUILD:-Y}" =~ ^[Nn]$ ]]; then
  2224. rm -rf build/
  2225. TEMP_SPACE_SAVED=$((TEMP_SPACE_SAVED + BUILD_SIZE_BYTES))
  2226. success "Build artifacts cleaned."
  2227. fi
  2228. else
  2229. info "No significant build artifacts found in '$KOBOLDCPP_DIR/build'."
  2230. fi
  2231. else
  2232. info "Build directory '$KOBOLDCPP_DIR/build' not found."
  2233. fi
  2234.  
  2235. # Compact Git repository
  2236. if [ -d ".git" ]; then
  2237. local GIT_SIZE_BEFORE_KB=$(du -sk ".git" 2>/dev/null | cut -f1)
  2238. if [ -n "$GIT_SIZE_BEFORE_KB" ] && [ "$GIT_SIZE_BEFORE_KB" -gt 0 ]; then
  2239. local GIT_SIZE_BEFORE_BYTES=$((GIT_SIZE_BEFORE_KB * 1024))
  2240. echo -n "Compact Git repository in '$KOBOLDCPP_DIR/.git' ($(numfmt --to=iec-i --suffix=B "$GIT_SIZE_BEFORE_BYTES" 2>/dev/null || echo "$GIT_SIZE_BEFORE_BYTES bytes"))? [Y/n]: "
  2241. read -r CLEAN_GIT
  2242. if [[ ! "${CLEAN_GIT:-Y}" =~ ^[Nn]$ ]]; then
  2243. log "Running git gc --aggressive..."
  2244. git gc --aggressive --prune=now 2>&1 | grep -v 'Counting objects:' | grep -v 'Delta compression' | grep -v 'Compressing objects' | grep -v 'Writing objects' | grep -v 'Total' # Suppress standard gc output
  2245. local GIT_SIZE_AFTER_KB=$(du -sk ".git" 2>/dev/null | cut -f1)
  2246. if [ -n "$GIT_SIZE_AFTER_KB" ] && [ "$GIT_SIZE_AFTER_KB" -ge 0 ]; then
  2247. local GIT_SIZE_AFTER_BYTES=$((GIT_SIZE_AFTER_KB * 1024))
  2248. local saved=$((GIT_SIZE_BEFORE_BYTES - GIT_SIZE_AFTER_BYTES))
  2249. if [ "$saved" -gt 0 ]; then
  2250. TEMP_SPACE_SAVED=$((TEMP_SPACE_SAVED + saved))
  2251. success "Git repository compacted, saved $(numfmt --to=iec-i --suffix=B "$saved" 2>/dev/null || echo "$saved bytes")."
  2252. else
  2253. info "Git repository already optimized, no significant space saved."
  2254. fi
  2255. else
  2256. warn "Could not determine Git repository size after compaction."
  2257. fi
  2258. fi
  2259. else
  2260. info "No significant Git repository data found in '$KOBOLDCPP_DIR/.git'."
  2261. fi
  2262. else
  2263. info "Git directory '$KOBOLDCPP_DIR/.git' not found."
  2264. fi
  2265. fi
  2266.  
  2267. # Clean up temporary files and manage old backups in BACKUP_DIR
  2268. if [ -d "$BACKUP_DIR" ]; then
  2269. local temp_backups=$(find "$BACKUP_DIR" -maxdepth 1 -name "pre_restore_*" -type d 2>/dev/null)
  2270. if [ -n "$temp_backups" ]; then
  2271. local TEMP_SIZE_KB=0
  2272. while IFS= read -r dir; do
  2273. local size_kb=$(du -sk "$dir" 2>/dev/null | cut -f1)
  2274. if [ -n "$size_kb" ]; then
  2275. TEMP_SIZE_KB=$((TEMP_SIZE_KB + size_kb))
  2276. fi
  2277. done <<< "$temp_backups"
  2278. local TEMP_SIZE_BYTES=$((TEMP_SIZE_KB * 1024))
  2279.  
  2280. if [ "$TEMP_SIZE_BYTES" -gt 0 ]; then
  2281. echo -n "Clean up temporary pre-restore backup directories ($(numfmt --to=iec-i --suffix=B "$TEMP_SIZE_BYTES" 2>/dev/null || echo "$TEMP_SIZE_BYTES bytes"))? [Y/n]: "
  2282. read -r CLEAN_TEMP
  2283. if [[ ! "${CLEAN_TEMP:-Y}" =~ ^[Nn]$ ]]; then
  2284. while IFS= read -r dir; do
  2285. rm -rf "$dir"
  2286. done <<< "$temp_backups"
  2287. SPACE_FREED=$((SPACE_FREED + TEMP_SIZE_BYTES)) # This is permanent space freed
  2288. success "Temporary backup files cleaned."
  2289. fi
  2290. else
  2291. info "No significant temporary backup directories found in '$BACKUP_DIR'."
  2292. fi
  2293. else
  2294. info "No temporary pre-restore backup directories found in '$BACKUP_DIR'."
  2295. fi
  2296.  
  2297. # Manage old backups
  2298. local backup_files=($(find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -type f -print0 2>/dev/null | xargs -0))
  2299. local backup_count=${#backup_files[@]}
  2300.  
  2301. if [ "$backup_count" -gt 5 ]; then
  2302. echo -n "You have $backup_count model backups. Keep only the 5 most recent? [y/N]: "
  2303. read -r CLEAN_BACKUPS
  2304. if [[ "${CLEAN_BACKUPS:-N}" =~ ^[Yy]$ ]]; then
  2305. # Find backups to remove (older than the 5 most recent)
  2306. local old_backups=($(find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -type f -printf "%T@ %p\n" 2>/dev/null | sort -n | head -n $((backup_count - 5)) | cut -d' ' -f2-))
  2307. local OLD_SIZE_BYTES=0
  2308. local files_removed=0
  2309. for file in "${old_backups[@]}"; do
  2310. if [ -f "$file" ]; then # Ensure file still exists before attempting to delete
  2311. local size=$(get_file_size_bytes "$file")
  2312. OLD_SIZE_BYTES=$((OLD_SIZE_BYTES + size))
  2313. if rm -f "$file"; then
  2314. ((files_removed++))
  2315. else
  2316. warn "Failed to delete old backup file: $file"
  2317. fi
  2318. fi
  2319. done
  2320. SPACE_FREED=$((SPACE_FREED + OLD_SIZE_BYTES)) # This is permanent space freed
  2321. success "Removed $files_removed old backups, freed $(numfmt --to=iec-i --suffix=B "$OLD_SIZE_BYTES" 2>/dev/null || echo "$OLD_SIZE_BYTES bytes")."
  2322. fi
  2323. elif [ "$backup_count" -gt 0 ]; then
  2324. info "You have $backup_count model backups. No old backups to remove (keeping 5 most recent)."
  2325. else
  2326. info "No model backups found in '$BACKUP_DIR'."
  2327. fi
  2328. else
  2329. info "Backup directory '$BACKUP_DIR' not found."
  2330. fi
  2331.  
  2332. # Log file rotation
  2333. if [ -f "$LOG_FILE" ]; then
  2334. local log_size=$(get_file_size_bytes "$LOG_FILE")
  2335. if [ "$log_size" -gt 1048576 ]; then # 1MB
  2336. echo -n "Rotate log file ($(numfmt --to=iec-i --suffix=B "$log_size" 2>/dev/null || echo "$log_size bytes"))? [Y/n]: "
  2337. read -r ROTATE_LOG
  2338. if [[ ! "${ROTATE_LOG:-Y}" =~ ^[Nn]$ ]]; then
  2339. mv "$LOG_FILE" "${LOG_FILE}.old"
  2340. touch "$LOG_FILE"
  2341. echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: Log rotated, old log saved as ${LOG_FILE}.old" >> "$LOG_FILE"
  2342. success "Log file rotated, freed $(numfmt --to=iec-i --suffix=B "$log_size" 2>/dev/null || echo "$log_size bytes")."
  2343. TEMP_SPACE_SAVED=$((TEMP_SPACE_SAVED + log_size)) # This space is potentially recoverable by deleting the .old file
  2344. fi
  2345. else
  2346. info "Log file size is manageable ($(numfmt --to=iec-i --suffix=B "$log_size" 2>/dev/null || echo "$log_size bytes"))."
  2347. fi
  2348. else
  2349. info "Log file '$LOG_FILE' not found."
  2350. fi
  2351.  
  2352.  
  2353. # Show summary
  2354. echo
  2355. if [ "$SPACE_FREED" -gt 0 ] || [ "$TEMP_SPACE_SAVED" -gt 0 ]; then
  2356. success "Cleanup complete."
  2357. if [ "$SPACE_FREED" -gt 0 ]; then
  2358. info "Permanent space freed: $(numfmt --to=iec-i --suffix=B "$SPACE_FREED" 2>/dev/null || echo "$SPACE_FREED bytes")."
  2359. fi
  2360. if [ "$TEMP_SPACE_SAVED" -gt 0 ]; then
  2361. info "Temporary/compressible space cleaned: $(numfmt --to=iec-i --suffix=B "$TEMP_SPACE_SAVED" 2>/dev/null || echo "$TEMP_SPACE_SAVED bytes")."
  2362. fi
  2363. else
  2364. info "No files were cleaned up."
  2365. fi
  2366.  
  2367. press_enter
  2368. return 0
  2369. }
  2370.  
  2371. install_update_menu() {
  2372. while true; do
  2373. clear_screen
  2374. echo -e "${BOLD}${CYAN}=== Installation, Updates, & Rebuild ===${NC}"
  2375. echo
  2376. local kobold_installed=false
  2377. if [ -d "$KOBOLDCPP_DIR" ] && ([ -f "$KOBOLDCPP_DIR/koboldcpp.py" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]); then
  2378. kobold_installed=true
  2379. echo -e "${GREEN}KoboldCpp is currently installed in $KOBOLDCPP_DIR.${NC}"
  2380. if [ -d "$KOBOLDCPP_DIR/.git" ]; then
  2381. local current_commit=$(git -C "$KOBOLDCPP_DIR" rev-parse --short HEAD 2>/dev/null || echo "Unknown")
  2382. echo -e "Current version (commit): ${YELLOW}$current_commit${NC}"
  2383. fi
  2384. else
  2385. echo -e "${YELLOW}KoboldCpp is not currently installed.${NC}"
  2386. fi
  2387. echo
  2388.  
  2389. echo -e "${BOLD}Options:${NC}"
  2390. echo
  2391. echo "1. Install / Reinstall KoboldCpp"
  2392. if [ "$kobold_installed" = true ]; then
  2393. echo "2. Update KoboldCpp (fetch latest code and rebuild)"
  2394. echo "3. Rebuild KoboldCpp (from current source code)"
  2395. fi
  2396. echo "0. Back to Main Menu"
  2397. echo
  2398. echo -n "Enter your choice: "
  2399. read -r CHOICE
  2400.  
  2401. case "${CHOICE:-}" in
  2402. 1) install_all ;;
  2403. 2)
  2404. if [ "$kobold_installed" = true ]; then
  2405. update_koboldcpp
  2406. else
  2407. error "Option 2 is only available if KoboldCpp is installed."
  2408. wait_for_keypress_keep_screen
  2409. fi
  2410. ;;
  2411. 3)
  2412. if [ "$kobold_installed" = true ]; then
  2413. rebuild
  2414. else
  2415. error "Option 3 is only available if KoboldCpp is installed."
  2416. wait_for_keypress_keep_screen
  2417. fi
  2418. ;;
  2419. 0) return ;;
  2420. *)
  2421. error "Invalid choice. Please try again."
  2422. wait_for_keypress_keep_screen
  2423. ;;
  2424. esac
  2425. done
  2426. }
  2427.  
  2428. backup_restore_menu() {
  2429. while true; do
  2430. clear_screen
  2431. echo -e "${BOLD}${CYAN}=== Backup & Restore Models ===${NC}"
  2432. echo
  2433. local backup_count=$(find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -type f 2>/dev/null | wc -l)
  2434. echo -e "Backups found in $BACKUP_DIR: ${YELLOW}${backup_count:-0}${NC}"
  2435. echo
  2436.  
  2437. echo -e "${BOLD}Options:${NC}"
  2438. echo
  2439. echo "1. Backup current models"
  2440. echo "2. Restore models from backup"
  2441. echo "0. Back to Main Menu"
  2442. echo
  2443. echo -n "Enter your choice: "
  2444. read -r CHOICE
  2445.  
  2446. case "${CHOICE:-}" in
  2447. 1) backup_models ;;
  2448. 2) restore_models ;;
  2449. 0) return ;;
  2450. *)
  2451. error "Invalid choice. Please try again."
  2452. wait_for_keypress_keep_screen
  2453. ;;
  2454. esac
  2455. done
  2456. }
  2457.  
  2458. advanced_menu() {
  2459. while true; do
  2460. clear_screen
  2461. echo -e "${BOLD}${CYAN}=== Advanced Options ===${NC}"
  2462. echo
  2463. echo -e "${YELLOW}Use these options with caution.${NC}"
  2464. echo
  2465.  
  2466. echo -e "${BOLD}Options:${NC}"
  2467. echo
  2468. echo "1. Clean up temporary files and old backups"
  2469. echo "2. Open log file ($LOG_FILE)"
  2470. echo "3. Reset configuration file ($CONFIG_FILE)"
  2471. echo "0. Back to Main Menu"
  2472. echo
  2473. echo -n "Enter your choice: "
  2474. read -r CHOICE
  2475.  
  2476. case "${CHOICE:-}" in
  2477. 1) cleanup ;;
  2478. 2)
  2479. if [ -f "$LOG_FILE" ]; then
  2480. log "Opening log file in default viewer..."
  2481. termux-open "$LOG_FILE" || {
  2482. warn "Failed to open log file. You might need to install a text editor or file viewer."
  2483. info "You can view it manually using: less \"$LOG_FILE\""
  2484. wait_for_keypress_keep_screen
  2485. }
  2486. else
  2487. warn "Log file '$LOG_FILE' not found."
  2488. wait_for_keypress_keep_screen
  2489. fi
  2490. ;;
  2491. 3)
  2492. echo -e "${RED}WARNING: This will delete your saved configurations (last used model, run parameters, etc.).${NC}"
  2493. echo -n "Are you sure you want to reset the configuration file '$CONFIG_FILE'? [y/N]: "
  2494. read -r RESET_CONFIRM
  2495. if [[ "${RESET_CONFIRM:-N}" =~ ^[Yy]$ ]]; then
  2496. if rm -f "$CONFIG_FILE"; then
  2497. success "Configuration file '$CONFIG_FILE' reset."
  2498. # Re-initialize default build options
  2499. save_config "build_options" "0"
  2500. info "Default build options reset."
  2501. else
  2502. error "Failed to reset configuration file '$CONFIG_FILE'. Check permissions."
  2503. fi
  2504. else
  2505. info "Configuration reset cancelled."
  2506. fi
  2507. press_enter
  2508. ;;
  2509. 0) return ;;
  2510. *)
  2511. error "Invalid choice. Please try again."
  2512. wait_for_keypress_keep_screen
  2513. ;;
  2514. esac
  2515. done
  2516. }
  2517.  
  2518.  
  2519. about() {
  2520. clear_screen
  2521. echo -e "${BOLD}${CYAN}=== About KoboldCpp Termux Manager ===${NC}"
  2522. echo
  2523. echo "KoboldCpp Termux Manager v$VERSION"
  2524. echo
  2525. echo "This tool helps you install, manage, and run KoboldCpp on Android using Termux."
  2526. echo
  2527. echo "Original KoboldCpp project: $REPO_URL"
  2528. echo
  2529. echo "Installation directory: ${BLUE}$KOBOLDCPP_DIR${NC}"
  2530. echo "Model Cache directory: ${BLUE}$MODEL_DIR${NC}" # Clarified as Model Cache
  2531. echo "Downloads directory: ${BLUE}$DOWNLOAD_DIR${NC}"
  2532. echo "Backups directory: ${BLUE}$BACKUP_DIR${NC}"
  2533. echo "Log file: ${BLUE}$LOG_FILE${NC}"
  2534. echo "Config file: ${BLUE}$CONFIG_FILE${NC}"
  2535. echo
  2536. log "The Model Cache directory ($MODEL_DIR) is where your downloaded or added model (.gguf) files are stored for quick access and management by this script."
  2537. echo
  2538.  
  2539. # Show system information
  2540. local device_info=$(get_device_info)
  2541. if [ -n "$device_info" ]; then
  2542. echo "Device information: ${BLUE}$device_info${NC}"
  2543. echo
  2544. fi
  2545.  
  2546. # Show usage statistics if available
  2547. if [ -f "$CONFIG_FILE" ]; then
  2548. local last_runtime_model=$(grep "^last_runtime_model=" "$CONFIG_FILE" | cut -d= -f2)
  2549. local last_runtime_hours=$(grep "^last_runtime_hours=" "$CONFIG_FILE" | cut -d= -f2)
  2550. local last_runtime_minutes=$(grep "^last_runtime_minutes=" "$CONFIG_FILE" | cut -d= -f2)
  2551. local last_runtime_seconds=$(grep "^last_runtime_seconds=" "$CONFIG_FILE" | cut -d= -f2)
  2552.  
  2553. if [ -n "$last_runtime_model" ]; then
  2554. echo "Last session:"
  2555. echo "- Model: ${CYAN}$last_runtime_model${NC}"
  2556. if [ -n "$last_runtime_hours" ]; then
  2557. echo "- Runtime: ${CYAN}${last_runtime_hours}h ${last_runtime_minutes}m ${last_runtime_seconds}s${NC}"
  2558. fi
  2559. echo
  2560. fi
  2561. fi
  2562.  
  2563. echo "New in version $VERSION:"
  2564. echo "- Completed Model Management menu."
  2565. echo "- Added 'Verify model integrity' option to Model Management."
  2566. echo "- Clarified '$MODEL_DIR' as the Model Cache directory."
  2567. echo "- Improved input validation for run parameters."
  2568. echo "- Enhanced file size checks and reporting."
  2569. echo "- Better error handling and cleanup for downloads and restores."
  2570. echo "- Added option to import backups in Restore menu."
  2571. echo "- Option to move backups to external storage."
  2572. echo "- More detailed output during cleanup."
  2573. echo
  2574. press_enter
  2575. }
  2576.  
  2577. clean_exit() {
  2578. clear_screen
  2579. if command -v termux-wake-unlock &>/dev/null; then
  2580. log "Releasing wake lock on exit (if active)..."
  2581. termux-wake-unlock 2>/dev/null || true # Ignore error if no lock was active
  2582. fi
  2583. echo "--- KoboldCpp Termux Manager Session Ended: $(date '+%Y-%m-%d %H:%M:%S') ---" >> "$LOG_FILE"
  2584. echo "Thank you for using KoboldCpp Termux Manager!"
  2585. # Remove trap for temp file if it was set and not cleared by _initialize_du_commands
  2586. # This is a general cleanup, though _initialize_du_commands tries to manage its own trap.
  2587. trap - EXIT
  2588. exit 0
  2589. }
  2590.  
  2591. # --- Check for updates ---
  2592. check_script_updates() {
  2593. # This is a placeholder function - in a real implementation, this would
  2594. # check a repository or URL for newer versions of the script
  2595. # For now, it just returns 0 to indicate no check is performed.
  2596. return 0
  2597. }
  2598.  
  2599. # --- Menus ---
  2600.  
  2601. main_menu() {
  2602. echo "--- KoboldCpp Termux Manager Session Started: $(date '+%Y-%m-%d %H:%M:%S') ---" >> "$LOG_FILE"
  2603.  
  2604. # Check for script updates (placeholder)
  2605. # check_script_updates
  2606.  
  2607. trap clean_exit SIGINT SIGTERM
  2608.  
  2609. while true; do
  2610. clear_screen
  2611. echo -e "${BOLD}${CYAN}=== KoboldCpp Termux Manager v$VERSION ===${NC}"
  2612. echo
  2613. log "Your model cache is located at: $MODEL_DIR"
  2614. echo
  2615.  
  2616. local MODEL_COUNT=0
  2617. if [ -d "$MODEL_DIR" ]; then
  2618. # Use find with null delimiter for safety
  2619. MODEL_COUNT=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | grep -c -z '\.gguf$')
  2620. fi
  2621.  
  2622. local py_script_exists=false
  2623. local binary_exists=false
  2624. local kobold_installed=false
  2625.  
  2626. if [ -d "$KOBOLDCPP_DIR" ]; then
  2627. if [ -f "$KOBOLDCPP_DIR/koboldcpp.py" ]; then
  2628. py_script_exists=true
  2629. fi
  2630. if [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]; then
  2631. binary_exists=true
  2632. fi
  2633. if [ "$py_script_exists" = true ] || [ "$binary_exists" = true ]; then
  2634. kobold_installed=true
  2635. fi
  2636. fi
  2637.  
  2638. if [ "$kobold_installed" = true ]; then
  2639. echo -e "${GREEN}KoboldCpp is installed.${NC}"
  2640. echo -e "Models in cache: ${YELLOW}${MODEL_COUNT:-0}${NC}"
  2641.  
  2642. # Show last run information if available
  2643. if [ -f "$CONFIG_FILE" ]; then
  2644. local last_runtime_model=$(grep "^last_runtime_model=" "$CONFIG_FILE" | cut -d= -f2)
  2645. local last_runtime_hours=$(grep "^last_runtime_hours=" "$CONFIG_FILE" | cut -d= -f2)
  2646. if [ -n "$last_runtime_model" ]; then
  2647. local last_model_base=$(basename "$last_runtime_model") # Show just filename
  2648. local last_model_status=""
  2649. if [ -n "$last_runtime_hours" ]; then
  2650. last_model_status="(last ran)"
  2651. else
  2652. last_model_status="(last selected, possibly failed)" # Indicate if runtime wasn't saved
  2653. fi
  2654. echo -e "Last model used: ${CYAN}$last_model_base${NC} $last_model_status"
  2655. fi
  2656. fi
  2657. else
  2658. echo -e "${YELLOW}KoboldCpp is not installed (or key components are missing).${NC}"
  2659. fi
  2660.  
  2661. # Show device info
  2662. local device_info=$(get_device_info)
  2663. if [ -n "$device_info" ]; then
  2664. echo -e "Device: ${BLUE}$device_info${NC}"
  2665. fi
  2666.  
  2667. echo
  2668. echo -e "${BOLD}Main Menu:${NC}"
  2669. echo
  2670. echo "1. Run KoboldCpp (Select model from cache)"
  2671. echo "2. Add model to cache (from Downloads)"
  2672. echo "3. Download model to cache (from URL)"
  2673. echo "4. Manage models in cache" # Renamed for clarity
  2674. echo "5. Install / Update / Rebuild KoboldCpp"
  2675. echo "6. Backup & Restore Models"
  2676. echo "7. Advanced options"
  2677. echo "8. About"
  2678. echo "0. Exit"
  2679. echo
  2680. echo -n "Enter your choice: "
  2681. read -r CHOICE
  2682.  
  2683. case "${CHOICE:-}" in # Handle empty CHOICE
  2684. 1) run_model ;;
  2685. 2) add_model ;;
  2686. 3) download_model ;;
  2687. 4) models_menu ;;
  2688. 5) install_update_menu ;;
  2689. 6) backup_restore_menu ;;
  2690. 7) advanced_menu ;;
  2691. 8) about ;;
  2692. 0) clean_exit ;;
  2693. *)
  2694. error "Invalid choice. Please try again."
  2695. wait_for_keypress_keep_screen
  2696. ;;
  2697. esac
  2698. done
  2699. }
  2700.  
  2701. models_menu() {
  2702. while true; do
  2703. clear_screen
  2704. echo -e "${BOLD}${CYAN}=== Model Cache Management ===${NC}"
  2705. echo
  2706. log "Manage your .gguf models stored in the cache directory: $MODEL_DIR"
  2707. echo
  2708.  
  2709. local MODEL_COUNT=0
  2710. if [ -d "$MODEL_DIR" ]; then
  2711. # Use find with null delimiter for safety
  2712. MODEL_COUNT=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | grep -c -z '\.gguf$')
  2713. fi
  2714. echo -e "Models currently in cache: ${YELLOW}${MODEL_COUNT:-0}${NC}"
  2715.  
  2716. echo
  2717. echo -e "${BOLD}Options:${NC}"
  2718. echo
  2719. echo "1. List all models in cache"
  2720. echo "2. Add model to cache (from Downloads)" # Re-added for convenience
  2721. echo "3. Download model to cache (from URL)" # Re-added for convenience
  2722. echo "4. Delete a model from cache"
  2723. echo "5. Verify integrity of a model in cache" # Calls the new interactive function
  2724. echo "0. Back to Main Menu"
  2725. echo
  2726. echo -n "Enter your choice: "
  2727. read -r CHOICE
  2728.  
  2729. case "${CHOICE:-}" in
  2730. 1) list_models ;;
  2731. 2) add_model ;;
  2732. 3) download_model ;;
  2733. 4) delete_model ;;
  2734. 5) verify_model_integrity_interactive ;; # Call the new function here
  2735. 0) return ;;
  2736. *)
  2737. error "Invalid choice. Please try again."
  2738. wait_for_keypress_keep_screen
  2739. ;;
  2740. esac
  2741. done
  2742. }
  2743.  
  2744. # --- Script Entry Point ---
  2745. main_menu
Advertisement
Add Comment
Please, Sign In to add comment