diff --git a/cursor_setup.sh b/cursor_setup.sh index 8a2bc8f..ebc7694 100755 --- a/cursor_setup.sh +++ b/cursor_setup.sh @@ -7,12 +7,12 @@ readonly SCRIPT_ALIAS_NAME="cursor-setup" readonly DOWNLOAD_DIR="$HOME/.AppImage" readonly ICON_DIR="$HOME/.local/share/icons" readonly USER_DESKTOP_FILE="$HOME/Desktop/cursor.desktop" -readonly DOWNLOAD_URL="https://downloader.cursor.sh/linux/appImage/x64" +readonly API_URL="https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=latest" readonly ICON_URL="https://mintlify.s3-us-west-1.amazonaws.com/cursor/images/logo/app-logo.svg" readonly VERSION_CHECK_TIMEOUT=5 # in seconds | if you have a slow connection, increase this value to 10, 15, or more readonly SPINNERS=("meter" "line" "dot" "minidot" "jump" "pulse" "points" "globe" "moon" "monkey" "hamburger") readonly SPINNER="${SPINNERS[0]}" -readonly DEPENDENCIES=("gum" "curl" "wget" "pv" "bc" "find:findutils" "chmod:coreutils" "timeout:coreutils" "mkdir:coreutils" "apparmor_parser:apparmor-utils") +readonly DEPENDENCIES=("gum" "curl" "wget" "pv" "bc" "find:findutils" "chmod:coreutils" "timeout:coreutils" "mkdir:coreutils" "apparmor_parser:apparmor-utils" "jq:jq") readonly GUM_VERSION_REQUIRED="0.14.5" readonly SYSTEM_DESKTOP_FILE="$HOME/.local/share/applications/cursor.desktop" readonly APPARMOR_PROFILE="/etc/apparmor.d/cursor-appimage" @@ -34,12 +34,13 @@ local_name="" local_size="" local_version="" local_path="" -local_md5="" +local_hash="" remote_name="" remote_size="" remote_version="" -remote_md5="" +remote_hash="" +download_url="" # Utility Functions validate_os() { @@ -169,45 +170,77 @@ logg() { fetch_remote_version() { logg prompt "Looking for the latest version online..." - headers=$(spinner "Fetching version info from the server..." \ - "sleep 1 && timeout \"$VERSION_CHECK_TIMEOUT\" wget -S \"$DOWNLOAD_URL\" -q -O /dev/null 2>&1 || true") - if [[ -z "$headers" ]]; then - logg error "$(echo -e "Failed to fetch headers from the server.\n • Ensure your internet connection is active and stable.\n • Ensure that 'VERSION_CHECK_TIMEOUT' ($VERSION_CHECK_TIMEOUT sec) is set high enough to retrieve the headers.\n • Also, verify if 'DOWNLOAD_URL' is correct: $DOWNLOAD_URL.\n\n ")" + local api_response + if ! api_response=$(spinner "Fetching version info from the API..." \ + "timeout \"$VERSION_CHECK_TIMEOUT\" curl -Ls \"$API_URL\""); then + logg error "$(echo -e "Failed to fetch data from the API server.\n • Ensure your internet connection is active and stable.\n • Ensure that 'VERSION_CHECK_TIMEOUT' ($VERSION_CHECK_TIMEOUT sec) is set high enough.\n • Also, verify if 'API_URL' is correct: $API_URL.\n\n ")" + return 1 + fi + if ! command -v jq &> /dev/null; then + logg error "jq is required to parse JSON response but it's not installed. Please install jq and try again." + return 1 + fi + download_url=$(echo "$api_response" | jq -r '.downloadUrl') + if [[ -z "$download_url" || "$download_url" == "null" ]]; then + logg error "Failed to extract download URL from API response. Response: $api_response" return 1 fi logg success "Latest version details retrieved successfully." - remote_name=$(echo "$headers" | grep -oE 'filename="[^"]+"' | sed 's/filename=//g; s/\"//g') || remote_name="" + headers=$(spinner "Fetching file details..." \ + "timeout \"$VERSION_CHECK_TIMEOUT\" wget -S \"$download_url\" -q -O /dev/null 2>&1 || true") + + if [[ -z "$headers" ]]; then + logg error "$(echo -e "Failed to fetch file details from the download server.\n • Ensure your internet connection is active and stable.\n • Check if the download URL is accessible: $download_url\n\n ")" + return 1 + fi + remote_name=$(basename "$download_url") remote_size=$(echo "$headers" | grep -oE 'Content-Length: [0-9]+' | sed 's/Content-Length: //') || remote_size="0" remote_version=$(extract_version "$remote_name") - remote_md5=$(echo "$headers" | grep -oE 'ETag: "[^"]+"' | sed 's/ETag: //; s/"//g' || echo "unknown") + remote_hash=$(echo "$headers" | grep -oE 'ETag: "[^"]+"' | sed 's/ETag: //; s/"//g' || echo "unknown") if [[ -z "$remote_name" ]]; then - logg error "Could not fetch the filename info. Please check that the 'DOWNLOAD_URL' variable is correct and try again." + logg error "Could not determine the filename from download URL. Please check the API response and try again." return 1 fi - logg info "$(echo -e "Latest version online:\n - name: $remote_name\n - version: $remote_version\n - size: $(convert_to_mb "$remote_size")\n - MD5 Hash: $remote_md5\n")" + logg info "$(echo -e "Latest version online:\n - name: $remote_name\n - version: $remote_version\n - size: $(convert_to_mb "$remote_size")\n - Hash: $remote_hash\n - download URL: $download_url\n")" } find_local_version() { show_log=${1:-false} [[ $show_log == true ]] && spinner "Searching for a local version..." "sleep 2;" mkdir -p "$DOWNLOAD_DIR" - local_path=$(find "$DOWNLOAD_DIR" -maxdepth 1 -type f -name 'cursor-*.AppImage' -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n 1 | cut -d' ' -f2-) + local_path=$(find "$DOWNLOAD_DIR" -maxdepth 1 -type f -name 'Cursor-*.AppImage' -printf '%T@ %p\n' 2>/dev/null | sort -nr | head -n 1 | cut -d' ' -f2-) if [[ -n "$local_path" ]]; then local_name=$(basename "$local_path") local_size=$(stat -c %s "$local_path" 2>/dev/null || echo "0") local_version=$(extract_version "$local_path") - local_md5=$(md5sum "$local_path" | cut -d' ' -f1) - [[ $show_log == true ]] && logg info "$(printf "Local version found:\n - name: %s\n - version: %s\n - size: %s\n - MD5 Hash: %s\n - path: %s\n" "$local_name" "$local_version" "$(convert_to_mb "$local_size")" "$local_md5" "$local_path")" + local_hash=$(calculate_etag "$local_path") + [[ $show_log == true ]] && logg info "$(printf "Local version found:\n - name: %s\n - version: %s\n - size: %s\n - Hash: %s\n - path: %s\n" "$local_name" "$local_version" "$(convert_to_mb "$local_size")" "$local_hash" "$local_path")" return 0 fi [[ $show_log == true ]] && logg error "$(echo -e "No local version found in $DOWNLOAD_DIR\n Go back to the menu and fetch it first.")" return 1 } +calculate_etag() { + local chunk_size=5 # Cursor appimage uses 5MB chunks + local file_size=$(du -b "$1" | cut -f 1) + local chunks=$((file_size / (chunk_size * 1024 * 1024))) + if [[ $((file_size % (chunk_size * 1024 * 1024))) -gt 0 ]]; then + chunks=$((chunks + 1)) + fi + local tmp_file=$(mktemp -t cursor-local-etag.XXXXXXXXXXXXX) + for (( chunk=0; chunk<$chunks; chunk++ )); do + dd bs=1M count=$chunk_size skip=$((chunk_size * chunk)) if="$1" 2> /dev/null | md5sum >> $tmp_file + done + local etag=$(echo "$(xxd -r -p "$tmp_file" | md5sum | cut -f 1 -d ' ')"-$chunks) + rm "$tmp_file" + echo "$etag" +} + download_logo() { logg prompt "Getting the Cursor logo ready..." mkdir -p "$ICON_DIR" - if spinner "Downloading the logo..." "sleep 1 && curl -s -o \"$ICON_DIR/cursor-icon.svg\" \"$ICON_URL\""; then + if spinner "Downloading the logo..." "sleep 1 && curl -Ls -o \"$ICON_DIR/cursor-icon.svg\" \"$ICON_URL\""; then logg success "Logo successfully downloaded to: $ICON_DIR/cursor-icon.svg" else logg error "Failed to download the logo. Please check your connection." @@ -217,15 +250,19 @@ download_logo() { download_appimage() { logg prompt "Starting the download of the latest version..." local output_document="$DOWNLOAD_DIR/$remote_name" + if [[ -z "$download_url" ]]; then + logg error "Download URL is empty. Please fetch the remote version first." + return 1 + fi if command -v pv >/dev/null; then if [[ "$remote_size" =~ ^[0-9]+$ ]]; then - wget --quiet --content-disposition -O - "$DOWNLOAD_URL" | pv -s "$remote_size" >"$output_document" + wget --quiet --content-disposition -O - "$download_url" | pv -s "$remote_size" >"$output_document" else logg warn "Couldn't determine file size. Proceeding with a standard download." - spinner "Downloading AppImage" "wget --quiet --content-disposition --output-document=\"$output_document\" \"$DOWNLOAD_URL\"" + spinner "Downloading AppImage" "wget --quiet --content-disposition --output-document=\"$output_document\" \"$download_url\"" fi else - if ! spinner "Downloading AppImage" "wget --quiet --show-progress --content-disposition --output-document=\"$output_document\" --trust-server-names \"$DOWNLOAD_URL\""; then + if ! spinner "Downloading AppImage" "wget --quiet --show-progress --content-disposition --output-document=\"$output_document\" --trust-server-names \"$download_url\""; then logg error "AppImage download failed. Please try again." return 1 fi @@ -319,7 +356,7 @@ menu() { case "$option" in "$(nostyle "$all_in_one")") fetch_remote_version - if ! find_local_version || [[ "$local_md5" != "$remote_md5" ]]; then + if ! find_local_version || [[ "$local_hash" != "$remote_hash" ]]; then download_appimage download_logo setup_launchers