Skip to content

Instantly share code, notes, and snippets.

@denisalevi
Last active April 5, 2026 23:07
Show Gist options
  • Select an option

  • Save denisalevi/b79ee60b915042e061dbd88bb127b72f to your computer and use it in GitHub Desktop.

Select an option

Save denisalevi/b79ee60b915042e061dbd88bb127b72f to your computer and use it in GitHub Desktop.
Script to download and install the latest VSCode and/or Cursor CLI on a remote server (using curl)
#!/usr/bin/env bash
# -----------------------------------------------------------------------
# update-vscode-cli: Download and install VSCode and/or Cursor CLI tools
# -----------------------------------------------------------------------
#
# This script downloads and installs the latest versions of CLI tools
# for VSCode on Linux systems.
#
# Usage:
# update-vscode-cli # Install/update all supported tools
# update-vscode-cli code # Install/update only VSCode CLI
# update-vscode-cli cursor # Install/update only Cursor CLI
#
# Features:
# - Installs tools in $HOME/.local/bin
# - Maintains versioned binaries (code-1.2.3, cursor-4.5.6)
# - Creates symbolic links to the latest versions (code, cursor)
# - Preserves previous versions
# - Automatically detects installed versions
# -----------------------------------------------------------------------
# Exit on error, undefined variables, and propagate pipe failures
set -euo pipefail
# Configuration
TEMP_DIR="/tmp/cli-update-$USER"
INSTALL_DIR="$HOME/.local/bin"
# Optional override for download tool selection. Set to 'curl' or 'wget'.
# Can be set via environment, e.g. `DOWNLOAD_TOOL=wget ./update-vscode-cli`.
: "${DOWNLOAD_TOOL:=}"
# Define application configurations with download details
declare -A APPS=(
["code"]="https://code.visualstudio.com/sha/download?build=stable&os=cli-alpine-x64"
["cursor"]="https://api2.cursor.sh/updates/download-latest?os=cli-alpine-x64"
)
# Define output filenames for downloads
declare -A OUTPUT_FILES=(
["code"]="vscode_cli.tar.gz"
["cursor"]="cursor_cli.tar.gz"
)
# Determine which downloader to use (curl preferred, fallback to wget), with optional override
determine_downloader() {
if [ -n "${DOWNLOAD_TOOL:-}" ]; then
case "$DOWNLOAD_TOOL" in
curl|wget)
DOWNLOADER="$DOWNLOAD_TOOL"
;;
*)
echo "Error: DOWNLOAD_TOOL must be 'curl' or 'wget' if set" >&2
exit 1
;;
esac
else
if command -v curl >/dev/null 2>&1; then
DOWNLOADER="curl"
elif command -v wget >/dev/null 2>&1; then
DOWNLOADER="wget"
else
echo "Error: neither 'curl' nor 'wget' is installed. Please install one." >&2
exit 1
fi
fi
echo "Using downloader: $DOWNLOADER"
}
# Download helper using the chosen tool
download_file() {
local url=$1
local output_file=$2
case "$DOWNLOADER" in
curl)
curl --fail --location --show-error --output "$output_file" "$url"
;;
wget)
wget --quiet --output-document="$output_file" "$url"
;;
*)
echo "Internal error: unknown downloader '$DOWNLOADER'" >&2
exit 1
;;
esac
}
# Function to install a specific CLI
install_cli() {
local BINARY_NAME=$1
local DOWNLOAD_URL=$2
local OUTPUT_FILE="${OUTPUT_FILES[$BINARY_NAME]}"
echo "===== Installing $BINARY_NAME CLI ====="
# Create clean temporary directory for this app
local APP_TEMP_DIR="$TEMP_DIR/$BINARY_NAME"
echo "Setting up temporary directory at $APP_TEMP_DIR"
rm -rf "$APP_TEMP_DIR"
mkdir -p "$APP_TEMP_DIR"
cd "$APP_TEMP_DIR"
# Download and extract
echo "Downloading $BINARY_NAME CLI for Linux x64..."
download_file "$DOWNLOAD_URL" "$OUTPUT_FILE"
echo "Extracting $BINARY_NAME binary from $OUTPUT_FILE..."
tar -xzf "$OUTPUT_FILE"
# Ensure the binary exists
if [ ! -f "$BINARY_NAME" ]; then
echo "Error: $BINARY_NAME binary not found after extraction"
# List directory contents to help diagnose the issue
echo "Directory contents:"
ls -la
return 1
fi
# Get version information
local VERSION=$("./$BINARY_NAME" --version | awk 'NR==1 {print $2}')
local VERSIONED_PATH="$INSTALL_DIR/$BINARY_NAME-$VERSION"
local SYMLINK_PATH="$INSTALL_DIR/$BINARY_NAME"
echo "Detected version: $VERSION"
# Ensure installation directory exists
mkdir -p "$INSTALL_DIR"
# Remove existing symlink if it exists
if [ -L "$SYMLINK_PATH" ]; then
echo "Removing existing symlink at $SYMLINK_PATH"
rm "$SYMLINK_PATH"
elif [ -e "$SYMLINK_PATH" ]; then
echo "Warning: $SYMLINK_PATH exists but is not a symlink. Renaming to $SYMLINK_PATH.bak"
mv "$SYMLINK_PATH" "$SYMLINK_PATH.bak"
fi
# Install the binary
echo "Installing $BINARY_NAME CLI version $VERSION"
if [ -e "$VERSIONED_PATH" ]; then
echo "Version $VERSION already exists at $VERSIONED_PATH. Skipping copy."
else
echo "Copying binary to $VERSIONED_PATH"
cp -v "$BINARY_NAME" "$VERSIONED_PATH"
chmod +x "$VERSIONED_PATH"
fi
# Create symlink
echo "Creating new symbolic link at $SYMLINK_PATH"
ln -sv "$VERSIONED_PATH" "$SYMLINK_PATH"
echo "✅ $BINARY_NAME CLI version $VERSION is now installed in $SYMLINK_PATH!"
echo "If $SYMLINK_PATH is in your PATH, you can now use the '$BINARY_NAME'"
echo "command from anywhere."
echo
}
# Choose downloader before processing any arguments
determine_downloader
# Process command line arguments
if [ $# -eq 0 ]; then
# No arguments, install all apps
for app in "${!APPS[@]}"; do
install_cli "$app" "${APPS[$app]}"
done
else
# Install only specified apps
for app in "$@"; do
if [[ -v APPS[$app] ]]; then
install_cli "$app" "${APPS[$app]}"
else
echo "Error: Unknown application '$app'"
echo "Available options: ${!APPS[@]}"
exit 1
fi
done
fi
# Cleanup
echo "Cleaning up temporary files"
rm -rf "$TEMP_DIR"
# Success message
echo
echo "All requested CLI tools have been installed to $INSTALL_DIR"
echo "Previous versions (if any) are preserved in $INSTALL_DIR"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment