Complete Guide: How to Uninstall LM Studio on Ubuntu 24.04

Introduction

LM Studio is an incredible tool for running local LLMs, but because it is typically distributed as an AppImage or manual download, it doesn’t always leave a clean trail. It scatters configuration files, cached blobs, and gigabytes of AI models across your home directory.

In this tutorial, we will use a specialized Bash script to perform a surgical uninstallation, ensuring your Ubuntu system remains lean and clutter-free.


1. Understanding the Uninstallation Logic

Before running a script with sudo privileges, it is vital to understand its “search and destroy” logic. Here is what happens behind the scenes:

  • Process Management: The script identifies and terminates any running instances (lm-studio, lmstudio) to prevent file-locking errors.
  • XDG Standards: It targets the official Linux directory standards where app data lives: ~/.local/share, ~/.config, and ~/.cache.
  • Model Preservation: Because AI models are massive, the script calculates the folder size and asks if you want to keep them for future use or wipe them to reclaim disk space.
  • System Scrubbing: If run with root privileges, it cleans up system-wide locations like /opt, /usr/local/bin, and removes desktop menu shortcuts.

2. How to Use the Uninstaller Script

Step 1: Create the Script

Open your terminal and create a new bash file:

nano uninstall_lm_studio.sh

Paste the script code below into the editor, then save and exit (Ctrl+O, Enter, Ctrl+X).

Step 2: Set Permissions

You must give the system permission to execute the script:

chmod +x uninstall_lm_studio.sh

Step 3: Choose Your Execution Mode

The script supports three different ways to run, depending on your needs:

  • The Safety Check (Dry Run): See what would be deleted without actually touching any files. ./uninstall_lm_studio.sh --dry-run
  • Standard Uninstall: Clean up your current user profile. ./uninstall_lm_studio.sh
  • System-Wide Wipe: Remove LM Studio for all users and root. sudo ./uninstall_lm_studio.sh

3. Navigating the Menu Options

OptionUse Case
1) Specific UserBest if you only want to clean up your own profile.
2) System-wideUse this if you used sudo to install LM Studio or want it gone from the entire machine.
3) Search and ListUse this if you used sudo to install LM Studio or want it gone from the entire machine.
4) ExitCloses the script without doing anything.

4. Manual Verification (Optional)

If you prefer to double-check the work manually, the script focuses on removing these specific directories. If they are gone, your uninstallation was successful:

  • ~/.cache/lm-studio (Temporary files/Shaders)
  • ~/.config/LM-Studio (User settings)
  • ~/.local/share/lm-studio (Application data)
  • ~/.local/share/applications/lm-studio.desktop (Menu icon)

Pro-Tip: If you chose to KEEP your models during uninstallation, you can find them later in ~/.cache/lm-studio/models. This is helpful if you want to move them to an external drive before a final wipe.


Conclusion

By using a structured script instead of manually hunting for files, you ensure that Ubuntu 24.04 stays optimized. Whether you’re troubleshooting a bad install or moving to a different LLM runner, this method is the safest way to ensure a clean slate.

Code:

#!/usr/bin/env bash
# LM Studio Uninstaller for Ubuntu 24.04
# Author: Windows-HQ.com
# 
# Usage: sudo ./lm_studio_uninstall.sh [--dry-run]

set -euo pipefail

# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
BLUE='\033[0;34m'; CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; NC='\033[0m'

# ── Global flags ─────────────────────────────────────────────────────────────
DRY_RUN=false
for arg in "$@"; do [[ "$arg" == "--dry-run" ]] && DRY_RUN=true; done

# ── Temp-file cleanup ─────────────────────────────────────────────────────────
SEARCH_TMP=""
cleanup() { [[ -n "$SEARCH_TMP" && -f "$SEARCH_TMP" ]] && rm -f "$SEARCH_TMP"; }
trap cleanup EXIT

# ── Helpers ───────────────────────────────────────────────────────────────────

banner() {
    clear
    echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
    echo -e "${CYAN}║${NC}         ${MAGENTA}LM Studio Uninstaller for Ubuntu 24.04${NC}              ${CYAN}║${NC}"
    if $DRY_RUN; then
        echo -e "${CYAN}║${NC}              ${YELLOW}⚠  DRY-RUN MODE — nothing will be deleted${NC}          ${CYAN}║${NC}"
    fi
    echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
    echo
}

# Safe remove: honours --dry-run and logs every deletion
safe_rm() {
    local path="$1"
    if [[ ! -e "$path" && ! -L "$path" ]]; then return 0; fi
    if $DRY_RUN; then
        echo -e "  ${YELLOW}[dry-run]${NC} would remove: $path"
    else
        echo "  Removing: $path"
        rm -rf -- "$path"
    fi
}

# Confirm with a default-No prompt; returns 0 on yes, 1 on no
confirm() {
    local prompt="$1"
    local reply
    read -r -p "  $prompt (y/N): " reply
    [[ "${reply,,}" == "y" ]]
}

# Kill processes owned by a user (or any user when called as root)
kill_lm_processes() {
    local user="${1:-}"
    local patterns=("lm-studio" "LM-Studio" "lmstudio" "LMStudio")
    for pat in "${patterns[@]}"; do
        if [[ -n "$user" ]]; then
            pkill -u "$user" -f "$pat" 2>/dev/null && echo "  Killed '$pat' processes for $user" || true
        else
            pkill -f "$pat" 2>/dev/null && echo "  Killed '$pat' processes" || true
        fi
    done
}

# ── Core: remove all LM Studio artefacts under one home directory ─────────────
#
# Parameters:
#   $1  home directory
#   $2  username (for display)
#   $3  "ask" | "keep" | "remove"  — how to handle model directories
#
uninstall_for_user() {
    local home_dir="$1"
    local username="$2"
    local model_policy="${3:-ask}"
    local removed=0

    echo -e "\n${GREEN}Uninstalling for user: $username${NC}"

    # ── XDG dirs: use find -iname so one pass covers every capitalisation ──────
    local xdg_roots=("$home_dir/.local/share" "$home_dir/.config" "$home_dir/.cache")
    for root in "${xdg_roots[@]}"; do
        [[ -d "$root" ]] || continue
        while IFS= read -r -d '' path; do
            # Skip model dirs — handled separately below
            [[ "$path" == */models || "$path" == */models/* ]] && continue
            safe_rm "$path"
            (( removed++ )) || true
        done < <(find "$root" -maxdepth 1 -iname "lm?studio*" -print0 2>/dev/null)
    done

    # ── Desktop entries ────────────────────────────────────────────────────────
    while IFS= read -r -d '' path; do
        safe_rm "$path"
        (( removed++ )) || true
    done < <(find "$home_dir/.local/share/applications" \
                  -maxdepth 1 -iname "lm?studio*.desktop" -print0 2>/dev/null)

    # ── Local binaries ─────────────────────────────────────────────────────────
    while IFS= read -r -d '' path; do
        safe_rm "$path"
        (( removed++ )) || true
    done < <(find "$home_dir/.local/bin" \
                  -maxdepth 1 -iname "lm?studio*" -print0 2>/dev/null)

    # ── Model directories ──────────────────────────────────────────────────────
    local model_found=""
    while IFS= read -r -d '' path; do
        [[ -d "$path" ]] && model_found="$path" && break
    done < <(find "$home_dir" -maxdepth 5 -type d -iname "models" \
                  -path "*lm*studio*" -print0 2>/dev/null)

    if [[ -n "$model_found" ]]; then
        local model_size
        model_size=$(du -sh "$model_found" 2>/dev/null | cut -f1)
        echo -e "\n  ${YELLOW}Found model directory${NC}: $model_found (${model_size})"
        local do_remove=false
        case "$model_policy" in
            remove) do_remove=true ;;
            keep)   echo "  Keeping models (policy=keep)" ;;
            ask)
                if confirm "Remove downloaded models for $username?"; then
                    do_remove=true
                else
                    echo "  Keeping models"
                fi
                ;;
        esac
        if $do_remove; then
            safe_rm "$(dirname "$model_found")"
            (( removed++ )) || true
        fi
    fi

    if (( removed == 0 )); then
        echo -e "  ${YELLOW}Nothing found for $username${NC}"
    else
        echo -e "  ${GREEN}Done (${removed} item(s) processed)${NC}"
    fi
}

# ── Iterate every regular user from /etc/passwd ───────────────────────────────
each_human_user() {
    # Callback receives (home, username, extra_args...)
    local callback="$1"; shift
    while IFS=: read -r username _ uid _ _ home _; do
        (( uid >= 1000 && uid < 65534 )) || continue
        [[ "$username" == "nobody" ]] && continue
        [[ -d "$home" ]] || continue
        "$callback" "$home" "$username" "$@"
    done < /etc/passwd
}

# ── Option 1: single selected user ───────────────────────────────────────────
uninstall_selected_user() {
    echo -e "\n${CYAN}Select a user to uninstall LM Studio${NC}"
    echo "────────────────────────────────────────"

    local -a users=() homes=()
    local i=1
    while IFS=: read -r username _ uid _ _ home _; do
        (( uid >= 1000 && uid < 65534 )) || continue
        [[ "$username" == "nobody" ]] && continue
        [[ -d "$home" ]] || continue
        users+=("$username"); homes+=("$home")
        printf "  ${GREEN}%d)${NC} %-20s  %s\n" "$i" "$username" "$home"
        (( i++ )) || true
    done < /etc/passwd

    if (( ${#users[@]} == 0 )); then
        echo -e "${RED}No regular users found.${NC}"
        read -r -p "Press Enter to continue…"; return
    fi

    local cancel=$i
    echo "  $cancel) Cancel"
    echo
    local choice
    read -r -p "Choice (1–$cancel): " choice
    if ! [[ "$choice" =~ ^[0-9]+$ ]] || (( choice < 1 || choice > cancel )); then
        echo -e "${RED}Invalid input.${NC}"
        read -r -p "Press Enter to continue…"; return
    fi
    (( choice == cancel )) && { echo -e "${GREEN}Cancelled.${NC}"; return; }

    local idx=$(( choice - 1 ))
    echo -e "\n${YELLOW}Uninstalling for: ${users[$idx]}${NC}"
    uninstall_for_user "${homes[$idx]}" "${users[$idx]}" ask
    kill_lm_processes "${users[$idx]}"

    read -r -p $'\nPress Enter to continue…'
}

# ── Option 2: system-wide ─────────────────────────────────────────────────────
uninstall_system_wide() {
    echo -e "\n${RED}⚠  SYSTEM-WIDE UNINSTALLATION${NC}"
    echo -e "${YELLOW}This removes LM Studio for ALL users, root, and system locations.${NC}"
    confirm "Continue?" || { echo -e "${GREEN}Cancelled.${NC}"; return; }

    # Ask once about models; apply to every user silently
    local model_policy
    echo
    if confirm "Remove downloaded AI models for all users?"; then
        model_policy="remove"
    else
        model_policy="keep"
    fi

    echo -e "\n${CYAN}Uninstalling for all regular users…${NC}"
    each_human_user uninstall_for_user "$model_policy"

    echo -e "\n${CYAN}Uninstalling for root…${NC}"
    uninstall_for_user /root root "$model_policy"

    echo -e "\n${CYAN}Removing system-wide locations…${NC}"
    local sys_roots=(/opt /usr/local /usr/share/applications)
    for root in "${sys_roots[@]}"; do
        [[ -d "$root" ]] || continue
        local depth=1
        [[ "$root" == /usr/share/applications ]] && depth=1
        while IFS= read -r -d '' path; do
            safe_rm "$path"
        done < <(find "$root" -maxdepth "$depth" \
                      \( -iname "lm?studio*" -o -iname "lm-studio*" \) \
                      -print0 2>/dev/null)
    done

    echo -e "\n${CYAN}Removing symlinks from PATH locations…${NC}"
    for bin_dir in /usr/local/bin /usr/bin; do
        [[ -d "$bin_dir" ]] || continue
        while IFS= read -r -d '' path; do
            safe_rm "$path"
        done < <(find "$bin_dir" -maxdepth 1 -type l -iname "lm?studio*" -print0 2>/dev/null)
    done

    echo -e "\n${CYAN}Stopping running processes…${NC}"
    kill_lm_processes ""

    command -v update-desktop-database &>/dev/null && update-desktop-database 2>/dev/null || true

    echo -e "\n${GREEN}System-wide uninstallation complete.${NC}"
    read -r -p $'\nPress Enter to continue…'
}

# ── Option 3: search entire filesystem ───────────────────────────────────────
search_all_files() {
    echo -e "\n${CYAN}Scanning filesystem for LM Studio files…${NC}"
    echo -e "${YELLOW}This may take several minutes.${NC}\n"

    SEARCH_TMP=$(mktemp)

    # Directories excluded from the search
    local -a prune_paths=(
        /proc /sys /dev /run /snap /boot /lost+found /tmp /var/tmp
        /var/cache/apt /var/lib/apt /var/lib/dpkg /var/cache/debconf
    )

    # Build a single find command using -prune for excluded dirs,
    # and -iname with -o for all name patterns — NO eval required
    local prune_expr=()
    for p in "${prune_paths[@]}"; do
        prune_expr+=( -path "$p" -o -path "$p/*" -o )
    done
    # The trailing -o leaves an open OR; close it with a never-true expression
    prune_expr+=( -path "/_no_such_path_" )

    # Collect all matching paths into the temp file (nullchar-delimited → sorted)
    find / \( "${prune_expr[@]}" \) -prune -o \
        \( -iname "lm-studio*" -o -iname "lm studio*" -o \
           -iname "lmstudio*" -o -iname "LMStudio*" \) -print0 2>/dev/null \
        | sort -zu > "$SEARCH_TMP"

    # Tally and display
    local -a dirs=() files=() links=() other=()
    while IFS= read -r -d '' path; do
        if   [[ -L  "$path" ]]; then links+=("$path")
        elif [[ -d  "$path" ]]; then dirs+=("$path")
        elif [[ -f  "$path" ]]; then files+=("$path")
        else                         other+=("$path")
        fi
    done < "$SEARCH_TMP"

    local total=$(( ${#dirs[@]} + ${#files[@]} + ${#links[@]} + ${#other[@]} ))

    echo -e "${BLUE}══════════════════════════════════════════════════════════════${NC}"
    echo -e "${MAGENTA}Directories (${#dirs[@]}):${NC}"
    for p in "${dirs[@]}";  do printf "  %s\n" "$p"; done
    [[ ${#dirs[@]}  -eq 0 ]] && echo "  (none)"

    echo -e "\n${MAGENTA}Files (${#files[@]}):${NC}"
    for p in "${files[@]}"; do
        local sz; sz=$(du -sh "$p" 2>/dev/null | cut -f1)
        printf "  %-60s  %s\n" "$p" "$sz"
    done
    [[ ${#files[@]} -eq 0 ]] && echo "  (none)"

    echo -e "\n${MAGENTA}Symlinks (${#links[@]}):${NC}"
    for p in "${links[@]}"; do printf "  %s -> %s\n" "$p" "$(readlink "$p")"; done
    [[ ${#links[@]} -eq 0 ]] && echo "  (none)"

    echo -e "${BLUE}══════════════════════════════════════════════════════════════${NC}"
    echo -e "${GREEN}Total: $total item(s) found  " \
            "(dirs: ${#dirs[@]}, files: ${#files[@]}, symlinks: ${#links[@]})${NC}"
    echo -e "${BLUE}══════════════════════════════════════════════════════════════${NC}"

    if confirm "Save these results to a file?"; then
        local out="lm_studio_search_$(date +%Y%m%d_%H%M%S).txt"
        {
            echo "LM Studio file search — $(date)"
            echo "DIRECTORIES:"; printf "  %s\n" "${dirs[@]:-}"
            echo "FILES:";       printf "  %s\n" "${files[@]:-}"
            echo "SYMLINKS:";    printf "  %s -> %s\n" "${links[@]:-}"
            echo "Total: $total"
        } > "$out"
        echo -e "${GREEN}Saved to: $out${NC}"
    fi

    if (( total > 0 )); then
        echo
        if confirm "Remove all found LM Studio items?"; then
            echo -e "${YELLOW}Removing…${NC}"
            local fail=0
            # Process deepest paths first so parent dirs aren't removed before children
            readarray -td '' sorted_paths < <(printf '%s\0' \
                "${dirs[@]:-}" "${files[@]:-}" "${links[@]:-}" "${other[@]:-}" \
                | sort -rzu)
            for path in "${sorted_paths[@]}"; do
                safe_rm "$path" || (( fail++ )) || true
            done
            (( fail > 0 )) && echo -e "${YELLOW}${fail} item(s) could not be removed.${NC}"
            echo -e "${GREEN}Done.${NC}"
        fi
    fi

    read -r -p $'\nPress Enter to continue…'
}

# ── Main menu ─────────────────────────────────────────────────────────────────
while true; do
    banner
    echo -e "${CYAN}Select an option:${NC}\n"
    echo -e "  ${GREEN}1)${NC} Uninstall for a specific user"
    echo -e "  ${GREEN}2)${NC} Uninstall system-wide (all users + root)  ${RED}[requires sudo]${NC}"
    echo -e "  ${GREEN}3)${NC} Search and list all LM Studio files"
    echo -e "  ${RED}4)${NC} Exit"
    echo
    read -r -p "Choice (1–4): " choice

    case "${choice// /}" in
        1) uninstall_selected_user ;;
        2)
            if (( EUID != 0 )); then
                echo -e "\n${RED}System-wide uninstallation requires root.${NC}"
                echo -e "${YELLOW}Re-run with: sudo $0${NC}"
                read -r -p $'\nPress Enter to continue…'
            else
                uninstall_system_wide
            fi
            ;;
        3)
            (( EUID != 0 )) && \
                echo -e "\n${YELLOW}Running without sudo — some directories may be inaccessible.${NC}\n"
            search_all_files
            ;;
        4) echo -e "\n${GREEN}Goodbye.${NC}"; exit 0 ;;
        *) echo -e "\n${RED}Invalid choice.${NC}"; sleep 1 ;;
    esac
done