- _local_model_task now tags a local diffusers pipeline that resolves to a video family (LTX / Wan / Hunyuan) as text-to-video, mirroring the cached-repo _cached_repo_task, so supported local video pipelines surface in the Video On-Device picker instead of being routed to the Images picker where the image loader rejects them. Gated on _local_is_diffusers so only a real loadable pipeline dir reaches the video check. - Video validate_load_request now rejects a local pipeline pick whose directory has no model_index.json before the GPU handoff, mirroring the image loader, so a bad local pipeline can no longer evict the resident model and only then fail deep in from_pretrained. - The custom/env-mode uninstall now removes a sibling stable-diffusion.cpp only when it carries the Studio owner marker. install_sd_cpp_prebuilt writes the canonical .unsloth-studio-owned marker on install; uninstall.sh and uninstall.ps1 keep any unowned checkout (a user's own git clone of stable-diffusion.cpp beside a custom Studio root is no longer deleted). A pre-marker Studio build is left behind rather than a user file removed. Adds regression tests: local video pipeline tagged text-to-video (and a video-named non-pipeline dir stays untagged so it can never trigger a doomed pipeline load), the video local-pipeline preflight rejection, the install ownership marker, and the uninstall keeping an unowned sibling while removing an owned one.
447 lines
22 KiB
Bash
Executable file
447 lines
22 KiB
Bash
Executable file
#!/usr/bin/env sh
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
#
|
|
# Unsloth Studio uninstaller (macOS / Linux / WSL).
|
|
# Stops running servers and removes install dir, launcher data,
|
|
# CLI shim, desktop shortcut, .app bundle, and Launch Services entry.
|
|
# Honors custom roots set via UNSLOTH_STUDIO_HOME / STUDIO_HOME at
|
|
# install time (read back from studio.conf).
|
|
#
|
|
# Usage: curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/main/scripts/uninstall.sh | sh
|
|
|
|
set -e
|
|
|
|
# Stop a Studio server via its PID file (written by install.sh's _spawn_terminal).
|
|
_kill_pid_file() {
|
|
_pid_file="$1"
|
|
[ -f "$_pid_file" ] || return 0
|
|
_pid=$(sed -n '1s/[^0-9].*//p' "$_pid_file" 2>/dev/null || true)
|
|
if [ -n "$_pid" ] && kill -0 "$_pid" 2>/dev/null; then
|
|
kill -TERM "$_pid" 2>/dev/null || true
|
|
# Wait up to 10s for graceful shutdown.
|
|
_i=0
|
|
while kill -0 "$_pid" 2>/dev/null && [ "$_i" -lt 20 ]; do
|
|
sleep 0.5
|
|
_i=$((_i + 1))
|
|
done
|
|
kill -0 "$_pid" 2>/dev/null && kill -KILL "$_pid" 2>/dev/null || true
|
|
fi
|
|
rm -f "$_pid_file" 2>/dev/null || true
|
|
}
|
|
|
|
# BRE-escape a path so it can be embedded in a pkill -f regex.
|
|
_pkill_escape() {
|
|
printf '%s' "$1" | sed -e 's:[][\\.^$*+?{|}()/]:\\&:g'
|
|
}
|
|
|
|
_pkill_studio() {
|
|
# Prefer PID files written by _spawn_terminal so we only touch our own installs.
|
|
for _data_dir in "$HOME/.local/share/unsloth" $(_custom_studio_data_dirs); do
|
|
[ -d "$_data_dir" ] || continue
|
|
for _pf in "$_data_dir"/studio-*.pid; do
|
|
[ -f "$_pf" ] && _kill_pid_file "$_pf"
|
|
done
|
|
done
|
|
|
|
command -v pkill >/dev/null 2>&1 || return 0
|
|
|
|
# Scope fallback patterns to the install roots we are removing so a
|
|
# different Studio install (different UNSLOTH_STUDIO_HOME) is not touched.
|
|
_kill_roots="$HOME/.unsloth/studio"
|
|
_roots_from_conf=$(_custom_studio_roots 2>/dev/null || true)
|
|
[ -n "$_roots_from_conf" ] && _kill_roots="$_kill_roots
|
|
$_roots_from_conf"
|
|
|
|
printf '%s\n' "$_kill_roots" | while IFS= read -r _root; do
|
|
[ -n "$_root" ] || continue
|
|
[ -d "$_root" ] || continue
|
|
_re=$(_pkill_escape "$_root")
|
|
# `unsloth studio` (default port) + `-p N` + `--port N` forms, all
|
|
# anchored on the install root's venv path.
|
|
for _pat in \
|
|
"${_re}/unsloth_studio/bin/[^ ]* studio( |\$|.*-p[ =][0-9])" \
|
|
"${_re}/unsloth_studio/bin/[^ ]* studio.*--port[ =][0-9]" \
|
|
"${_re}/.*studio/backend/run\.py"
|
|
do
|
|
pkill -TERM -f "$_pat" 2>/dev/null || true
|
|
done
|
|
done
|
|
sleep 0.5
|
|
printf '%s\n' "$_kill_roots" | while IFS= read -r _root; do
|
|
[ -n "$_root" ] || continue
|
|
[ -d "$_root" ] || continue
|
|
_re=$(_pkill_escape "$_root")
|
|
for _pat in \
|
|
"${_re}/unsloth_studio/bin/[^ ]* studio( |\$|.*-p[ =][0-9])" \
|
|
"${_re}/unsloth_studio/bin/[^ ]* studio.*--port[ =][0-9]" \
|
|
"${_re}/.*studio/backend/run\.py"
|
|
do
|
|
pkill -KILL -f "$_pat" 2>/dev/null || true
|
|
done
|
|
done
|
|
}
|
|
|
|
_remove_path() {
|
|
_p="$1"
|
|
if [ -e "$_p" ] || [ -L "$_p" ]; then
|
|
rm -rf "$_p" 2>/dev/null && echo " removed: $_p" || echo " could not remove: $_p" >&2
|
|
fi
|
|
}
|
|
|
|
# Accept as Studio root only if Studio sentinels exist (matches install.sh's
|
|
# env-mode ownership guard at install.sh:1358-1361). A bare unsloth_studio/
|
|
# directory is NOT enough -- require the install-time owner marker so a user
|
|
# directory that happens to contain a folder named "unsloth_studio" is safe.
|
|
_is_studio_root() {
|
|
_r="$1"
|
|
[ -n "$_r" ] || return 1
|
|
[ -f "$_r/share/studio.conf" ] && return 0
|
|
[ -f "$_r/unsloth_studio/.unsloth-studio-owned" ] && return 0
|
|
if [ -L "$_r/bin/unsloth" ]; then
|
|
_t=$(readlink "$_r/bin/unsloth" 2>/dev/null || true)
|
|
case "$_t" in *unsloth_studio/bin/unsloth) return 0 ;; esac
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
# Hard deny list: never delete /, $HOME, $HOME's parent, or system paths.
|
|
_is_unsafe_root() {
|
|
_r="$1"
|
|
[ -z "$_r" ] && return 0
|
|
case "$_r" in /|""|"$HOME"|"$HOME/") return 0 ;; esac
|
|
case "$_r" in /bin|/sbin|/etc|/usr|/usr/*|/var|/var/*|/opt|/opt/*|/Library|/Library/*|/System|/System/*|/Applications|/Applications/*) return 0 ;; esac
|
|
_parent=$(dirname "$HOME" 2>/dev/null || echo "")
|
|
[ -n "$_parent" ] && [ "$_r" = "$_parent" ] && return 0
|
|
return 1
|
|
}
|
|
|
|
# Print share/ dirs of known custom roots (where PID files live).
|
|
_custom_studio_data_dirs() {
|
|
_custom_studio_roots 2>/dev/null | while IFS= read -r _r; do
|
|
[ -d "$_r/share" ] && printf '%s\n' "$_r/share"
|
|
done
|
|
}
|
|
|
|
# Resolve a custom install root from any of:
|
|
# 1. UNSLOTH_STUDIO_HOME / STUDIO_HOME env vars at uninstall time
|
|
# 2. Default-mode studio.conf at $HOME/.local/share/unsloth/studio.conf
|
|
# 3. Env-mode studio.conf at $<root>/share/studio.conf (discovered via 1)
|
|
# install.sh writes UNSLOTH_EXE='<root>/unsloth_studio/bin/unsloth', so
|
|
# the install root is three dirnames up. Prints each discovered non-default
|
|
# root on its own line; the caller iterates and de-duplicates.
|
|
_custom_studio_roots() {
|
|
_seen=""
|
|
_emit() {
|
|
_r="$1"
|
|
[ -z "$_r" ] && return 0
|
|
# Tilde expansion (env vars are not subject to it on quoted assignment),
|
|
# matches install.sh's _resolve_studio_destinations. The literal "~/"
|
|
# pattern is intentional; SC2088 is a false positive here.
|
|
# shellcheck disable=SC2088
|
|
case "$_r" in
|
|
"~") _r="$HOME" ;;
|
|
"~/"*) _r="$HOME/${_r#'~/'}" ;;
|
|
esac
|
|
# Canonicalize so syntactic variants ($HOME/../$USER, trailing slash)
|
|
# resolve to the same path and hit the _is_unsafe_root deny list.
|
|
# shellcheck disable=SC1007
|
|
_canon=$(CDPATH= cd -P -- "$_r" 2>/dev/null && pwd -P)
|
|
[ -n "$_canon" ] && _r="$_canon"
|
|
case "$_r" in "$HOME/.unsloth/studio"|/|"") return 0 ;; esac
|
|
case ":$_seen:" in *":$_r:"*) return 0 ;; esac
|
|
_seen="$_seen:$_r"
|
|
printf '%s\n' "$_r"
|
|
}
|
|
_from_conf() {
|
|
[ -f "$1" ] || return 0
|
|
# Tolerate paths containing apostrophes (install.sh emits '\'' for them).
|
|
_exe=$(sed -n "s/^UNSLOTH_EXE='\(.*\)'\$/\1/p" "$1" | head -n1)
|
|
_exe=$(printf '%s' "$_exe" | sed "s/'\\\\''/'/g")
|
|
[ -n "$_exe" ] || return 0
|
|
_emit "$(dirname "$(dirname "$(dirname "$_exe")")")"
|
|
}
|
|
# Mirror install.sh's precedence: UNSLOTH_STUDIO_HOME wins, STUDIO_HOME is
|
|
# ignored when both are set. Otherwise uninstalling install A could also
|
|
# delete install B if the user has STUDIO_HOME left over from B.
|
|
if [ -n "${UNSLOTH_STUDIO_HOME:-}" ]; then
|
|
_emit "$UNSLOTH_STUDIO_HOME"
|
|
_from_conf "$UNSLOTH_STUDIO_HOME/share/studio.conf"
|
|
elif [ -n "${STUDIO_HOME:-}" ]; then
|
|
_emit "$STUDIO_HOME"
|
|
_from_conf "$STUDIO_HOME/share/studio.conf"
|
|
fi
|
|
# Default-mode conf.
|
|
_from_conf "$HOME/.local/share/unsloth/studio.conf"
|
|
}
|
|
|
|
# Remove $HOME/.local/bin/unsloth only if it's a Studio-managed symlink.
|
|
# Studio's install.sh writes this as a symlink into the studio venv
|
|
# (install.sh: `ln -sfn "$VENV_DIR/bin/unsloth" "$_shim_path"`). A
|
|
# pip-installed `unsloth` CLI is a regular file — leave it alone to avoid
|
|
# wiping an unrelated install.
|
|
_remove_cli_shim() {
|
|
_shim="$HOME/.local/bin/unsloth"
|
|
[ -L "$_shim" ] || return 0
|
|
_target=$(readlink "$_shim" 2>/dev/null || true)
|
|
case "$_target" in
|
|
*/unsloth_studio/bin/unsloth) _remove_path "$_shim" ;;
|
|
*) ;;
|
|
esac
|
|
}
|
|
|
|
_uid=$(id -u 2>/dev/null || echo 0)
|
|
_os=$(uname 2>/dev/null || echo unknown)
|
|
_is_wsl=0
|
|
[ "$_os" = "Linux" ] && grep -qi microsoft /proc/version 2>/dev/null && _is_wsl=1
|
|
|
|
echo "Stopping any running Unsloth Studio servers..."
|
|
_pkill_studio
|
|
|
|
echo "Removing data and install directories..."
|
|
_custom_studio_roots | while IFS= read -r _custom_root; do
|
|
[ -n "$_custom_root" ] || continue
|
|
if _is_unsafe_root "$_custom_root"; then
|
|
echo " refusing to remove unsafe path: $_custom_root" >&2
|
|
continue
|
|
fi
|
|
if ! _is_studio_root "$_custom_root"; then
|
|
echo " refusing to remove non-Studio path: $_custom_root" >&2
|
|
continue
|
|
fi
|
|
_remove_path "$_custom_root"
|
|
# Native diffusion (stable-diffusion.cpp) for a custom/env-mode Studio installs beside
|
|
# the root at <parent>/stable-diffusion.cpp -- find_sd_cpp_binary resolves it from
|
|
# UNSLOTH_STUDIO_HOME.parent (sd_cpp_engine.py) -- so removing only the root leaves the
|
|
# build behind. Only remove a sibling Studio installed: <parent> is a user-chosen dir
|
|
# and "stable-diffusion.cpp" is exactly what `git clone` of leejet/stable-diffusion.cpp
|
|
# produces, so require our owner marker (written by install_sd_cpp_prebuilt) before rm,
|
|
# and keep any unowned checkout. A pre-marker Studio build is left behind, never a user
|
|
# file deleted. Guard the derived parent path the same way.
|
|
_custom_sd_cpp="$(dirname "$_custom_root")/stable-diffusion.cpp"
|
|
if _is_unsafe_root "$_custom_sd_cpp"; then
|
|
echo " refusing to remove unsafe path: $_custom_sd_cpp" >&2
|
|
elif [ -e "$_custom_sd_cpp" ] && [ ! -f "$_custom_sd_cpp/.unsloth-studio-owned" ]; then
|
|
echo " keeping sd.cpp without Studio owner marker: $_custom_sd_cpp" >&2
|
|
else
|
|
_remove_path "$_custom_sd_cpp"
|
|
fi
|
|
done
|
|
_remove_path "$HOME/.unsloth/studio"
|
|
# Default-mode shared llama.cpp build + cache are siblings of studio (not removed
|
|
# by deleting it). No-op in env/custom mode (they nest under the custom root) and
|
|
# when absent. A user-set UNSLOTH_LLAMA_CPP_PATH is intentionally kept.
|
|
_remove_path "$HOME/.unsloth/llama.cpp"
|
|
# Default-mode native diffusion (stable-diffusion.cpp / sd-cli) build, a sibling of
|
|
# studio like llama.cpp (install_sd_cpp_prebuilt.default_install_dir()). No-op in
|
|
# env/custom mode and when absent. A user-set UNSLOTH_SD_CPP_PATH is kept.
|
|
_remove_path "$HOME/.unsloth/stable-diffusion.cpp"
|
|
_remove_path "$HOME/.unsloth/.cache"
|
|
# Isolated Node.js runtime (install_node_prebuilt.py), a sibling of studio in
|
|
# default mode. No-op in env/custom mode (nested under the custom root) and absent.
|
|
_remove_path "$HOME/.unsloth/node"
|
|
# llama.cpp atomic-install staging root (install_llama_prebuilt.py .staging).
|
|
# Normally pruned after activate, but an interrupted build can leave it behind;
|
|
# removing it lets the rmdir below succeed. No-op in env/custom mode and absent.
|
|
_remove_path "$HOME/.unsloth/.staging"
|
|
# llama.cpp install lock (serializes the shared build); a stray one keeps ~/.unsloth
|
|
# from being pruned below. No-op in env/custom mode and when absent.
|
|
_remove_path "$HOME/.unsloth/.llama.cpp.install.lock"
|
|
# ROCm-on-WSL helper artifacts (librocdxg build clone + smoke-test venv). No-op
|
|
# where they don't exist; removing them lets the rmdir below succeed.
|
|
_remove_path "$HOME/.unsloth/librocdxg"
|
|
_remove_path "$HOME/.unsloth/rocm-smoketest"
|
|
# Drop ~/.unsloth only if now empty (rmdir refuses non-empty, so user content is kept).
|
|
rmdir "$HOME/.unsloth" 2>/dev/null || true
|
|
_remove_path "$HOME/.local/share/unsloth"
|
|
# CLI shim: only the symlink Studio created, never a pip-installed file.
|
|
_remove_cli_shim
|
|
|
|
echo "Removing desktop shortcut and launcher lock..."
|
|
# install.sh creates Desktop/Unsloth Studio as a symlink. If the user has an
|
|
# unrelated regular directory by that name, leave it alone.
|
|
_desktop_link="$HOME/Desktop/Unsloth Studio"
|
|
if [ -L "$_desktop_link" ] || [ ! -e "$_desktop_link" ]; then
|
|
_remove_path "$_desktop_link"
|
|
else
|
|
echo " refusing to remove non-symlink Desktop path: $_desktop_link" >&2
|
|
fi
|
|
_remove_path "$HOME/Desktop/unsloth-studio.desktop"
|
|
# Locks are namespaced per-uid; env-mode adds an extra suffix.
|
|
_lock_glob="${XDG_RUNTIME_DIR:-/tmp}/unsloth-studio-launcher-${_uid}"
|
|
for _lock in "$_lock_glob".lock "$_lock_glob"-*.lock; do
|
|
[ -e "$_lock" ] && _remove_path "$_lock"
|
|
done
|
|
|
|
case "$_os" in
|
|
Darwin)
|
|
echo "Removing macOS .app bundle and Launch Services entry..."
|
|
_remove_path "$HOME/Applications/Unsloth Studio.app"
|
|
_lsr="/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/Support/lsregister"
|
|
if [ -x "$_lsr" ]; then
|
|
"$_lsr" -u "$HOME/Applications/Unsloth Studio.app" 2>/dev/null || true
|
|
fi
|
|
;;
|
|
Linux)
|
|
if [ "$_is_wsl" = "1" ]; then
|
|
echo "Removing WSL Windows-side shortcuts..."
|
|
# install.sh creates per-distro 'Unsloth Studio (WSL - <distro>).lnk'
|
|
# on the Windows Desktop + Start Menu via powershell.exe. Scope removal
|
|
# to THIS distro (passed as $args[0]) so a multi-distro install keeps the
|
|
# other distros' launchers; the TARGET=wsl.exe check still spares a
|
|
# native install's "Unsloth Studio.lnk". Prefer powershell.exe; test it
|
|
# can EXECUTE (`command -v` succeeds even with interop OFF -- .exe then
|
|
# fails "Exec format error", common on systemd-enabled distros).
|
|
_wsl_distro="${WSL_DISTRO_NAME:-}"
|
|
_ps_ran=0
|
|
if command -v powershell.exe >/dev/null 2>&1 && \
|
|
powershell.exe -NoProfile -Command "exit 0" >/dev/null 2>&1; then
|
|
_ps_ran=1
|
|
# Inject the distro into the command: a -Command string does not
|
|
# receive trailing tokens as $args. WSL distro names are safe to
|
|
# embed (no quotes/$/backtick).
|
|
# shellcheck disable=SC2016
|
|
powershell.exe -NoProfile -Command '$distro = "'"$_wsl_distro"'";
|
|
$dirs = @(
|
|
[Environment]::GetFolderPath("Desktop"),
|
|
(Join-Path $env:APPDATA "Microsoft\Windows\Start Menu\Programs")
|
|
);
|
|
$ws = New-Object -ComObject WScript.Shell;
|
|
foreach ($d in $dirs) {
|
|
if (-not $d -or -not (Test-Path -LiteralPath $d)) { continue }
|
|
Get-ChildItem -LiteralPath $d -Filter "Unsloth Studio*.lnk" -ErrorAction SilentlyContinue | ForEach-Object {
|
|
try {
|
|
$sc = $ws.CreateShortcut($_.FullName);
|
|
if ("$($sc.TargetPath) $($sc.Arguments)" -notmatch "wsl\.exe") { return }
|
|
# When the distro is known, require the per-distro
|
|
# name for this distro or its -d "<distro>" argument
|
|
# so launchers for other distros are not removed.
|
|
if ($distro) {
|
|
$nameMatch = ($_.Name -eq "Unsloth Studio (WSL - $distro).lnk");
|
|
$argMatch = ($sc.Arguments -match ("-d\s+`"?" + [regex]::Escape($distro) + "`"?"));
|
|
if (-not ($nameMatch -or $argMatch)) { return }
|
|
}
|
|
Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue
|
|
} catch { }
|
|
}
|
|
}
|
|
# Keep the shared icon while any Unsloth shortcut still uses it (native
|
|
# install or another WSL distro); drop it only with the last one.
|
|
$iconInUse = $false;
|
|
foreach ($d in $dirs) {
|
|
if (-not $d -or -not (Test-Path -LiteralPath $d)) { continue }
|
|
if (Get-ChildItem -LiteralPath $d -Filter "Unsloth Studio*.lnk" -ErrorAction SilentlyContinue) { $iconInUse = $true; break }
|
|
}
|
|
# Guard LOCALAPPDATA: empty on a service/SYSTEM account makes
|
|
# Join-Path throw, aborting the icon cleanup (mirror uninstall.ps1).
|
|
if (-not [string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) {
|
|
$iconDir = Join-Path $env:LOCALAPPDATA "Unsloth Studio";
|
|
$ico = Join-Path $iconDir "unsloth.ico";
|
|
if ((-not $iconInUse) -and (Test-Path -LiteralPath $ico)) { Remove-Item -LiteralPath $ico -Force -ErrorAction SilentlyContinue }
|
|
if ((Test-Path -LiteralPath $iconDir) -and -not (Get-ChildItem -LiteralPath $iconDir -Force -ErrorAction SilentlyContinue)) { Remove-Item -LiteralPath $iconDir -Recurse -Force -ErrorAction SilentlyContinue }
|
|
}' >/dev/null 2>&1 || true
|
|
fi
|
|
# Remove $1's shared unsloth.ico only if no Unsloth shortcut (native install
|
|
# or another WSL distro) still uses it, then drop the dir if empty. Reciprocal
|
|
# of uninstall.ps1's _RemoveDataDirKeepingWslIcon (keeps the icon for a
|
|
# surviving WSL shortcut when the native side is removed).
|
|
_drop_shared_icon_if_unused() {
|
|
_du="$1"
|
|
_icodir="$_du/AppData/Local/Unsloth Studio"
|
|
_icon_in_use=0
|
|
for _sd in \
|
|
"$_du/Desktop" \
|
|
"$_du/OneDrive/Desktop" \
|
|
"$_du"/OneDrive*/Desktop \
|
|
"$_du/AppData/Roaming/Microsoft/Windows/Start Menu/Programs"; do
|
|
[ -d "$_sd" ] || continue
|
|
for _any in "$_sd"/"Unsloth Studio"*.lnk; do
|
|
[ -e "$_any" ] && { _icon_in_use=1; break; }
|
|
done
|
|
[ "$_icon_in_use" = "1" ] && break
|
|
done
|
|
if [ "$_icon_in_use" = "0" ]; then
|
|
[ -f "$_icodir/unsloth.ico" ] && rm -f "$_icodir/unsloth.ico" 2>/dev/null || true
|
|
fi
|
|
[ -d "$_icodir" ] && rmdir "$_icodir" 2>/dev/null || true
|
|
}
|
|
# Fallback when powershell.exe can't run (interop disabled): remove WSL .lnk
|
|
# files via drvfs. The "Unsloth Studio (WSL..." name is WSL-specific, so a
|
|
# native install's "Unsloth Studio.lnk" never matches.
|
|
if [ "$_ps_ran" = "0" ]; then
|
|
for _drive in /mnt/c /mnt/d /mnt/e; do
|
|
[ -d "$_drive/Users" ] || continue
|
|
for _udir in "$_drive"/Users/*; do
|
|
[ -d "$_udir" ] || continue
|
|
for _scdir in \
|
|
"$_udir/Desktop" \
|
|
"$_udir/OneDrive/Desktop" \
|
|
"$_udir"/OneDrive*/Desktop \
|
|
"$_udir/AppData/Roaming/Microsoft/Windows/Start Menu/Programs"; do
|
|
[ -d "$_scdir" ] || continue
|
|
if [ -n "$_wsl_distro" ]; then
|
|
# Exact per-distro name (no glob) so other distros survive.
|
|
_lnk="$_scdir/Unsloth Studio (WSL - ${_wsl_distro}).lnk"
|
|
[ -e "$_lnk" ] && rm -f "$_lnk" 2>/dev/null && echo " removed: $_lnk" || true
|
|
else
|
|
# Distro unknown: fall back to the broad WSL prefix.
|
|
for _lnk in "$_scdir"/"Unsloth Studio (WSL"*.lnk; do
|
|
[ -e "$_lnk" ] && rm -f "$_lnk" 2>/dev/null && echo " removed: $_lnk" || true
|
|
done
|
|
fi
|
|
done
|
|
# Drop the shared icon only when no shortcut still needs it.
|
|
_drop_shared_icon_if_unused "$_udir"
|
|
done
|
|
done
|
|
fi
|
|
# ── ROCm-on-WSL config (install_rocm_wsl_strixhalo.sh) ──
|
|
# Remove Unsloth's own ROCDXG config (the env it persisted). The system
|
|
# ROCm userspace is a shared prereq (like CUDA) and is LEFT IN PLACE by
|
|
# default; set UNSLOTH_UNINSTALL_ROCM=1 to remove it too.
|
|
echo "Removing ROCm-on-WSL config..."
|
|
_sudo=""
|
|
if [ "$_uid" != "0" ] && command -v sudo >/dev/null 2>&1; then _sudo="sudo"; fi
|
|
$_sudo rm -f /etc/profile.d/unsloth-rocm-wsl.sh 2>/dev/null || true
|
|
if [ -f "$HOME/.bashrc" ] && grep -q "Unsloth ROCm-on-WSL" "$HOME/.bashrc" 2>/dev/null; then
|
|
_bk=$(mktemp 2>/dev/null || echo "$HOME/.bashrc.unsloth.tmp")
|
|
if sed '/# >>> Unsloth ROCm-on-WSL/,/# <<< Unsloth ROCm-on-WSL/d' "$HOME/.bashrc" > "$_bk" 2>/dev/null; then
|
|
cat "$_bk" > "$HOME/.bashrc" 2>/dev/null || true
|
|
echo " cleaned ROCm-on-WSL block from ~/.bashrc"
|
|
fi
|
|
rm -f "$_bk" 2>/dev/null || true
|
|
fi
|
|
if [ "${UNSLOTH_UNINSTALL_ROCM:-0}" = "1" ]; then
|
|
echo " removing system ROCm (UNSLOTH_UNINSTALL_ROCM=1)..."
|
|
$_sudo rm -f /etc/apt/sources.list.d/rocm.list /etc/apt/preferences.d/rocm-pin-600 \
|
|
/etc/apt/keyrings/rocm.gpg /etc/ld.so.conf.d/rocm.conf 2>/dev/null || true
|
|
$_sudo sh -c 'rm -rf /opt/rocm /opt/rocm-*' 2>/dev/null || true
|
|
if command -v ldconfig >/dev/null 2>&1; then $_sudo ldconfig 2>/dev/null || true; fi
|
|
elif [ -d /opt/rocm ]; then
|
|
echo " Note: ROCm userspace (/opt/rocm*) left in place (shared prereq)."
|
|
echo " Remove it by re-running with UNSLOTH_UNINSTALL_ROCM=1, or manually:"
|
|
echo " sudo rm -rf /opt/rocm /opt/rocm-* && sudo ldconfig"
|
|
fi
|
|
fi
|
|
echo "Removing Linux .desktop entry..."
|
|
_remove_path "$HOME/.local/share/applications/unsloth-studio.desktop"
|
|
if command -v update-desktop-database >/dev/null 2>&1; then
|
|
update-desktop-database "$HOME/.local/share/applications" 2>/dev/null || true
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
echo ""
|
|
echo "Unsloth Studio uninstalled."
|
|
echo "Note: Hugging Face model cache at ~/.cache/huggingface was left in place."
|
|
echo "Remove it manually with 'rm -rf ~/.cache/huggingface/hub' if desired."
|
|
# Env-mode installs leave no breadcrumb in $HOME, so a custom root can
|
|
# only be located if the user re-exports the variable. Print a hint when
|
|
# neither var is set so the bare `curl | sh` flow doesn't silently miss.
|
|
if [ -z "${UNSLOTH_STUDIO_HOME:-}" ] && [ -z "${STUDIO_HOME:-}" ]; then
|
|
echo ""
|
|
echo "If you installed Unsloth Studio with UNSLOTH_STUDIO_HOME or STUDIO_HOME"
|
|
echo "pointing at a custom directory, re-run this script with the same variable"
|
|
echo "set to also remove that install tree, e.g.:"
|
|
echo " UNSLOTH_STUDIO_HOME=/your/path sh -c \"\$(curl -fsSL https://raw.githubusercontent.com/unslothai/unsloth/main/scripts/uninstall.sh)\""
|
|
fi
|