Skip to content

Instantly share code, notes, and snippets.

@evgenyneu
Last active July 4, 2025 04:56
Show Gist options
  • Save evgenyneu/5c5c37ca68886bf1bea38026f60603b6 to your computer and use it in GitHub Desktop.
Save evgenyneu/5c5c37ca68886bf1bea38026f60603b6 to your computer and use it in GitHub Desktop.
Install Cursor AI code editor on Ubuntu 24.04 LTS

Install Cursor AI editor on Ubuntu 24.04

  1. Use the Download button on www.cursor.com web site. It will download the NAME.AppImage file.

  2. Copy the .AppImage file to your Applications directory

cd ~/Downloads
mkdir -p ~/Applications
mv NAME.AppImage ~/Applications/cursor.AppImage
  1. Install libfuse2
sudo apt update
sudo apt install libfuse2
  1. Make it an executable
chmod +x ~/Applications/cursor.AppImage
  1. Run
~/Applications/cursor.AppImage --no-sandbox
  1. Add cursor shortcut

Add to .bashrc or .zshrc

alias cursor='~/Applications/cursor.AppImage --no-sandbox'
@quando2299
Copy link

i update the script for zsh executions but onlyc hange the bin/zsh and the extension for .sh and execute, that allows update and handle update with the last version

additional something simple as put and alias for execute when you wnat

# Custom functions and script aliases
alias update-cursor="$HOME/scripts/update-cursor"
#!/bin/zsh
#
# Cursor AI IDE Installer/Updater
# This script installs or updates Cursor AI IDE on Linux systems
#

# -----------------------------------------------------------------------------
# Configuration
# -----------------------------------------------------------------------------
APPIMAGE_PATH="/opt/cursor/cursor.AppImage"
ICON_PATH="/opt/cursor/cursor.png"
DESKTOP_ENTRY_PATH="/usr/share/applications/cursor.desktop"
ICON_URL="https://us1.discourse-cdn.com/flex020/uploads/cursor1/original/2X/a/a4f78589d63edd61a2843306f8e11bad9590f0ca.png"
API_URL="https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable"

# Cursor shell function to be added to config files
read -r -d '' CURSOR_FUNCTION << 'EOL'
# Cursor AI IDE launcher function
function cursor() {
    local args=""
    if [ $# -eq 0 ]; then
        args=$(pwd)
    else
        for arg in "$@"; do
            args="$args $arg"
        done
    fi
    local executable=$(find /opt/cursor/cursor.AppImage -type f)
    (nohup $executable --no-sandbox "$args" >/dev/null 2>&1 &)
}
EOL

# -----------------------------------------------------------------------------
# Helper Functions
# -----------------------------------------------------------------------------

# Ensure required dependencies are installed
ensure_dependencies() {
    if ! command -v curl &> /dev/null; then
        echo "curl is not installed. Installing..."
        sudo apt-get update
        sudo apt-get install -y curl
    fi
}

# Check if Cursor is currently running
check_cursor_running() {
    if pgrep -f "cursor.AppImage" > /dev/null; then
        return 0  # Cursor is running
    else
        return 1  # Cursor is not running
    fi
}

# Fetch the latest version information from the Cursor API
fetch_latest_version() {
    local api_response
    api_response=$(curl -s "$API_URL")
    CURSOR_URL=$(echo "$api_response" | grep -o '"downloadUrl":"[^"]*"' | cut -d'"' -f4)
    LATEST_VERSION=$(echo "$api_response" | grep -o '"version":"[^"]*"' | cut -d'"' -f4)

    if [[ -z "$CURSOR_URL" || -z "$LATEST_VERSION" ]]; then
        echo "Error: Failed to fetch version information from API"
        return 1
    fi

    return 0
}

# Get the currently installed version of Cursor
get_current_version() {
    if [[ -f "$APPIMAGE_PATH" ]]; then
        CURRENT_VERSION=$("$APPIMAGE_PATH" --version 2>/dev/null | grep -o "[0-9]\+\.[0-9]\+\.[0-9]\+" | head -1)
        if [[ -z "$CURRENT_VERSION" ]]; then
            CURRENT_VERSION="unknown"
        fi
    else
        CURRENT_VERSION="not installed"
    fi
}

# Create desktop entry file for application menu integration
create_desktop_entry() {
    echo "Creating desktop entry for Cursor..."
    sudo bash -c "cat > $DESKTOP_ENTRY_PATH" <<EOL
[Desktop Entry]
Name=Cursor AI IDE
Exec=$APPIMAGE_PATH --no-sandbox
Icon=$ICON_PATH
Type=Application
StartupWMClass=Cursor
Categories=Development;
EOL
}

# Download and install the Cursor AppImage
download_and_install() {
    local version="$1"

    # Check if Cursor is running
    if check_cursor_running; then
        echo "Error: Cursor is currently running."
        echo "Please close all instances of Cursor AI IDE and try again."
        return 1
    fi

    # Create installation directory
    sudo mkdir -p "$(dirname "$APPIMAGE_PATH")"

    # Download and set up AppImage
    echo "Downloading Cursor AppImage..."
    sudo curl -L "$CURSOR_URL" -o "$APPIMAGE_PATH"
    sudo chmod +x "$APPIMAGE_PATH"

    # Download and set up icon
    echo "Downloading Cursor icon..."
    sudo curl -L "$ICON_URL" -o "$ICON_PATH"

    # Create desktop entry
    create_desktop_entry

    echo "Cursor AI IDE version $version installation complete."
}

# Determine and get the appropriate shell config file
get_shell_config_file() {
    # Check which shell the user is running
    local shell_path=$(echo "$SHELL")
    local config_file=""

    case "$shell_path" in
        */zsh)
            if [[ -f "$HOME/.zshrc" ]]; then
                config_file="$HOME/.zshrc"
            else
                # Create .zshrc if it doesn't exist
                touch "$HOME/.zshrc"
                config_file="$HOME/.zshrc"
            fi
            ;;
        */bash)
            if [[ -f "$HOME/.bashrc" ]]; then
                config_file="$HOME/.bashrc"
            else
                touch "$HOME/.bashrc"
                config_file="$HOME/.bashrc"
            fi
            ;;
        *)
            # Default to .bashrc for unknown shells
            if [[ -f "$HOME/.bashrc" ]]; then
                config_file="$HOME/.bashrc"
            else
                touch "$HOME/.bashrc"
                config_file="$HOME/.bashrc"
            fi
            ;;
    esac

    echo "$config_file"
}

# Add the cursor function to shell configuration if it doesn't exist
setup_shell_function() {
    local config_file=$(get_shell_config_file)

    echo "Checking for existing cursor function in $config_file..."

    # Check if function already exists in the config file
    if grep -q "function cursor()" "$config_file"; then
        echo "Cursor function already exists in $config_file. No changes needed."
        return 0
    fi

    echo "Adding cursor function to $config_file..."
    echo -e "\n$CURSOR_FUNCTION" >> "$config_file"
    echo "Cursor function added to $config_file."
    echo "Please restart your terminal or run 'source $config_file' to use the cursor command."

    return 0
}

# -----------------------------------------------------------------------------
# Main Functions
# -----------------------------------------------------------------------------

# Fresh installation of Cursor
install_cursor() {
    echo "Installing Cursor AI IDE..."
    ensure_dependencies

    if ! fetch_latest_version; then
        echo "Installation failed. Could not get latest version information."
        return 1
    fi

    echo "Installing Cursor version $LATEST_VERSION"
    download_and_install "$LATEST_VERSION"

    # Setup shell function
    setup_shell_function

    echo "You can find Cursor AI IDE in your application menu or run it by typing 'cursor' in your terminal."
}

# Update an existing Cursor installation
update_cursor() {
    echo "Checking for Cursor updates..."
    ensure_dependencies

    if ! fetch_latest_version; then
        echo "Update check failed. Could not get latest version information."
        return 1
    fi

    get_current_version

    if [[ "$CURRENT_VERSION" == "not installed" ]]; then
        echo "Cursor is not currently installed. Installing now..."
        install_cursor
        return
    fi

    if [[ "$CURRENT_VERSION" == "$LATEST_VERSION" ]]; then
        echo "Cursor is already up to date (version $CURRENT_VERSION)."

        # Make sure shell function is set up even if no update is needed
        setup_shell_function
        return
    fi

    echo "Current version: $CURRENT_VERSION"
    echo "Latest version: $LATEST_VERSION"
    echo "Updating Cursor..."

    # Check if Cursor is running
    if check_cursor_running; then
        echo "Error: Cannot update while Cursor is running."
        echo "Please close all instances of Cursor AI IDE and try again."
        return 1
    fi

    # Download and install the new version
    sudo curl -L "$CURSOR_URL" -o "$APPIMAGE_PATH"
    sudo chmod +x "$APPIMAGE_PATH"

    # Setup shell function
    setup_shell_function

    echo "Cursor has been updated to version $LATEST_VERSION"
}

# Main function to handle installation or update
manage_cursor() {
    if [[ -f "$APPIMAGE_PATH" ]]; then
        echo "Cursor AI IDE is already installed."
        update_cursor
    else
        install_cursor
    fi
}

# -----------------------------------------------------------------------------
# Execute
# -----------------------------------------------------------------------------
manage_cursor

work well, thank you so much !

@LaurentDumont
Copy link

I recommend to rename the original AppImage as well.

  • The original download has the version number in the file name.
  • An appimage, when executed, will try to update itself.
  • If you are not careful, you will have the original version in the name, but the binaries will be new and shiny.

Source : https://docs.appimage.org/packaging-guide/optional/updates.html

ZSync2 based methods will furthermore always keep the old file as a backup. 
If the overwrite flag is true, the current file will be moved to my.AppImage.zs-old. 
If it is false, the old file will remain untouched.
➜  Downloads ls -alh | grep Cursor
-rwxrwxr-x  1 coldadmin coldadmin  178M Mar 31 05:30 Cursor-0.47.9-x86_64.AppImage
-rwxrwxr-x  1 coldadmin coldadmin  166M Mar 30 11:33 Cursor-0.47.9-x86_64.AppImage.zs-old
Version: 0.48.6
VSCode Version: 1.96.2
Commit: 1649e229afdef8fd1d18ea173f063563f1e722e0
Date: 2025-03-31T05:05:28.011Z
Electron: 34.3.4
Chromium: 132.0.6834.210
Node.js: 20.18.3
V8: 13.2.152.41-electron.0
OS: Linux x64 5.15.0-135-generic

@italocjs
Copy link

italocjs commented Apr 8, 2025

Installing fuse2 breaks ubuntu 24.04. upon restart ui stops working.

@melroy89
Copy link

melroy89 commented Apr 9, 2025

Cursor app also doesn't seems to start fully in a separate thread and detached from the terminal when starting from the terminal. More over, I also notice sometimes issues with like using grep in the built-in terminal within Cursor.

So I dunno what the hack those Cursor people thinking, but they should just build a deb file and distribute those for the people who just want to have a deb. I know its possible since Windsurf does it correctly.

I would like to have Cursor features with the build & releasing of Windsurf using deb files.

@evagabond
Copy link

Is it working for Ubuntu 22.04?

Yes!

@blakete
Copy link

blakete commented Apr 16, 2025

Thanks! This worked smoothly for me on Ubuntu 22.04.5

@NoviaDroid
Copy link

NoviaDroid commented Apr 20, 2025

the online cursor icon to download should be replaced as : https://www.cursor.com/apple-touch-icon.png

@Hassan-Mehmood
Copy link

The link in the script is not working for some reason. Anyone else facing the same issue?
NOT WORKING: https://downloader.cursor.sh/linux/appImage/x64

@melroy89
Copy link

Cursor now really really need to just publish a Debian repo sooner than later.

@Sayeh-1337
Copy link

Sayeh-1337 commented Apr 22, 2025

Simply do that

./Cursor-xxxxxx.AppImage --appimage-extract cursor

# Navigate to the source directory
cd cursor/usr

# Copy the contents to the system /usr directory
sudo cp -r bin/* /usr/bin/
sudo cp -r lib/* /usr/lib/
sudo cp -r share/* /usr/share/

# Set the correct ownership and permissions for the Chrome sandbox
sudo chown root:root /usr/share/cursor/chrome-sandbox
sudo chmod 4755 /usr/share/cursor/chrome-sandbox

@melroy89
Copy link

melroy89 commented Apr 22, 2025

Just saying.. In my case --appimage-extract doesn't accept the parameter for a directory (cursor in this case). Without cursor extracting works as expected into squashfs-root dir. Thanks!

Improved script from above snippet (for Linux): https://gitlab.melroy.org/-/snippets/621

That being said, it's still NOT ideal.. since normally the executable (eg. vscode or windsurf) goes to background when executing it. My code snippet fixing this again, by using a custom launcher script just like vscode. Basically doing:

ELECTRON="/usr/local/share/cursor/cursor"
CLI="/usr/local/share/cursor/resources/app/out/cli.js"
ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" "$@"

Cursor fix your deb files!

So now the only down-side is that an icon is missing Cursor fixed that issue apparently.

@bpinazmul18
Copy link

I was getting this error while execute this script.

error: curl: (6) Could not resolve host: downloader.cursor.sh

sudo chmod +x ./install_script_cursor.sh
./install_script_cursor.sh

machine info

ubuntu 24.04

#!/bin/bash

installCursor() {
if ! [ -f /opt/cursor.appimage ]; then
    echo "Installing Cursor AI IDE..."

    # Check for sudo privileges
    if [ "$(id -u)" -ne 0 ]; then
        echo "This script needs sudo privileges to install Cursor AI."
        echo "Please run with: sudo $0"
        return 1
    fi

    # URLs for Cursor AppImage and Icon
    CURSOR_URL="https://downloader.cursor.sh/linux/appImage/x64"
    ICON_URL="https://registry.npmmirror.com/@lobehub/icons-static-png/latest/files/dark/cursor.png"

    # Paths for installation
    APPIMAGE_PATH="/opt/cursor.appimage"
    ICON_PATH="/opt/cursor.png"
    DESKTOP_ENTRY_PATH="/usr/share/applications/cursor.desktop"

    # Install curl if not installed
    if ! command -v curl &> /dev/null; then
        echo "curl is not installed. Installing..."
        apt-get update
        apt-get install -y curl
    fi

    # Download Cursor AppImage
    echo "Downloading Cursor AppImage..."
    if ! curl -L $CURSOR_URL -o $APPIMAGE_PATH; then
        echo "Failed to download Cursor AppImage. Please check your internet connection."
        return 1
    fi
    chmod +x $APPIMAGE_PATH

    # Download Cursor icon
    echo "Downloading Cursor icon..."
    if ! curl -L $ICON_URL -o $ICON_PATH; then
        echo "Failed to download Cursor icon, but continuing installation."
    fi

    # Create a .desktop entry for Cursor
    echo "Creating .desktop entry for Cursor..."
    cat > $DESKTOP_ENTRY_PATH <<EOL
[Desktop Entry]
Name=Cursor AI IDE
Exec=$APPIMAGE_PATH --no-sandbox
Icon=$ICON_PATH
Type=Application
Categories=Development;
EOL

    echo "Adding cursor alias to .bashrc..."
    if [ -f "$HOME/.bashrc" ]; then
        cat >> $HOME/.bashrc <<EOL

# Cursor alias
function cursor() {
    /opt/cursor.appimage --no-sandbox "\${@}" > /dev/null 2>&1 & disown
}
EOL
        echo "Alias added. To use it in this terminal session, run: source $HOME/.bashrc"
    else
        echo "Could not find .bashrc file. Cursor can still be launched from the application menu."
    fi

    echo "Cursor AI IDE installation complete. You can find it in your application menu."
else
    echo "Cursor AI IDE is already installed."
fi
}

installCursor

@rammariana
Copy link

I installed Cursor on Ubuntu 24.04. I created the executable and when I use it, it constantly tells me "Not responding." How can I fix this?

@hieutt192
Copy link

I updated the script to automatically install a cursor on Ubuntu 22.04. If anyone is interested, details are in my repo

`

#!/bin/bash

installCursor() {
if ! [ -f /opt/Cursor/cursor.appimage ]; then
echo "Installing Cursor AI IDE on Ubuntu 22.04..."

    # 📝 Enter the AppImage download URL
    read -p "Enter Cursor AppImage URL " CURSOR_URL
    # 📝 Enter the icon file name (e.g., cursor-icon.png or cursor-black-icon.png)
    read -p "Enter icon filename (from GitHub): " ICON_NAME

    # for Cursor Icon
    ICON_URL="https://raw.githubusercontent.com/hieutt192/Cursor-ubuntu/main/images/$ICON_NAME"

    # Paths for installation
    APPIMAGE_PATH="/opt/Cursor/cursor.appimage"
    ICON_PATH="/opt/Cursor/cursor-icon.png"
    DESKTOP_ENTRY_PATH="/usr/share/applications/cursor.desktop"

    # Install curl if not installed
    if ! command -v curl &> /dev/null; then
        echo "curl is not installed. Installing..."
        sudo apt-get update
        sudo apt-get install -y curl
    fi

    # Create install directory if not exists
    sudo mkdir -p /opt/Cursor

    # Download Cursor AppImage
    echo "move Cursor AppImage to /opt/ folder..."
    sudo mv $CURSOR_URL $APPIMAGE_PATH
    sudo chmod +x $APPIMAGE_PATH

    # Download Cursor icon
    echo "Downloading Cursor icon..."
    sudo curl -L $ICON_URL -o $ICON_PATH

    # Create a .desktop entry for Cursor
    echo "Creating .desktop entry for Cursor..."
    sudo bash -c "cat > $DESKTOP_ENTRY_PATH" <<EOL

[Desktop Entry]
Name=Cursor AI IDE
Exec=$APPIMAGE_PATH --no-sandbox
Icon=$ICON_PATH
Type=Application
Categories=Development;
EOL

    echo "✅ Cursor AI IDE installation complete. You can find it in your application menu."
else
    echo "ℹ️ Cursor AI IDE is already installed."
fi

}

installCursor

`

@bpinazmul18
Copy link

@hieutt192 Thanks. It's perfectly working my Ubuntu 24.04

𝗧𝗵𝗲 𝗙𝗶𝘅
Later, I found a GitHub repo with an updated installation script:
git clone [email protected]:hieutt192/Cursor-ubuntu.git

I followed the instructions in the repo, and finally, Cursor was installed successfully!

@hieutt192
Copy link

@bpinazmul18 Glad I could help! And now, Cursor offers 1 year free for student emails. Let's get it!

@sudipto68
Copy link

This works for me too. Thanks @hieutt192 👍

@MamadoubarryGLRSB
Copy link

MamadoubarryGLRSB commented May 9, 2025

thanx

@wafarifki
Copy link

#New Update install_cursor.sh Fix Download URL

#!/bin/bash

installCursor() {
if ! [ -f /opt/cursor.appimage ]; then
echo "Installing Cursor AI IDE..."

    # URLs for Cursor AppImage and Icon
    CURSOR_URL="https://downloads.cursor.com/production/8ea935e79a50a02da912a034bbeda84a6d3d355d/linux/x64/Cursor-0.50.4-x86_64.AppImage"
    ICON_URL="https://us1.discourse-cdn.com/flex020/uploads/cursor1/original/2X/a/a4f78589d63edd61a2843306f8e11bad9590f0ca.png"

    # Paths for installation
    APPIMAGE_PATH="/opt/cursor.appimage"
    ICON_PATH="/opt/cursor.png"
    DESKTOP_ENTRY_PATH="/usr/share/applications/cursor.desktop"

    # Install curl if not installed
    if ! command -v curl &> /dev/null; then
        echo "curl is not installed. Installing..."
        sudo apt-get update
        sudo apt-get install -y curl
    fi

    # Download Cursor AppImage
    echo "Downloading Cursor AppImage..."
    sudo curl -L $CURSOR_URL -o $APPIMAGE_PATH
    sudo chmod +x $APPIMAGE_PATH

    # Download Cursor icon
    echo "Downloading Cursor icon..."
    sudo curl -L $ICON_URL -o $ICON_PATH

    # Create a .desktop entry for Cursor
    echo "Creating .desktop entry for Cursor..."
    sudo bash -c "cat > $DESKTOP_ENTRY_PATH" <<EOL
[Desktop Entry]
Name=Cursor AI IDE
Exec=$APPIMAGE_PATH --no-sandbox
Icon=$ICON_PATH
Type=Application
Categories=Development;
EOL

    echo "Adding cursor alias to .bashrc..."
    bash -c "cat >> $HOME/.bashrc" <<EOL

# Cursor alias
function cursor() {
    /opt/cursor.appimage --no-sandbox "${@}" > /dev/null 2>&1 & disown
}
EOL

    source $HOME/.bashrc

    echo "Cursor AI IDE installation complete. You can find it in your application menu."
else
    echo "Cursor AI IDE is already installed."
fi
}

installCursor

@fcsouza
Copy link

fcsouza commented May 17, 2025

if i need to update cursor, what i need to do?the update button inside cursor its not working.

@bpinazmul18
Copy link

@bpinazmul18 Glad I could help! And now, Cursor offers 1 year free for student emails. Let's get it!

Sorry, I couldn't because my university doesn't provide email.

@vadim-davydchenko
Copy link

if i need to update cursor, what i need to do?the update button inside cursor its not working.

Only download latest Cursor.Appimage and replace current Cursor.Appimage with new one.
Maybe has another approach, but I don't know, I use this method.

@Kaushik-Iyer
Copy link

For new ubuntu installs, you might need to install libfuse2 as well. I ran the above script, and it installed cursor properly, but without libfuse2 you cannot start up AppImages. Therefore run this after the above script:
sudo apt update
sudo apt install libfuse2

@hieutt192
Copy link

hieutt192 commented May 21, 2025

if i need to update cursor, what i need to do?the update button inside cursor its not working.

@fcsouza cc @vadim-davydchenko I updated the script to update the new Cursor.

For new ubuntu installs, you might need to install libfuse2 as well. I ran the above script, and it installed cursor properly, but without libfuse2 you cannot start up AppImages. Therefore run this after the above script:
sudo apt update
sudo apt install libfuse2

@Kaushik-Iyer Thanks for your comment. I have added the libfuse2 installation to the install function.

The new scrpit to update and install cursor: Github

Everyone can choose either the install option or the update option. Please refer to the README.md file for installation instructions or update.

image

@tarcisiomiranda
Copy link

tarcisiomiranda commented May 24, 2025

This Python script installs or removes the Cursor AI IDE for the current user on Linux, always downloading the latest stable version. It sets up the AppImage, icon, desktop launcher, and bash alias, using a Firefox User-Agent header to avoid HTTP 403 errors.

#!/usr/bin/env python3
"""
cursor_installer.py – installs or removes the Cursor AI IDE for the current user only.

✔ AppImage → ~/.local/bin/cursor.appimage
✔ Icon     → ~/.local/share/icons/cursor.png
✔ Launcher  → ~/.local/share/applications/cursor.desktop

Usage:
    python3 cursor_installer.py install
    python3 cursor_installer.py uninstall

Notes:
* The latest AppImage is obtained from the official Cursor endpoint
    `https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable`.
* Some servers return **HTTP 403** if the *User-Agent* header is missing.
    This script now sends a generic User-Agent to avoid blocks.
"""

import argparse
import json
import os
import stat
import sys
import urllib.request
from pathlib import Path
from urllib.error import HTTPError

API_URL = "https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable"
ICON_URL = (
    "https://us1.discourse-cdn.com/flex020/uploads/cursor1/original/2X/a/a4f78589d63edd61a2843306f8e11bad9590f0ca.png"
)

HOME = Path.home()
APPIMAGE_PATH = HOME / ".local" / "bin" / "cursor.appimage"
ICON_PATH = HOME / ".local" / "share" / "icons" / "cursor.png"
DATA_HOME = Path(os.environ.get("XDG_DATA_HOME", HOME / ".local" / "share"))
DESKTOP_ENTRY_PATH = DATA_HOME / "applications" / "cursor.desktop"
BASHRC_ALIAS_COMMENT = "# Cursor alias"

HEADERS = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}

def _ensure_dirs():
    for d in (
        APPIMAGE_PATH.parent,
        ICON_PATH.parent,
        DESKTOP_ENTRY_PATH.parent,
    ):
        d.mkdir(parents=True, exist_ok=True)


def _download(url: str, dest: Path):
    """Downloads a file (supports JSON wrapper) and adds User-Agent."""
    print(f"Downloading {url}{dest}")
    try:
        req = urllib.request.Request(url, headers=HEADERS)
        with urllib.request.urlopen(req) as resp:
            ctype = resp.headers.get("Content-Type", "")
            if "application/json" in ctype:
                data = json.loads(resp.read().decode())
                final_url = data.get("url") or data.get("downloadUrl")
                if not final_url:
                    print("[ERROR] Unexpected download JSON.")
                    sys.exit(1)
                return _download(final_url, dest)
            with open(dest, "wb") as fp:
                while True:
                    chunk = resp.read(8192)
                    if not chunk:
                        break
                    fp.write(chunk)
    except HTTPError as e:
        print(f"[ERROR] Download failed: {e}")
        sys.exit(1)


def _add_exec_permission(path: Path):
    path.chmod(path.stat().st_mode | stat.S_IXUSR)


def _write_desktop_entry():
    entry = f"""[Desktop Entry]
Name=Cursor AI IDE
Exec={APPIMAGE_PATH} --no-sandbox
Icon={ICON_PATH}
Type=Application
Categories=Development;
"""
    DESKTOP_ENTRY_PATH.write_text(entry)
    print(f"Launcher created at {DESKTOP_ENTRY_PATH}")


def _add_alias_to_bashrc():
    bashrc = HOME / ".bashrc"
    if bashrc.exists() and BASHRC_ALIAS_COMMENT in bashrc.read_text():
        return
    alias_block = f"""

{BASHRC_ALIAS_COMMENT}
function cursor() {{
    "{APPIMAGE_PATH}" --no-sandbox "$@" > /dev/null 2>&1 & disown
}}
"""
    with bashrc.open("a") as fp:
        fp.write(alias_block)
    print("Alias added to ~/.bashrc (reopen shell).")


def _remove_alias_from_bashrc():
    bashrc = HOME / ".bashrc"
    if not bashrc.exists():
        return
    lines = bashrc.read_text().splitlines()
    new_lines = []
    skip = False
    for line in lines:
        if line.strip() == BASHRC_ALIAS_COMMENT:
            skip = True
            continue
        if skip and line.startswith("}"):
            skip = False
            continue
        if not skip:
            new_lines.append(line)
    bashrc.write_text("\n".join(new_lines))

def install():
    if APPIMAGE_PATH.exists():
        print("Cursor AI IDE is already installed.")
        return

    print("Installing Cursor AI IDE…")
    _ensure_dirs()
    _download(API_URL, APPIMAGE_PATH)
    _add_exec_permission(APPIMAGE_PATH)
    _download(ICON_URL, ICON_PATH)
    _write_desktop_entry()
    _add_alias_to_bashrc()
    print("Installation complete! It will appear in the applications menu.")


def uninstall():
    print("Removing Cursor AI IDE…")
    for p in (APPIMAGE_PATH, ICON_PATH, DESKTOP_ENTRY_PATH):
        if p.exists():
            print(f"Deleting {p}")
            p.unlink()
    _remove_alias_from_bashrc()
    for d in (
        APPIMAGE_PATH.parent,
        ICON_PATH.parent,
        DESKTOP_ENTRY_PATH.parent,
    ):
        try:
            d.rmdir()
        except OSError:
            pass
    print("Uninstallation complete.")

def main():
    parser = argparse.ArgumentParser(description="Installs or removes the Cursor AI IDE for the current user only.")
    parser.add_argument("action", choices=["install", "uninstall"], help="Desired action")
    args = parser.parse_args()

    if args.action == "install":
        install()
    elif args.action == "uninstall":
        uninstall()
    else:
        print("Invalid action. Please use 'install' or 'uninstall'.")
        sys.exit(1)


if __name__ == "__main__":
    main()

@luisbonilla90
Copy link

@tarcisiomiranda this is awesome, thank you!
I've done this manually and just added a function just like you suggested there but a little different:

cursor() {
    nohup ~/Applications/Cursor.AppImage --no-sandbox "$@" > /dev/null 2>&1 &
    printf ""
}

I'm going to try disown.

@jonathan-apptega
Copy link

jonathan-apptega commented May 29, 2025

This Python script installs or removes the Cursor AI IDE for the current user on Linux, always downloading the latest stable version. It sets up the AppImage, icon, desktop launcher, and bash alias, using a Firefox User-Agent header to avoid HTTP 403 errors.

#!/usr/bin/env python3
"""
cursor_installer.py – installs or removes the Cursor AI IDE for the current user only.

✔ AppImage → ~/.local/bin/cursor.appimage
✔ Icon     → ~/.local/share/icons/cursor.png
✔ Launcher  → ~/.local/share/applications/cursor.desktop

Usage:
    python3 cursor_installer.py install
    python3 cursor_installer.py uninstall

Notes:
* The latest AppImage is obtained from the official Cursor endpoint
    `https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable`.
* Some servers return **HTTP 403** if the *User-Agent* header is missing.
    This script now sends a generic User-Agent to avoid blocks.
"""

import argparse
import json
import os
import stat
import sys
import urllib.request
from pathlib import Path
from urllib.error import HTTPError

API_URL = "https://www.cursor.com/api/download?platform=linux-x64&releaseTrack=stable"
ICON_URL = (
    "https://us1.discourse-cdn.com/flex020/uploads/cursor1/original/2X/a/a4f78589d63edd61a2843306f8e11bad9590f0ca.png"
)

HOME = Path.home()
APPIMAGE_PATH = HOME / ".local" / "bin" / "cursor.appimage"
ICON_PATH = HOME / ".local" / "share" / "icons" / "cursor.png"
DATA_HOME = Path(os.environ.get("XDG_DATA_HOME", HOME / ".local" / "share"))
DESKTOP_ENTRY_PATH = DATA_HOME / "applications" / "cursor.desktop"
BASHRC_ALIAS_COMMENT = "# Cursor alias"

HEADERS = {
    "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
    "(KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}

def _ensure_dirs():
    for d in (
        APPIMAGE_PATH.parent,
        ICON_PATH.parent,
        DESKTOP_ENTRY_PATH.parent,
    ):
        d.mkdir(parents=True, exist_ok=True)


def _download(url: str, dest: Path):
    """Downloads a file (supports JSON wrapper) and adds User-Agent."""
    print(f"Downloading {url}{dest}")
    try:
        req = urllib.request.Request(url, headers=HEADERS)
        with urllib.request.urlopen(req) as resp:
            ctype = resp.headers.get("Content-Type", "")
            if "application/json" in ctype:
                data = json.loads(resp.read().decode())
                final_url = data.get("url") or data.get("downloadUrl")
                if not final_url:
                    print("[ERROR] Unexpected download JSON.")
                    sys.exit(1)
                return _download(final_url, dest)
            with open(dest, "wb") as fp:
                while True:
                    chunk = resp.read(8192)
                    if not chunk:
                        break
                    fp.write(chunk)
    except HTTPError as e:
        print(f"[ERROR] Download failed: {e}")
        sys.exit(1)


def _add_exec_permission(path: Path):
    path.chmod(path.stat().st_mode | stat.S_IXUSR)


def _write_desktop_entry():
    entry = f"""[Desktop Entry]
Name=Cursor AI IDE
Exec={APPIMAGE_PATH} --no-sandbox
Icon={ICON_PATH}
Type=Application
Categories=Development;
"""
    DESKTOP_ENTRY_PATH.write_text(entry)
    print(f"Launcher created at {DESKTOP_ENTRY_PATH}")


def _add_alias_to_bashrc():
    bashrc = HOME / ".bashrc"
    if bashrc.exists() and BASHRC_ALIAS_COMMENT in bashrc.read_text():
        return
    alias_block = f"""

{BASHRC_ALIAS_COMMENT}
function cursor() {{
    "{APPIMAGE_PATH}" --no-sandbox "$@" > /dev/null 2>&1 & disown
}}
"""
    with bashrc.open("a") as fp:
        fp.write(alias_block)
    print("Alias added to ~/.bashrc (reopen shell).")


def _remove_alias_from_bashrc():
    bashrc = HOME / ".bashrc"
    if not bashrc.exists():
        return
    lines = bashrc.read_text().splitlines()
    new_lines = []
    skip = False
    for line in lines:
        if line.strip() == BASHRC_ALIAS_COMMENT:
            skip = True
            continue
        if skip and line.startswith("}"):
            skip = False
            continue
        if not skip:
            new_lines.append(line)
    bashrc.write_text("\n".join(new_lines))

def install():
    if APPIMAGE_PATH.exists():
        print("Cursor AI IDE is already installed.")
        return

    print("Installing Cursor AI IDE…")
    _ensure_dirs()
    _download(API_URL, APPIMAGE_PATH)
    _add_exec_permission(APPIMAGE_PATH)
    _download(ICON_URL, ICON_PATH)
    _write_desktop_entry()
    _add_alias_to_bashrc()
    print("Installation complete! It will appear in the applications menu.")


def uninstall():
    print("Removing Cursor AI IDE…")
    for p in (APPIMAGE_PATH, ICON_PATH, DESKTOP_ENTRY_PATH):
        if p.exists():
            print(f"Deleting {p}")
            p.unlink()
    _remove_alias_from_bashrc()
    for d in (
        APPIMAGE_PATH.parent,
        ICON_PATH.parent,
        DESKTOP_ENTRY_PATH.parent,
    ):
        try:
            d.rmdir()
        except OSError:
            pass
    print("Uninstallation complete.")

def main():
    parser = argparse.ArgumentParser(description="Installs or removes the Cursor AI IDE for the current user only.")
    parser.add_argument("action", choices=["install", "uninstall"], help="Desired action")
    args = parser.parse_args()

    if args.action == "install":
        install()
    elif args.action == "uninstall":
        uninstall()
    else:
        print("Invalid action. Please use 'install' or 'uninstall'.")
        sys.exit(1)


if __name__ == "__main__":
    main()

This script is very nice, but it is missing the installation of libfuse when required, so you will have to install it before running.

sudo apt-get install fuse libfuse2

@tarcisiomiranda
Copy link

@jonathan-apptega thanks for the comment
@luisbonilla90 yeah, the nohup command is better.

Just updated the gist here:
https://gist.github.com/tarcisiomiranda/c9059e4071dcdaafa8a16eb6ae049d33

@dennisushi
Copy link

dennisushi commented Jun 5, 2025

This script is very nice, but it is missing the installation of libfuse when required, so you will have to install it before running.

Please put a warning in your comment - installing fuse on Ubuntu 24 can break systems and we don't want some person to randomly copy and paste it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment