diff --git a/.github/workflows/update-cursor.yml b/.github/workflows/update-cursor.yml index b8f9663..5ace713 100644 --- a/.github/workflows/update-cursor.yml +++ b/.github/workflows/update-cursor.yml @@ -5,6 +5,10 @@ on: - cron: '0 12 * * *' # Run daily at 12:00 UTC workflow_dispatch: # Allow manual triggering +concurrency: + group: update-cursor + cancel-in-progress: true + jobs: update-cursor: runs-on: ubuntu-latest @@ -30,19 +34,16 @@ jobs: # Run the update script (it will auto-confirm in CI mode) ./update-cursor.sh - - # Capture version info from script output - if [[ -f "$GITHUB_OUTPUT" ]]; then - grep "CURSOR_VERSION_INFO=" "$GITHUB_OUTPUT" || echo "CURSOR_VERSION_INFO=no_update" >> "$GITHUB_OUTPUT" - fi - name: Update flake lock file + if: steps.update-cursor.outputs.CURSOR_VERSION_INFO != 'no_update' run: | nix flake update - name: Test the updated flake + if: steps.update-cursor.outputs.CURSOR_VERSION_INFO != 'no_update' run: | - nix flake check + nix flake check --no-build nix build .#cursor --dry-run - name: Commit and push changes @@ -75,7 +76,7 @@ jobs: git config --local user.email "action@github.com" git config --local user.name "GitHub Action" - git add flake.nix flake.lock + git add flake.nix flake.lock cursor-release.nix git commit -m "$COMMIT_MSG" git push diff --git a/README.md b/README.md index 344a311..42e3c18 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A Nix flake that provides the Cursor code editor with automatic daily updates vi - 🔧 **Manual updates**: Local script for immediate updates - 🛡️ **Robust**: Proper error handling and fallbacks - 📦 **AppImage-based**: Uses Cursor's official AppImage for maximum compatibility -- 🎯 **Linux x86_64**: Optimized for Linux AMD64 architecture +- 🎯 **Linux x86_64 + aarch64**: Supports both AMD64 and ARM64 (aarch64) Linux ## Quick Start @@ -31,18 +31,18 @@ Then use in your configuration: ```nix # For NixOS system configuration environment.systemPackages = with pkgs; [ - cursor.packages.x86_64-linux.cursor + cursor.packages.${pkgs.system}.cursor ]; # For home-manager home.packages = with pkgs; [ - cursor.packages.x86_64-linux.cursor + cursor.packages.${pkgs.system}.cursor ]; # For devShell devShells.default = pkgs.mkShell { packages = with pkgs; [ - cursor.packages.x86_64-linux.cursor + cursor.packages.${pkgs.system}.cursor ]; }; ``` @@ -65,6 +65,8 @@ To manually update Cursor to the latest version: ./update-cursor.sh ``` +Note: on a fresh fork, `cursor-release.nix` may contain a placeholder aarch64 hash until you run the updater once (or trigger the GitHub Action via `workflow_dispatch`). + To update to a specific version: ```bash @@ -78,9 +80,9 @@ The GitHub Actions workflow runs daily and: 1. Checks Cursor's API for the latest version 2. Only updates if a new version is available 3. Downloads the AppImage and calculates SHA256 hash -4. Updates `flake.nix` with new version and hash +4. Updates `cursor-release.nix` with the new version and per-architecture hashes 5. Tests the build and commits changes -6. Creates a GitHub release for tracking +6. Pushes the update back to the repository ### Manual Updates The `update-cursor.sh` script provides: diff --git a/cursor-release.nix b/cursor-release.nix new file mode 100644 index 0000000..7bf10b2 --- /dev/null +++ b/cursor-release.nix @@ -0,0 +1,12 @@ +{ + # Managed by ./update-cursor.sh + # + # Notes: + # - `sha256` values are Nix base32 hashes (as used by `fetchurl { sha256 = "..."; }`). + version = "0.0.0"; + + sha256 = { + x86_64-linux = "0000000000000000000000000000000000000000000000000000"; + aarch64-linux = "0000000000000000000000000000000000000000000000000000"; + }; +} diff --git a/flake.nix b/flake.nix index 2b48909..7b403d6 100644 --- a/flake.nix +++ b/flake.nix @@ -5,12 +5,20 @@ nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; }; - outputs = { self, nixpkgs }: + outputs = { self, nixpkgs }: let - system = "x86_64-linux"; - pkgs = import nixpkgs { inherit system; }; - - buildCursor = { version, url, sha256 }: + lib = nixpkgs.lib; + systems = [ "x86_64-linux" "aarch64-linux" ]; + cursorRelease = import ./cursor-release.nix; + + channelForSystem = { + x86_64-linux = "linux-x64"; + aarch64-linux = "linux-arm64"; + }; + + pkgsFor = system: import nixpkgs { inherit system; }; + + buildCursor = pkgs: { version, url, sha256 }: let src = pkgs.fetchurl { inherit url sha256; }; @@ -103,18 +111,22 @@ }; in { - packages.${system} = { - default = self.packages.${system}.cursor; - cursor = buildCursor { - version = "2.2.43"; - url = "https://downloads.cursor.com/production/32cfbe848b35d9eb320980195985450f244b303d/linux/x64/Cursor-2.2.43-x86_64.AppImage"; - sha256 = "1bgifjyscqymbz0srw8np9pc88gwcccic3z4cnydyfskxhnyj0sr"; # Will be updated by GitHub Actions - }; - }; + packages = lib.genAttrs systems (system: + let + pkgs = pkgsFor system; + version = cursorRelease.version; + channel = channelForSystem.${system} or (throw "Unsupported system: ${system}"); + url = "https://api2.cursor.sh/updates/download/golden/${channel}/cursor/${version}"; + sha256 = cursorRelease.sha256.${system} or "0000000000000000000000000000000000000000000000000000"; + in + { + default = self.packages.${system}.cursor; + cursor = buildCursor pkgs { inherit version url sha256; }; + }); # Overlay for easy integration into other flakes overlays.default = final: prev: { - cursor = self.packages.${system}.cursor; + cursor = self.packages.${final.system}.cursor; }; }; } diff --git a/test-setup.sh b/test-setup.sh index ee098f9..3b8b0fa 100755 --- a/test-setup.sh +++ b/test-setup.sh @@ -63,24 +63,32 @@ fi # Test 4: Check API connectivity echo "4. Testing API connectivity..." -if command -v curl >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then - if curl -s --max-time 10 "https://api2.cursor.sh/updates/check/golden/linux-x64-deb/cursor" | jq -r '.version' >/dev/null 2>&1; then +if command -v curl >/dev/null 2>&1; then + if curl -s --max-time 10 -I "https://api2.cursor.sh/updates/download/golden/linux-x64/cursor/latest" >/dev/null 2>&1; then echo " ✓ Cursor API is accessible" else echo " ⚠ Cursor API not accessible (this might be temporary)" fi else - echo " ⚠ Skipping API test (curl or jq not available)" + echo " ⚠ Skipping API test (curl not available)" fi # Test 5: Check current version echo "5. Checking current version..." -CURRENT_VERSION=$(grep -o 'version = "[^"]*"' flake.nix | head -1 | cut -d'"' -f2) +if [[ -f "cursor-release.nix" ]]; then + CURRENT_VERSION=$(grep -o 'version = "[^"]*"' cursor-release.nix | head -1 | cut -d'"' -f2) +else + CURRENT_VERSION=$(grep -o 'version = "[^"]*"' flake.nix | head -1 | cut -d'"' -f2) +fi echo " Current version: $CURRENT_VERSION" # Test 6: Check if SHA256 is placeholder echo "6. Checking SHA256 hash..." -CURRENT_SHA256=$(grep -o 'sha256 = "[^"]*"' flake.nix | head -1 | cut -d'"' -f2) +if [[ -f "cursor-release.nix" ]]; then + CURRENT_SHA256=$(grep -o 'x86_64-linux = "[^"]*"' cursor-release.nix | head -1 | cut -d'"' -f2) +else + CURRENT_SHA256=$(grep -o 'sha256 = "[^"]*"' flake.nix | head -1 | cut -d'"' -f2) +fi if [[ "$CURRENT_SHA256" == "0000000000000000000000000000000000000000000000000000" ]]; then echo " ⚠ SHA256 is placeholder - needs to be updated" else diff --git a/update-cursor.sh b/update-cursor.sh index 080460b..32a79d5 100755 --- a/update-cursor.sh +++ b/update-cursor.sh @@ -2,11 +2,14 @@ set -euo pipefail -# Script to manually update Cursor version in the flake +# Script to manually update Cursor version/hashes used by the flake # Usage: ./update-cursor.sh [version] SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -FLAKE_FILE="$SCRIPT_DIR/flake.nix" +RELEASE_FILE="$SCRIPT_DIR/cursor-release.nix" + +CHANNEL_FOR_SYSTEM_x86_64_linux="linux-x64" +CHANNEL_FOR_SYSTEM_aarch64_linux="linux-arm64" # Function to get latest version from API redirect get_latest_version() { @@ -23,56 +26,86 @@ get_latest_version() { fi } -# Function to get current version from flake.nix +# Function to get current version from cursor-release.nix get_current_version() { - grep -o 'version = "[^"]*"' "$FLAKE_FILE" | head -1 | cut -d'"' -f2 + grep -o 'version = "[^"]*"' "$RELEASE_FILE" | head -1 | cut -d'"' -f2 } -# Function to update flake.nix -update_flake() { - local version="$1" - local appimage_url="https://api2.cursor.sh/updates/download/golden/linux-x64/cursor/$version" - - echo "Fetching AppImage from: $appimage_url" - - # Get the actual download URL by following redirects - local actual_url - actual_url=$(curl -s -I "$appimage_url" | grep -i location | cut -d' ' -f2 | tr -d '\r\n') - - if [[ -z "$actual_url" ]]; then - echo "Error: Could not get actual download URL" +# Function to compute sha256 for a given system+version +prefetch_sha256() { + local system="$1" + local version="$2" + local channel="" + + case "$system" in + x86_64-linux) channel="$CHANNEL_FOR_SYSTEM_x86_64_linux" ;; + aarch64-linux) channel="$CHANNEL_FOR_SYSTEM_aarch64_linux" ;; + *) echo "Error: Unsupported system: $system" >&2; return 1 ;; + esac + + local url="https://api2.cursor.sh/updates/download/golden/${channel}/cursor/${version}" + + if ! command -v nix-prefetch-url >/dev/null 2>&1; then + echo "Error: nix-prefetch-url not found. Please install Nix (or nix-prefetch-url) and retry." >&2 return 1 fi - - echo "Actual download URL: $actual_url" - - # Get SHA256 hash - local sha256 - if command -v nix-prefetch-url >/dev/null 2>&1; then - sha256=$(nix-prefetch-url --type sha256 "$actual_url") - else - echo "Warning: nix-prefetch-url not found. You'll need to update the SHA256 manually." - sha256="0000000000000000000000000000000000000000000000000000" + + # IMPORTANT: this function is captured by command substitution. Keep stdout as the hash only. + echo "Prefetching ($system) from: $url" >&2 + + local out hash + out="$(nix-prefetch-url --type sha256 "$url" 2>&1)" + echo "$out" >&2 + + # Extract the Nix base32 sha256 hash (52 chars, Nix alphabet). + hash="$(echo "$out" | grep -Eo '[0-9abcdfghijklmnpqrsvwxyz]{52}' | tail -n1 || true)" + if [[ -z "$hash" ]]; then + echo "Error: Could not parse sha256 from nix-prefetch-url output for $system" >&2 + return 1 fi - - echo "SHA256: $sha256" - + + echo "$hash" +} + +# Function to update cursor-release.nix +update_release() { + local version="$1" + + echo "Computing hashes for version: $version" + + local sha_x86 sha_aarch64 + sha_x86="$(prefetch_sha256 x86_64-linux "$version")" + sha_aarch64="$(prefetch_sha256 aarch64-linux "$version")" + + echo "x86_64-linux sha256: $sha_x86" + echo "aarch64-linux sha256: $sha_aarch64" + # Create backup - cp "$FLAKE_FILE" "$FLAKE_FILE.backup" - - # Update flake.nix - be more specific to avoid replacing nixpkgs URL - sed -i "s/version = \"[^\"]*\"/version = \"$version\"/" "$FLAKE_FILE" - sed -i "/cursor = buildCursor {/,/};/s|url = \"[^\"]*\"|url = \"$actual_url\"|" "$FLAKE_FILE" - sed -i "/cursor = buildCursor {/,/};/s/sha256 = \"[^\"]*\"/sha256 = \"$sha256\"/" "$FLAKE_FILE" - - echo "Updated flake.nix with version $version" + cp "$RELEASE_FILE" "$RELEASE_FILE.backup" + + cat > "$RELEASE_FILE" </dev/null 2>&1; then - nix flake check + nix flake check --no-build echo "Flake check passed!" else echo "Warning: nix command not found. Skipping flake check." @@ -110,9 +143,6 @@ main() { fi echo "Update needed: $current_version -> $target_version" - if [[ -n "${GITHUB_OUTPUT:-}" ]]; then - echo "CURSOR_VERSION_INFO=updated:$current_version:$target_version" >> "$GITHUB_OUTPUT" - fi # Check if running in CI/GitHub Actions (auto-confirm) if [[ -n "${CI:-}" ]] || [[ -n "${GITHUB_ACTIONS:-}" ]]; then @@ -124,14 +154,14 @@ main() { fi if [[ $REPLY =~ ^[Yy]$ ]]; then - update_flake "$target_version" + update_release "$target_version" test_flake echo "Update completed successfully!" if [[ -n "${GITHUB_OUTPUT:-}" ]]; then echo "CURSOR_VERSION_INFO=completed:$current_version:$target_version" >> "$GITHUB_OUTPUT" fi echo "You can now commit the changes:" - echo " git add flake.nix" + echo " git add flake.nix cursor-release.nix" echo " git commit -m \"Update Cursor to version $target_version\"" else echo "Update cancelled." @@ -146,9 +176,8 @@ check_dependencies() { if ! command -v curl >/dev/null 2>&1; then missing_deps+=("curl") fi - - if ! command -v jq >/dev/null 2>&1; then - missing_deps+=("jq") + if [[ ! -f "$RELEASE_FILE" ]]; then + missing_deps+=("cursor-release.nix (missing file)") fi if [[ ${#missing_deps[@]} -gt 0 ]]; then