Last active
February 28, 2025 18:29
-
-
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
This file contains 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
""" | |
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