Skip to content

Instantly share code, notes, and snippets.

@mahmoudimus
Last active February 28, 2025 18:29
Show Gist options
  • Save mahmoudimus/cfc05ab147cdc8d4ef2e5c771577e74d to your computer and use it in GitHub Desktop.
Save mahmoudimus/cfc05ab147cdc8d4ef2e5c771577e74d to your computer and use it in GitHub Desktop.
Missing cli-win32-x64.tar.gz File Causes Remote SSH Connection Failure in Cursor
"""
On remote machine:
## windows (powershell)
$ python.exe fix-cursor-remote-ssh-connection-failure.py `
--commit b1e87884330fc271d5eb589e368c35f14e76dec0 `
--username ${YOUR_USERNAME_HERE} `
--file-hash-override 84b9c6d907219bb8c2874f299540eb6a079187a0
## (linux/macos)
$ python fix-cursor-remote-ssh-connection-failure.py \
--commit b1e87884330fc271d5eb589e368c35f14e76dec0 \
--username ${YOUR_USERNAME_HERE} \
--file-hash-override 84b9c6d907219bb8c2874f299540eb6a079187a0
"""
import argparse
import os
import subprocess
import sys
import tempfile
import zipfile
from pathlib import Path
from urllib.error import URLError
from urllib.request import urlopen
def construct_download_url(commit_hash):
"""Construct the download URL for the given commit hash."""
base_url = "cursor.blob.core.windows.net/remote-releases"
return f"https://{base_url}/{commit_hash}/cli-win32-x64.zip"
def download_with_atomic_replace(output_path, download_fn):
"""Template for downloading with atomic replace using a temporary file.
Args:
output_path: Path where the final file should be written
download_fn: Function that takes a temporary Path and performs the actual download
"""
# Create temporary file in the same directory as the target using tempfile.mkstemp()
# On Windows, using the context manager approach might not since those functions
# keep the file handle open, you run into the issue where the file is
# locked—preventing operations like rename (which needs to be performed atomically).
# By using the file descriptor and filename returned by tempfile.mkstemp(), we can
# close the file descriptor immediately using os.close(), so the file isn't locked
# when we later try to rename it.
fd, temp_name = tempfile.mkstemp(dir=output_path.parent)
tmp_path = Path(temp_name)
try:
# Close the file descriptor immediately so we can use the file
os.close(fd)
download_fn(tmp_path)
tmp_path.replace(output_path)
except Exception as e:
tmp_path.unlink(missing_ok=True)
raise e
def download_file_urllib(url, output_path):
"""Download a file from URL using urllib."""
def _do_urllib_download(tmp_path):
with urlopen(url) as response:
tmp_path.write_bytes(response.read())
download_with_atomic_replace(output_path, _do_urllib_download)
def download_file_curl(url, output_path):
"""Download a file from URL using curl."""
def _do_curl_download(tmp_path):
subprocess.run(["curl", "-L", "-o", str(tmp_path), url], check=True)
download_with_atomic_replace(output_path, _do_curl_download)
def download_file(url, output_path, use_curl=False):
"""Download file using the specified method."""
print(f"Downloading from {url}...")
if use_curl:
download_file_curl(url, output_path)
else:
download_file_urllib(url, output_path)
def extract_zip(zip_path, extract_path):
"""Extract the zip file to the specified path."""
print("Extracting zip file...")
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(extract_path)
def rename_executable(temp_dir, commit_hash, file_hash_override):
"""Rename the Cursor executable with the commit hash."""
old_exe_path = temp_dir / "Cursor.exe"
new_exe_name = (
f"Cursor-{file_hash_override if file_hash_override else commit_hash}.exe"
)
new_exe_path = temp_dir / new_exe_name
if not old_exe_path.exists():
raise FileNotFoundError("Cursor.exe not found in the extracted files")
old_exe_path.rename(new_exe_path)
return new_exe_path, new_exe_name
def setup_destination_directory(username):
"""Create and return the destination directory path."""
dest_dir = Path.home().parent / username / ".cursor-server"
dest_dir.mkdir(parents=True, exist_ok=True)
return dest_dir
def move_executable(source_path, dest_dir, exe_name):
"""Move the executable to its final destination."""
final_path = dest_dir / exe_name
source_path.rename(final_path)
return final_path
def execute(args):
# Setup paths and URLs
download_url = construct_download_url(args.commit)
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir = Path(temp_dir) # Convert to Path object for easier manipulation
zip_path = temp_dir / "cursor.zip"
try:
# Download and extract
download_file(download_url, zip_path, args.use_curl)
extract_zip(zip_path, temp_dir)
except URLError as e:
print(f"Error downloading file: {e}")
raise
except subprocess.CalledProcessError as e:
print(f"Error executing curl command: {e}")
raise
# Rename and move executable
new_exe_path, new_exe_name = rename_executable(
temp_dir, args.commit, args.file_hash_override
)
parent_dir = setup_destination_directory(args.username)
fpath = move_executable(new_exe_path, parent_dir, new_exe_name)
print(f"Successfully installed {new_exe_name} to {parent_dir} at {fpath}")
def parse_arguments():
"""Parse and return command line arguments."""
parser = argparse.ArgumentParser(description="Download and setup Cursor executable")
parser.add_argument(
"--commit", required=True, help="Commit hash for the Cursor version"
)
parser.add_argument(
"--file-hash-override",
required=False,
default=None,
help="File hash to rename the downloaded file to",
)
parser.add_argument("--username", required=True, help="Windows username")
parser.add_argument(
"--use-curl", action="store_true", help="Use curl instead of urllib"
)
return parser.parse_args()
def main(args=sys.argv[1:]):
try:
# Parse arguments
args = parse_arguments()
execute(args)
except Exception as e:
print(f"An error occurred: {e}")
raise
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment