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 first stops any running instances (
lm-studio,lmstudio), so the app can’t write its files back while they are being removed. In a dry run it only lists them. - App Data: It removes LM Studio’s own folder,
~/.lmstudio, and its folders in the standard Linux (XDG) locations:~/.config,~/.cacheand~/.local/share. It also deletes the AppImage if you saved it in~/Applications,~/Downloads,~/Desktopor~/bin. - Model Preservation: Because AI models are massive, the script shows the size of your models folder and asks if you want to keep it for future use or wipe it to reclaim disk space. It asks before anything is deleted, and if you keep the models, everything around them is still removed.
- 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 full script from section 5 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. Each one opens the same menu (see below):
- The Safety Check (Dry Run): See what would be deleted, and which LM Studio processes would be stopped, without touching anything.
./uninstall_lm_studio.sh --dry-run - Standard Uninstall: Clean up your current user profile (option 1, then pick your account).
./uninstall_lm_studio.sh - System-Wide Wipe: Remove LM Studio for all users and root (option 2).
sudo ./uninstall_lm_studio.sh
3. Navigating the Menu Options
| Option | Use Case |
|---|---|
| 1) Specific User | Cleans up one account. Your own works without sudo; anyone else’s needs it. |
| 2) System-wide | Use this if you used sudo to install LM Studio or want it gone from the entire machine. Needs sudo. |
| 3) Search and List | Searches the whole system for anything named like LM Studio, lists it (and can save the list to a file), then offers to delete it all. Run it with sudo to see every folder. |
| 4) Exit | Closes the script without doing anything. |
4. Manual Verification (Optional)
If you prefer to double-check the work manually, these are the locations the script cleans up. If they are gone, your uninstallation was successful:
~/.lmstudio(Models, thelmscommand-line tool and app data in current versions)~/.config/LM Studio(User settings)~/.cache/lm-studio(Temporary files; older versions also keep models here)~/.local/share/applications/lm-studio.desktop(Menu icon, if you created one)- The
LM-Studio-….AppImagefile itself, wherever you saved it
If you used the lms command-line tool, lms bootstrap also added ~/.lmstudio/bin to your PATH. The script points that line out but leaves your shell files alone; find it with grep -n lmstudio ~/.bashrc ~/.zshrc ~/.profile and delete it.
Pro-Tip: If you chose to KEEP your models during uninstallation, you can find them later in
~/.lmstudio/models(or~/.cache/lm-studio/modelsif you used an older version). This is helpful if you want to move them to an external drive before a final wipe.
5. The Full Uninstaller Script
Save this as uninstall_lm_studio.sh (Step 1 above):
#!/usr/bin/env bash
# LM Studio Uninstaller for Ubuntu 24.04
# Author: Windows-HQ.com
#
# Usage: ./uninstall_lm_studio.sh [--dry-run] (run with sudo for the system-wide option)
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() { if [[ -n "$SEARCH_TMP" && -f "$SEARCH_TMP" ]]; then rm -f "$SEARCH_TMP"; fi; }
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; returns 1 if it fails
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"
if ! rm -rf -- "$path"; then
echo -e " ${RED}Could not remove${NC}: $path"
return 1
fi
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). Called before
# anything is removed, so a closing app can't write its files back. With
# --dry-run it only reports what it would stop.
kill_lm_processes() {
local user="${1:-}"
local patterns=("lm-studio" "LM-Studio" "lmstudio" "LMStudio")
local -a owner=()
if [[ -n "$user" ]]; then owner=(-u "$user"); fi
for pat in "${patterns[@]}"; do
if $DRY_RUN; then
pgrep "${owner[@]}" -f "$pat" >/dev/null 2>&1 \
&& echo -e " ${YELLOW}[dry-run]${NC} would kill '$pat' processes${user:+ for $user}" || true
else
pkill "${owner[@]}" -f "$pat" 2>/dev/null \
&& echo " Killed '$pat' processes${user:+ for $user}" || 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
local path
echo -e "\n${GREEN}Uninstalling for user: $username${NC}"
# ── App data: ~/.lmstudio (current versions) and the XDG dirs ──────────────
# (~/.config/LM Studio, ~/.cache/lm-studio, ...); find -iname covers
# every capitalisation
local -a targets=()
if [[ -e "$home_dir/.lmstudio" ]]; then targets+=("$home_dir/.lmstudio"); fi
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
targets+=("$path")
done < <(find "$root" -mindepth 1 -maxdepth 1 \
\( -iname "lm?studio*" -o -iname "lmstudio*" \) -print0 2>/dev/null)
done
# ── Models: ~/.lmstudio/models now, ~/.cache/lm-studio/models in older ─────
# versions. Decide before anything is removed.
local -a model_dirs=()
for path in "${targets[@]}"; do
if [[ -d "$path/models" ]]; then model_dirs+=("$path/models"); fi
done
local keep_models=false
if (( ${#model_dirs[@]} > 0 )); then
local model_size
model_size=$(du -shc "${model_dirs[@]}" 2>/dev/null | tail -n 1 | cut -f1)
echo -e "\n ${YELLOW}Found model directory${NC}: ${model_dirs[*]} (${model_size})"
case "$model_policy" in
remove) ;;
keep) keep_models=true; echo " Keeping models (policy=keep)" ;;
ask)
if confirm "Remove downloaded models for $username?"; then
:
else
keep_models=true
echo " Keeping models"
fi
;;
esac
fi
# ── Remove the app data; kept models stay where they are ───────────────────
local item
for path in "${targets[@]}"; do
if $keep_models && [[ -d "$path/models" ]]; then
while IFS= read -r -d '' item; do
[[ "$item" == "$path/models" ]] && continue
safe_rm "$item" || true
done < <(find "$path" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
else
safe_rm "$path" || true
fi
(( removed++ )) || true
done
# ── Desktop entries ────────────────────────────────────────────────────────
while IFS= read -r -d '' path; do
safe_rm "$path" || true
(( removed++ )) || true
done < <(find "$home_dir/.local/share/applications" -maxdepth 1 \
\( -iname "lm?studio*.desktop" -o -iname "lmstudio*.desktop" \) -print0 2>/dev/null)
# ── Local binaries ─────────────────────────────────────────────────────────
while IFS= read -r -d '' path; do
safe_rm "$path" || true
(( removed++ )) || true
done < <(find "$home_dir/.local/bin" -maxdepth 1 \
\( -iname "lm?studio*" -o -iname "lmstudio*" \) -print0 2>/dev/null)
# ── The AppImage, wherever it was saved ────────────────────────────────────
while IFS= read -r -d '' path; do
safe_rm "$path" || true
(( removed++ )) || true
done < <(find "$home_dir/Applications" "$home_dir/Downloads" "$home_dir/Desktop" "$home_dir/bin" \
-maxdepth 1 \( -iname "lm?studio*.appimage" -o -iname "lmstudio*.appimage" \) -print0 2>/dev/null)
# ── The PATH line 'lms bootstrap' added (left alone, but pointed out) ──────
local rc
for rc in "$home_dir/.bashrc" "$home_dir/.zshrc" "$home_dir/.profile" "$home_dir/.bash_profile"; do
if [[ -f "$rc" ]] && grep -q '\.lmstudio/bin' "$rc" 2>/dev/null; then
echo -e " ${YELLOW}Note${NC}: $rc still adds ~/.lmstudio/bin to PATH (from 'lms bootstrap'); delete that line if you like."
fi
done
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}"
kill_lm_processes "${users[$idx]}"
uninstall_for_user "${homes[$idx]}" "${users[$idx]}" ask
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}Stopping running processes…${NC}"
kill_lm_processes ""
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
while IFS= read -r -d '' path; do
safe_rm "$path" || true
done < <(find "$root" -maxdepth 1 \
\( -iname "lm?studio*" -o -iname "lmstudio*" \) \
-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" || true
done < <(find "$bin_dir" -maxdepth 1 -type l \( -iname "lm?studio*" -o -iname "lmstudio*" \) -print0 2>/dev/null)
done
if ! $DRY_RUN && command -v update-desktop-database &>/dev/null; then
update-desktop-database 2>/dev/null || true
fi
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
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:"
for p in "${links[@]}"; do printf " %s -> %s\n" "$p" "$(readlink "$p")"; done
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
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.