-
-
Save zudsniper/0117371b931d0026b03710d73c2ececf to your computer and use it in GitHub Desktop.
install_cursor.sh -- auto-download latest and version compare
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
#!/bin/bash | |
# Exit on error | |
set -e | |
# --- Configuration --- | |
# (Configuration remains the same) | |
INSTALL_DIR="/opt" | |
SYMLINK_NAME="cursor" | |
SYMLINK_PATH="${INSTALL_DIR}/${SYMLINK_NAME}" | |
ICON_NAME="cursor.png" | |
ICON_PATH="${INSTALL_DIR}/${ICON_NAME}" | |
ICON_URL="https://raw.githubusercontent.com/getcursor/cursor/main/apps/desktop/resources/images/icon.png" | |
DESKTOP_ENTRY_NAME="cursor.desktop" | |
DESKTOP_ENTRY_PATH="/usr/share/applications/${DESKTOP_ENTRY_NAME}" | |
API_URL="https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable" | |
TEMP_DIR="/tmp" | |
# --- End Configuration --- | |
# Function to check if a command exists | |
command_exists() { | |
command -v "$1" &> /dev/null | |
} | |
# Function to compare versions (requires sort supporting -V) | |
# (version_ge function remains the same) | |
version_ge() { | |
if ! echo -e "1.10\n1.2" | sort -V >/dev/null 2>&1; then | |
echo "Warning: Your 'sort' command does not support -V (version sort)." >&2 | |
echo "Version comparison might be inaccurate. Comparing lexicographically." >&2 | |
[ "$1" = "$2" ] || [ "$1" \> "$2" ] | |
else | |
[ "$1" = "$(printf '%s\n%s' "$1" "$2" | sort -V | tail -n1)" ] | |
fi | |
} | |
# Function to install required packages | |
install_packages() { | |
local missing_packages=() | |
# Loop through packages passed as arguments (e.g., curl, jq) | |
for pkg in "$@"; do | |
if ! command_exists "$pkg"; then | |
# If the COMMAND doesn't exist, assume the PACKAGE is missing | |
missing_packages+=("$pkg") | |
fi | |
done | |
if [ ${#missing_packages[@]} -gt 0 ]; then | |
echo "The following required packages appear missing: ${missing_packages[*]}" | |
echo "Attempting to install them..." | |
local manager_cmd="" | |
local update_cmd="" | |
local install_cmd="" | |
if command_exists apt-get; then | |
manager_cmd="apt-get" | |
update_cmd="sudo apt-get update" | |
install_cmd="sudo apt-get install -y" | |
elif command_exists dnf; then | |
manager_cmd="dnf" | |
# dnf usually doesn't require a separate update command before install | |
update_cmd="echo 'Using dnf, update not usually required before install.'" # Placeholder | |
install_cmd="sudo dnf install -y" | |
elif command_exists yum; then | |
manager_cmd="yum" | |
update_cmd="echo 'Using yum, update not usually required before install.'" # Placeholder | |
install_cmd="sudo yum install -y" | |
elif command_exists pacman; then | |
manager_cmd="pacman" | |
update_cmd="sudo pacman -Syu --noconfirm" # Update and install together | |
install_cmd="sudo pacman -S --noconfirm" # Install command for pacman | |
else | |
echo "Error: Cannot determine package manager. Please install manually: ${missing_packages[*]}" | |
exit 1 | |
fi | |
# Run update command if defined and needed | |
if [[ "$manager_cmd" == "apt-get" || "$manager_cmd" == "pacman" ]]; then | |
$update_cmd | |
fi | |
# Run install command | |
$install_cmd "${missing_packages[@]}" || { | |
echo "Error: Package installation failed. Please install manually: ${missing_packages[*]}" | |
exit 1 | |
} | |
# Verify installation *after* attempting install | |
echo "Verifying installation..." | |
local still_missing=() | |
for pkg in "${missing_packages[@]}"; do | |
if ! command_exists "$pkg"; then | |
# It's possible the package name doesn't match the command name exactly | |
# But for curl and jq, it usually does. | |
echo "Warning: Command '$pkg' still not found after installation attempt." >&2 | |
echo "The script will continue, but might fail later if '$pkg' is truly needed." >&2 | |
# Decide if you want to exit here or just warn. Warning is often okay. | |
# still_missing+=("$pkg") # Optionally track and exit if any are still missing | |
fi | |
done | |
# if [ ${#still_missing[@]} -gt 0 ]; then | |
# echo "Error: Failed to install or find commands for: ${still_missing[*]}" | |
# exit 1 | |
# fi | |
echo "Required packages check complete." | |
else | |
echo "Dependencies satisfied." | |
fi | |
} | |
# Function to extract version from filename or URL path | |
# (extract_version function remains the same) | |
extract_version() { | |
local input_string="$1" | |
local version=$(echo "$input_string" | grep -oP '/\K([0-9]+\.[0-9]+\.[0-9]+)(?=/)' || true) | |
if [ -z "$version" ]; then | |
version=$(echo "$input_string" | grep -oP '(?<=-)([0-9]+\.[0-9]+\.[0-9]+)(?=-[a-zA-Z0-9]*)' || true) | |
fi | |
if [ -z "$version" ]; then | |
version=$(echo "$input_string" | grep -oP '(?<=-)([0-9]+\.[0-9]+\.[0-9]+)(?=\.AppImage)' || true) | |
fi | |
echo "$version" | |
} | |
installCursor() { | |
local CUSTOM_APPIMAGE_PATH="$1" | |
local TEMP_APPIMAGE="" | |
local REMOTE_VERSION="" | |
local REMOTE_APPIMAGE_URL="" | |
local REMOTE_FILENAME="" | |
local LOCAL_VERSION="" | |
local CURRENT_APPIMAGE_TARGET="" | |
echo "Starting Cursor AI IDE installation/update script..." | |
# 1. Check dependencies (ONLY check for curl and jq now) | |
echo "Checking dependencies (curl, jq)..." | |
install_packages curl jq # Removed coreutils from here | |
# (Rest of the installCursor function remains the same) | |
# 2. Handle custom AppImage path | |
if [ -n "$CUSTOM_APPIMAGE_PATH" ]; then | |
if [ -f "$CUSTOM_APPIMAGE_PATH" ] && [ -r "$CUSTOM_APPIMAGE_PATH" ]; then | |
echo "Using provided AppImage file: $CUSTOM_APPIMAGE_PATH" | |
REMOTE_FILENAME=$(basename "$CUSTOM_APPIMAGE_PATH") | |
TEMP_APPIMAGE="$CUSTOM_APPIMAGE_PATH" | |
REMOTE_VERSION=$(extract_version "$REMOTE_FILENAME") | |
if [ -z "$REMOTE_VERSION" ]; then | |
echo "Warning: Could not extract version from custom AppImage filename '$REMOTE_FILENAME'." | |
echo "Proceeding with installation without version check." | |
LOCAL_VERSION="0.0.0" # Force update if local exists | |
else | |
echo "Extracted version $REMOTE_VERSION from custom AppImage filename." | |
fi | |
else | |
echo "Error: The specified AppImage file does not exist or is not readable: $CUSTOM_APPIMAGE_PATH" | |
exit 1 | |
fi | |
else | |
# 3. Fetch latest version info from API | |
echo "Fetching latest version information from Cursor API..." | |
local api_response | |
api_response=$(curl -fsSL "$API_URL") || { echo "Error: Failed to fetch from API: $API_URL"; exit 1; } | |
REMOTE_APPIMAGE_URL=$(echo "$api_response" | jq -r '.downloadUrl') | |
if [ -z "$REMOTE_APPIMAGE_URL" ] || [ "$REMOTE_APPIMAGE_URL" = "null" ]; then | |
echo "Error: Could not parse download URL from API response." | |
echo "Response: $api_response" | |
exit 1 | |
fi | |
REMOTE_VERSION=$(extract_version "$REMOTE_APPIMAGE_URL") | |
if [ -z "$REMOTE_VERSION" ]; then | |
REMOTE_FILENAME=$(basename "$REMOTE_APPIMAGE_URL") | |
REMOTE_VERSION=$(extract_version "$REMOTE_FILENAME") | |
fi | |
if [ -z "$REMOTE_VERSION" ]; then | |
echo "Error: Could not extract version information from URL or filename: $REMOTE_APPIMAGE_URL" | |
exit 1 | |
fi | |
REMOTE_FILENAME=$(basename "$REMOTE_APPIMAGE_URL") | |
echo "Latest version available: $REMOTE_VERSION" | |
fi | |
# 4. Check currently installed version (if exists) | |
echo "Checking for existing installation..." | |
if [ -L "$SYMLINK_PATH" ]; then | |
CURRENT_APPIMAGE_TARGET=$(readlink -f "$SYMLINK_PATH") | |
if [ -f "$CURRENT_APPIMAGE_TARGET" ]; then | |
LOCAL_VERSION=$(extract_version "$CURRENT_APPIMAGE_TARGET") | |
if [ -n "$LOCAL_VERSION" ]; then | |
echo "Currently installed version: $LOCAL_VERSION (at $CURRENT_APPIMAGE_TARGET)" | |
else | |
echo "Warning: Could not determine version of installed AppImage: $CURRENT_APPIMAGE_TARGET" | |
LOCAL_VERSION="0.0.0" | |
fi | |
else | |
echo "Symlink $SYMLINK_PATH points to a non-existent file: $CURRENT_APPIMAGE_TARGET" | |
echo "Treating as a fresh install." | |
LOCAL_VERSION="" | |
CURRENT_APPIMAGE_TARGET="" | |
fi | |
elif [ -f "$SYMLINK_PATH" ] && ! [ -L "$SYMLINK_PATH" ]; then # Check if it's a file but NOT a symlink | |
echo "Warning: Found a regular file instead of a symlink at $SYMLINK_PATH." | |
echo "Attempting to determine version, but installation structure is non-standard." | |
CURRENT_APPIMAGE_TARGET="$SYMLINK_PATH" | |
LOCAL_VERSION=$(extract_version "$CURRENT_APPIMAGE_TARGET") | |
if [ -n "$LOCAL_VERSION" ]; then | |
echo "Found version: $LOCAL_VERSION" | |
else | |
echo "Warning: Could not determine version of installed file: $CURRENT_APPIMAGE_TARGET" | |
LOCAL_VERSION="0.0.0" | |
fi | |
else | |
echo "No existing installation found at $SYMLINK_PATH." | |
LOCAL_VERSION="" | |
fi | |
# 5. Compare versions and decide whether to install/update | |
if [ -n "$LOCAL_VERSION" ] && [ -n "$REMOTE_VERSION" ]; then | |
if version_ge "$LOCAL_VERSION" "$REMOTE_VERSION"; then | |
echo "Current version ($LOCAL_VERSION) is the same or newer than the available version ($REMOTE_VERSION)." | |
echo "Skipping installation." | |
# Optionally ensure desktop entry/alias still point to SYMLINK_PATH? Usually not needed. | |
exit 0 | |
else | |
echo "Newer version ($REMOTE_VERSION) available. Proceeding with update from $LOCAL_VERSION." | |
fi | |
elif [ -n "$CUSTOM_APPIMAGE_PATH" ]; then | |
echo "Installing provided custom AppImage..." | |
if [ -n "$LOCAL_VERSION" ]; then | |
echo "Replacing version $LOCAL_VERSION with custom AppImage." | |
fi | |
else | |
echo "Performing fresh installation of version $REMOTE_VERSION." | |
fi | |
# --- Installation/Update Steps --- | |
sudo mkdir -p "$INSTALL_DIR" | |
# 6. Download AppImage (if not using custom path) | |
if [ -z "$CUSTOM_APPIMAGE_PATH" ]; then | |
TEMP_APPIMAGE="${TEMP_DIR}/${REMOTE_FILENAME}" | |
echo "Downloading Cursor AppImage ($REMOTE_VERSION) to $TEMP_APPIMAGE..." | |
curl --progress-bar -L "$REMOTE_APPIMAGE_URL" -o "$TEMP_APPIMAGE" || { echo "Error: Failed to download AppImage."; rm -f "$TEMP_APPIMAGE"; exit 1; } | |
fi | |
# 7. Download Icon | |
echo "Downloading Cursor icon..." | |
curl -fsSL "$ICON_URL" -o "${TEMP_DIR}/${ICON_NAME}" || echo "Warning: Failed to download icon. Skipping icon installation." | |
# 8. Install files | |
echo "Installing Cursor files..." | |
local NEW_APPIMAGE_PATH="${INSTALL_DIR}/${REMOTE_FILENAME}" | |
# Ensure AppImage is executable before moving (if downloaded) | |
if [ -z "$CUSTOM_APPIMAGE_PATH" ]; then | |
chmod +x "$TEMP_APPIMAGE" | |
fi | |
# Move AppImage to final destination | |
# Use cp then rm for sudo permissions across filesystems /tmp might be different | |
sudo cp "$TEMP_APPIMAGE" "$NEW_APPIMAGE_PATH" || { echo "Error: Failed to copy AppImage to $NEW_APPIMAGE_PATH"; exit 1; } | |
sudo chmod +x "$NEW_APPIMAGE_PATH" # Ensure executable after copy | |
# If copy succeeded and we downloaded it (not custom path), remove temp file | |
if [ -z "$CUSTOM_APPIMAGE_PATH" ]; then | |
rm -f "$TEMP_APPIMAGE" | |
fi | |
# Move Icon | |
if [ -f "${TEMP_DIR}/${ICON_NAME}" ]; then | |
sudo mv "${TEMP_DIR}/${ICON_NAME}" "$ICON_PATH" || echo "Warning: Failed to move icon to $ICON_PATH" | |
fi | |
# 9. Create/Update Symlink | |
echo "Creating/Updating symlink: $SYMLINK_PATH -> $NEW_APPIMAGE_PATH" | |
sudo ln -sfn "$NEW_APPIMAGE_PATH" "$SYMLINK_PATH" || { echo "Error: Failed to create symlink."; exit 1; } | |
# 10. Create .desktop entry | |
echo "Creating/Updating .desktop entry..." | |
sudo mkdir -p "$(dirname "$DESKTOP_ENTRY_PATH")" | |
sudo bash -c "cat > $DESKTOP_ENTRY_PATH" <<EOL | |
[Desktop Entry] | |
Name=Cursor AI IDE | |
Comment=AI First Code Editor based on VSCode | |
Exec=$SYMLINK_PATH --no-sandbox %U | |
Icon=$ICON_PATH | |
Type=Application | |
Terminal=false | |
Categories=Development;IDE;TextEditor; | |
Keywords=vscode;ai;ide;editor;development; | |
StartupWMClass=cursor | |
MimeType=text/plain;inode/directory;application/x-cursor-workspace; | |
EOL | |
if command_exists update-desktop-database; then | |
echo "Updating desktop database..." | |
sudo update-desktop-database "$(dirname "$DESKTOP_ENTRY_PATH")" &> /dev/null || echo "Warning: Failed to update desktop database." | |
fi | |
# 11. Add alias (if needed) | |
local SHELL_NAME=$(basename "$SHELL") | |
local RC_FILE="" | |
case "$SHELL_NAME" in | |
bash) RC_FILE="$HOME/.bashrc" ;; | |
zsh) RC_FILE="$HOME/.zshrc" ;; | |
fish) RC_FILE="$HOME/.config/fish/config.fish" ;; | |
*) echo "Unsupported shell: $SHELL_NAME. Please add alias manually if desired." ;; | |
esac | |
if [ -n "$RC_FILE" ] && [ -f "$RC_FILE" ]; then | |
echo "Checking alias in $RC_FILE..." | |
local alias_exists=false | |
if grep -q "# Cursor alias (function)" "$RC_FILE"; then # Look for our comment marker | |
if [ "$SHELL_NAME" = "fish" ]; then | |
if grep -Eq "^\s*function cursor\s*$" "$RC_FILE"; then alias_exists=true; fi | |
else | |
if grep -Eq "^\s*function cursor\s*\(\s*\)\s*\{" "$RC_FILE"; then alias_exists=true; fi | |
fi | |
fi | |
if ! $alias_exists; then | |
echo "Adding cursor alias/function to $RC_FILE..." | |
if [ "$SHELL_NAME" = "fish" ]; then | |
# Fish shell syntax | |
printf '\n# Cursor alias (function)\nfunction cursor\n nohup %s --no-sandbox $argv > /dev/null 2>&1 & disown\nend\n' "$SYMLINK_PATH" >> "$RC_FILE" | |
else | |
# Bash/Zsh syntax | |
printf '\n# Cursor alias (function)\nfunction cursor() {\n nohup "%s" --no-sandbox "$@" > /dev/null 2>&1 & disown\n}\n' "$SYMLINK_PATH" >> "$RC_FILE" | |
fi | |
echo "Alias added. Please restart your terminal or run 'source $RC_FILE'" | |
else | |
echo "Cursor alias/function already seems to exist in $RC_FILE." | |
# Here you could add logic to check if the path inside the existing alias is correct ($SYMLINK_PATH), | |
# but since we use a symlink, it should always point to the latest version anyway. | |
fi | |
fi | |
# 12. Cleanup old AppImage | |
echo "Cleaning up..." | |
if [ -n "$CURRENT_APPIMAGE_TARGET" ] && [ "$CURRENT_APPIMAGE_TARGET" != "$NEW_APPIMAGE_PATH" ] && [ -f "$CURRENT_APPIMAGE_TARGET" ]; then | |
# Make sure we are not deleting the file we just installed or the symlink itself | |
if [[ "$(readlink -f "$CURRENT_APPIMAGE_TARGET")" != "$(readlink -f "$NEW_APPIMAGE_PATH")" ]] && \ | |
[[ "$(readlink -f "$CURRENT_APPIMAGE_TARGET")" != "$(readlink -f "$SYMLINK_PATH")" ]]; then | |
echo "Removing old AppImage: $CURRENT_APPIMAGE_TARGET" | |
sudo rm -f "$CURRENT_APPIMAGE_TARGET" || echo "Warning: Failed to remove old AppImage." | |
fi | |
fi | |
echo "------------------------------------------------------------------" | |
echo "Cursor AI IDE installation/update complete!" | |
echo "Version: $REMOTE_VERSION" | |
echo "Installed to: $NEW_APPIMAGE_PATH" | |
echo "Executable link: $SYMLINK_PATH" | |
echo "Icon: $ICON_PATH" | |
echo "Desktop Entry: $DESKTOP_ENTRY_PATH" | |
echo "------------------------------------------------------------------" | |
if [ -n "$RC_FILE" ] && grep -q "# Cursor alias (function)" "$RC_FILE"; then | |
echo "NOTE: If your shell was already open, run 'source $RC_FILE' or restart it to use the 'cursor' command." | |
fi | |
echo "You should find 'Cursor AI IDE' in your application menu (might take a moment to appear)." | |
} | |
# Run the installation function | |
installCursor "$1" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I updated the script to get the most recent one in the base script. Please take a look if you want to.