Skip to content

Commit fbdb843

Browse files
ZanzyTHEbarwatzonCopilot
authored
Fix: Download and update Cursor AppImage (#15)
* fix: download cursor app image - Cursor changed their download link behaviour and now how a dynamic HTML page. These changes use HTML scraping instead of jq to grab the download link - Refactor download info retrieval in cursor.shfix: * Update cursor script URL in install.sh - temporary switch to point to my cursor installer script * fix: FUSE installation and improve AppImage handling - Updated FUSE installation checks and improved error handling. - Enhanced architecture verification for the Cursor AppImage and added safeguards for execution permissions. - Ensured that the right binary version is downloaded * chore: Update cursor script URL in install.sh - revert to offcial link for pr * Update cursor.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Chris Watson <cawatson1993@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent c9efadb commit fbdb843

1 file changed

Lines changed: 118 additions & 49 deletions

File tree

cursor.sh

Lines changed: 118 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -11,37 +11,44 @@ function check_fuse() {
1111
cmd_prefix="sudo"
1212
fi
1313

14-
# Check and install FUSE using the appropriate package manager
14+
# FIXED: Check and install FUSE2 using the appropriate package manager
1515
if command -v apt-get &>/dev/null; then
16-
if ! dpkg -l | grep -q "^ii.*fuse "; then
17-
echo "Installing FUSE..."
16+
if ! dpkg -l | grep -q "^ii.*libfuse2 "; then # FIXED: libfuse2, not fuse
17+
echo "Installing libfuse2..."
1818
$cmd_prefix apt-get update
19-
$cmd_prefix apt-get install -y fuse
19+
$cmd_prefix apt-get install -y libfuse2 # FIXED: libfuse2 for AppImage compat
2020
else
21-
echo "FUSE is already installed."
21+
echo "libfuse2 is already installed."
2222
fi
2323
elif command -v dnf &>/dev/null; then
2424
if ! rpm -q fuse >/dev/null 2>&1; then
25-
echo "Installing FUSE..."
25+
echo "Installing fuse..."
2626
$cmd_prefix dnf install -y fuse
2727
else
28-
echo "FUSE is already installed."
28+
echo "fuse is already installed."
2929
fi
3030
elif command -v pacman &>/dev/null; then
3131
if ! pacman -Qi fuse2 >/dev/null 2>&1; then
32-
echo "Installing FUSE..."
32+
echo "Installing fuse2..."
3333
$cmd_prefix pacman -S fuse2
3434
else
35-
echo "FUSE is already installed."
35+
echo "fuse2 is already installed."
3636
fi
3737
else
38-
echo "Unsupported package manager. Please install FUSE manually."
39-
echo "You can install FUSE using your system's package manager:"
40-
echo " - Debian/Ubuntu: ${cmd_prefix}apt-get install fuse"
38+
echo "Unsupported package manager. Please install libfuse2 manually."
39+
echo "You can install FUSE2 using your system's package manager:"
40+
echo " - Debian/Ubuntu: ${cmd_prefix}apt-get install libfuse2" # FIXED: libfuse2
4141
echo " - Fedora: ${cmd_prefix}dnf install fuse"
4242
echo " - Arch Linux: ${cmd_prefix}pacman -S fuse2"
4343
exit 1
4444
fi
45+
46+
# Verify FUSE2 is functional
47+
if ! fusermount -V >/dev/null 2>&1; then
48+
echo "Warning: FUSE2 verification failed. AppImage may not run." >&2
49+
return 1
50+
fi
51+
echo "FUSE2 is ready."
4552
}
4653

4754
function get_arch() {
@@ -82,45 +89,53 @@ function get_install_dir() {
8289

8390
function get_fallback_download_info() {
8491
local arch=$(get_arch)
85-
86-
# this AppImage is potentially older than expected, see
87-
# https://github.com/watzon/cursor-linux-installer/issues/5
88-
echo "MESSAGE=$1"
89-
echo "URL=https://downloader.cursor.sh/linux/appImage/$arch"
90-
echo "VERSION=fallback"
91-
return 1
92+
local path_arch="$arch" # NEW: x64/arm64 for path
93+
local file_arch="x86_64" # NEW: Map for filename
94+
if [ "$arch" = "arm64" ]; then
95+
file_arch="aarch64"
96+
fi
97+
local fallback_hash="8ea935e79a50a02da912a034bbeda84a6d3d355d" # FIXED: Recent from 0.50.4 (May 2025)
98+
local fallback_version="0.50.4" # FIXED: Updated version
99+
echo "URL=https://downloads.cursor.com/production/$fallback_hash/linux/$path_arch/Cursor-$fallback_version-$file_arch.AppImage"
100+
echo "VERSION=$fallback_version"
101+
return 1 # Still error, but usable URL
92102
}
93103

94104
function get_download_info() {
95-
if ! which jq >/dev/null 2>&1; then
96-
get_fallback_download_info "jq not available"
97-
return 1
98-
fi
99-
100-
local temp_file=$(mktemp)
105+
local temp_html=$(mktemp)
101106
local release_track=${1:-stable} # Default to stable if not specified
102-
local api_url="https://www.cursor.com/api/download?platform=linux-$(get_arch)&releaseTrack=$release_track"
103-
104-
echo "Fetching download info for $release_track track..."
105-
if ! curl -sL "$api_url" -o "$temp_file"; then
106-
rm -f "$temp_file"
107-
get_fallback_download_info "curl failed on $api_url"
108-
return 1
107+
local arch=$(get_arch) # x64 or arm64
108+
local path_arch="$arch" # NEW: For platform param (x64/arm64)
109+
local file_arch="x86_64" # NEW: For filename filter (x86_64/aarch64)
110+
if [ "$arch" = "arm64" ]; then
111+
file_arch="aarch64"
109112
fi
113+
local platform="linux-${path_arch}"
114+
local api_url="https://cursor.com/api/download?platform=$platform&releaseTrack=$release_track"
110115

111-
if ! download_url=$(jq -er '.downloadUrl' "$temp_file"); then
112-
rm -f "$temp_file"
113-
get_fallback_download_info "jq failed: downloadUrl not found in JSON response"
116+
echo "Fetching download info for $release_track track ($file_arch)..."
117+
if ! curl -sL "$api_url" -o "$temp_html"; then
118+
rm -f "$temp_html"
119+
get_fallback_download_info
114120
return 1
115121
fi
116122

117-
if ! version=$(jq -er '.version' "$temp_file"); then
118-
rm -f "$temp_file"
119-
get_fallback_download_info "jq failed: version not found in JSON response"
123+
# FIXED: Arch-specific scrape (filters to correct binary)
124+
local download_url=$(grep -o "https://downloads\.cursor\.com/[^[:space:]]*${file_arch}\.AppImage" "$temp_html" | head -1 | sed 's/["'\'']\?$//')
125+
126+
rm -f "$temp_html"
127+
128+
if [ -z "$download_url" ]; then
129+
get_fallback_download_info
120130
return 1
121131
fi
122132

123-
rm -f "$temp_file"
133+
# Extract version from filename (e.g., Cursor-1.6.35-x86_64.AppImage → 1.6.35)
134+
local version=$(basename "$download_url" | sed -E 's/.*Cursor-([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
135+
136+
if [ -z "$version" ]; then
137+
version="unknown" # Rare fallback
138+
fi
124139

125140
echo "URL=$download_url"
126141
echo "VERSION=$version"
@@ -132,6 +147,7 @@ function install_cursor() {
132147
local release_track=${2:-stable} # Default to stable if not specified
133148
local temp_file=$(mktemp)
134149
local current_dir=$(pwd)
150+
local arch=$(get_arch) # NEW: For verification
135151
local download_info=$(get_download_info "$release_track")
136152
local message=$(echo "$download_info" | grep "MESSAGE=" | sed 's/^MESSAGE=//')
137153

@@ -141,7 +157,7 @@ function install_cursor() {
141157
fi
142158

143159
# Check for FUSE before proceeding with installation
144-
check_fuse
160+
check_fuse || return 1 # NEW: Propagate FUSE error
145161

146162
local download_url=$(echo "$download_info" | grep "URL=" | sed 's/^URL=//')
147163
local version=$(echo "$download_info" | grep "VERSION=" | sed 's/^VERSION=//')
@@ -156,6 +172,28 @@ function install_cursor() {
156172
chmod +x "$temp_file"
157173
mv "$temp_file" "$install_dir/cursor.appimage"
158174

175+
# Ensure execution permissions persist post-move (robust against FS quirks)
176+
chmod +x "$install_dir/cursor.appimage"
177+
if [ -x "$install_dir/cursor.appimage" ]; then
178+
echo "Execution permissions confirmed for $install_dir/cursor.appimage"
179+
else
180+
echo "Warning: Failed to set execution permissions—check filesystem." >&2
181+
return 1
182+
fi
183+
184+
# NEW: Verify binary architecture matches host
185+
local binary_info=$(file "$install_dir/cursor.appimage" 2>/dev/null || echo "unreadable")
186+
local expected_grep="x86-64"
187+
if [ "$arch" = "arm64" ]; then
188+
expected_grep="ARM aarch64"
189+
fi
190+
if ! echo "$binary_info" | grep -q "$expected_grep"; then
191+
echo "Error: Arch mismatch detected ($binary_info). Expected $expected_grep. Aborting install." >&2
192+
rm -f "$install_dir/cursor.appimage"
193+
return 1
194+
fi
195+
echo "Binary verified: $binary_info"
196+
159197
# Store version information in a simple file
160198
echo "$version" >"$install_dir/.cursor_version"
161199

@@ -164,25 +202,46 @@ function install_cursor() {
164202
cd "$temp_extract_dir"
165203

166204
# Extract icons
167-
"$install_dir/cursor.appimage" --appimage-extract "usr/share/icons" >/dev/null 2>&1
205+
if ! "$install_dir/cursor.appimage" --appimage-extract "usr/share/icons" >/dev/null 2>&1; then
206+
echo "Warning: Icon extraction failed—skipping." >&2
207+
fi
168208
# Extract desktop file
169-
"$install_dir/cursor.appimage" --appimage-extract "cursor.desktop" >/dev/null 2>&1
209+
if ! "$install_dir/cursor.appimage" --appimage-extract "cursor.desktop" >/dev/null 2>&1; then
210+
echo "Warning: Desktop extraction failed—skipping." >&2
211+
fi
212+
213+
# NEW: Verify extraction succeeded before copying
214+
if [ ! -d "squashfs-root" ]; then
215+
echo "Error: Extraction failed (squashfs-root missing). Check arch/FUSE." >&2
216+
cd "$current_dir"
217+
rm -rf "$temp_extract_dir"
218+
return 1
219+
fi
170220

171221
# Copy icons
172222
local icon_dir="$HOME/.local/share/icons/hicolor"
173223
mkdir -p "$icon_dir"
174-
cp -r squashfs-root/usr/share/icons/hicolor/* "$icon_dir/"
224+
if [ -d "squashfs-root/usr/share/icons/hicolor" ]; then
225+
cp -r squashfs-root/usr/share/icons/hicolor/* "$icon_dir/" 2>/dev/null || echo "Warning: Icon copy failed."
226+
fi
175227

176228
# Copy desktop file
177229
local apps_dir="$HOME/.local/share/applications"
178230
mkdir -p "$apps_dir"
179-
cp squashfs-root/cursor.desktop "$apps_dir/"
231+
if [ -f "squashfs-root/cursor.desktop" ]; then
232+
cp squashfs-root/cursor.desktop "$apps_dir/"
233+
# Update desktop file to point to the correct AppImage location
234+
sed -i "s|Exec=.*|Exec=$install_dir/cursor.appimage --no-sandbox|g" "$apps_dir/cursor.desktop"
180235

181-
# Update desktop file to point to the correct AppImage location
182-
sed -i "s|Exec=.*|Exec=$install_dir/cursor.appimage --no-sandbox|g" "$apps_dir/cursor.desktop"
236+
# Fix potential icon name mismatch in the extracted desktop file
237+
sed -i 's/^Icon=co.anysphere.cursor/Icon=cursor/' "$apps_dir/cursor.desktop"
183238

184-
# Fix potential icon name mismatch in the extracted desktop file
185-
sed -i 's/^Icon=co.anysphere.cursor/Icon=cursor/' "$apps_dir/cursor.desktop"
239+
# NEW: Refresh desktop database for menu visibility
240+
update-desktop-database "$apps_dir" 2>/dev/null || true
241+
echo ".desktop file installed and updated."
242+
else
243+
echo "Warning: cursor.desktop not found in extraction—manual setup needed."
244+
fi
186245

187246
# Clean up
188247
cd "$current_dir"
@@ -194,7 +253,6 @@ function install_cursor() {
194253

195254
function update_cursor() {
196255
echo "Updating Cursor..."
197-
local arch=$(get_arch)
198256
local current_appimage=$(find_cursor_appimage)
199257
local install_dir
200258
local release_track=${1:-stable} # Default to stable if not specified
@@ -217,6 +275,17 @@ function launch_cursor() {
217275
cursor_appimage=$(find_cursor_appimage)
218276
fi
219277

278+
# NEW: Pre-launch safeguard (re-chmod + arch check)
279+
if [ ! -x "$cursor_appimage" ]; then
280+
echo "Fixing execution permissions..."
281+
chmod +x "$cursor_appimage"
282+
fi
283+
local binary_info=$(file "$cursor_appimage" 2>/dev/null || echo "unreadable")
284+
if ! echo "$binary_info" | grep -q "$(get_arch | sed 's/x64/x86-64/;s/arm64/ARM aarch64/')"; then # Simplified arch map
285+
echo "Error: Arch mismatch in binary ($binary_info). Re-update." >&2
286+
return 1
287+
fi
288+
220289
# Create a log file to capture output and errors
221290
local log_file="/tmp/cursor_appimage.log"
222291

0 commit comments

Comments
 (0)