Skip to content

Instantly share code, notes, and snippets.

@FalconNL93
Last active July 28, 2025 12:22
Show Gist options
  • Save FalconNL93/7b243e2daeac66c8edc9755114a94b76 to your computer and use it in GitHub Desktop.
Save FalconNL93/7b243e2daeac66c8edc9755114a94b76 to your computer and use it in GitHub Desktop.
#!/bin/bash
set -e
DOTNET_DIR="$HOME/.dotnet"
INSTALL_SCRIPT="$HOME/dotnet-install.sh"
DOTNET_RELEASES_URL="https://dotnetcli.blob.core.windows.net/dotnet/release-metadata/releases-index.json"
DOTNET_INSTALL_SH_URL="https://dot.net/v1/dotnet-install.sh"
DEPENDENCIES_MARKER="$HOME/.dotnet_deps_installed"
REMOVE_OLD_MINOR_VERSIONS=false # Set to true to enable removal of older minor versions
# Dependency lists per distro
DEPS_DEBIAN=(libc6 libgcc1 libgssapi-krb5-2 libicu70 libssl3 libstdc++6 zlib1g libcurl4 libkrb5-3 libunwind8)
DEPS_FEDORA=(glibc libgcc krb5-libs libicu openssl libstdc++ zlib libcurl libunwind)
DEPS_ARCH=(glibc gcc krb5 icu openssl libstdc++ zlib curl libunwind)
# Shell RC files to update
SHELL_RC_FILES=("$HOME/.bashrc" "$HOME/.zshrc")
# Dotnet tools to manage
DOTNET_TOOLS=(dotnet-ef dotnet-outdated-tool)
# Ensure required tools (curl, jq) are installed
ensure_base_tools() {
local missing=()
for tool in curl jq; do
command -v "$tool" &>/dev/null || missing+=("$tool")
done
if [ ${#missing[@]} -eq 0 ]; then return; fi
echo "Installing missing tools: ${missing[*]}"
if [ -f /etc/os-release ]; then
. /etc/os-release
if [[ "$ID" =~ ^(ubuntu|debian)$ || "$ID_LIKE" == *"debian"* || "$ID_LIKE" == *"ubuntu"* ]]; then
sudo apt update && sudo apt install -y "${missing[@]}"
elif [[ "$ID" =~ ^(fedora|rhel|centos)$ || "$ID_LIKE" == *"rhel"* || "$ID_LIKE" == *"fedora"* ]]; then
sudo dnf install -y "${missing[@]}"
elif [[ "$ID" == "arch" || "$ID_LIKE" == *"arch"* ]]; then
sudo pacman -Sy --noconfirm "${missing[@]}"
else
echo "Unsupported OS. Please install: ${missing[*]} manually."
exit 1
fi
else
echo "Unable to detect OS. Please install: ${missing[*]} manually."
exit 1
fi
}
# Ensure script is run with sudo/root
ensure_sudo() {
if [ "$EUID" -ne 0 ]; then
if ! command -v sudo &>/dev/null; then
echo "This script requires root or sudo privileges. Please install sudo or run as root."
exit 1
fi
sudo -v || {
echo "Sudo authentication failed."
exit 1
}
fi
}
# Remove all older patch versions for all installed major.minor SDKs
clean_old_minor_versions() {
echo "Cleaning up older patch versions for all installed .NET SDKs..."
[ ! -d "$DOTNET_DIR/sdk" ] && echo "No SDKs found." && return 0
for major_minor in $(ls "$DOTNET_DIR/sdk" | awk -F. '{print $1"."$2}' | sort -u); do
mapfile -t versions_found < <(ls "$DOTNET_DIR/sdk" | grep "^$major_minor\." | sort -V)
if [ "${#versions_found[@]}" -gt 1 ]; then
for ((i = 0; i < ${#versions_found[@]} - 1; i++)); do
echo "Removing older .NET SDK version ${versions_found[$i]}..."
rm -rf "$DOTNET_DIR/sdk/${versions_found[$i]}"
done
fi
done
echo "Cleanup complete."
}
# Ensure the "dialog" package is installed
ensure_dialog_installed() {
command -v dialog &>/dev/null && return
echo "The 'dialog' package is not installed. Installing it now..."
if [ -f /etc/os-release ]; then
. /etc/os-release
if [[ "$ID" =~ ^(ubuntu|debian)$ || "$ID_LIKE" == *"debian"* || "$ID_LIKE" == *"ubuntu"* ]]; then
sudo apt update && sudo apt install -y dialog
elif [[ "$ID" =~ ^(fedora|rhel|centos)$ || "$ID_LIKE" == *"rhel"* || "$ID_LIKE" == *"fedora"* ]]; then
sudo dnf install -y dialog
elif [[ "$ID" == "arch" || "$ID_LIKE" == *"arch"* ]]; then
sudo pacman -Sy --noconfirm dialog
else
echo "Unsupported OS. Please install 'dialog' manually."
exit 1
fi
else
echo "Unable to detect OS. Please install 'dialog' manually."
exit 1
fi
}
# Install required dependencies
install_dependencies() {
[ -f "$DEPENDENCIES_MARKER" ] && return
echo "Installing required system dependencies..."
if [ -f /etc/os-release ]; then
. /etc/os-release
if [[ "$ID" =~ ^(ubuntu|debian)$ || "$ID_LIKE" == *"debian"* || "$ID_LIKE" == *"ubuntu"* ]]; then
sudo apt update && sudo apt install -y --no-install-recommends "${DEPS_DEBIAN[@]}"
elif [[ "$ID" =~ ^(fedora|rhel|centos)$ || "$ID_LIKE" == *"rhel"* || "$ID_LIKE" == *"fedora"* ]]; then
sudo dnf install -y "${DEPS_FEDORA[@]}"
elif [[ "$ID" == "arch" || "$ID_LIKE" == *"arch"* ]]; then
sudo pacman -Sy --noconfirm "${DEPS_ARCH[@]}"
else
echo "Unsupported OS. Please install dependencies manually."
exit 1
fi
else
echo "Unable to detect OS. Please install dependencies manually."
exit 1
fi
touch "$DEPENDENCIES_MARKER"
}
# Retrieve all major .NET SDK versions dynamically, including preview versions
get_dotnet_versions() {
curl -s "$DOTNET_RELEASES_URL" |
grep -oP '"channel-version":\s*"\K[0-9]+\.[0-9]+(\.[0-9]+(-[a-zA-Z0-9.]+)?)?' |
sort -uV
}
# Display the latest release per channel from releases-index.json
show_versions() {
echo "Fetching the latest .NET SDK releases per channel..."
curl -s "$DOTNET_RELEASES_URL" |
jq -r '."releases-index"[] | "\(.["channel-version"]): \(.["latest-release"])"'
exit 0
}
# Select .NET SDK versions to install
select_versions() {
if [ "$UPDATE_ALL" = true ]; then return; fi
echo "Select the .NET SDK versions you want to install:"
echo "(Use arrow keys and space to select, enter to confirm.)"
# Get already installed full versions
[ ! -d "$DOTNET_DIR/sdk" ] && INSTALLED="" || INSTALLED=$(ls "$DOTNET_DIR/sdk" | awk -F. '{print $1"."$2}' | sort -u)
# Build dialog checklist options: version "on"/"off"
OPTIONS=()
for version in "${VERSIONS[@]}"; do
if echo "$INSTALLED" | grep -qx "$version"; then
OPTIONS+=("$version" "$version" "on")
else
OPTIONS+=("$version" "$version" "off")
fi
done
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
dialog --clear --no-tags --checklist "Select .NET SDK versions to install:" 20 60 10 \
"${OPTIONS[@]}" 2>"$TMPFILE"
if [ $? -ne 0 ]; then
echo "No versions selected. Exiting."
exit 1
fi
SELECTED=$(cat "$TMPFILE" | tr -d '"')
if [ -z "$SELECTED" ]; then
echo "No versions selected. Exiting."
exit 1
fi
SELECTED_VERSIONS=($SELECTED)
# Uninstall deselected versions
if [ -d "$DOTNET_DIR/sdk" ]; then
for full_version in $(ls "$DOTNET_DIR/sdk"); do
major_minor=$(echo "$full_version" | awk -F. '{print $1"."$2}')
if ! echo "${SELECTED_VERSIONS[@]}" | grep -qw "$major_minor"; then
echo "Uninstalling .NET SDK version $full_version..."
SDK_DIR="$DOTNET_DIR/sdk/$full_version"
if [ -d "$SDK_DIR" ]; then
rm -rf "$SDK_DIR"
echo "Removed $SDK_DIR"
else
echo "SDK version $full_version not found in $DOTNET_DIR/sdk"
fi
fi
done
fi
trap - EXIT
}
# Download the dotnet-install.sh script
download_install_script() {
[ -f "$INSTALL_SCRIPT" ] && return
echo "Downloading dotnet-install.sh..."
curl -sSL "$DOTNET_INSTALL_SH_URL" -o "$INSTALL_SCRIPT"
chmod +x "$INSTALL_SCRIPT"
}
# Install or update a list of .NET SDK major.minor versions
update_dotnet_sdks_for_versions() {
local versions=("$@")
install_dependencies
download_install_script
update_path
mkdir -p "$DOTNET_DIR"
for version in "${versions[@]}"; do
echo "Installing or updating .NET SDK version $version..."
"$INSTALL_SCRIPT" --channel "$version" --install-dir "$DOTNET_DIR" || {
echo "Failed to install .NET SDK version $version. Skipping cleanup."
return 1
}
done
# Remove older patch versions if the switch is enabled and installation was successful
if [ "$REMOVE_OLD_MINOR_VERSIONS" = true ]; then
clean_old_minor_versions
fi
}
# Update PATH permanently (idempotent)
update_path() {
for rc in "${SHELL_RC_FILES[@]}"; do
# Remove any previous .NET SDK setup block
if grep -q '# BEGIN .NET SDK setup' "$rc"; then
# Remove lines between markers
sed -i '/# BEGIN .NET SDK setup/,/# END .NET SDK setup/d' "$rc"
fi
# Add the block only if not present
if ! grep -q '# BEGIN .NET SDK setup' "$rc"; then
{
echo -e '\n# BEGIN .NET SDK setup'
echo "export DOTNET_ROOT=\"$DOTNET_DIR\""
echo "export PATH=\"$DOTNET_DIR:\$HOME/.dotnet/tools:\$PATH\""
echo '# END .NET SDK setup'
} >>"$rc"
fi
done
# Ensure $HOME/.dotnet/tools is in PATH for the current session
case ":$PATH:" in
*:"$HOME/.dotnet/tools":*) ;;
*) export PATH="$HOME/.dotnet/tools:$PATH" ;;
esac
}
# Prompt user to enable or disable cleanup of older minor versions
configure_cleanup() {
echo "Do you want to enable cleanup of older minor .NET SDK versions?"
dialog --clear --yesno "Enable cleanup of older minor .NET SDK versions?" 10 50
if [ $? -eq 0 ]; then
REMOVE_OLD_MINOR_VERSIONS=true
echo "Cleanup of older minor versions is enabled."
else
REMOVE_OLD_MINOR_VERSIONS=false
echo "Cleanup of older minor versions is disabled."
fi
}
# Main menu dialog
main_menu() {
local CHOICE
CHOICE=$(dialog --clear --no-tags --menu "Select an action:" 15 60 5 \
1 "Install / Update dotnet SDK versions" \
2 "Install / Update dotnet tools" \
3>&1 1>&2 2>&3)
echo "$CHOICE"
}
# Install/Update dotnet tools dialog and logic
manage_dotnet_tools() {
# Show a please wait dialog while retrieving installed tools
TMPWAIT=$(mktemp)
dialog --infobox "Please wait... Retrieving installed dotnet tools." 7 50 &
WAITPID=$!
INSTALLED_TOOLS=$(dotnet tool list -g | awk 'NR>2 {print $1}')
kill $WAITPID 2>/dev/null; wait $WAITPID 2>/dev/null
rm -f "$TMPWAIT"
OPTIONS=()
for tool in "${DOTNET_TOOLS[@]}"; do
if echo "$INSTALLED_TOOLS" | grep -qx "$tool"; then
OPTIONS+=("$tool" "$tool" "on")
else
OPTIONS+=("$tool" "$tool" "off")
fi
done
TMPFILE=$(mktemp)
trap 'rm -f "$TMPFILE"' EXIT
dialog --clear --no-tags --checklist "Select dotnet tools to install globally:" 15 60 5 \
"${OPTIONS[@]}" 2>"$TMPFILE"
if [ $? -ne 0 ]; then
echo "No tools selected. Exiting."
exit 1
fi
SELECTED_TOOLS=$(cat "$TMPFILE" | tr -d '"')
# Convert to array
SELECTED_TOOLS_ARR=($SELECTED_TOOLS)
# Install selected tools
for tool in "${DOTNET_TOOLS[@]}"; do
if echo "${SELECTED_TOOLS_ARR[@]}" | grep -qw "$tool"; then
if ! echo "$INSTALLED_TOOLS" | grep -qx "$tool"; then
echo "Installing $tool..."
dotnet tool install -g "$tool"
else
echo "$tool is already installed. Updating..."
dotnet tool update -g "$tool"
fi
else
if echo "$INSTALLED_TOOLS" | grep -qx "$tool"; then
echo "Uninstalling $tool..."
dotnet tool uninstall -g "$tool"
fi
fi
done
echo -e "\nAll requested dotnet tools have been installed, updated, or removed as selected!"
trap - EXIT
}
# Main execution
ensure_base_tools
ensure_sudo
if [[ "$1" == "versions" ]]; then
show_versions
fi
if [[ "$1" == "clean" ]]; then
REMOVE_OLD_MINOR_VERSIONS=true
clean_old_minor_versions
exit 0
fi
if [[ "$1" == "update" ]]; then
# Unattended update: update all installed major.minor versions and dotnet tools
CLEAN=false
for arg in "$@"; do
if [[ "$arg" == "--clean" ]]; then
CLEAN=true
REMOVE_OLD_MINOR_VERSIONS=true
fi
done
if [ -d "$DOTNET_DIR/sdk" ]; then
mapfile -t INSTALLED_VERSIONS < <(ls "$DOTNET_DIR/sdk" | awk -F. '{print $1"."$2}' | sort -u)
if [ ${#INSTALLED_VERSIONS[@]} -eq 0 ]; then
echo "No .NET SDKs are currently installed. Exiting."
exit 1
fi
VERSIONS=($(get_dotnet_versions))
# Only update installed major.minor versions
TO_UPDATE=()
for v in "${INSTALLED_VERSIONS[@]}"; do
if printf '%s\n' "${VERSIONS[@]}" | grep -qx "$v"; then
TO_UPDATE+=("$v")
fi
done
if [ ${#TO_UPDATE[@]} -eq 0 ]; then
echo "No matching .NET SDK channels found for installed versions. Exiting."
exit 1
fi
update_dotnet_sdks_for_versions "${TO_UPDATE[@]}"
echo -e "\nAll installed .NET SDKs have been updated successfully!"
if [ "$CLEAN" = true ]; then
clean_old_minor_versions
fi
else
echo "No .NET SDKs are currently installed. Exiting."
exit 1
fi
# Update all installed dotnet tools
echo "Updating all globally installed dotnet tools..."
dotnet tool update --global --all || echo "Some tools may not have updated successfully."
echo -e "\nAll globally installed dotnet tools have been updated (where possible)!"
exit 0
fi
ensure_dialog_installed
CHOICE=$(main_menu)
case "$CHOICE" in
1)
# Show a please wait dialog while retrieving available SDK versions
TMPWAIT=$(mktemp)
VERSIONS=()
(
VERSIONS=($(get_dotnet_versions))
echo done > "$TMPWAIT"
) &
dialog --infobox "Please wait... Retrieving available .NET SDK versions." 7 50
while [ ! -s "$TMPWAIT" ]; do sleep 0.1; done
rm -f "$TMPWAIT"
# Re-fetch VERSIONS in main shell after background subshell
VERSIONS=($(get_dotnet_versions))
SELECTED_VERSIONS=("${VERSIONS[@]}")
select_versions
# Only prompt for cleanup if not running with --clean
if [[ "$*" != *--clean* ]]; then
configure_cleanup
fi
update_dotnet_sdks_for_versions "${SELECTED_VERSIONS[@]}"
echo -e "\nAll requested .NET SDKs have been installed or updated successfully!"
;;
2)
install_dependencies
download_install_script
update_path
manage_dotnet_tools
;;
*)
echo "No valid option selected. Exiting."
exit 1
;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment