Last active
June 3, 2025 00:45
-
-
Save jorcelinojunior/131748e12a039c02148a6f66e1fb312c to your computer and use it in GitHub Desktop.
Automate the integration of Cursor .AppImage on Linux: updates .desktop, manages icons, ensures the latest version, and configures AppArmor (Required for Ubuntu 24.04).
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 | |
set -euo pipefail | |
# Definition of colors for terminal output | |
readonly RED_COLOR="\e[31m" | |
readonly GREEN_COLOR="\e[32m" | |
readonly YELLOW_COLOR="\e[33m" | |
readonly BLUE_COLOR="\e[34m" | |
readonly MAGENTA_COLOR="\e[35m" | |
readonly RESET_COLOR="\e[0m" | |
# General settings | |
readonly DOWNLOAD_APP_DIR="$HOME/Downloads/.AppImage" | |
readonly ICON_DIR="$HOME/.local/share/icons" | |
readonly ICON_URL="https://www.cursor.com/brand/icon.svg" | |
readonly DESKTOP_FILE_PATH="$HOME/.local/share/applications/cursor.desktop" | |
readonly APPARMOR_PROFILE="/etc/apparmor.d/cursor-appimage" | |
readonly DOWNLOAD_URL="https://downloader.cursor.sh/linux/appImage/x64" | |
readonly TEMP_DIR="/tmp/AppImage" | |
readonly VERSION_CHECK_TIMEOUT=10 | |
cursor_appimage_path="" | |
cursor_icon_path="" | |
# Support function to print steps | |
print_step() { | |
echo -e "${YELLOW_COLOR}$1${RESET_COLOR}" | |
} | |
# Support function to print success | |
print_success() { | |
echo -e "${GREEN_COLOR}$1${RESET_COLOR}" | |
} | |
# Support function for error handling | |
error_exit() { | |
echo -e "${RED_COLOR}$1${RESET_COLOR}" >&2 | |
kill $$ | |
} | |
# Check necessary dependencies | |
check_dependencies() { | |
print_step "Checking necessary dependencies..." | |
for cmd in curl chmod find mkdir wget; do | |
if ! command -v "$cmd" >/dev/null 2>&1; then | |
error_exit "This script requires $cmd, but it is not installed.\nPlease install it with 'sudo apt install $cmd' and try again." | |
fi | |
done | |
print_success "All necessary dependencies are installed." | |
} | |
# Function to get the name of the latest version available for download | |
fetch_online_version() { | |
local HEADERS=$(timeout $VERSION_CHECK_TIMEOUT wget -S "$DOWNLOAD_URL" -q -O /dev/null 2>&1) | |
local FILENAME=$(echo "$HEADERS" | grep -o -E 'filename="[^"]+"' | sed 's/filename="//;s/"//') | |
if [[ -n "$FILENAME" ]]; then | |
echo "$FILENAME" | |
else | |
error_exit "Failed to fetch the latest version. If your connection is slow, try increasing \"VERSION_CHECK_TIMEOUT\"." | |
fi | |
} | |
# Function to get the path of latest version of the AppImage in the local directory | |
get_path_local_version() { | |
mkdir -p "$DOWNLOAD_APP_DIR" # Ensure the directory exists | |
local appimage_files | |
mapfile -t appimage_files < <(find "$DOWNLOAD_APP_DIR" -name 'cursor-*.AppImage' -printf '%T@ %p\n' | sort -nr | cut -d' ' -f2-) | |
if [ "${#appimage_files[@]}" -eq 0 ]; then | |
return 0 | |
fi | |
echo "${appimage_files[0]}" | |
} | |
# Search for the most recent AppImage file path | |
fetch_app_path() { | |
print_step "Searching for the most recent AppImage path of cursor.sh" | |
local appimage_files | |
mapfile -t appimage_files < <(find "$DOWNLOAD_APP_DIR" -name "cursor-*.AppImage" -printf '%T@ %p\n' | sort -rn | cut -d' ' -f2-) | |
if [ "${#appimage_files[@]}" -eq 0 ]; then | |
echo -e "${RED_COLOR}No cursor.sh AppImage files found in $DOWNLOAD_APP_DIR/${RESET_COLOR}" | |
error_exit "You need to download the latest version first (OPTION 1) and run this script again." | |
fi | |
cursor_appimage_path=${appimage_files[0]} | |
print_success "Path found: $cursor_appimage_path" | |
} | |
# Download the application icon | |
download_logo() { | |
print_step "Downloading the logo" | |
cursor_icon_path="$ICON_DIR/cursor-icon.svg" | |
mkdir -p "$ICON_DIR" | |
if ! curl -s -o "$cursor_icon_path" "$ICON_URL"; then | |
error_exit "Failed to download the logo from $ICON_URL" | |
fi | |
print_success "Logo downloaded: $ICON_DIR/cursor-icon.svg" | |
} | |
# Create the .desktop file for the application | |
create_desktop_file() { | |
print_step "Creating the .desktop file" | |
cat <<-EOF >"$DESKTOP_FILE_PATH" | |
[Desktop Entry] | |
Type=Application | |
Name=Cursor | |
Exec=$cursor_appimage_path | |
Icon=$cursor_icon_path | |
Categories=Utility;Development | |
Terminal=false | |
StartupWMClass=Cursor | |
X-AppImage-Version=latest | |
Comment=Cursor is an AI-first coding environment. | |
MimeType=x-scheme-handler/cursor; | |
EOF | |
print_success "Desktop file created: $DESKTOP_FILE_PATH" | |
} | |
# Configure the AppArmor profile for the application | |
create_apparmor_profile() { | |
local appimage_path=${1-""} | |
if [[ -z $appimage_path ]]; then | |
appimage_path=$(get_path_local_version) | |
if [[ -z "$appimage_path" ]]; then | |
error_exit "No local version found. Please download the latest version first (OPTION 1) and run this script again." | |
fi | |
fi | |
print_step "Configuring the AppArmor profile..." | |
cat <<-EOF | sudo tee $APPARMOR_PROFILE > /dev/null | |
# This profile allows everything and only exists to give the | |
# application a name instead of having the label "unconfined" | |
abi <abi/4.0>, | |
include <tunables/global> | |
profile cursor $appimage_path flags=(unconfined) { | |
userns, | |
# Site-specific additions and overrides. See local/README for details. | |
include if exists <local/cursor> | |
} | |
EOF | |
sudo apparmor_parser -r $APPARMOR_PROFILE || error_exit "Failed to apply the AppArmor profile." | |
print_success "AppArmor profile successfully configured for $appimage_path" | |
} | |
# Main function that checks and downloads the latest version if it is not yet locally present | |
check_and_download_version() { | |
print_step "Checking for updates and downloading if a newer version is available" | |
check_dependencies | |
mkdir -p "$DOWNLOAD_APP_DIR" # Ensure the directory exists | |
local filename_local_version | |
local path_local_version=$(get_path_local_version) | |
if [[ -z "$path_local_version" ]]; then | |
echo -e "${YELLOW_COLOR}No local version found. A new version will be downloaded.${RESET_COLOR}" | |
else | |
filename_local_version=$(basename "$path_local_version") | |
echo -e "${BLUE_COLOR}Latest local version: ${filename_local_version}${RESET_COLOR}" | |
fi | |
# Gets the name of the latest online version | |
echo -e "${YELLOW_COLOR}Searching for the latest version. Please wait...${RESET_COLOR}" | |
local filename_online_version=$(fetch_online_version) | |
local path_online_version="$DOWNLOAD_APP_DIR/$filename_online_version" | |
# Compares versions and downloads if necessary | |
if [[ -z "$path_local_version" ]] || [[ "$filename_local_version" != "$filename_online_version" ]]; then | |
echo -e "${YELLOW_COLOR}A new version is available: ${RESET_COLOR}${GREEN_COLOR}$filename_online_version${RESET_COLOR}" | |
echo -e "${YELLOW_COLOR}Downloading the newest version...${RESET_COLOR}" | |
wget --quiet --show-progress --content-disposition -P "$DOWNLOAD_APP_DIR" --trust-server-names "$DOWNLOAD_URL" | |
echo -e "${GREEN_COLOR}Download completed: ${path_online_version}${RESET_COLOR}" | |
if [[ -f "${path_online_version}" ]]; then | |
chmod +x "${path_online_version}" | |
echo -e "${GREEN_COLOR}Made the AppImage executable: ${path_online_version}${RESET_COLOR}" | |
echo -e "${MAGENTA_COLOR}Now run the script again and choose option 2 to install the latest version.${RESET_COLOR}" | |
else | |
error_exit "Downloaded AppImage file not found at ${path_online_version}" | |
fi | |
else | |
echo -e "${GREEN_COLOR}No download needed — your local version ($filename_online_version) is the most current.${RESET_COLOR}" | |
fi | |
} | |
# Function to search and prepare the AppImage | |
fetch_and_prepare_appimage() { | |
check_dependencies | |
fetch_app_path | |
download_logo | |
create_desktop_file | |
create_apparmor_profile | |
echo -e "${GREEN_COLOR}Application installed and ready to use.${RESET_COLOR}" | |
} | |
# Function to create the AppArmor profile | |
setup_apparmor_profile() { | |
check_dependencies | |
fetch_app_path | |
create_apparmor_profile | |
} | |
# Interactive menu | |
PS3='Please choose an option: ' | |
options=( | |
"Fetch the latest version and download (if not already present)" | |
"Re/Install the latest downloaded version" | |
"Just configure AppArmor (Required for Ubuntu 24.04)" | |
"Exit" | |
) | |
select opt in "${options[@]}" | |
do | |
case $opt in | |
"Fetch the latest version and download (if not already present)") | |
check_and_download_version | |
;; | |
"Re/Install the latest downloaded version") | |
fetch_and_prepare_appimage | |
;; | |
"Just configure AppArmor (Required for Ubuntu 24.04)") | |
setup_apparmor_profile | |
;; | |
"Exit") | |
break | |
;; | |
*) echo "Invalid option $REPLY";; | |
esac | |
done |
Hello! The official website has version 0.46.8, but your script is swinging automatically at 0.45.14. Is there any way to fix this? how to download the latest version through your script! Thanks!
@sercherton This PR jorcelinojunior/cursor-setup-wizard#15 addresses the problem you mention.
You may also be interested in this issue jorcelinojunior/cursor-setup-wizard#23.
Updated script, tested with Ubuntu 25.04 and Cursor 0.50.5 (requires jq
):
#!/bin/bash
set -euo pipefail
# Farben für die Terminalausgabe
readonly RED_COLOR="\e[31m"
readonly GREEN_COLOR="\e[32m"
readonly YELLOW_COLOR="\e[33m"
readonly BLUE_COLOR="\e[34m"
readonly MAGENTA_COLOR="\e[35m"
readonly RESET_COLOR="\e[0m"
# Allgemeine Einstellungen
readonly DOWNLOAD_APP_DIR="$HOME/Downloads/.AppImage"
readonly ICON_DIR="$HOME/.local/share/icons"
readonly ICON_URL="https://www.cursor.com/brand/icon.svg"
readonly DESKTOP_FILE_PATH="$HOME/.local/share/applications/cursor.desktop"
readonly APPARMOR_PROFILE="/etc/apparmor.d/cursor-appimage"
readonly DOWNLOAD_URL="https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable"
readonly TEMP_DIR="/tmp/AppImage"
readonly VERSION_CHECK_TIMEOUT=10
cursor_appimage_path=""
cursor_icon_path=""
print_step() {
echo -e "${YELLOW_COLOR}$1${RESET_COLOR}"
}
print_success() {
echo -e "${GREEN_COLOR}$1${RESET_COLOR}"
}
error_exit() {
echo -e "${RED_COLOR}$1${RESET_COLOR}" >&2
kill $$
}
check_dependencies() {
print_step "Checking necessary dependencies..."
for cmd in curl chmod find mkdir jq; do
if ! command -v "$cmd" >/dev/null 2>&1; then
error_exit "This script requires $cmd, but it is not installed.\nPlease install it with 'sudo apt install $cmd' and try again."
fi
done
print_success "All necessary dependencies are installed."
}
fetch_online_version() {
local JSON=$(curl -s "$DOWNLOAD_URL")
local FINAL_URL=$(echo "$JSON" | jq -r '.downloadUrl')
local FILENAME=$(basename "$FINAL_URL")
if [[ "$FILENAME" == *.AppImage ]]; then
echo "$FILENAME"
else
error_exit "Could not extract valid AppImage URL from API response."
fi
}
get_path_local_version() {
mkdir -p "$DOWNLOAD_APP_DIR"
local appimage_files
mapfile -t appimage_files < <(find "$DOWNLOAD_APP_DIR" -iname 'Cursor-*.AppImage' -printf '%T@ %p\n' | sort -nr | cut -d' ' -f2-)
if [ "${#appimage_files[@]}" -eq 0 ]; then
return 0
fi
echo "${appimage_files[0]}"
}
fetch_app_path() {
print_step "Searching for the most recent AppImage path of Cursor"
local appimage_files
mapfile -t appimage_files < <(find "$DOWNLOAD_APP_DIR" -iname "Cursor-*.AppImage" -printf '%T@ %p\n' | sort -rn | cut -d' ' -f2-)
if [ "${#appimage_files[@]}" -eq 0 ]; then
echo -e "${RED_COLOR}No Cursor AppImage files found in $DOWNLOAD_APP_DIR/${RESET_COLOR}"
error_exit "You need to download the latest version first (OPTION 1) and run this script again."
fi
cursor_appimage_path=${appimage_files[0]}
print_success "Path found: $cursor_appimage_path"
}
download_logo() {
print_step "Downloading the logo"
cursor_icon_path="$ICON_DIR/cursor-icon.svg"
mkdir -p "$ICON_DIR"
if ! curl -s -o "$cursor_icon_path" "$ICON_URL"; then
error_exit "Failed to download the logo from $ICON_URL"
fi
print_success "Logo downloaded: $cursor_icon_path"
}
create_desktop_file() {
print_step "Creating the .desktop file"
cat <<-EOF >"$DESKTOP_FILE_PATH"
[Desktop Entry]
Type=Application
Name=Cursor
Exec=$cursor_appimage_path
Icon=$cursor_icon_path
Categories=Utility;Development
Terminal=false
StartupWMClass=Cursor
X-AppImage-Version=latest
Comment=Cursor is an AI-first coding environment.
MimeType=x-scheme-handler/cursor;
EOF
print_success "Desktop file created: $DESKTOP_FILE_PATH"
}
create_apparmor_profile() {
local appimage_path=${1-""}
if [[ -z $appimage_path ]]; then
appimage_path=$(get_path_local_version)
if [[ -z "$appimage_path" ]]; then
error_exit "No local version found. Please download the latest version first (OPTION 1) and run this script again."
fi
fi
print_step "Configuring the AppArmor profile..."
cat <<-EOF | sudo tee $APPARMOR_PROFILE > /dev/null
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile cursor $appimage_path flags=(unconfined) {
userns,
include if exists <local/cursor>
}
EOF
sudo apparmor_parser -r $APPARMOR_PROFILE || error_exit "Failed to apply the AppArmor profile."
print_success "AppArmor profile successfully configured for $appimage_path"
}
check_and_download_version() {
print_step "Checking for updates and downloading if a newer version is available"
check_dependencies
mkdir -p "$DOWNLOAD_APP_DIR"
local filename_local_version
local path_local_version=$(get_path_local_version)
if [[ -z "$path_local_version" ]]; then
echo -e "${YELLOW_COLOR}No local version found. A new version will be downloaded.${RESET_COLOR}"
else
filename_local_version=$(basename "$path_local_version")
echo -e "${BLUE_COLOR}Latest local version: ${filename_local_version}${RESET_COLOR}"
fi
local JSON=$(curl -s "$DOWNLOAD_URL")
local FINAL_URL=$(echo "$JSON" | jq -r '.downloadUrl')
local filename_online_version=$(basename "$FINAL_URL")
local path_online_version="$DOWNLOAD_APP_DIR/$filename_online_version"
if [[ -z "$path_local_version" ]] || [[ "$filename_local_version" != "$filename_online_version" ]]; then
echo -e "${YELLOW_COLOR}A new version is available: ${RESET_COLOR}${GREEN_COLOR}$filename_online_version${RESET_COLOR}"
echo -e "${YELLOW_COLOR}Downloading the newest version...${RESET_COLOR}"
curl -L --output "$path_online_version" "$FINAL_URL" || error_exit "Download failed."
chmod +x "$path_online_version"
echo -e "${GREEN_COLOR}Download completed and saved as: ${path_online_version}${RESET_COLOR}"
echo -e "${MAGENTA_COLOR}Now run the script again and choose option 2 to install the latest version.${RESET_COLOR}"
else
echo -e "${GREEN_COLOR}No download needed — your local version ($filename_online_version) is the most current.${RESET_COLOR}"
fi
}
fetch_and_prepare_appimage() {
check_dependencies
fetch_app_path
download_logo
create_desktop_file
create_apparmor_profile
echo -e "${GREEN_COLOR}Application installed and ready to use.${RESET_COLOR}"
}
setup_apparmor_profile() {
check_dependencies
fetch_app_path
create_apparmor_profile
}
PS3='Please choose an option: '
options=(
"Fetch the latest version and download (if not already present)"
"Re/Install the latest downloaded version"
"Just configure AppArmor (Required for Ubuntu 24.04)"
"Exit"
)
select opt in "${options[@]}"
do
case $opt in
"Fetch the latest version and download (if not already present)")
check_and_download_version
;;
"Re/Install the latest downloaded version")
fetch_and_prepare_appimage
;;
"Just configure AppArmor (Required for Ubuntu 24.04)")
setup_apparmor_profile
;;
"Exit")
break
;;
*) echo "Invalid option $REPLY";;
esac
done
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hey everyone!
I just wanted to say a big thank you for all your comments, suggestions, and ideas. They’ve been incredibly helpful and really inspired me to improve things further.
To make everything easier to use and contribute to, I’ve created a new repository. You can check it out here: 👉 Cursor Setup Wizard
This wouldn’t have been possible without your support—thank you so much! 🙌✨