Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/data/data/com.termux/files/usr/bin/env bash
- #
- # KoboldCpp Termux Interactive Installer & Manager
- # Version 3.1 (with Model Cache Management enhancements)
- #
- # --- Usage & Requirements ---
- #
- # Usage:
- # bash koboldcpp_manager.sh
- #
- # Requirements:
- # - Termux environment on Android (v0.118+ recommended)
- # - Bash shell
- # - Internet connection for downloading KoboldCpp and models
- # - Termux:API package (optional, but recommended for wake lock functionality)
- # Install with: pkg install termux-api
- #
- # For more advanced KoboldCpp setup and options, refer to the official documentation:
- # https://github.com/LostRuins/koboldcpp
- #
- # --- Prevent Sourcing ---
- if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then
- echo "This script is meant to be executed, not sourced." >&2
- if (return 0 2>/dev/null); then return 1; else exit 1; fi
- fi
- # --- Script Configuration ---
- set -euo pipefail # Exit on error, undefined variable, and pipe failure.
- # --- Early Initializations ---
- # Global flag to ensure log directory creation is attempted only once.
- LOG_FILE_DIR_INIT_ATTEMPTED=false
- # Function to ensure the log directory exists
- _ensure_log_dir_exists_once() {
- if [ "$LOG_FILE_DIR_INIT_ATTEMPTED" = true ]; then
- return
- fi
- local current_log_file_path="$HOME/koboldcpp_install.log"
- local log_dir
- log_dir=$(dirname "$current_log_file_path")
- if ! mkdir -p "$log_dir"; then
- echo "CRITICAL ERROR: Could not create log directory '$log_dir'. Logging will fail. Aborting." >&2
- exit 1
- fi
- LOG_FILE_DIR_INIT_ATTEMPTED=true
- }
- _ensure_log_dir_exists_once # Call immediately
- # --- Global Readonly Variables ---
- readonly KOBOLDCPP_DIR="$HOME/koboldcpp"
- readonly MODEL_DIR="$KOBOLDCPP_DIR/models" # This is your model cache directory
- readonly EXPECTED_MODEL_DIR_PATH="$HOME/koboldcpp/models"
- readonly DOWNLOAD_DIR="$HOME/storage/downloads"
- readonly BACKUP_DIR="$HOME/koboldcpp_backup"
- readonly LOG_FILE="$HOME/koboldcpp_install.log"
- readonly REPO_URL="https://github.com/LostRuins/koboldcpp.git"
- readonly DEFAULT_BRANCH="main"
- readonly CONFIG_FILE="$HOME/.koboldcpp_config"
- readonly VERSION="3.1"
- # Colors for prettier output
- readonly RED='\033[0;31m'
- readonly GREEN='\033[0;32m'
- readonly YELLOW='\033[0;33m'
- readonly BLUE='\033[0;34m'
- readonly CYAN='\033[0;36m'
- readonly BOLD='\033[1m'
- readonly NC='\033[0m' # No Color
- # --- Portable File Size Command Initialization ---
- DU_BYTES_CMD=""
- DU_BYTES_TYPE="unknown" # 'du_b', 'du_bytes', 'stat', or 'unknown'
- _initialize_du_commands() {
- local test_file_path="/proc/self/exe" # Generally reliable link
- local temp_file_created=false
- if [ ! -r "$test_file_path" ] || [ ! -f "$test_file_path" ]; then
- test_file_path=$(mktemp)
- if [ -z "$test_file_path" ] || [ ! -f "$test_file_path" ]; then
- if typeset -f warn > /dev/null; then warn "mktemp failed for 'du' test."; else echo "Warning: mktemp failed for 'du' test." >&2; fi
- DU_BYTES_TYPE="unknown"
- return
- fi
- temp_file_created=true
- trap '_cleanup_temp_file "$test_file_path"; trap - EXIT' EXIT
- fi
- if du -b "$test_file_path" >/dev/null 2>&1; then
- DU_BYTES_CMD="du -b"
- DU_BYTES_TYPE="du_b"
- elif du --bytes "$test_file_path" >/dev/null 2>&1; then
- DU_BYTES_CMD="du --bytes"
- DU_BYTES_TYPE="du_bytes"
- elif stat -c %s "$test_file_path" >/dev/null 2>&1; then
- DU_BYTES_CMD="stat -c %s"
- DU_BYTES_TYPE="stat"
- else
- DU_BYTES_TYPE="unknown"
- 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
- fi
- if [ "$temp_file_created" = true ]; then
- _cleanup_temp_file "$test_file_path"
- trap - EXIT # Clear the specific trap if we cleaned up
- fi
- }
- _cleanup_temp_file() {
- if [ -n "${1:-}" ] && [ -f "$1" ]; then
- rm -f "$1"
- fi
- }
- _initialize_du_commands # Initialize once
- # --- Utility functions ---
- log() {
- echo -e "${GREEN}→${NC} $1"
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: $1" >> "$LOG_FILE"
- }
- error() {
- echo -e "${RED}❌ ERROR:${NC} $1" >&2
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $1" >> "$LOG_FILE"
- return 1
- }
- success() {
- echo -e "${GREEN}✅${NC} $1"
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] SUCCESS: $1" >> "$LOG_FILE"
- }
- warn() {
- echo -e "${YELLOW}⚠️ WARNING:${NC} $1"
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARNING: $1" >> "$LOG_FILE"
- }
- info() {
- echo -e "${BLUE}ℹ️ INFO:${NC} $1"
- }
- # Core pause logic.
- _pause_script_execution() {
- local clear_behavior="${1:-clear}" # Default to "clear"
- echo
- echo -n "Press Enter to continue..."
- read -r
- if [ "$clear_behavior" = "clear" ]; then
- clear
- fi
- }
- # Pauses script and keeps screen content.
- wait_for_keypress_keep_screen() {
- _pause_script_execution "keep"
- }
- # Pauses script and clears screen (default for progressing through menus/steps).
- press_enter() {
- _pause_script_execution "clear"
- }
- clear_screen() {
- clear
- }
- # Function to save user configurations
- save_config() {
- local key="$1"
- local value="$2"
- # Create config file if it doesn't exist
- touch "$CONFIG_FILE"
- # Check if key already exists, update if it does, add if it doesn't
- if grep -q "^$key=" "$CONFIG_FILE"; then
- sed -i "s/^$key=.*/$key=$value/" "$CONFIG_FILE"
- else
- echo "$key=$value" >> "$CONFIG_FILE"
- fi
- }
- # Function to load user configurations
- load_config() {
- local key="$1"
- local default="$2"
- if [ -f "$CONFIG_FILE" ] && grep -q "^$key=" "$CONFIG_FILE"; then
- grep "^$key=" "$CONFIG_FILE" | cut -d= -f2
- else
- echo "$default"
- fi
- }
- get_file_size_bytes() {
- local filepath="${1:-}"
- if [ -z "$filepath" ] || [ ! -e "$filepath" ]; then
- echo "0"
- return
- fi
- local size_output=""
- case "$DU_BYTES_TYPE" in
- "du_b") size_output=$(du -b "$filepath" 2>/dev/null | cut -f1) ;;
- "du_bytes") size_output=$(du --bytes "$filepath" 2>/dev/null | cut -f1) ;;
- "stat") size_output=$(stat -c %s "$filepath" 2>/dev/null) ;;
- *)
- local size_k
- size_k=$(du -k "$filepath" 2>/dev/null | cut -f1)
- if [[ "$size_k" =~ ^[0-9]+$ ]]; then
- size_output=$((size_k * 1024))
- else
- size_output="0"
- fi
- ;;
- esac
- echo "${size_output:-0}" # Ensure output, default to 0 if empty
- }
- get_file_size_human() {
- local filepath="${1:-}"
- if [ -z "$filepath" ] || [ ! -e "$filepath" ]; then
- echo "N/A"
- return
- fi
- du -h "$filepath" 2>/dev/null | cut -f1 || echo "N/A"
- }
- # Check if a model is corrupted (basic checks)
- # Returns 0 for likely OK, 1 for likely corrupted.
- check_model_integrity() {
- local model_path="$1"
- local model_name=$(basename "$model_path")
- # Simple size check - empty or very small files are likely corrupted
- local size_bytes=$(get_file_size_bytes "$model_path")
- if [ "$size_bytes" -lt 10240 ]; then # Less than 10KB
- warn "Model $model_name appears to be too small ($(get_file_size_human "$model_path")). It may be corrupted."
- return 1
- fi
- # Check file header for GGUF signature (basic check, requires hexdump)
- if command -v hexdump >/dev/null 2>&1; then
- # Read the first 8 bytes and check for the GGUF magic number (0x46554747)
- # This is "GGUF" in ASCII, followed by version number (typically 0x00000003 or 0x00000002)
- # We check for 47 47 55 46 (GGUF) followed by any 4 bytes.
- local header=$(hexdump -n 8 -e '1/1 "%02x"' "$model_path" 2>/dev/null)
- if [[ "$header" != "47475546"* ]]; then
- warn "Model $model_name does not start with a valid GGUF magic number. It may be corrupted or not a GGUF file."
- return 1
- fi
- else
- # If hexdump is not available, we can only do the size check
- if [ "$size_bytes" -lt 1000000 ]; then # Arbitrary larger size threshold if no hexdump
- 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."
- return 1
- fi
- fi
- # If basic checks pass
- return 0
- }
- # Validates critical directory paths against expected values and common pitfalls.
- _validate_critical_directory_path() {
- local dir_to_check="${1:-}"
- local expected_path="${2:-}"
- local operation_name="${3:-operation}" # e.g., "backup", "restore"
- if [ -z "$dir_to_check" ]; then
- error "Safety check failed for $operation_name: Directory path is empty."
- return 1
- fi
- if [ "$dir_to_check" = "/" ] || [ "$dir_to_check" = "$HOME" ]; then
- error "Safety check failed for $operation_name: Directory path ('$dir_to_check') is a root or home directory."
- return 1
- fi
- # Disabled exact path check for more flexibility, rely on starts-with and critical path checks
- # if [ -n "$expected_path" ] && [ "$dir_to_check" != "$expected_path" ]; then
- # error "Safety check failed for $operation_name: Directory path ('$dir_to_check') does not match expected path ('$expected_path')."
- # return 1
- # fi
- # Add check to prevent operations outside HOME
- if [[ ! "$dir_to_check" == "$HOME"* ]]; then
- error "Safety check failed for $operation_name: Directory path ('$dir_to_check') is outside the home directory."
- return 1
- fi
- return 0
- }
- # Function to check available storage space
- check_available_space() {
- local required_space="$1" # in bytes
- local directory="$2"
- if [ ! -d "$directory" ]; then
- warn "Target directory '$directory' does not exist. Cannot check available space."
- return 0 # Assume enough space if dir doesn't exist yet (mkdir -p will create)
- fi
- # Get available space in the target directory
- local available_space=0
- if command -v df >/dev/null 2>&1; then
- available_space=$(df -B1 "$directory" | awk 'NR==2 {print $4}')
- else
- # If df is not available, assume there's enough space
- warn "df command not found. Cannot reliably check available storage space."
- return 0
- fi
- if [ -z "$available_space" ] || [ "$available_space" -lt "$required_space" ]; then
- local required_human=$(numfmt --to=iec-i --suffix=B "$required_space" 2>/dev/null || echo "$required_space bytes")
- local available_human=$(numfmt --to=iec-i --suffix=B "$available_space" 2>/dev/null || echo "$available_space bytes")
- warn "Not enough storage space in '$directory'."
- warn "Required: $required_human, Available: $available_human"
- return 1
- fi
- return 0
- }
- # Find device CPU info
- get_device_info() {
- local device_info=""
- # Get CPU model
- if [ -f "/proc/cpuinfo" ]; then
- local cpu_model=$(grep "model name\|Processor" /proc/cpuinfo | head -n1 | cut -d: -f2 | xargs)
- if [ -n "$cpu_model" ]; then
- device_info="CPU: $cpu_model"
- fi
- fi
- # Get total RAM
- if [ -f "/proc/meminfo" ]; then
- local total_ram=$(grep "MemTotal" /proc/meminfo | awk '{print $2}')
- if [ -n "$total_ram" ]; then
- local ram_gb=$(awk -v ram="$total_ram" 'BEGIN {printf "%.2f", ram/1024/1024}')
- device_info="$device_info, RAM: ${ram_gb}GB"
- fi
- fi
- # Get Android version if available
- if command -v getprop >/dev/null 2>&1; then
- local android_ver=$(getprop ro.build.version.release)
- if [ -n "$android_ver" ]; then
- device_info="$device_info, Android: $android_ver"
- fi
- fi
- echo "$device_info"
- }
- # --- Prerequisite Checks ---
- check_install() {
- local py_script_exists=false
- local binary_exists=false
- if [ -f "$KOBOLDCPP_DIR/koboldcpp.py" ]; then
- py_script_exists=true
- fi
- if [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]; then
- binary_exists=true
- fi
- if [ ! -d "$KOBOLDCPP_DIR" ] || { [ "$py_script_exists" = false ] && [ "$binary_exists" = false ]; }; then
- echo -e "${YELLOW}KoboldCpp is not installed or key components are missing.${NC}"
- echo
- echo -n "Would you like to install/reinstall it now? [Y/n]: "
- read -r INSTALL_CHOICE
- if [[ "${INSTALL_CHOICE:-Y}" =~ ^[Nn]$ ]]; then # Default to Y
- error "KoboldCpp installation is required to continue."
- wait_for_keypress_keep_screen
- return 1
- else
- install_all
- return $?
- fi
- fi
- return 0
- }
- check_termux_storage() {
- if [ ! -d "$DOWNLOAD_DIR" ]; then
- log "Setting up Termux storage access..."
- if command -v termux-setup-storage &>/dev/null; then
- termux-setup-storage
- else
- error "termux-setup-storage command not found. Please ensure Termux:API is installed and configured."
- info "You might need to run: pkg install termux-api && termux-setup-storage"
- wait_for_keypress_keep_screen
- return 1
- fi
- for i in {1..10}; do
- if [ -d "$DOWNLOAD_DIR" ]; then
- break
- fi
- log "Waiting for storage access... ($i/10)"
- sleep 1
- done
- if [ ! -d "$DOWNLOAD_DIR" ]; then
- error "Storage access not granted or Downloads directory '$DOWNLOAD_DIR' not found."
- info "Please ensure you granted storage permission and the directory exists."
- wait_for_keypress_keep_screen
- return 1
- fi
- fi
- return 0
- }
- # Check internet connectivity
- check_internet() {
- log "Checking internet connectivity..."
- if ping -c 1 8.8.8.8 >/dev/null 2>&1 || ping -c 1 google.com >/dev/null 2>&1; then
- return 0
- else
- error "No internet connection detected. Some functions may not work properly."
- return 1
- fi
- }
- # --- Core Functionality ---
- install_all() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Installing KoboldCpp ===${NC}"
- echo
- log "Starting KoboldCpp installation..."
- if command -v termux-wake-lock &>/dev/null; then
- log "Acquiring wake lock to prevent sleep..."
- termux-wake-lock
- else
- warn "termux-api not installed or termux-wake-lock not available. Consider 'pkg install termux-api'."
- fi
- check_termux_storage || { return 1; }
- # Check internet connection before proceeding
- check_internet
- log "Updating package lists..."
- pkg update -y || { error "Failed to update package lists"; wait_for_keypress_keep_screen; return 1; }
- log "Upgrading installed packages..."
- pkg upgrade -y || { warn "Failed to upgrade packages. Continuing, but this might lead to issues."; }
- log "Installing dependencies: openssl wget git python build-essential clang cmake libjpeg-turbo..."
- 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; }
- if ! command -v termux-wake-lock &>/dev/null; then
- log "Attempting to install termux-api for better system integration..."
- pkg install -y termux-api || warn "Could not install termux-api, continuing anyway."
- fi
- if [ -d "$KOBOLDCPP_DIR/.git" ]; then
- log "KoboldCpp repository already exists, updating..."
- cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
- git fetch --all --prune || { error "Failed to fetch updates"; wait_for_keypress_keep_screen; return 1; }
- git reset --hard "origin/$DEFAULT_BRANCH" || { error "Failed to update to latest version of branch '$DEFAULT_BRANCH'"; wait_for_keypress_keep_screen; return 1; }
- else
- log "Cloning KoboldCpp repository (branch: $DEFAULT_BRANCH)..."
- git clone --depth 1 --branch "$DEFAULT_BRANCH" "$REPO_URL" "$KOBOLDCPP_DIR" || { error "Failed to clone repository"; wait_for_keypress_keep_screen; return 1; }
- cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR' after cloning"; wait_for_keypress_keep_screen; return 1; }
- fi
- mkdir -p "$MODEL_DIR" || { error "Failed to create models directory: '$MODEL_DIR'"; wait_for_keypress_keep_screen; return 1; }
- # Get system information for optimized build
- local device_info=$(get_device_info)
- log "Building for: $device_info"
- log "Building KoboldCpp with portable settings for Android..."
- export LLAMA_PORTABLE=1
- export KOBOLDCPP_PLATFORM="android"
- log "Cleaning previous build files (if any)..."
- make clean || warn "Failed to clean build files, continuing anyway."
- log "Building with optimizations for your device..."
- make -j$(nproc) || {
- warn "Build failed with default optimizations, trying with safer settings (LLAMA_NO_ACCELERATE=1)..."
- make clean
- make -j$(nproc) LLAMA_NO_ACCELERATE=1 || {
- warn "Build failed with first fallback, trying with fewer threads..."
- make clean
- make LLAMA_NO_ACCELERATE=1 || {
- error "Build failed with all fallback options. Please check the error messages above."
- wait_for_keypress_keep_screen
- return 1
- }
- }
- }
- local py_script_exists=false
- local binary_exists=false
- if [ -f "$KOBOLDCPP_DIR/koboldcpp.py" ]; then
- py_script_exists=true
- fi
- if [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]; then
- binary_exists=true
- fi
- if [ "$py_script_exists" = false ] && [ "$binary_exists" = false ]; then
- error "Build verification failed. Neither koboldcpp.py nor a compiled binary (e.g., koboldcpp-android-arm64 or koboldcpp) was found."
- info "Please check the build logs in '$LOG_FILE' for errors."
- wait_for_keypress_keep_screen
- return 1
- fi
- # Save build information to help with future builds
- if [ "$binary_exists" = true ]; then
- # local build_success="1" # This variable was declared but not used. Removed.
- save_config "last_successful_build" "$(date +%Y%m%d)"
- # Save the last used build option (0 for default, 1 for LLAMA_NO_ACCELERATE=1)
- # Check if LLAMA_NO_ACCELERATE is set to 1 in the environment or last command
- if [[ "${build_command:-}" == *"LLAMA_NO_ACCELERATE=1"* ]]; then
- save_config "build_options" "1"
- else
- save_config "build_options" "0"
- fi
- fi
- success "KoboldCpp installation complete!"
- if command -v termux-wake-unlock &>/dev/null; then
- log "Releasing wake lock..."
- termux-wake-unlock
- fi
- press_enter
- return 0
- }
- add_model() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Add Model from Downloads ===${NC}"
- echo
- log "This copies a .gguf model file from your Termux Downloads directory ($DOWNLOAD_DIR) to the KoboldCpp models directory ($MODEL_DIR)."
- echo
- check_install || { return 1; }
- check_termux_storage || { return 1; }
- # Search for models recursively in Downloads
- echo -e "${YELLOW}Searching for .gguf files in Downloads directory...${NC}"
- local FILES=()
- local SEARCH_DEPTH=3 # Search up to 3 levels deep
- # Use a safer find command with -print0 and read -d $'\0'
- while IFS= read -r -d $'\0' file; do
- # Filter out files directly in DOWNLOAD_DIR if they don't end with .gguf - already done by -name "*.gguf"
- # Additional check to prevent adding files already in MODEL_DIR (unlikely if copying, but safe)
- # if [[ "$(dirname "$file")" == "$MODEL_DIR" ]]; then
- # continue # Skip files already in the target directory
- # fi
- FILES+=("$file")
- done < <(find "$DOWNLOAD_DIR" -maxdepth "$SEARCH_DEPTH" -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
- if [ ${#FILES[@]} -eq 0 ]; then
- error "No .gguf files found in your Downloads directory (searched $SEARCH_DEPTH levels deep)."
- echo "Please download a model (ending with .gguf) to that directory first."
- press_enter
- return 1
- fi
- echo -e "${YELLOW}Found models:${NC}"
- for i in "${!FILES[@]}"; do
- local FILE="${FILES[$i]}"
- local FILENAME
- FILENAME=$(basename "$FILE")
- local REL_PATH=${FILE#"$DOWNLOAD_DIR/"}
- local SIZE
- SIZE=$(get_file_size_human "$FILE")
- echo "[$i] $FILENAME ($SIZE)"
- echo " Location: $REL_PATH"
- done
- echo
- echo -n "Enter number of the model to add (or 'q' to return to menu): "
- read -r CHOICE
- if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then # Handle empty CHOICE
- return 0
- fi
- if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#FILES[@]}" ]; then
- error "Invalid selection."
- wait_for_keypress_keep_screen
- return 1
- fi
- local SRC="${FILES[$CHOICE]}"
- local FILENAME
- FILENAME=$(basename "$SRC")
- local DEST="$MODEL_DIR/$FILENAME"
- if [ -f "$DEST" ]; then
- echo -n "File '$FILENAME' already exists in models directory. Overwrite? [y/N]: "
- read -r CONFIRM
- if [[ ! "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default to N
- info "Operation cancelled."
- press_enter
- return 0
- fi
- fi
- # Check for available space before copying
- local file_size=$(get_file_size_bytes "$SRC")
- check_available_space "$file_size" "$MODEL_DIR" || {
- error "Not enough space to copy the model. Please free up some storage."
- wait_for_keypress_keep_screen
- return 1
- }
- log "Copying '$FILENAME' to models directory..."
- mkdir -p "$MODEL_DIR"
- if cp "$SRC" "$DEST"; then
- success "Model '$FILENAME' copied to '$MODEL_DIR'."
- # Verify the copied model integrity
- log "Verifying integrity of copied model..."
- if check_model_integrity "$DEST"; then
- success "Integrity check passed for $FILENAME."
- else
- warn "Integrity check failed for $FILENAME. The copied model may be corrupted."
- warn "Consider deleting it and adding it again, or re-downloading the original file."
- wait_for_keypress_keep_screen
- fi
- else
- error "Failed to copy model file '$FILENAME'. Check permissions and storage space."
- wait_for_keypress_keep_screen
- return 1
- fi
- press_enter
- return 0
- }
- download_model() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Download Model ===${NC}"
- echo
- log "This downloads a .gguf model file directly to the KoboldCpp models directory ($MODEL_DIR)."
- echo
- check_install || { return 1; }
- check_internet || {
- error "Internet connection is required to download models."
- wait_for_keypress_keep_screen
- return 1
- }
- # Show recently used URLs if they exist
- local recent_urls=()
- local config_urls=$(grep "^recent_url_" "$CONFIG_FILE" 2>/dev/null | cut -d= -f2)
- if [ -n "$config_urls" ]; then
- echo -e "${YELLOW}Recently used URLs:${NC}"
- local url_index=0
- while IFS= read -r url; do
- recent_urls+=("$url")
- echo "[$url_index] $url"
- ((url_index++))
- done <<< "$config_urls"
- echo "[n] Enter a new URL"
- echo
- echo -n "Select an option: "
- read -r URL_CHOICE
- local URL=""
- if [[ "$URL_CHOICE" =~ ^[0-9]+$ ]] && [ "$URL_CHOICE" -ge 0 ] && [ "$URL_CHOICE" -lt "${#recent_urls[@]}" ]; then
- URL="${recent_urls[$URL_CHOICE]}"
- info "Selected recent URL: $URL"
- else
- echo "Enter a direct download URL for the model file (must be a .gguf file):"
- echo -n "> "
- read -r URL
- # Add new valid URL to recent list
- if [ -n "$URL" ] && [[ "$URL" == *.gguf* ]]; then
- save_config "recent_url_${#recent_urls[@]}" "$URL"
- fi
- fi
- else
- echo "Enter a direct download URL for the model file (must be a .gguf file):"
- echo -n "> "
- read -r URL
- # Add new valid URL to recent list
- if [ -n "$URL" ] && [[ "$URL" == *.gguf* ]]; then
- save_config "recent_url_0" "$URL"
- fi
- fi
- if [ -z "${URL:-}" ]; then
- info "Operation cancelled (empty URL)."
- press_enter
- return 0
- fi
- local FILENAME
- FILENAME=$(basename "${URL:-}") # Handle potentially empty URL
- FILENAME="${FILENAME%%\?*}" # Remove query string parameters
- if [ -z "$FILENAME" ] || [[ ! "$FILENAME" == *.gguf ]]; then
- warn "Couldn't determine a valid .gguf filename from the URL ('$FILENAME')."
- echo -n "Please enter a filename for this model (should end with .gguf): "
- read -r CUSTOM_FILENAME
- if [ -z "${CUSTOM_FILENAME:-}" ]; then
- info "Operation cancelled (empty filename)."
- press_enter
- return 0
- fi
- FILENAME="$CUSTOM_FILENAME"
- fi
- if [[ ! "$FILENAME" == *.gguf ]]; then
- FILENAME="${FILENAME}.gguf"
- info "Added .gguf extension. Filename will be: $FILENAME"
- fi
- local DEST="$MODEL_DIR/$FILENAME"
- if [ -f "$DEST" ]; then
- echo -n "File '$FILENAME' already exists in models directory. Overwrite? [y/N]: "
- read -r CONFIRM
- if [[ ! "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default N
- info "Operation cancelled."
- press_enter
- return 0
- fi
- fi
- # Check file size before downloading if possible
- local file_size=0
- if command -v curl >/dev/null 2>&1; then
- log "Checking file size before download..."
- # Use -s silent, -I head request, -L follow redirects, grep Content-Length, awk to get value, tr to remove carriage return
- local size_output=$(curl -sIL "$URL" 2>/dev/null | grep -i Content-Length | tail -n 1 | awk '{print $2}' | tr -d '\r')
- if [ -n "$size_output" ] && [[ "$size_output" =~ ^[0-9]+$ ]]; then
- file_size="$size_output"
- fi
- if [ "$file_size" -gt 0 ]; then
- local size_human=$(numfmt --to=iec-i --suffix=B "$file_size" 2>/dev/null || echo "$file_size bytes")
- info "Estimated model size: $size_human"
- # Check available space
- check_available_space "$file_size" "$MODEL_DIR" || {
- error "Not enough space to download the model. Please free up some storage."
- wait_for_keypress_keep_screen
- return 1
- }
- else
- warn "Could not reliably determine model size. Proceeding anyway, but ensure sufficient space is available."
- # As a fallback, check if there's at least 5GB free (a common minimum for larger models)
- check_available_space 5368709120 "$MODEL_DIR" || { # 5GB in bytes
- warn "Less than 5GB free space detected and could not get exact file size. Download might fail due to lack of space."
- echo -n "Continue download anyway? [y/N]: "
- read -r FORCE_DOWNLOAD
- if [[ ! "${FORCE_DOWNLOAD:-N}" =~ ^[Yy]$ ]]; then
- info "Download cancelled due to potential space issues."
- press_enter
- return 1
- fi
- }
- fi
- else
- warn "curl command not found. Cannot check file size before downloading. Proceeding anyway, but ensure sufficient space is available."
- # As a fallback, check if there's at least 5GB free (a common minimum for larger models)
- check_available_space 5368709120 "$MODEL_DIR" || { # 5GB in bytes
- warn "Less than 5GB free space detected and could not get exact file size. Download might fail due to lack of space."
- echo -n "Continue download anyway? [y/N]: "
- read -r FORCE_DOWNLOAD
- if [[ ! "${FORCE_DOWNLOAD:-N}" =~ ^[Yy]$ ]]; then
- info "Download cancelled due to potential space issues."
- press_enter
- return 1
- fi
- }
- fi
- log "Downloading model from: $URL"
- log "This may take a while depending on file size and your connection."
- log "Model will be saved to: $DEST"
- if command -v termux-wake-lock &>/dev/null; then
- termux-wake-lock
- fi
- mkdir -p "$MODEL_DIR"
- # Use curl with progress bar if available, otherwise fall back to wget
- if command -v curl >/dev/null 2>&1; then
- log "Downloading with curl..."
- if ! curl -L --progress-bar "$URL" -o "$DEST"; then
- error "Download failed. Please check the URL and your internet connection."
- # Clean up potentially incomplete file
- rm -f "$DEST" 2>/dev/null
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- wait_for_keypress_keep_screen
- return 1
- fi
- elif command -v wget >/dev/null 2>&1; then # Added explicit check for wget
- log "Downloading with wget..."
- if ! wget -c --show-progress "$URL" -O "$DEST"; then
- error "Download failed. Please check the URL and your internet connection."
- # Clean up potentially incomplete file (wget often cleans up .part files itself, but clean the target too)
- rm -f "$DEST" "$DEST.part" 2>/dev/null
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- wait_for_keypress_keep_screen
- return 1
- fi
- else
- error "Neither 'curl' nor 'wget' found. Cannot download the file."
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- wait_for_keypress_keep_screen
- return 1
- fi
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- if [ -f "$DEST" ] && [ -s "$DEST" ]; then
- local downloaded_size=$(get_file_size_human "$DEST")
- success "Model '$FILENAME' ($downloaded_size) downloaded successfully to '$DEST'"
- # Verify the downloaded model integrity
- log "Verifying integrity of downloaded model..."
- if check_model_integrity "$DEST"; then
- success "Integrity check passed for $FILENAME."
- else
- warn "Integrity check failed for $FILENAME. The downloaded model may be corrupted."
- warn "Consider deleting it and downloading it again."
- wait_for_keypress_keep_screen # Pause to show integrity warning
- fi
- # Show notification if available
- if command -v termux-notification &>/dev/null; then
- termux-notification --title "Download Complete" --content "Model $FILENAME has been downloaded successfully."
- fi
- else
- error "Download failed or resulted in an empty file: File not found or empty after download completion."
- rm -f "$DEST" 2>/dev/null
- wait_for_keypress_keep_screen
- fi
- press_enter
- return 0
- }
- delete_model() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Delete Model ===${NC}"
- echo
- log "This will permanently delete a model file from your KoboldCpp models directory ($MODEL_DIR)."
- echo
- check_install || { return 1; }
- echo -e "${YELLOW}Available models in $MODEL_DIR:${NC}"
- local MODELS=()
- while IFS= read -r -d '' file; do # Use '' for null delimiter, portable
- MODELS+=("$file")
- done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
- if [ ${#MODELS[@]} -eq 0 ]; then
- error "No models found in '$MODEL_DIR'."
- wait_for_keypress_keep_screen
- return 1
- fi
- for i in "${!MODELS[@]}"; do
- local MODEL="${MODELS[$i]}"
- local FILENAME
- FILENAME=$(basename "$MODEL")
- local SIZE
- SIZE=$(get_file_size_human "$MODEL")
- echo "[$i] $FILENAME ($SIZE)"
- done
- echo
- echo -n "Enter number of the model to delete (or 'q' to return to menu): "
- read -r CHOICE
- if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
- return 0
- fi
- if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#MODELS[@]}" ]; then
- error "Invalid selection."
- wait_for_keypress_keep_screen
- return 1
- fi
- local TARGET="${MODELS[$CHOICE]}"
- local FILENAME
- FILENAME=$(basename "$TARGET")
- local SIZE
- SIZE=$(get_file_size_human "$TARGET")
- echo -e "${RED}WARNING: This action is irreversible!${NC}"
- echo -n "Are you sure you want to delete '$FILENAME' ($SIZE)? [y/N]: "
- read -r CONFIRM
- if [[ "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default N
- log "Deleting model file: $TARGET"
- if rm "$TARGET"; then
- success "Deleted model '$FILENAME' ($SIZE freed)."
- else
- error "Failed to delete '$FILENAME'. Check permissions."
- wait_for_keypress_keep_screen
- return 1
- fi
- else
- info "Deletion cancelled."
- fi
- press_enter
- return 0
- }
- list_models() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Available Models ===${NC}"
- echo
- log "Listing .gguf models in the cache directory: $MODEL_DIR"
- echo
- check_install || { return 1; }
- mkdir -p "$MODEL_DIR"
- local MODELS=()
- while IFS= read -r -d '' file; do # Use '' for null delimiter, portable
- MODELS+=("$file")
- done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
- if [ ${#MODELS[@]} -eq 0 ]; then
- echo "No models found in '$MODEL_DIR'."
- echo
- echo "You can add models using the main menu options:"
- echo "2. Add model from Downloads"
- echo "3. Download model from URL"
- press_enter
- return 0
- fi
- echo -e "${YELLOW}Size\tStatus\tModel${NC}"
- local TOTAL_SIZE_BYTES=0
- local CORRUPT_COUNT=0
- for MODEL_PATH in "${MODELS[@]}"; do
- local SIZE_BYTES
- SIZE_BYTES=$(get_file_size_bytes "$MODEL_PATH")
- TOTAL_SIZE_BYTES=$((TOTAL_SIZE_BYTES + SIZE_BYTES))
- local HUMAN_SIZE
- HUMAN_SIZE=$(get_file_size_human "$MODEL_PATH")
- local FILENAME
- FILENAME=$(basename "$MODEL_PATH")
- # Check if model might be corrupted
- local status="✓ OK"
- local status_color="$GREEN"
- if ! check_model_integrity "$MODEL_PATH" >/dev/null 2>&1; then # Check silently for listing
- status="⚠️ Issues"
- status_color="$YELLOW"
- ((CORRUPT_COUNT++))
- fi
- # Use printf for consistent formatting
- printf "%s\t${status_color}%s${NC}\t%s\n" "$HUMAN_SIZE" "$status" "$FILENAME"
- done
- local HUMAN_TOTAL_SIZE="0B"
- if [ "$TOTAL_SIZE_BYTES" -gt 0 ]; then
- if command -v numfmt &>/dev/null; then
- HUMAN_TOTAL_SIZE=$(numfmt --to=iec-i --suffix=B "$TOTAL_SIZE_BYTES")
- else
- if [ "$TOTAL_SIZE_BYTES" -ge 1073741824 ]; then
- HUMAN_TOTAL_SIZE=$(awk -v ts="$TOTAL_SIZE_BYTES" 'BEGIN { printf "%.2fGiB", ts/1073741824 }')
- elif [ "$TOTAL_SIZE_BYTES" -ge 1048576 ]; then
- HUMAN_TOTAL_SIZE=$(awk -v ts="$TOTAL_SIZE_BYTES" 'BEGIN { printf "%.2fMiB", ts/1048576 }')
- elif [ "$TOTAL_SIZE_BYTES" -ge 1024 ]; then
- HUMAN_TOTAL_SIZE=$(awk -v ts="$TOTAL_SIZE_BYTES" 'BEGIN { printf "%.2fKiB", ts/1024 }')
- else
- HUMAN_TOTAL_SIZE="${TOTAL_SIZE_BYTES}B"
- fi
- fi
- fi
- echo -e "${YELLOW}------------------------------------${NC}"
- echo -e "${GREEN}Total Models:\t${#MODELS[@]}${NC}"
- echo -e "${GREEN}Total Size:\t$HUMAN_TOTAL_SIZE${NC}"
- if [ "$CORRUPT_COUNT" -gt 0 ]; then
- echo -e "${YELLOW}Models with Potential Issues:\t$CORRUPT_COUNT${NC}"
- fi
- echo
- echo "Legend: ✓ OK = Basic checks passed, ⚠️ Issues = Potential corruption or not a GGUF file"
- press_enter
- return 0
- }
- # New function to interactively verify a specific model's integrity
- verify_model_integrity_interactive() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Verify Model Integrity ===${NC}"
- echo
- log "This will perform basic checks on a selected model file to detect potential corruption."
- echo
- check_install || { return 1; }
- mkdir -p "$MODEL_DIR" # Ensure directory exists before checking
- local MODELS=()
- while IFS= read -r -d '' file; do
- MODELS+=("$file")
- done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
- if [ ${#MODELS[@]} -eq 0 ]; then
- error "No models found in '$MODEL_DIR' to verify."
- wait_for_keypress_keep_screen
- return 1
- fi
- echo -e "${YELLOW}Available models in $MODEL_DIR:${NC}"
- for i in "${!MODELS[@]}"; do
- local MODEL="${MODELS[$i]}"
- local FILENAME=$(basename "$MODEL")
- local SIZE=$(get_file_size_human "$MODEL")
- echo "[$i] $FILENAME ($SIZE)"
- done
- echo
- echo -n "Enter number of the model to verify (or 'q' to return to menu): "
- read -r CHOICE
- if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
- return 0
- fi
- if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#MODELS[@]}" ]; then
- error "Invalid selection."
- wait_for_keypress_keep_screen
- return 1
- fi
- local TARGET_MODEL_PATH="${MODELS[$CHOICE]}"
- local TARGET_MODEL_FILENAME=$(basename "$TARGET_MODEL_PATH")
- log "Verifying integrity of '$TARGET_MODEL_FILENAME'..."
- if check_model_integrity "$TARGET_MODEL_PATH"; then
- success "Model '$TARGET_MODEL_FILENAME' appears to be intact based on basic checks."
- else
- error "Model '$TARGET_MODEL_FILENAME' failed integrity checks. It may be corrupted or incomplete."
- info "This model might not load or run correctly."
- fi
- press_enter
- return 0
- }
- run_model() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Run KoboldCpp ===${NC}"
- echo
- log "Starting the KoboldCpp server with a selected model from the cache directory ($MODEL_DIR)."
- echo
- check_install || { return 1; }
- mkdir -p "$MODEL_DIR"
- echo -e "${YELLOW}Available models:${NC}"
- local MODELS=()
- while IFS= read -r -d '' file; do # Use '' for null delimiter, portable
- MODELS+=("$file")
- done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | sort -z)
- if [ ${#MODELS[@]} -eq 0 ]; then
- error "No models found in '$MODEL_DIR'."
- echo
- echo "You need to add models first. Return to the main menu and select:"
- echo "2. Add model from Downloads"
- echo "3. Download model from URL"
- wait_for_keypress_keep_screen
- return 1
- fi
- # Sort models by last used time if available
- local last_used_model=""
- if [ -f "$CONFIG_FILE" ]; then
- last_used_model=$(grep "^last_used_model=" "$CONFIG_FILE" | cut -d= -f2)
- # Ensure last_used_model still exists
- if [ -n "$last_used_model" ] && [ ! -f "$last_used_model" ]; then
- warn "Last used model '$last_used_model' not found. Resetting last used model setting."
- last_used_model=""
- # Consider removing the invalid config entry
- # sed -i '/^last_used_model=/d' "$CONFIG_FILE"
- fi
- if [ -n "$last_used_model" ]; then
- for i in "${!MODELS[@]}"; do
- if [ "${MODELS[$i]}" = "$last_used_model" ]; then
- # Move last used model to the top
- local temp=${MODELS[0]}
- MODELS[0]=${MODELS[$i]}
- MODELS[$i]=$temp
- break
- fi
- done
- fi
- fi
- for i in "${!MODELS[@]}"; do
- local MODEL="${MODELS[$i]}"
- local FILENAME
- FILENAME=$(basename "$MODEL")
- local SIZE
- SIZE=$(get_file_size_human "$MODEL")
- # Mark the last used model
- local marker=""
- if [ "$MODEL" = "$last_used_model" ]; then
- marker=" (last used)"
- fi
- echo "[$i] $FILENAME ($SIZE)$marker"
- done
- echo
- echo -n "Enter number of the model to run (or 'q' to return to menu): "
- read -r CHOICE
- if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
- return 0
- fi
- if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#MODELS[@]}" ]; then
- error "Invalid selection."
- wait_for_keypress_keep_screen
- return 1
- fi
- local TARGET_MODEL_PATH="${MODELS[$CHOICE]}"
- local TARGET_MODEL_FILENAME=$(basename "$TARGET_MODEL_PATH")
- local TARGET_MODEL_SIZE=$(get_file_size_human "$TARGET_MODEL_PATH")
- # Save last used model
- save_config "last_used_model" "$TARGET_MODEL_PATH"
- # Check model integrity before running
- if ! check_model_integrity "$TARGET_MODEL_PATH"; then
- echo -e "${RED}WARNING: This model may have integrity issues.${NC}"
- echo -n "Do you still want to continue? [y/N]: "
- read -r INTEGRITY_CONFIRM
- if [[ ! "${INTEGRITY_CONFIRM:-N}" =~ ^[Yy]$ ]]; then
- info "Operation cancelled."
- press_enter
- return 0
- fi
- fi
- clear_screen
- echo -e "${BOLD}${CYAN}=== Run Configuration: $TARGET_MODEL_FILENAME ===${NC}"
- echo
- echo "Model: $TARGET_MODEL_FILENAME ($TARGET_MODEL_SIZE)"
- echo
- info "You can customize parameters like threads, context size, and port."
- info "For optimal performance, consider GPU acceleration if your device/build supports CLBlast (--useclblast 0 0)."
- echo
- # Load saved configuration if exists
- local saved_config=false
- local saved_threads=""
- local saved_context=""
- local saved_port=""
- local saved_extra=""
- local model_config_key="model_${TARGET_MODEL_FILENAME//[^a-zA-Z0-9_]/_}" # Sanitize filename for config key
- if [ -f "$CONFIG_FILE" ]; then
- saved_threads=$(grep "^${model_config_key}_threads=" "$CONFIG_FILE" | cut -d= -f2)
- saved_context=$(grep "^${model_config_key}_context=" "$CONFIG_FILE" | cut -d= -f2)
- saved_port=$(grep "^${model_config_key}_port=" "$CONFIG_FILE" | cut -d= -f2)
- saved_extra=$(grep "^${model_config_key}_extra=" "$CONFIG_FILE" | cut -d= -f2)
- if [ -n "$saved_threads" ] && [ -n "$saved_context" ] && [ -n "$saved_port" ]; then
- saved_config=true
- echo "Found saved configuration for this model:"
- echo "- Threads: ${YELLOW}$saved_threads${NC}"
- echo "- Context: ${YELLOW}$saved_context${NC}"
- echo "- Port: ${YELLOW}$saved_port${NC}"
- if [ -n "$saved_extra" ]; then
- echo "- Extra options: ${YELLOW}$saved_extra${NC}"
- fi
- echo
- fi
- fi
- echo "Select run configuration:"
- echo "[1] Quick start (recommended settings for this device)"
- if [ "$saved_config" = true ]; then
- echo "[2] Use saved configuration for this model"
- echo "[3] Custom settings"
- else
- echo "[2] Custom settings"
- fi
- echo "[q] Return to main menu"
- echo
- echo -n "Your choice: "
- read -r CONFIG_CHOICE
- local ARGS_STRING=""
- local PORT_NUMBER="5001"
- local NPROC_COUNT=4
- if command -v nproc &>/dev/null; then
- NPROC_COUNT=$(nproc)
- if [ "$NPROC_COUNT" -eq 0 ]; then NPROC_COUNT=1; fi # Avoid 0 threads
- fi
- local SUGGESTED_THREADS="$NPROC_COUNT"
- if [ "$NPROC_COUNT" -gt 8 ]; then # Suggest fewer threads on high-core count devices for balance
- SUGGESTED_THREADS=$((NPROC_COUNT / 2))
- if [ "$SUGGESTED_THREADS" -lt 4 ]; then SUGGESTED_THREADS=4; fi
- fi
- local current_choice="${CONFIG_CHOICE:-1}" # Default to 1 (Quick Start)
- if [ "$saved_config" = false ] && [ "$current_choice" = "3" ]; then
- # If saved_config is false, option 3 means custom, but the menu showed [2] Custom. Adjust choice.
- current_choice="2"
- fi
- case "$current_choice" in
- 1)
- ARGS_STRING="--threads ${SUGGESTED_THREADS} --contextsize 2048 --port $PORT_NUMBER"
- info "Using Quick Start with $SUGGESTED_THREADS threads, context 2048, port $PORT_NUMBER."
- ;;
- 2)
- if [ "$saved_config" = true ]; then
- # Use saved configuration (only if option 2 was explicitly chosen AND saved config exists)
- ARGS_STRING="--threads $saved_threads --contextsize $saved_context --port $saved_port"
- if [ -n "$saved_extra" ]; then
- ARGS_STRING="$ARGS_STRING $saved_extra"
- fi
- info "Using saved configuration with $saved_threads threads, context $saved_context, port $saved_port."
- else
- # Saved config doesn't exist, this was the "Custom settings" option
- # Fall through to custom settings block
- CONFIG_CHOICE="custom" # Internal flag
- fi
- ;;
- 3) # This case is only reached if saved_config is true AND user chose 3
- CONFIG_CHOICE="custom" # Internal flag
- ;;
- q|Q)
- return 0
- ;;
- *)
- info "Invalid configuration choice, using Quick Start defaults."
- ARGS_STRING="--threads ${SUGGESTED_THREADS} --contextsize 2048 --port $PORT_NUMBER"
- ;;
- esac
- # Custom settings block (executed if CONFIG_CHOICE was "custom")
- if [ "${CONFIG_CHOICE:-}" = "custom" ]; then
- clear_screen
- echo -e "${BOLD}${CYAN}=== Custom Settings for $TARGET_MODEL_FILENAME ===${NC}"
- echo
- echo -n "Number of CPU threads (suggested: $SUGGESTED_THREADS, max detected: $NPROC_COUNT): "
- read -r THREADS_INPUT
- local THREADS=${THREADS_INPUT:-$SUGGESTED_THREADS}
- # Add validation for threads input
- if ! [[ "$THREADS" =~ ^[0-9]+$ ]] || [ "$THREADS" -le 0 ]; then
- warn "Invalid thread count '$THREADS_INPUT', using suggested count: $SUGGESTED_THREADS."
- THREADS="$SUGGESTED_THREADS"
- fi
- ARGS_STRING="--threads $THREADS"
- # Get model size to suggest appropriate context size
- local MODEL_SIZE_BYTES=$(get_file_size_bytes "$TARGET_MODEL_PATH")
- local SUGGESTED_CONTEXT=2048
- if [ "$MODEL_SIZE_BYTES" -gt 10737418240 ]; then # >10GB models (10 * 1024^3)
- SUGGESTED_CONTEXT=4096
- elif [ "$MODEL_SIZE_BYTES" -lt 5368709120 ]; then # <5GB models (5 * 1024^3)
- SUGGESTED_CONTEXT=8192
- fi
- echo -n "Context size (e.g., 2048, 4096, 8192; suggested: $SUGGESTED_CONTEXT): "
- read -r CONTEXT_INPUT
- local CONTEXT=${CONTEXT_INPUT:-$SUGGESTED_CONTEXT}
- # Add validation for context size input
- if ! [[ "$CONTEXT" =~ ^[0-9]+$ ]] || [ "$CONTEXT" -le 0 ]; then
- warn "Invalid context size '$CONTEXT_INPUT', using suggested size: $SUGGESTED_CONTEXT."
- CONTEXT="$SUGGESTED_CONTEXT"
- fi
- ARGS_STRING="$ARGS_STRING --contextsize $CONTEXT"
- echo -n "Server port (default: 5001): "
- read -r PORT_INPUT
- PORT_NUMBER=${PORT_INPUT:-5001}
- # Add validation for port number
- if ! [[ "$PORT_NUMBER" =~ ^[0-9]+$ ]] || [ "$PORT_NUMBER" -lt 1024 ] || [ "$PORT_NUMBER" -gt 65535 ]; then
- warn "Invalid port number '$PORT_INPUT', using default port: 5001."
- PORT_NUMBER="5001"
- fi
- ARGS_STRING="$ARGS_STRING --port $PORT_NUMBER"
- echo
- echo "Additional options:"
- echo "[1] No additional options - continue with settings above"
- echo "[2] Enable GPU acceleration (CLBlast - if supported by your device/build)"
- echo "[3] Use low VRAM mode (slower, uses less memory, implies --nostream)"
- echo "[4] Enable Mmapped IO (faster loading but more memory usage)"
- echo "[5] Set custom LORA files (if you have them)"
- echo "[6] Custom extra arguments (enter raw arguments)"
- echo
- echo -n "Your choice for additional options [1]: "
- read -r EXTRA_OPTIONS_CHOICE
- local EXTRA_ARGS_VAL=""
- case "${EXTRA_OPTIONS_CHOICE:-1}" in # Default to 1
- 2)
- EXTRA_ARGS_VAL="--useclblast 0 0"
- info "GPU acceleration (CLBlast) enabled. Ensure your KoboldCpp is built with CLBlast support."
- ;;
- 3)
- EXTRA_ARGS_VAL="--lowvram --nostream"
- info "Low VRAM mode enabled."
- ;;
- 4)
- EXTRA_ARGS_VAL="--mmap"
- info "Memory-mapped IO enabled."
- ;;
- 5)
- echo
- echo "Enter full path to LORA file:"
- read -r LORA_PATH
- if [ -n "$LORA_PATH" ] && [ -f "$LORA_PATH" ]; then
- echo "Enter LORA scale factor (default: 1.0):"
- read -r LORA_SCALE
- LORA_SCALE=${LORA_SCALE:-1.0}
- EXTRA_ARGS_VAL="--lora \"$LORA_PATH:$LORA_SCALE\"" # Quote path in case of spaces
- info "LORA '$LORA_PATH' will be loaded with scale $LORA_SCALE."
- else
- warn "LORA file not found or path empty. No LORA will be loaded."
- fi
- ;;
- 6)
- echo
- echo "Enter custom additional arguments (e.g., --gpulayers 10 --unbantokens):"
- read -r RAW_EXTRA_ARGS
- EXTRA_ARGS_VAL="$RAW_EXTRA_ARGS"
- info "Using custom extra arguments: $RAW_EXTRA_ARGS"
- ;;
- *) # Includes 1 and any other input
- info "No additional special options selected."
- ;;
- esac
- if [ -n "$EXTRA_ARGS_VAL" ]; then
- ARGS_STRING="$ARGS_STRING $EXTRA_ARGS_VAL"
- fi
- # Save this configuration for future use
- echo -n "Save this configuration for future use with this model? [Y/n]: "
- read -r SAVE_CONFIG_CHOICE
- if [[ ! "${SAVE_CONFIG_CHOICE:-Y}" =~ ^[Nn]$ ]]; then # Default Y
- save_config "${model_config_key}_threads" "$THREADS"
- save_config "${model_config_key}_context" "$CONTEXT"
- save_config "${model_config_key}_port" "$PORT_NUMBER"
- save_config "${model_config_key}_extra" "$EXTRA_ARGS_VAL"
- success "Configuration saved for future use."
- fi
- fi # End Custom settings block
- clear_screen
- log "Starting KoboldCpp with model: $TARGET_MODEL_FILENAME ($TARGET_MODEL_SIZE)"
- if [ -n "$ARGS_STRING" ]; then
- log "Run arguments: $ARGS_STRING"
- fi
- # Show available network interfaces
- local DEVICE_IP_MSG=""
- if command -v ip &>/dev/null; then
- local interface_ip
- interface_ip=$(ip -o -4 addr show scope global | awk '!/ lo / && $4 !~ /^169\.254\./ {print $4}' | cut -d/ -f1 | head -n1)
- if [ -n "$interface_ip" ]; then
- DEVICE_IP_MSG=" (or http://$interface_ip:$PORT_NUMBER from another device on the same network)"
- fi
- fi
- log "Server will be available at: http://localhost:$PORT_NUMBER${DEVICE_IP_MSG}"
- log "Press Ctrl+C in this terminal to stop the server."
- echo
- cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
- local KOBOLDCPP_BASE_CMD=""
- if [ -f "./koboldcpp-android-arm64" ] && [ -x "./koboldcpp-android-arm64" ]; then
- KOBOLDCPP_BASE_CMD="./koboldcpp-android-arm64"
- elif [ -f "./koboldcpp" ] && [ -x "./koboldcpp" ]; then
- KOBOLDCPP_BASE_CMD="./koboldcpp"
- elif [ -f "./koboldcpp.py" ]; then
- KOBOLDCPP_BASE_CMD="python koboldcpp.py"
- else
- error "Could not find a runnable KoboldCpp executable (e.g., koboldcpp-android-arm64, koboldcpp) or koboldcpp.py script in $KOBOLDCPP_DIR."
- info "Please ensure KoboldCpp is installed and built correctly."
- wait_for_keypress_keep_screen
- return 1
- fi
- local cmd_array=()
- if [[ "$KOBOLDCPP_BASE_CMD" == "python koboldcpp.py" ]]; then
- cmd_array+=("python" "koboldcpp.py")
- else
- cmd_array+=("$KOBOLDCPP_BASE_CMD")
- fi
- cmd_array+=("--model" "$TARGET_MODEL_PATH")
- local args_temp_array=()
- # Safely split ARGS_STRING into arguments, handling quotes
- # This is tricky in pure bash without eval/set -f, but read -r -a is better than simple split
- # Using a temporary array and disabling/enabling globbing around read -a
- set -f # Disable globbing
- read -r -a args_temp_array <<< "$ARGS_STRING"
- set +f # Re-enable globbing
- if [ ${#args_temp_array[@]} -gt 0 ]; then
- cmd_array+=("${args_temp_array[@]}")
- fi
- info "Executing: ${cmd_array[*]}"
- if command -v termux-wake-lock &>/dev/null; then
- termux-wake-lock
- fi
- # Start time measurement
- local start_time=$(date +%s)
- # Execute the command array
- if ! "${cmd_array[@]}"; then
- local exit_code=$?
- local end_time=$(date +%s)
- local runtime=$((end_time - start_time))
- local hours=$((runtime / 3600))
- local minutes=$(( (runtime % 3600) / 60 ))
- local seconds=$((runtime % 60))
- warn "KoboldCpp server exited with code $exit_code. Runtime: ${hours}h ${minutes}m ${seconds}s"
- echo "Check the console output above for error messages."
- # Save partial runtime statistics on error
- save_config "last_runtime_hours" "$hours"
- save_config "last_runtime_minutes" "$minutes"
- save_config "last_runtime_seconds" "$seconds"
- save_config "last_runtime_model" "$TARGET_MODEL_FILENAME (exit $exit_code)"
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- wait_for_keypress_keep_screen
- return 1
- fi
- # End time measurement (only if command finished without error)
- local end_time=$(date +%s)
- local runtime=$((end_time - start_time))
- local hours=$((runtime / 3600))
- local minutes=$(( (runtime % 3600) / 60 ))
- local seconds=$((runtime % 60))
- # Save runtime statistics
- save_config "last_runtime_hours" "$hours"
- save_config "last_runtime_minutes" "$minutes"
- save_config "last_runtime_seconds" "$seconds"
- save_config "last_runtime_model" "$TARGET_MODEL_FILENAME"
- if command -v termux-wake-unlock &>/dev/null; then
- log "Releasing wake lock..."
- termux-wake-unlock
- fi
- echo
- info "KoboldCpp server stopped normally. Runtime: ${hours}h ${minutes}m ${seconds}s"
- press_enter
- return 0
- }
- rebuild() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Rebuild KoboldCpp ===${NC}"
- echo
- log "Rebuilding KoboldCpp from source in $KOBOLDCPP_DIR..."
- # Check if KoboldCpp directory exists and is a git repo
- if [ ! -d "$KOBOLDCPP_DIR/.git" ]; then
- error "KoboldCpp directory '$KOBOLDCPP_DIR' is not a git repository. Cannot rebuild."
- info "Please use the 'Install / Update / Rebuild' option from the main menu to install first."
- wait_for_keypress_keep_screen
- return 1
- fi
- cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
- if command -v termux-wake-lock &>/dev/null; then
- termux-wake-lock
- fi
- log "Cleaning build files..."
- make clean || warn "Failed to clean build files, attempting rebuild anyway."
- # Get system information for optimized build
- local device_info=$(get_device_info)
- log "Building for: $device_info"
- # Try to use previously successful build options
- local build_options=$(load_config "build_options" "0") # Default to 0 (default build)
- export LLAMA_PORTABLE=1
- export KOBOLDCPP_PLATFORM="android"
- local build_command="make -j$(nproc)"
- if [ "$build_options" = "1" ]; then
- log "Using previously successful build options (LLAMA_NO_ACCELERATE=1)..."
- build_command="make -j$(nproc) LLAMA_NO_ACCELERATE=1"
- fi
- log "Starting build with command: $build_command"
- # Use eval to execute the build_command string
- if eval "$build_command"; then
- # If the build was successful, save the options used
- if [[ "$build_command" == *"LLAMA_NO_ACCELERATE=1"* ]]; then
- save_config "build_options" "1"
- else
- save_config "build_options" "0"
- fi
- success "KoboldCpp rebuilt successfully."
- else
- local build_exit_code=$?
- error "Rebuild failed with command: $build_command (Exit code $build_exit_code)."
- # If default build failed, try safer settings regardless of previous config
- if [ "$build_options" != "1" ]; then
- warn "Trying rebuild with safer settings (LLAMA_NO_ACCELERATE=1)..."
- make clean # Clean before retrying
- if make LLAMA_NO_ACCELERATE=1; then
- success "KoboldCpp rebuilt successfully with LLAMA_NO_ACCELERATE=1."
- save_config "build_options" "1" # Save the successful option
- else
- error "Rebuild failed even with safer settings. Please check error messages."
- wait_for_keypress_keep_screen
- return 1
- fi
- else
- # Safer settings were already tried or previously saved, and it failed again
- error "Rebuild failed with saved safer settings. Please check error messages."
- wait_for_keypress_keep_screen
- return 1
- fi
- fi
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- press_enter
- return 0
- }
- update_koboldcpp() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Update KoboldCpp ===${NC}"
- echo
- log "Updating the KoboldCpp source code from the repository."
- # Check if KoboldCpp directory exists and is a git repo
- if [ ! -d "$KOBOLDCPP_DIR/.git" ]; then
- error "KoboldCpp directory '$KOBOLDCPP_DIR' is not a git repository. Cannot update."
- info "Please use the 'Install / Update / Rebuild' option from the main menu to install first."
- wait_for_keypress_keep_screen
- return 1
- fi
- check_install || { return 1; }
- check_internet || {
- warn "Internet connection is required to update KoboldCpp."
- echo -n "Do you want to continue anyway? [y/N]: "
- read -r CONTINUE
- if [[ ! "${CONTINUE:-N}" =~ ^[Yy]$ ]]; then
- press_enter
- return 0
- fi
- }
- log "Checking for KoboldCpp updates..."
- cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
- # Check for updates before proceeding
- # Use git fetch --dry-run or similar for a cleaner check without fetching
- # git fetch origin "$DEFAULT_BRANCH" --quiet --dry-run # --dry-run isn't always supported or clear
- # Alternative: fetch and compare
- if ! git fetch origin "$DEFAULT_BRANCH" --quiet; then
- warn "Failed to fetch updates from origin. Cannot determine if updates are available."
- echo -n "Attempt update anyway? [y/N]: "
- read -r ATTEMPT_UPDATE
- if [[ ! "${ATTEMPT_UPDATE:-N}" =~ ^[Yy]$ ]]; then
- press_enter
- return 0
- fi
- # If user wants to attempt, proceed with reset below which might fail or succeed with old data
- local updates_available=false # Assume no updates to skip the log output below
- else
- local LOCAL=$(git rev-parse HEAD)
- local REMOTE=$(git rev-parse origin/"$DEFAULT_BRANCH")
- if [ "$LOCAL" = "$REMOTE" ]; then
- info "KoboldCpp is already up to date (on branch '$DEFAULT_BRANCH')."
- echo -n "Force update/rebuild anyway? [y/N]: "
- read -r FORCE_UPDATE
- if [[ ! "${FORCE_UPDATE:-N}" =~ ^[Yy]$ ]]; then
- press_enter
- return 0
- fi
- local updates_available=false # No actual updates
- else
- echo -e "${YELLOW}Updates available:${NC}"
- git log --oneline --graph --decorate HEAD..origin/"$DEFAULT_BRANCH" | head -n 10
- if [ "$(git log --oneline HEAD..origin/"$DEFAULT_BRANCH" 2>/dev/null | wc -l)" -gt 10 ]; then
- echo "... and more changes"
- fi
- echo
- local updates_available=true
- fi
- fi
- 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]: "
- read -r CONFIRM
- if [[ "${CONFIRM:-Y}" =~ ^[Nn]$ ]]; then # Default Y
- info "Update cancelled."
- press_enter
- return 0
- fi
- local stashed_changes=false
- local stash_pop_failed=false
- # Check for uncommitted changes that are tracked or untracked
- if ! git diff-index --quiet HEAD -- || git ls-files --others --exclude-standard | grep .; then
- log "Stashing local changes..."
- if git stash push -u -m "Auto-stash by KCPP Manager script $(date)"; then
- stashed_changes=true
- info "Local changes stashed."
- else
- warn "Failed to stash local changes. If you have modifications, they might be overwritten or cause conflicts."
- fi
- else
- info "No local changes to stash."
- fi
- log "Fetching updates from origin..."
- # Re-fetch in case --quiet fetch missed something or was skipped earlier
- if ! git fetch --all --prune; then
- error "Failed to fetch updates from repository."
- # Attempt to reapply stash before exiting on fetch failure
- if [ "$stashed_changes" = true ]; then
- info "Attempting to reapply stashed changes after fetch failure..."
- if git stash pop; then
- success "Successfully reapplied stashed local changes."
- else
- warn "Could not automatically reapply stashed local changes after fetch failure. Conflicts may have occurred."
- 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."
- stash_pop_failed=true
- fi
- fi
- if [ "$stash_pop_failed" = true ]; then wait_for_keypress_keep_screen; fi
- return 1
- fi
- log "Resetting to latest version of '$DEFAULT_BRANCH'..."
- if ! git reset --hard "origin/$DEFAULT_BRANCH"; then
- error "Failed to update to latest version of branch '$DEFAULT_BRANCH'. Your local repository might be in an inconsistent state."
- # Attempt to reapply stash before exiting on reset failure
- if [ "$stashed_changes" = true ]; then
- info "Attempting to reapply stashed changes after reset failure..."
- if git stash pop; then
- success "Successfully reapplied stashed local changes."
- else
- warn "Could not automatically reapply stashed local changes after reset failure. Conflicts may have occurred."
- 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."
- stash_pop_failed=true
- fi
- fi
- if [ "$stash_pop_failed" = true ]; then wait_for_keypress_keep_screen; fi
- return 1
- fi
- log "Rebuilding KoboldCpp after update..."
- if ! rebuild; then
- # rebuild() calls wait_for_keypress_keep_screen on its own error
- # Stash reapplying is handled below after rebuild attempt
- local rebuild_failed=true
- else
- local rebuild_failed=false
- fi
- if [ "$stashed_changes" = true ]; then
- info "Attempting to reapply stashed local changes..."
- if git stash pop; then
- success "Successfully reapplied stashed local changes."
- else
- stash_pop_failed=true
- warn "Could not automatically reapply stashed local changes. Conflicts may have occurred."
- 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."
- fi
- fi
- if [ "$rebuild_failed" = true ]; then
- error "KoboldCpp update completed (source fetched and reset), but rebuild failed."
- wait_for_keypress_keep_screen # Keep rebuild error message visible
- return 1 # Indicate failure
- elif [ "$updates_available" = true ]; then
- success "KoboldCpp updated to the latest version from '$DEFAULT_BRANCH' and rebuilt."
- else
- info "KoboldCpp source code was already up-to-date."
- success "Rebuild completed."
- fi
- if [ "$stash_pop_failed" = true ]; then
- wait_for_keypress_keep_screen # Keep complex stash message visible
- else
- press_enter
- fi
- return 0
- }
- backup_models() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Backup Models ===${NC}"
- echo
- log "Creating a compressed archive of all .gguf models in '$MODEL_DIR'."
- echo
- check_install || { return 1; }
- _validate_critical_directory_path "$MODEL_DIR" "$EXPECTED_MODEL_DIR_PATH" "backup" || {
- wait_for_keypress_keep_screen; return 1;
- }
- local MODELS_EXIST_COUNT
- MODELS_EXIST_COUNT=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | awk 'END{print NR}' RS='\0')
- if [ "${MODELS_EXIST_COUNT:-0}" -eq 0 ]; then
- warn "No models found in '$MODEL_DIR' to backup."
- press_enter
- return 0
- fi
- mkdir -p "$BACKUP_DIR" || { error "Failed to create backup directory: '$BACKUP_DIR'"; wait_for_keypress_keep_screen; return 1; }
- local BACKUP_FILE="$BACKUP_DIR/koboldcpp_models_$(date +%Y%m%d_%H%M%S).tar.gz"
- # Estimate backup size
- local TOTAL_MODELS_SIZE_BYTES=0
- while IFS= read -r -d '' model_file; do
- local file_size=$(get_file_size_bytes "$model_file")
- TOTAL_MODELS_SIZE_BYTES=$((TOTAL_MODELS_SIZE_BYTES + file_size))
- done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null)
- if [ "$TOTAL_MODELS_SIZE_BYTES" -eq 0 ]; then
- warn "Found .gguf files, but their total size is zero. Skipping backup."
- press_enter
- return 0
- fi
- # Add 5% overhead for tar/gzip, minimum 1MB
- local ESTIMATED_BACKUP_SIZE=$((TOTAL_MODELS_SIZE_BYTES * 105 / 100))
- if [ "$ESTIMATED_BACKUP_SIZE" -lt 1048576 ] && [ "$TOTAL_MODELS_SIZE_BYTES" -gt 0 ]; then
- ESTIMATED_BACKUP_SIZE=1048576 # Minimum 1MB estimate if models exist
- fi
- # Check available space
- if ! check_available_space "$ESTIMATED_BACKUP_SIZE" "$BACKUP_DIR"; then
- error "Not enough space for backup. Please free up some storage or backup to an external device."
- wait_for_keypress_keep_screen
- return 1
- fi
- log "Backing up models from '$MODEL_DIR' to '$BACKUP_FILE'..."
- log "This may take a while for large models."
- if command -v termux-wake-lock &>/dev/null; then
- termux-wake-lock
- fi
- # Use tar with progress bar if 'pv' is installed
- if command -v pv &>/dev/null; then
- log "Using 'pv' for progress indication."
- 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
- local tar_gzip_exit=$?
- error "Backup failed (tar/gzip with pv exited with code $tar_gzip_exit). Check permissions and available space."
- rm -f "$BACKUP_FILE" # Clean up failed backup file
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- wait_for_keypress_keep_screen
- return 1
- fi
- else
- log "Using 'tar' and 'gzip' without progress indicator (pv not found)."
- if ! tar -czf "$BACKUP_FILE" -C "$(dirname "$MODEL_DIR")" "$(basename "$MODEL_DIR")"; then
- local tar_gzip_exit=$?
- error "Backup failed (tar/gzip exited with code $tar_gzip_exit). Check permissions and available space."
- rm -f "$BACKUP_FILE" # Clean up failed backup file
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- wait_for_keypress_keep_screen
- return 1
- fi
- fi
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- if [ -f "$BACKUP_FILE" ]; then
- success "Models backed up successfully to '$BACKUP_FILE'"
- info "Backup size: $(get_file_size_human "$BACKUP_FILE")"
- # Ask if user wants to share/copy the backup elsewhere
- echo
- echo "What would you like to do with the backup file?"
- echo "[1] Keep the backup in the default location ($BACKUP_DIR)"
- echo "[2] Share the backup file via Android share menu"
- echo "[3] Copy the backup to external storage"
- echo "[4] Move the backup to external storage (removes from default location)"
- echo
- echo -n "Your choice [1]: "
- read -r BACKUP_ACTION
- case "${BACKUP_ACTION:-1}" in
- 2)
- if command -v termux-share &>/dev/null; then
- log "Sharing backup file '$BACKUP_FILE'..."
- termux-share "$BACKUP_FILE" || warn "Failed to share file using termux-share."
- else
- warn "termux-api not installed. Cannot share the backup file. Install with 'pkg install termux-api'."
- fi
- ;;
- 3|4)
- local action="Copy"
- if [ "${BACKUP_ACTION:-}" = "4" ]; then action="Move"; fi
- echo
- echo "Enter destination directory (e.g., /sdcard/Download or $DOWNLOAD_DIR):"
- read -r DEST_DIR
- # Basic validation
- if [ -z "$DEST_DIR" ]; then
- error "Destination directory cannot be empty. Operation cancelled."
- elif [ ! -d "$DEST_DIR" ]; then
- error "Destination directory '$DEST_DIR' does not exist. Operation cancelled."
- elif [ ! -w "$DEST_DIR" ]; then
- error "Destination directory '$DEST_DIR' is not writable. Check permissions. Operation cancelled."
- else
- log "${action}ing '$BACKUP_FILE' to '$DEST_DIR/'..."
- if [ "$action" = "Copy" ]; then
- if cp "$BACKUP_FILE" "$DEST_DIR/"; then
- success "Backup copied to $DEST_DIR/$(basename "$BACKUP_FILE")"
- else
- error "Failed to copy backup to $DEST_DIR. Check permissions or space."
- fi
- else # Move
- if mv "$BACKUP_FILE" "$DEST_DIR/"; then
- success "Backup moved to $DEST_DIR/$(basename "$BACKUP_FILE")"
- else
- error "Failed to move backup to $DEST_DIR. Check permissions or space."
- warn "Original backup might still be in '$BACKUP_DIR'."
- fi
- fi
- fi
- ;;
- *)
- info "Backup kept in default location: $BACKUP_FILE"
- ;;
- esac
- else
- error "Backup file '$BACKUP_FILE' was not created successfully."
- fi
- press_enter
- return 0
- }
- restore_models() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Restore Models ===${NC}"
- echo
- log "This will extract models from a backup archive (.tar.gz) into your KoboldCpp models directory ($MODEL_DIR)."
- echo -e "${RED}WARNING: This will REPLACE your current models in '$MODEL_DIR'.${NC}"
- echo
- check_install || { return 1; }
- _validate_critical_directory_path "$MODEL_DIR" "$EXPECTED_MODEL_DIR_PATH" "restore" || {
- wait_for_keypress_keep_screen; return 1;
- }
- # Check for backups in the default location
- if [ ! -d "$BACKUP_DIR" ]; then
- mkdir -p "$BACKUP_DIR"
- info "Backup directory '$BACKUP_DIR' created, but no backups found."
- echo
- echo "Options:"
- echo "[1] Import a backup file from elsewhere into the backup directory"
- echo "[2] Return to menu"
- echo
- echo -n "Your choice: "
- read -r IMPORT_CHOICE
- if [ "$IMPORT_CHOICE" = "1" ]; then
- echo
- echo "Enter full path to the backup file (.tar.gz) you want to import:"
- read -r IMPORT_FILE
- if [ -f "$IMPORT_FILE" ] && [[ "$IMPORT_FILE" == *.tar.gz ]]; then
- log "Importing '$IMPORT_FILE' to '$BACKUP_DIR/'..."
- if cp "$IMPORT_FILE" "$BACKUP_DIR/"; then
- success "Backup file imported to $BACKUP_DIR/$(basename "$IMPORT_FILE")"
- else
- error "Failed to import backup file '$IMPORT_FILE'. Check permissions or space."
- wait_for_keypress_keep_screen
- return 1
- fi
- else
- error "File doesn't exist, isn't a .tar.gz file, or path was empty."
- wait_for_keypress_keep_screen
- return 1
- fi
- else
- return 0 # User chose to return
- fi
- fi # End of backup directory check and import option
- echo -e "${YELLOW}Available backups in $BACKUP_DIR:${NC}"
- local BACKUPS=()
- # Sort by modification time, newest first
- while IFS= read -r -d '' file; do
- BACKUPS+=("$file")
- done < <(find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -type f -print0 2>/dev/null | sort -rz)
- if [ ${#BACKUPS[@]} -eq 0 ]; then
- error "No backup archives (.tar.gz) found in '$BACKUP_DIR'."
- wait_for_keypress_keep_screen
- return 1
- fi
- for i in "${!BACKUPS[@]}"; do
- local BACKUP_ARCHIVE="${BACKUPS[$i]}"
- local SIZE
- SIZE=$(get_file_size_human "$BACKUP_ARCHIVE")
- local DATE_MODIFIED
- DATE_MODIFIED=$(date -r "$BACKUP_ARCHIVE" "+%Y-%m-%d %H:%M:%S")
- echo "[$i] $(basename "$BACKUP_ARCHIVE") ($SIZE) - $DATE_MODIFIED"
- done
- echo
- echo -n "Enter number of the backup to restore (or 'q' to return to menu): "
- read -r CHOICE
- if [[ "${CHOICE:-}" == "q" || "${CHOICE:-}" == "Q" ]]; then
- return 0
- fi
- if [[ ! "$CHOICE" =~ ^[0-9]+$ ]] || [ "$CHOICE" -lt 0 ] || [ "$CHOICE" -ge "${#BACKUPS[@]}" ]; then
- error "Invalid selection."
- wait_for_keypress_keep_screen
- return 1
- fi
- local BACKUP_TO_RESTORE="${BACKUPS[$CHOICE]}"
- log "Selected backup for restore: $(basename "$BACKUP_TO_RESTORE")"
- # Preview backup contents
- echo
- echo -e "${YELLOW}Contents of the backup (first 10 .gguf files):${NC}"
- local backup_contents=$(tar -tzf "$BACKUP_TO_RESTORE" 2>/dev/null | grep -E '\.gguf$' | head -n 10)
- if [ -n "$backup_contents" ]; then
- echo "$backup_contents"
- local total_items=$(tar -tzf "$BACKUP_TO_RESTORE" 2>/dev/null | grep -E '\.gguf$' | wc -l)
- if [ "$total_items" -gt 10 ]; then
- echo "... and $((total_items - 10)) more .gguf items"
- fi
- else
- warn "Could not list contents or no .gguf files found in the backup archive."
- fi
- echo
- # Check if backup has expected directory structure ('models/')
- if ! tar -tzf "$BACKUP_TO_RESTORE" 2>/dev/null | grep -q "^models/.*\.gguf$"; then
- warn "This backup may not have the expected directory structure ('models/*.gguf')."
- info "It might extract files directly into the parent directory of '$MODEL_DIR'."
- echo -n "Continue anyway? [y/N]: "
- read -r CONTINUE_RESTORE
- if [[ ! "${CONTINUE_RESTORE:-N}" =~ ^[Yy]$ ]]; then
- info "Restore operation cancelled."
- press_enter
- return 0
- fi
- fi
- # Check available space for restore
- local BACKUP_SIZE=$(get_file_size_bytes "$BACKUP_TO_RESTORE")
- # Estimate uncompressed size (gzip compression ratio varies, estimate 2x as a minimum)
- local ESTIMATED_RESTORE_SIZE=$((BACKUP_SIZE * 3)) # Use 3x for a slightly safer estimate
- if [ "$ESTIMATED_RESTORE_SIZE" -lt 1048576 ]; then ESTIMATED_RESTORE_SIZE=1048576; fi # Minimum 1MB estimate
- if ! check_available_space "$ESTIMATED_RESTORE_SIZE" "$(dirname "$MODEL_DIR")"; then
- 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."
- wait_for_keypress_keep_screen
- return 1
- fi
- echo -e "${RED}WARNING: This will DELETE all existing files in '$MODEL_DIR' before restoring!${NC}"
- echo -n "Are you absolutely sure you want to restore from this backup? [y/N]: "
- read -r CONFIRM
- if [[ ! "${CONFIRM:-N}" =~ ^[Yy]$ ]]; then # Default N
- info "Restore operation cancelled."
- press_enter
- return 0
- fi
- local PRE_RESTORE_BACKUP_DIR="" # Initialize as empty
- local current_models_count=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f 2>/dev/null | wc -l)
- if [ -d "$MODEL_DIR" ] && [ "$current_models_count" -gt 0 ]; then
- log "Backing up current models to a temporary location before restoring..."
- PRE_RESTORE_BACKUP_DIR="$BACKUP_DIR/pre_restore_$(date +%Y%m%d_%H%M%S)_$$" # Add PID for uniqueness
- if mkdir -p "$PRE_RESTORE_BACKUP_DIR"; then
- if cp -a "$MODEL_DIR/." "$PRE_RESTORE_BACKUP_DIR/"; then
- info "Current models backed up to '$PRE_RESTORE_BACKUP_DIR'"
- else
- warn "Failed to backup current models to '$PRE_RESTORE_BACKUP_DIR'. Continuing with restore, but current models are NOT backed up by this operation."
- PRE_RESTORE_BACKUP_DIR="" # Clear path if backup failed
- fi
- else
- warn "Could not create directory '$PRE_RESTORE_BACKUP_DIR' for pre-restore backup. Skipping this step."
- PRE_RESTORE_BACKUP_DIR="" # Clear path if directory creation failed
- fi
- else
- info "No existing models in '$MODEL_DIR' to back up before restoring."
- fi
- log "Clearing existing models in '$MODEL_DIR'..."
- # Use find -delete for safer recursive deletion within the specific directory
- if [ -d "$MODEL_DIR" ]; then
- if find "$MODEL_DIR" -mindepth 1 -delete; then
- log "Existing models cleared from '$MODEL_DIR'."
- else
- error "Failed to clear existing models from '$MODEL_DIR'. Check permissions."
- _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore pre-restore backup
- return 1
- fi
- else
- mkdir -p "$MODEL_DIR" || { error "Failed to create model directory: '$MODEL_DIR'"; wait_for_keypress_keep_screen; return 1; }
- fi
- log "Extracting backup: $(basename "$BACKUP_TO_RESTORE") to '$(dirname "$MODEL_DIR")'..."
- log "This may take a while for large backups."
- # Ensure the parent directory exists for extraction
- mkdir -p "$(dirname "$MODEL_DIR")" || { error "Failed to create parent directory for models: '$(dirname "$MODEL_DIR")'"; wait_for_keypress_keep_screen; return 1; }
- if command -v termux-wake-lock &>/dev/null; then
- termux-wake-lock
- fi
- # Extract with progress if available
- if command -v pv &>/dev/null; then
- log "Using 'pv' for progress indication during extraction."
- if ! pv "$BACKUP_TO_RESTORE" | tar -xzf - -C "$(dirname "$MODEL_DIR")"; then
- local extract_exit=$?
- error "Restore failed during extraction (Exit code $extract_exit). The backup might be corrupted or there might be permission/space issues."
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore the pre-restore backup
- return 1
- fi
- else
- log "Extracting with 'tar' without progress indicator (pv not found)."
- if ! tar -xzf "$BACKUP_TO_RESTORE" -C "$(dirname "$MODEL_DIR")"; then
- local extract_exit=$?
- error "Restore failed during extraction (Exit code $extract_exit). The backup might be corrupted or there might be permission/space issues."
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore the pre-restore backup
- return 1
- fi
- fi
- if command -v termux-wake-unlock &>/dev/null; then
- termux-wake-unlock
- fi
- # Verify restored models integrity
- log "Verifying restored models integrity..."
- local corrupt_models=0
- local restored_models_count=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f 2>/dev/null | wc -l)
- if [ "$restored_models_count" -eq 0 ]; then
- warn "No .gguf files found in '$MODEL_DIR' after extraction. Restore may have failed or the backup was empty/incorrect."
- _handle_restore_failure "$PRE_RESTORE_BACKUP_DIR" "$MODEL_DIR" # Try to restore pre-restore backup
- return 1
- fi
- while IFS= read -r -d '' model_file; do
- if ! check_model_integrity "$model_file" >/dev/null 2>&1; then # Check silently
- warn "Model $(basename "$model_file") may have integrity issues after restoring."
- ((corrupt_models++))
- fi
- done < <(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null)
- if [ "$corrupt_models" -gt 0 ]; then
- warn "$corrupt_models models out of $restored_models_count may have integrity issues after restoring. Consider verifying them individually."
- wait_for_keypress_keep_screen # Pause to show warnings
- fi
- success "Models restored successfully from $(basename "$BACKUP_TO_RESTORE"). Total restored models: $restored_models_count."
- # Clean up the temporary pre-restore backup if it exists and restore was successful
- if [ -n "$PRE_RESTORE_BACKUP_DIR" ] && [ -d "$PRE_RESTORE_BACKUP_DIR" ]; then
- log "Cleaning up temporary pre-restore backup at '$PRE_RESTORE_BACKUP_DIR'..."
- rm -rf "$PRE_RESTORE_BACKUP_DIR" || warn "Failed to remove temporary pre-restore backup."
- fi
- press_enter
- return 0
- }
- # Helper function for restore failures
- _handle_restore_failure() {
- local pre_restore_dir="$1"
- local model_dir="$2"
- error "Restore operation failed."
- info "Attempting to restore the pre-restore backup if it exists and contains files..."
- if [ -n "$pre_restore_dir" ] && [ -d "$pre_restore_dir" ] && [ "$(ls -A "$pre_restore_dir" 2>/dev/null)" ]; then
- info "Clearing potentially inconsistent state in '$model_dir'..."
- find "$model_dir" -mindepth 1 -delete || warn "Failed to clear '$model_dir' before attempting pre-restore recovery."
- mkdir -p "$model_dir" || warn "Failed to recreate '$model_dir'."
- info "Copying models back from pre-restore backup '$pre_restore_dir'..."
- if cp -a "$pre_restore_dir/." "$model_dir/"; then
- success "Successfully restored models from the pre-restore backup at '$pre_restore_dir'."
- info "You may want to verify the integrity of these models."
- # Clean up the temporary backup after successful recovery
- log "Cleaning up temporary pre-restore backup..."
- rm -rf "$pre_restore_dir" || warn "Failed to remove temporary pre-restore backup."
- else
- warn "Failed to restore models from the pre-restore backup."
- warn "Model directory '$model_dir' might be in an inconsistent state."
- warn "Your original models might still be in '$pre_restore_dir'."
- fi
- else
- warn "No valid pre-restore backup found or it was empty. Model directory '$model_dir' might be empty or in an inconsistent state."
- fi
- wait_for_keypress_keep_screen # Ensure the failure message and recovery status are seen
- }
- # Function to clean up unnecessary or temporary files
- cleanup() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== Clean Up Files ===${NC}"
- echo
- log "This utility helps free up disk space by removing temporary files, old backups, and build artifacts."
- echo
- log "Searching for files to clean up..."
- local SPACE_FREED=0
- local TEMP_SPACE_SAVED=0
- # Clean up build artifacts in KoboldCpp directory
- if [ -d "$KOBOLDCPP_DIR" ]; then
- cd "$KOBOLDCPP_DIR" || { error "Could not cd to '$KOBOLDCPP_DIR'"; wait_for_keypress_keep_screen; return 1; }
- # Clean build directory if it exists
- if [ -d "build" ]; then
- local BUILD_SIZE_KB=$(du -sk "build" 2>/dev/null | cut -f1)
- if [ -n "$BUILD_SIZE_KB" ] && [ "$BUILD_SIZE_KB" -gt 0 ]; then
- local BUILD_SIZE_BYTES=$((BUILD_SIZE_KB * 1024))
- 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]: "
- read -r CLEAN_BUILD
- if [[ ! "${CLEAN_BUILD:-Y}" =~ ^[Nn]$ ]]; then
- rm -rf build/
- TEMP_SPACE_SAVED=$((TEMP_SPACE_SAVED + BUILD_SIZE_BYTES))
- success "Build artifacts cleaned."
- fi
- else
- info "No significant build artifacts found in '$KOBOLDCPP_DIR/build'."
- fi
- else
- info "Build directory '$KOBOLDCPP_DIR/build' not found."
- fi
- # Compact Git repository
- if [ -d ".git" ]; then
- local GIT_SIZE_BEFORE_KB=$(du -sk ".git" 2>/dev/null | cut -f1)
- if [ -n "$GIT_SIZE_BEFORE_KB" ] && [ "$GIT_SIZE_BEFORE_KB" -gt 0 ]; then
- local GIT_SIZE_BEFORE_BYTES=$((GIT_SIZE_BEFORE_KB * 1024))
- 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]: "
- read -r CLEAN_GIT
- if [[ ! "${CLEAN_GIT:-Y}" =~ ^[Nn]$ ]]; then
- log "Running git gc --aggressive..."
- 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
- local GIT_SIZE_AFTER_KB=$(du -sk ".git" 2>/dev/null | cut -f1)
- if [ -n "$GIT_SIZE_AFTER_KB" ] && [ "$GIT_SIZE_AFTER_KB" -ge 0 ]; then
- local GIT_SIZE_AFTER_BYTES=$((GIT_SIZE_AFTER_KB * 1024))
- local saved=$((GIT_SIZE_BEFORE_BYTES - GIT_SIZE_AFTER_BYTES))
- if [ "$saved" -gt 0 ]; then
- TEMP_SPACE_SAVED=$((TEMP_SPACE_SAVED + saved))
- success "Git repository compacted, saved $(numfmt --to=iec-i --suffix=B "$saved" 2>/dev/null || echo "$saved bytes")."
- else
- info "Git repository already optimized, no significant space saved."
- fi
- else
- warn "Could not determine Git repository size after compaction."
- fi
- fi
- else
- info "No significant Git repository data found in '$KOBOLDCPP_DIR/.git'."
- fi
- else
- info "Git directory '$KOBOLDCPP_DIR/.git' not found."
- fi
- fi
- # Clean up temporary files and manage old backups in BACKUP_DIR
- if [ -d "$BACKUP_DIR" ]; then
- local temp_backups=$(find "$BACKUP_DIR" -maxdepth 1 -name "pre_restore_*" -type d 2>/dev/null)
- if [ -n "$temp_backups" ]; then
- local TEMP_SIZE_KB=0
- while IFS= read -r dir; do
- local size_kb=$(du -sk "$dir" 2>/dev/null | cut -f1)
- if [ -n "$size_kb" ]; then
- TEMP_SIZE_KB=$((TEMP_SIZE_KB + size_kb))
- fi
- done <<< "$temp_backups"
- local TEMP_SIZE_BYTES=$((TEMP_SIZE_KB * 1024))
- if [ "$TEMP_SIZE_BYTES" -gt 0 ]; then
- 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]: "
- read -r CLEAN_TEMP
- if [[ ! "${CLEAN_TEMP:-Y}" =~ ^[Nn]$ ]]; then
- while IFS= read -r dir; do
- rm -rf "$dir"
- done <<< "$temp_backups"
- SPACE_FREED=$((SPACE_FREED + TEMP_SIZE_BYTES)) # This is permanent space freed
- success "Temporary backup files cleaned."
- fi
- else
- info "No significant temporary backup directories found in '$BACKUP_DIR'."
- fi
- else
- info "No temporary pre-restore backup directories found in '$BACKUP_DIR'."
- fi
- # Manage old backups
- local backup_files=($(find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -type f -print0 2>/dev/null | xargs -0))
- local backup_count=${#backup_files[@]}
- if [ "$backup_count" -gt 5 ]; then
- echo -n "You have $backup_count model backups. Keep only the 5 most recent? [y/N]: "
- read -r CLEAN_BACKUPS
- if [[ "${CLEAN_BACKUPS:-N}" =~ ^[Yy]$ ]]; then
- # Find backups to remove (older than the 5 most recent)
- 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-))
- local OLD_SIZE_BYTES=0
- local files_removed=0
- for file in "${old_backups[@]}"; do
- if [ -f "$file" ]; then # Ensure file still exists before attempting to delete
- local size=$(get_file_size_bytes "$file")
- OLD_SIZE_BYTES=$((OLD_SIZE_BYTES + size))
- if rm -f "$file"; then
- ((files_removed++))
- else
- warn "Failed to delete old backup file: $file"
- fi
- fi
- done
- SPACE_FREED=$((SPACE_FREED + OLD_SIZE_BYTES)) # This is permanent space freed
- success "Removed $files_removed old backups, freed $(numfmt --to=iec-i --suffix=B "$OLD_SIZE_BYTES" 2>/dev/null || echo "$OLD_SIZE_BYTES bytes")."
- fi
- elif [ "$backup_count" -gt 0 ]; then
- info "You have $backup_count model backups. No old backups to remove (keeping 5 most recent)."
- else
- info "No model backups found in '$BACKUP_DIR'."
- fi
- else
- info "Backup directory '$BACKUP_DIR' not found."
- fi
- # Log file rotation
- if [ -f "$LOG_FILE" ]; then
- local log_size=$(get_file_size_bytes "$LOG_FILE")
- if [ "$log_size" -gt 1048576 ]; then # 1MB
- echo -n "Rotate log file ($(numfmt --to=iec-i --suffix=B "$log_size" 2>/dev/null || echo "$log_size bytes"))? [Y/n]: "
- read -r ROTATE_LOG
- if [[ ! "${ROTATE_LOG:-Y}" =~ ^[Nn]$ ]]; then
- mv "$LOG_FILE" "${LOG_FILE}.old"
- touch "$LOG_FILE"
- echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: Log rotated, old log saved as ${LOG_FILE}.old" >> "$LOG_FILE"
- success "Log file rotated, freed $(numfmt --to=iec-i --suffix=B "$log_size" 2>/dev/null || echo "$log_size bytes")."
- TEMP_SPACE_SAVED=$((TEMP_SPACE_SAVED + log_size)) # This space is potentially recoverable by deleting the .old file
- fi
- else
- info "Log file size is manageable ($(numfmt --to=iec-i --suffix=B "$log_size" 2>/dev/null || echo "$log_size bytes"))."
- fi
- else
- info "Log file '$LOG_FILE' not found."
- fi
- # Show summary
- echo
- if [ "$SPACE_FREED" -gt 0 ] || [ "$TEMP_SPACE_SAVED" -gt 0 ]; then
- success "Cleanup complete."
- if [ "$SPACE_FREED" -gt 0 ]; then
- info "Permanent space freed: $(numfmt --to=iec-i --suffix=B "$SPACE_FREED" 2>/dev/null || echo "$SPACE_FREED bytes")."
- fi
- if [ "$TEMP_SPACE_SAVED" -gt 0 ]; then
- info "Temporary/compressible space cleaned: $(numfmt --to=iec-i --suffix=B "$TEMP_SPACE_SAVED" 2>/dev/null || echo "$TEMP_SPACE_SAVED bytes")."
- fi
- else
- info "No files were cleaned up."
- fi
- press_enter
- return 0
- }
- install_update_menu() {
- while true; do
- clear_screen
- echo -e "${BOLD}${CYAN}=== Installation, Updates, & Rebuild ===${NC}"
- echo
- local kobold_installed=false
- if [ -d "$KOBOLDCPP_DIR" ] && ([ -f "$KOBOLDCPP_DIR/koboldcpp.py" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]); then
- kobold_installed=true
- echo -e "${GREEN}KoboldCpp is currently installed in $KOBOLDCPP_DIR.${NC}"
- if [ -d "$KOBOLDCPP_DIR/.git" ]; then
- local current_commit=$(git -C "$KOBOLDCPP_DIR" rev-parse --short HEAD 2>/dev/null || echo "Unknown")
- echo -e "Current version (commit): ${YELLOW}$current_commit${NC}"
- fi
- else
- echo -e "${YELLOW}KoboldCpp is not currently installed.${NC}"
- fi
- echo
- echo -e "${BOLD}Options:${NC}"
- echo
- echo "1. Install / Reinstall KoboldCpp"
- if [ "$kobold_installed" = true ]; then
- echo "2. Update KoboldCpp (fetch latest code and rebuild)"
- echo "3. Rebuild KoboldCpp (from current source code)"
- fi
- echo "0. Back to Main Menu"
- echo
- echo -n "Enter your choice: "
- read -r CHOICE
- case "${CHOICE:-}" in
- 1) install_all ;;
- 2)
- if [ "$kobold_installed" = true ]; then
- update_koboldcpp
- else
- error "Option 2 is only available if KoboldCpp is installed."
- wait_for_keypress_keep_screen
- fi
- ;;
- 3)
- if [ "$kobold_installed" = true ]; then
- rebuild
- else
- error "Option 3 is only available if KoboldCpp is installed."
- wait_for_keypress_keep_screen
- fi
- ;;
- 0) return ;;
- *)
- error "Invalid choice. Please try again."
- wait_for_keypress_keep_screen
- ;;
- esac
- done
- }
- backup_restore_menu() {
- while true; do
- clear_screen
- echo -e "${BOLD}${CYAN}=== Backup & Restore Models ===${NC}"
- echo
- local backup_count=$(find "$BACKUP_DIR" -maxdepth 1 -name "*.tar.gz" -type f 2>/dev/null | wc -l)
- echo -e "Backups found in $BACKUP_DIR: ${YELLOW}${backup_count:-0}${NC}"
- echo
- echo -e "${BOLD}Options:${NC}"
- echo
- echo "1. Backup current models"
- echo "2. Restore models from backup"
- echo "0. Back to Main Menu"
- echo
- echo -n "Enter your choice: "
- read -r CHOICE
- case "${CHOICE:-}" in
- 1) backup_models ;;
- 2) restore_models ;;
- 0) return ;;
- *)
- error "Invalid choice. Please try again."
- wait_for_keypress_keep_screen
- ;;
- esac
- done
- }
- advanced_menu() {
- while true; do
- clear_screen
- echo -e "${BOLD}${CYAN}=== Advanced Options ===${NC}"
- echo
- echo -e "${YELLOW}Use these options with caution.${NC}"
- echo
- echo -e "${BOLD}Options:${NC}"
- echo
- echo "1. Clean up temporary files and old backups"
- echo "2. Open log file ($LOG_FILE)"
- echo "3. Reset configuration file ($CONFIG_FILE)"
- echo "0. Back to Main Menu"
- echo
- echo -n "Enter your choice: "
- read -r CHOICE
- case "${CHOICE:-}" in
- 1) cleanup ;;
- 2)
- if [ -f "$LOG_FILE" ]; then
- log "Opening log file in default viewer..."
- termux-open "$LOG_FILE" || {
- warn "Failed to open log file. You might need to install a text editor or file viewer."
- info "You can view it manually using: less \"$LOG_FILE\""
- wait_for_keypress_keep_screen
- }
- else
- warn "Log file '$LOG_FILE' not found."
- wait_for_keypress_keep_screen
- fi
- ;;
- 3)
- echo -e "${RED}WARNING: This will delete your saved configurations (last used model, run parameters, etc.).${NC}"
- echo -n "Are you sure you want to reset the configuration file '$CONFIG_FILE'? [y/N]: "
- read -r RESET_CONFIRM
- if [[ "${RESET_CONFIRM:-N}" =~ ^[Yy]$ ]]; then
- if rm -f "$CONFIG_FILE"; then
- success "Configuration file '$CONFIG_FILE' reset."
- # Re-initialize default build options
- save_config "build_options" "0"
- info "Default build options reset."
- else
- error "Failed to reset configuration file '$CONFIG_FILE'. Check permissions."
- fi
- else
- info "Configuration reset cancelled."
- fi
- press_enter
- ;;
- 0) return ;;
- *)
- error "Invalid choice. Please try again."
- wait_for_keypress_keep_screen
- ;;
- esac
- done
- }
- about() {
- clear_screen
- echo -e "${BOLD}${CYAN}=== About KoboldCpp Termux Manager ===${NC}"
- echo
- echo "KoboldCpp Termux Manager v$VERSION"
- echo
- echo "This tool helps you install, manage, and run KoboldCpp on Android using Termux."
- echo
- echo "Original KoboldCpp project: $REPO_URL"
- echo
- echo "Installation directory: ${BLUE}$KOBOLDCPP_DIR${NC}"
- echo "Model Cache directory: ${BLUE}$MODEL_DIR${NC}" # Clarified as Model Cache
- echo "Downloads directory: ${BLUE}$DOWNLOAD_DIR${NC}"
- echo "Backups directory: ${BLUE}$BACKUP_DIR${NC}"
- echo "Log file: ${BLUE}$LOG_FILE${NC}"
- echo "Config file: ${BLUE}$CONFIG_FILE${NC}"
- echo
- 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."
- echo
- # Show system information
- local device_info=$(get_device_info)
- if [ -n "$device_info" ]; then
- echo "Device information: ${BLUE}$device_info${NC}"
- echo
- fi
- # Show usage statistics if available
- if [ -f "$CONFIG_FILE" ]; then
- local last_runtime_model=$(grep "^last_runtime_model=" "$CONFIG_FILE" | cut -d= -f2)
- local last_runtime_hours=$(grep "^last_runtime_hours=" "$CONFIG_FILE" | cut -d= -f2)
- local last_runtime_minutes=$(grep "^last_runtime_minutes=" "$CONFIG_FILE" | cut -d= -f2)
- local last_runtime_seconds=$(grep "^last_runtime_seconds=" "$CONFIG_FILE" | cut -d= -f2)
- if [ -n "$last_runtime_model" ]; then
- echo "Last session:"
- echo "- Model: ${CYAN}$last_runtime_model${NC}"
- if [ -n "$last_runtime_hours" ]; then
- echo "- Runtime: ${CYAN}${last_runtime_hours}h ${last_runtime_minutes}m ${last_runtime_seconds}s${NC}"
- fi
- echo
- fi
- fi
- echo "New in version $VERSION:"
- echo "- Completed Model Management menu."
- echo "- Added 'Verify model integrity' option to Model Management."
- echo "- Clarified '$MODEL_DIR' as the Model Cache directory."
- echo "- Improved input validation for run parameters."
- echo "- Enhanced file size checks and reporting."
- echo "- Better error handling and cleanup for downloads and restores."
- echo "- Added option to import backups in Restore menu."
- echo "- Option to move backups to external storage."
- echo "- More detailed output during cleanup."
- echo
- press_enter
- }
- clean_exit() {
- clear_screen
- if command -v termux-wake-unlock &>/dev/null; then
- log "Releasing wake lock on exit (if active)..."
- termux-wake-unlock 2>/dev/null || true # Ignore error if no lock was active
- fi
- echo "--- KoboldCpp Termux Manager Session Ended: $(date '+%Y-%m-%d %H:%M:%S') ---" >> "$LOG_FILE"
- echo "Thank you for using KoboldCpp Termux Manager!"
- # Remove trap for temp file if it was set and not cleared by _initialize_du_commands
- # This is a general cleanup, though _initialize_du_commands tries to manage its own trap.
- trap - EXIT
- exit 0
- }
- # --- Check for updates ---
- check_script_updates() {
- # This is a placeholder function - in a real implementation, this would
- # check a repository or URL for newer versions of the script
- # For now, it just returns 0 to indicate no check is performed.
- return 0
- }
- # --- Menus ---
- main_menu() {
- echo "--- KoboldCpp Termux Manager Session Started: $(date '+%Y-%m-%d %H:%M:%S') ---" >> "$LOG_FILE"
- # Check for script updates (placeholder)
- # check_script_updates
- trap clean_exit SIGINT SIGTERM
- while true; do
- clear_screen
- echo -e "${BOLD}${CYAN}=== KoboldCpp Termux Manager v$VERSION ===${NC}"
- echo
- log "Your model cache is located at: $MODEL_DIR"
- echo
- local MODEL_COUNT=0
- if [ -d "$MODEL_DIR" ]; then
- # Use find with null delimiter for safety
- MODEL_COUNT=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | grep -c -z '\.gguf$')
- fi
- local py_script_exists=false
- local binary_exists=false
- local kobold_installed=false
- if [ -d "$KOBOLDCPP_DIR" ]; then
- if [ -f "$KOBOLDCPP_DIR/koboldcpp.py" ]; then
- py_script_exists=true
- fi
- if [ -f "$KOBOLDCPP_DIR/koboldcpp-android-arm64" ] || [ -f "$KOBOLDCPP_DIR/koboldcpp" ]; then
- binary_exists=true
- fi
- if [ "$py_script_exists" = true ] || [ "$binary_exists" = true ]; then
- kobold_installed=true
- fi
- fi
- if [ "$kobold_installed" = true ]; then
- echo -e "${GREEN}KoboldCpp is installed.${NC}"
- echo -e "Models in cache: ${YELLOW}${MODEL_COUNT:-0}${NC}"
- # Show last run information if available
- if [ -f "$CONFIG_FILE" ]; then
- local last_runtime_model=$(grep "^last_runtime_model=" "$CONFIG_FILE" | cut -d= -f2)
- local last_runtime_hours=$(grep "^last_runtime_hours=" "$CONFIG_FILE" | cut -d= -f2)
- if [ -n "$last_runtime_model" ]; then
- local last_model_base=$(basename "$last_runtime_model") # Show just filename
- local last_model_status=""
- if [ -n "$last_runtime_hours" ]; then
- last_model_status="(last ran)"
- else
- last_model_status="(last selected, possibly failed)" # Indicate if runtime wasn't saved
- fi
- echo -e "Last model used: ${CYAN}$last_model_base${NC} $last_model_status"
- fi
- fi
- else
- echo -e "${YELLOW}KoboldCpp is not installed (or key components are missing).${NC}"
- fi
- # Show device info
- local device_info=$(get_device_info)
- if [ -n "$device_info" ]; then
- echo -e "Device: ${BLUE}$device_info${NC}"
- fi
- echo
- echo -e "${BOLD}Main Menu:${NC}"
- echo
- echo "1. Run KoboldCpp (Select model from cache)"
- echo "2. Add model to cache (from Downloads)"
- echo "3. Download model to cache (from URL)"
- echo "4. Manage models in cache" # Renamed for clarity
- echo "5. Install / Update / Rebuild KoboldCpp"
- echo "6. Backup & Restore Models"
- echo "7. Advanced options"
- echo "8. About"
- echo "0. Exit"
- echo
- echo -n "Enter your choice: "
- read -r CHOICE
- case "${CHOICE:-}" in # Handle empty CHOICE
- 1) run_model ;;
- 2) add_model ;;
- 3) download_model ;;
- 4) models_menu ;;
- 5) install_update_menu ;;
- 6) backup_restore_menu ;;
- 7) advanced_menu ;;
- 8) about ;;
- 0) clean_exit ;;
- *)
- error "Invalid choice. Please try again."
- wait_for_keypress_keep_screen
- ;;
- esac
- done
- }
- models_menu() {
- while true; do
- clear_screen
- echo -e "${BOLD}${CYAN}=== Model Cache Management ===${NC}"
- echo
- log "Manage your .gguf models stored in the cache directory: $MODEL_DIR"
- echo
- local MODEL_COUNT=0
- if [ -d "$MODEL_DIR" ]; then
- # Use find with null delimiter for safety
- MODEL_COUNT=$(find "$MODEL_DIR" -maxdepth 1 -name "*.gguf" -type f -print0 2>/dev/null | grep -c -z '\.gguf$')
- fi
- echo -e "Models currently in cache: ${YELLOW}${MODEL_COUNT:-0}${NC}"
- echo
- echo -e "${BOLD}Options:${NC}"
- echo
- echo "1. List all models in cache"
- echo "2. Add model to cache (from Downloads)" # Re-added for convenience
- echo "3. Download model to cache (from URL)" # Re-added for convenience
- echo "4. Delete a model from cache"
- echo "5. Verify integrity of a model in cache" # Calls the new interactive function
- echo "0. Back to Main Menu"
- echo
- echo -n "Enter your choice: "
- read -r CHOICE
- case "${CHOICE:-}" in
- 1) list_models ;;
- 2) add_model ;;
- 3) download_model ;;
- 4) delete_model ;;
- 5) verify_model_integrity_interactive ;; # Call the new function here
- 0) return ;;
- *)
- error "Invalid choice. Please try again."
- wait_for_keypress_keep_screen
- ;;
- esac
- done
- }
- # --- Script Entry Point ---
- main_menu
Advertisement
Add Comment
Please, Sign In to add comment