Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 48 additions & 14 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,48 @@ else
fi

# ── Install uv ──
if ! command -v uv >/dev/null 2>&1; then
UV_MIN_VERSION="0.7.14"

version_ge() {
# returns 0 if $1 >= $2
_a=$1
_b=$2

while [ -n "$_a" ] || [ -n "$_b" ]; do
_a_part=${_a%%.*}
_b_part=${_b%%.*}

[ "$_a" = "$_a_part" ] && _a="" || _a=${_a#*.}
[ "$_b" = "$_b_part" ] && _b="" || _b=${_b#*.}

[ -z "$_a_part" ] && _a_part=0
[ -z "$_b_part" ] && _b_part=0

if [ "$_a_part" -gt "$_b_part" ]; then
return 0
fi
if [ "$_a_part" -lt "$_b_part" ]; then
return 1
fi
done

return 0
}

_uv_version_ok() {
_raw=$("$1" --version 2>/dev/null | awk '{print $2}') || return 1
[ -n "$_raw" ] || return 1
_ver=${_raw%%[-+]*}
case "$_ver" in
''|*[!0-9.]*) return 1 ;;
esac
version_ge "$_ver" "$UV_MIN_VERSION" || return 1
# Prerelease of the exact minimum (e.g. 0.7.14-rc1) is still below stable 0.7.14
[ "$_ver" = "$UV_MIN_VERSION" ] && [ "$_raw" != "$_ver" ] && return 1
return 0
}

if ! command -v uv >/dev/null 2>&1 || ! _uv_version_ok uv; then
echo "==> Installing uv package manager..."
_uv_tmp=$(mktemp)
download "https://astral.sh/uv/install.sh" "$_uv_tmp"
Expand Down Expand Up @@ -207,6 +248,7 @@ if [ -n "$VENV_ABS_BIN" ]; then
fi

echo "==> Running unsloth studio setup..."
REQUESTED_PYTHON_VERSION="$(cd "$VENV_NAME/bin" && pwd)/python" \
"$VENV_NAME/bin/unsloth" studio setup </dev/null

echo ""
Expand All @@ -215,16 +257,8 @@ echo " Unsloth Studio installed!"
echo "========================================="
echo ""

# Launch studio automatically in interactive terminals;
# in non-interactive environments (Docker, CI, cloud-init) just print instructions.
if [ -t 0 ]; then
echo "==> Launching Unsloth Studio..."
echo ""
exec "$VENV_NAME/bin/unsloth" studio -H 0.0.0.0 -p 8888
else
echo " To launch, run:"
echo ""
echo " source ${VENV_NAME}/bin/activate"
echo " unsloth studio -H 0.0.0.0 -p 8888"
echo ""
fi
echo " To launch, run:"
echo ""
echo " source ${VENV_NAME}/bin/activate"
echo " unsloth studio -H 0.0.0.0 -p 8888"
echo ""
67 changes: 51 additions & 16 deletions studio/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,42 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

# ── Helper: run command quietly, show output only on failure ──
run_quiet() {
local label="$1"
shift
_run_quiet() {
local on_fail=$1
local label=$2
shift 2

local tmplog
tmplog=$(mktemp)
if "$@" > "$tmplog" 2>&1; then
tmplog=$(mktemp) || {
printf '%s\n' "Failed to create temporary file" >&2
[ "$on_fail" = "exit" ] && exit 1 || return 1
}

if "$@" >"$tmplog" 2>&1; then
rm -f "$tmplog"
return 0
else
local exit_code=$?
echo "❌ $label failed (exit code $exit_code):"
cat "$tmplog"
printf 'Failed: %s (exit code %s):\n' "$label" "$exit_code" >&2
cat "$tmplog" >&2
rm -f "$tmplog"
exit $exit_code

if [ "$on_fail" = "exit" ]; then
exit "$exit_code"
else
return "$exit_code"
fi
fi
}

run_quiet() {
_run_quiet exit "$@"
}

run_quiet_no_exit() {
_run_quiet return "$@"
}

echo "╔══════════════════════════════════════╗"
echo "║ Unsloth Studio Setup Script ║"
echo "╚══════════════════════════════════════╝"
Expand Down Expand Up @@ -187,9 +207,24 @@ fi
MIN_PY_MINOR=11 # minimum minor version (>= 3.11)
MAX_PY_MINOR=13 # maximum minor version (< 3.14)
BEST_PY=""
BEST_MAJOR=0
BEST_MINOR=0

# If the caller (e.g. install.sh) already chose a Python, use it directly.
if [ -n "${REQUESTED_PYTHON_VERSION:-}" ] && [ -x "$REQUESTED_PYTHON_VERSION" ]; then
_req_ver=$("$REQUESTED_PYTHON_VERSION" --version 2>&1 | awk '{print $2}')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle failing requested Python probe gracefully

With set -euo pipefail, the command substitution $("$REQUESTED_PYTHON_VERSION" --version ...) will terminate the whole script if that executable exits non-zero. That means a bad-but-executable REQUESTED_PYTHON_VERSION (for example a wrapper script that fails at runtime) now aborts setup before the existing fallback search can run, even though other valid Python 3.11–3.13 interpreters may be available.

Useful? React with 👍 / 👎.

_req_major=$(echo "$_req_ver" | cut -d. -f1)
_req_minor=$(echo "$_req_ver" | cut -d. -f2)
if [ "$_req_major" -eq 3 ] 2>/dev/null && \
[ "$_req_minor" -ge "$MIN_PY_MINOR" ] 2>/dev/null && \
[ "$_req_minor" -le "$MAX_PY_MINOR" ] 2>/dev/null; then
Comment on lines +214 to +219
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[8/8 reviewers] High -- REQUESTED_PYTHON_VERSION only validates the minor version, not the major version. A hypothetical Python 4.12.x would pass the _req_minor range check and be accepted.

Extract _req_major and add a [ "$_req_major" -eq 3 ] guard:

Suggested change
_req_ver=$("$REQUESTED_PYTHON_VERSION" --version 2>&1 | awk '{print $2}')
_req_minor=$(echo "$_req_ver" | cut -d. -f2)
if [ "$_req_minor" -ge "$MIN_PY_MINOR" ] 2>/dev/null && \
[ "$_req_minor" -le "$MAX_PY_MINOR" ] 2>/dev/null; then
_req_ver=$("$REQUESTED_PYTHON_VERSION" --version 2>&1 | awk '{print $2}')
_req_major=$(echo "$_req_ver" | cut -d. -f1)
_req_minor=$(echo "$_req_ver" | cut -d. -f2)
if [ "$_req_major" -eq 3 ] 2>/dev/null && \
[ "$_req_minor" -ge "$MIN_PY_MINOR" ] 2>/dev/null && \
[ "$_req_minor" -le "$MAX_PY_MINOR" ] 2>/dev/null; then

BEST_PY="$REQUESTED_PYTHON_VERSION"
echo "Using requested Python version: $BEST_PY"
else
echo "Ignoring requested Python $REQUESTED_PYTHON_VERSION ($_req_ver) -- outside supported range"
fi
fi

if [ -z "$BEST_PY" ]; then
# Collect candidate python3 binaries (python3, python3.9, python3.10, …)
for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?$' | sort -u); do
if ! command -v "$candidate" &>/dev/null; then
Expand All @@ -206,7 +241,7 @@ for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?
continue
fi

# Skip versions below 3.12 (require > 3.11)
# Skip versions below 3.11
if [ "$py_minor" -lt "$MIN_PY_MINOR" ] 2>/dev/null; then
continue
fi
Expand All @@ -219,11 +254,11 @@ for candidate in $(compgen -c python3 2>/dev/null | grep -E '^python3(\.[0-9]+)?
# Keep the highest qualifying version
if [ "$py_minor" -gt "$BEST_MINOR" ]; then
BEST_PY="$candidate"
BEST_MAJOR="$py_major"
BEST_MINOR="$py_minor"
fi
done
echo "finished finding best python"
fi

if [ -z "$BEST_PY" ]; then
echo "❌ ERROR: No Python version between 3.${MIN_PY_MINOR} and 3.${MAX_PY_MINOR} found on this system."
echo " Detected Python 3 installations:"
Expand Down Expand Up @@ -402,7 +437,7 @@ rm -rf "$LLAMA_CPP_DIR"
echo "Building llama-server for GGUF inference..."

BUILD_OK=true
run_quiet "clone llama.cpp" git clone --depth 1 https://github.com/ggml-org/llama.cpp.git "$LLAMA_CPP_DIR" || BUILD_OK=false
run_quiet_no_exit "clone llama.cpp" git clone --depth 1 https://github.com/ggml-org/llama.cpp.git "$LLAMA_CPP_DIR" || BUILD_OK=false

if [ "$BUILD_OK" = true ]; then
# Skip tests/examples we don't need (faster build)
Expand Down Expand Up @@ -474,16 +509,16 @@ rm -rf "$LLAMA_CPP_DIR"
CMAKE_GENERATOR_ARGS="-G Ninja"
fi

run_quiet "cmake llama.cpp" cmake $CMAKE_GENERATOR_ARGS -S "$LLAMA_CPP_DIR" -B "$LLAMA_CPP_DIR/build" $CMAKE_ARGS || BUILD_OK=false
run_quiet_no_exit "cmake llama.cpp" cmake $CMAKE_GENERATOR_ARGS -S "$LLAMA_CPP_DIR" -B "$LLAMA_CPP_DIR/build" $CMAKE_ARGS || BUILD_OK=false
fi

if [ "$BUILD_OK" = true ]; then
run_quiet "build llama-server" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-server -j"$NCPU" || BUILD_OK=false
run_quiet_no_exit "build llama-server" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-server -j"$NCPU" || BUILD_OK=false
fi

# Also build llama-quantize (needed by unsloth-zoo's GGUF export pipeline)
if [ "$BUILD_OK" = true ]; then
run_quiet "build llama-quantize" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-quantize -j"$NCPU" || true
run_quiet_no_exit "build llama-quantize" cmake --build "$LLAMA_CPP_DIR/build" --config Release --target llama-quantize -j"$NCPU" || true
# Symlink to llama.cpp root — check_llama_cpp() looks for the binary there
QUANTIZE_BIN="$LLAMA_CPP_DIR/build/bin/llama-quantize"
if [ -f "$QUANTIZE_BIN" ]; then
Expand Down