fix(backends): bootstrap uv when missing so install.sh can build venvs on minimal images (#10720)

Minimal base images such as the nvidia-l4t variants ship neither uv nor
pip. When a backend is installed onto a persistent /backends volume there,
`local-ai backends install` downloads the OCI layer fine but the follow-up
install.sh aborts at `uv venv` with "uv: command not found", leaving a
half-built backend with no in-container recovery path.

Add ensureUv(), called from ensureVenv() before any uv-based venv work.
When uv is absent (and not in USE_PIP mode) it bootstraps a private copy
into the backend's own directory (${EDIR}/uv-bin) using the same official
installer the base images use. The directory lives on the persistent
volume, so the download happens only once and survives image upgrades.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude:opus-4.8 [Claude Code]
This commit is contained in:
Ettore Di Giacinto
2026-07-17 15:05:34 +00:00
committed by localai-org-maint-bot
parent bc21f832aa
commit d57c1acc85

View File

@@ -333,6 +333,49 @@ _activateVenv() {
unset PYTHONHOME
}
# ensureUv makes sure the `uv` package manager is on PATH before we try to build
# or populate a venv with it.
#
# Minimal base images (notably the nvidia-l4t variants) ship neither uv nor pip.
# A backend installed onto a persistent /backends volume there can therefore
# never build its venv: `uv venv` aborts with "uv: command not found" and
# install.sh leaves a half-installed backend, with no way to recover from inside
# the container. See mudler/LocalAI#10720.
#
# When uv is missing (and we are not in USE_PIP mode) bootstrap a private copy
# into the backend directory itself, which lives on the same persistent volume,
# so it survives image upgrades and is only downloaded once.
function ensureUv() {
# pip mode never invokes uv.
if [ "x${USE_PIP}" == "xtrue" ]; then
return 0
fi
if command -v uv >/dev/null 2>&1; then
return 0
fi
local uv_dir="${EDIR}/uv-bin"
if [ -x "${uv_dir}/uv" ]; then
export PATH="${uv_dir}:${PATH}"
return 0
fi
echo "uv not found; bootstrapping a private copy into ${uv_dir}"
mkdir -p "${uv_dir}"
# The official installer picks the right binary for the current platform and
# honors UV_INSTALL_DIR. This is the same mechanism the base images use to
# install uv (see backend/Dockerfile.python). INSTALLER_NO_MODIFY_PATH keeps
# it from editing shell profiles we don't control.
if command -v curl >/dev/null 2>&1; then
curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="${uv_dir}" INSTALLER_NO_MODIFY_PATH=1 sh
elif command -v wget >/dev/null 2>&1; then
wget -qO- https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="${uv_dir}" INSTALLER_NO_MODIFY_PATH=1 sh
else
echo "ERROR: uv is missing and neither curl nor wget is available to bootstrap it" >&2
return 1
fi
export PATH="${uv_dir}:${PATH}"
command -v uv >/dev/null 2>&1
}
# ensureVenv makes sure that the venv for the backend both exists, and is activated.
#
# This function is idempotent, so you can call it as many times as you want and it will
@@ -340,6 +383,9 @@ _activateVenv() {
function ensureVenv() {
local interpreter=""
# Make sure uv is available before any uv-based venv work below.
ensureUv
if [ "x${PORTABLE_PYTHON}" == "xtrue" ] || [ -e "$(_portable_python)" ]; then
echo "Using portable Python"
ensurePortablePython