Last active
July 16, 2025 15:30
-
-
Save dotysan/fdbfc77b924a08ceab7197d010280dac to your computer and use it in GitHub Desktop.
Simple non-POSIX UV installer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env bash | |
# | |
# Simple non-POSIX UV installer. | |
# | |
# You can download and run it. Or just run it off the web with: | |
# $ curl --location https://gist.github.com/dotysan/fdbfc77b924a08ceab7197d010280dac/raw/uv-install.sh |bash | |
# | |
# If you want to see what it does, before running: | |
# $ export DEBUG=yes | |
# | |
# optional | |
#DEBUG=yep | |
main() { | |
get_uv | |
} | |
#======================================================================= | |
get_uv() { | |
# Most modern linux distros will add ~/.local/bin to the $PATH on | |
# next login--as soon as it exists. But if we're creating it here, | |
# inject the $PATH now, so we don't need to log out & back in! | |
if ! grep -qE "(^|:)$HOME/.local/bin:" <<<"$PATH" | |
then export PATH="$HOME/.local/bin:$PATH" | |
fi | |
# and some very minimalist containers don't add /usr/local/bin | |
if ! grep -qE "(^|:)/usr/local/bin:" <<<"$PATH" | |
then export PATH="/usr/local/bin:$PATH" | |
fi | |
if ! hash uv 2>/dev/null || ! hash uvx 2>/dev/null | |
then | |
chk_deps | |
local inst_dir | |
if [[ $EUID -eq 0 ]] | |
then inst_dir=/usr/local/bin | |
# if we are root install globally otherwise personal | |
else inst_dir="$HOME/.local/bin" | |
fi | |
get_github_latest astral-sh uv "$inst_dir" | |
create_uv_receipt "$inst_dir" | |
fi | |
uv self update | |
} | |
#----------------------------------------------------------------------- | |
chk_deps() { | |
chkadep curl | |
chkadep uname | |
} | |
chkadep() { | |
if ! hash "$@" | |
then | |
echo "ERROR: Must install $@ to use this script." | |
return 1 | |
fi >&2 | |
} | |
get_github_latest() { | |
local owner="$1" | |
local repo="$2" | |
local inst_dir="${3:-.}" | |
local os=$(uname --operating-system) | |
if ! [[ ${os,,} =~ linux ]] | |
then | |
echo "ERROR: OS:$os This script only understands linux." | |
return 1 | |
fi >&2 | |
VERS=$(github_latest_tag "$owner" "$repo") | |
VERS="${VERS#https://github.com/$owner/$repo/releases/tag/}" | |
local machine=$(uname --machine) | |
local clib | |
if ldd --version 2>&1 |grep -q 'musl' | |
then clib=musl | |
else clib=gnu | |
fi | |
local asset="https://github.com/$owner/$repo/releases/download/$VERS/$repo-$machine-unknown-linux-$clib.tar.gz" | |
mkdir --parents "$inst_dir" | |
curl --silent --show-error --fail --location "$asset" |\ | |
tar --extract --gzip --no-same-owner \ | |
--directory="$inst_dir" --strip-components=1 | |
} | |
github_latest_tag() { | |
local owner="$1" | |
local repo="$2" | |
curl --fail --silent --show-error --location --head \ | |
--output /dev/null --write-out '%{url_effective}' \ | |
"https://github.com/$owner/$repo/releases/latest" | |
} | |
create_uv_receipt() { | |
local inst_dir="${1:-.}" | |
mkdir --parents "$HOME/.config/uv" | |
cat >"$HOME/.config/uv/uv-receipt.json" <<-EOF | |
{ | |
"binaries": ["uv", "uvx"], | |
"install_layout": "flat", | |
"install_prefix": "$inst_dir", | |
"modify_path": false, | |
"provider": { | |
"source": "dotysan", | |
"version": "0.1.0" | |
}, | |
"source": { | |
"app_name": "uv", | |
"name": "uv", | |
"owner": "astral-sh", | |
"release_type": "github" | |
}, | |
"version": "$VERS" | |
} | |
EOF | |
} | |
# ====================================================================== | |
# shellcheck disable=SC2128 | |
if [ -z "$BASH_VERSINFO" ] || [[ ${BASH_VERSINFO[0]} -lt 4 ]] | |
then | |
echo "ERROR: Only tested on Bourne-Again SHell v4/v5." | |
exit 1 | |
fi >&2 | |
# poor man's __main__ | |
return 2>/dev/null ||: | |
if [ "$(type -t "$1")" = "function" ] | |
then | |
func="$1" | |
shift 1 | |
"$func" "$@" | |
exit 0 | |
fi | |
set -o errtrace | |
set -o errexit | |
set -o nounset | |
# set -o pipefail # may break ldd --version 2>&1 |grep -q 'musl' | |
if [[ "${DEBUG:-}" ]] | |
then | |
PS4func() { | |
local normal="\033[0m" | |
#local red="\033[0;31m" | |
#local green="\033[0;32m" | |
local yellow="\033[0;33m" | |
#local blue="\033[0;34m" | |
#local magenta="\033[0;35m" | |
local cyan="\033[0;36m" | |
local i caller_idx=-1 | |
# walk the call stack to see if we are sourced and from where | |
if [[ ${#BASH_SOURCE[@]} -gt 2 ]] | |
then | |
for (( i=2; i<${#BASH_SOURCE[@]}; i++ )) | |
do | |
if [[ ${BASH_SOURCE[1]} != "${BASH_SOURCE[i]}" ]] | |
then | |
caller_idx=$i | |
break | |
fi | |
done | |
fi | |
local src func='' | |
local caller deep | |
if [[ $caller_idx -ne -1 ]] | |
then # we're in a sourced script | |
if [[ ${BASH_SOURCE[0]} == main ]] | |
then caller='<stdin>' | |
else caller="${BASH_SOURCE[0]##*/}" | |
fi | |
src="$caller:${BASH_LINENO[caller_idx-1]}${cyan}source->$yellow${BASH_SOURCE[1]##*/}:${BASH_LINENO[0]}" | |
if [[ ${FUNCNAME[-2]} == main && ${FUNCNAME[-1]} == main ]] | |
then deep=$((${#FUNCNAME[@]}-5)) | |
else deep=$((${#FUNCNAME[@]}-4)) | |
fi | |
elif [[ ${BASH_SOURCE[0]} == main ]] | |
then # reading from standard input instead of a script file | |
src="<stdin>:${BASH_LINENO[0]}" | |
deep=$((${#FUNCNAME[@]}-1)) | |
else # a normal script file | |
src="${BASH_SOURCE[0]##*/}:${BASH_LINENO[0]}" | |
deep=$((${#FUNCNAME[@]}-2)) | |
fi | |
if (( deep >= 1 )) | |
then | |
if [[ ${FUNCNAME[1]} != source ]] | |
then func+="${FUNCNAME[1]}()" | |
fi | |
fi | |
# local lines="${BASH_LINENO[@]}" | |
# local sources="${BASH_SOURCE[@]##*/}" | |
# local funcs="${FUNCNAME[@]}" | |
# printf "${red}sources(%d): %s\nlines(%d): %s | funcs(%d): %s|%b" \ | |
# "${#BASH_SOURCE[@]}" "$sources" \ | |
# "${#BASH_LINENO[@]}" "$lines" \ | |
# "${#FUNCNAME[@]}" "$funcs" \ | |
# "$yellow$src$cyan$func$normal " | |
# run the above to debug your stacks | |
printf "%b" "$yellow$src$cyan$func$normal " | |
} | |
PS4='\r$(PS4func)' | |
set -o xtrace | |
fi | |
main "$@" | |
exit 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment