From 882f1b8c70884f23f7f58601246b4c7cdf6787b0 Mon Sep 17 00:00:00 2001 From: David Helmus Date: Sat, 12 Sep 2026 20:01:43 +0200 Subject: [PATCH] feat(update): refresh the ARM Cursor AppImage during omarchy update The aarch64 installer disabled in-app updates and left a pinned AppImage in place, so Cursor never moved after first install. Fetch the current linux-arm64 release and replace /opt/cursor when the recorded version is behind. Co-authored-by: Cursor --- bin/omarchy-install-cursor | 12 ++- bin/omarchy-update | 1 + bin/omarchy-update-cursor | 36 +++++++ docs/update-process.md | 10 +- test/shell.d/update-cursor-test.sh | 133 +++++++++++++++++++++++++ test/shell.d/update-disk-space-test.sh | 1 + test/shell.d/update-lock-test.sh | 1 + test/shell.d/update-sequence-test.sh | 2 + 8 files changed, 190 insertions(+), 6 deletions(-) create mode 100755 bin/omarchy-update-cursor create mode 100755 test/shell.d/update-cursor-test.sh diff --git a/bin/omarchy-install-cursor b/bin/omarchy-install-cursor index c336ac99775..4e5c59a1005 100755 --- a/bin/omarchy-install-cursor +++ b/bin/omarchy-install-cursor @@ -3,10 +3,11 @@ set -euo pipefail -VERSION="2.6.20" ARCH="$(uname -m)" CURSOR_DIR="/opt/cursor" CURSOR_APPIMAGE="$CURSOR_DIR/cursor.AppImage" +CURSOR_VERSION_FILE="$CURSOR_DIR/version" +CURSOR_RELEASE_API="https://www.cursor.com/api/download?platform=linux-arm64&releaseTrack=latest" CURSOR_BIN="/usr/local/bin/cursor" CURSOR_DESKTOP="/usr/share/applications/cursor.desktop" CURSOR_URL_HANDLER_DESKTOP="/usr/share/applications/cursor-url-handler.desktop" @@ -44,7 +45,7 @@ EOF } install_cursor_aarch64() { - local tmp_dir appimage_url wrapper_path desktop_template url_handler_template icon_size icon_source + local tmp_dir metadata version appimage_url wrapper_path desktop_template url_handler_template icon_size icon_source if [[ -x $CURSOR_BIN && -f $CURSOR_APPIMAGE ]]; then echo "Cursor is already installed at $CURSOR_APPIMAGE" @@ -54,10 +55,12 @@ install_cursor_aarch64() { tmp_dir=$(mktemp -d) trap 'rm -rf "$tmp_dir"' RETURN - appimage_url="https://api2.cursor.sh/updates/download/golden/linux-arm64/cursor/$VERSION" + metadata=$(curl -fsSL "$CURSOR_RELEASE_API") + version=$(jq -er '.version' <<<"$metadata") + appimage_url=$(jq -er '.downloadUrl' <<<"$metadata") wrapper_path="$tmp_dir/cursor" - echo "Downloading Cursor $VERSION for aarch64..." + echo "Downloading Cursor $version for aarch64..." curl -L --fail --output "$tmp_dir/cursor.AppImage" "$appimage_url" chmod +x "$tmp_dir/cursor.AppImage" @@ -78,6 +81,7 @@ EOF sudo mkdir -p "$CURSOR_DIR" sudo install -m 755 "$tmp_dir/cursor.AppImage" "$CURSOR_APPIMAGE" sudo install -m 755 "$wrapper_path" "$CURSOR_BIN" + printf '%s\n' "$version" | sudo tee "$CURSOR_VERSION_FILE" >/dev/null desktop_template="$tmp_dir/squashfs-root/usr/share/applications/cursor.desktop" url_handler_template="$tmp_dir/squashfs-root/usr/share/applications/cursor-url-handler.desktop" diff --git a/bin/omarchy-update b/bin/omarchy-update index e71e808664e..a15bbe7471f 100755 --- a/bin/omarchy-update +++ b/bin/omarchy-update @@ -49,6 +49,7 @@ if [[ ${1:-} == "-y" ]] || omarchy-update-confirm; then omarchy-hook post-update omarchy-update-aur-pkgs omarchy-update-mise + omarchy-update-cursor omarchy-update-orphan-pkgs omarchy-update-analyze-logs diff --git a/bin/omarchy-update-cursor b/bin/omarchy-update-cursor new file mode 100755 index 00000000000..9f71758a76a --- /dev/null +++ b/bin/omarchy-update-cursor @@ -0,0 +1,36 @@ +#!/bin/bash + +# omarchy:summary=Update the Cursor AppImage on Apple Silicon +# omarchy:requires-sudo=true + +set -euo pipefail + +CURSOR_DIR="${OMARCHY_CURSOR_DIR:-/opt/cursor}" +CURSOR_APPIMAGE="$CURSOR_DIR/cursor.AppImage" +CURSOR_VERSION_FILE="$CURSOR_DIR/version" +CURSOR_RELEASE_API="https://www.cursor.com/api/download?platform=linux-arm64&releaseTrack=latest" + +# x86_64 installs Cursor as cursor-bin, so pacman/AUR already moved it. +# Machines that never chose Cursor have no AppImage to refresh. +if [[ $(uname -m) == "aarch64" && -f $CURSOR_APPIMAGE ]]; then + echo -e "\e[32m\nUpdate Cursor\e[0m" + + metadata=$(curl -fsSL "$CURSOR_RELEASE_API") + latest_version=$(jq -er '.version' <<<"$metadata") + download_url=$(jq -er '.downloadUrl' <<<"$metadata") + + if [[ -f $CURSOR_VERSION_FILE && $(<"$CURSOR_VERSION_FILE") == "$latest_version" ]]; then + echo "Cursor $latest_version already installed" + else + tmp_dir=$(mktemp -d) + trap 'rm -rf "$tmp_dir"' EXIT + + echo "Downloading Cursor $latest_version..." + curl -L --fail --output "$tmp_dir/cursor.AppImage" "$download_url" + chmod +x "$tmp_dir/cursor.AppImage" + + sudo mkdir -p "$CURSOR_DIR" + sudo install -m 755 "$tmp_dir/cursor.AppImage" "$CURSOR_APPIMAGE" + printf '%s\n' "$latest_version" | sudo tee "$CURSOR_VERSION_FILE" >/dev/null + fi +fi diff --git a/docs/update-process.md b/docs/update-process.md index bec3350d1d4..89117400840 100644 --- a/docs/update-process.md +++ b/docs/update-process.md @@ -283,6 +283,7 @@ scripts. | `omarchy-update-available` | Update checker for shell widget and post-update refresh. | **Keep.** Could eventually be renamed `omarchy-update-check`, but current name matches widget semantics. | | `omarchy-update-aur-pkgs` | Updates AUR packages with `yay -Sua` if foreign packages exist and AUR is reachable. | **Question.** Omarchy is package-backed now, but users may still install AUR packages. Keep for now. | | `omarchy-update-mise` | Runs `MISE_MINIMUM_RELEASE_AGE=0 mise up` for mise-managed tools — the override of mise's release-age cooldown is the point. | **Keep.** Mise-managed tools are intentionally part of the blessed update path. | +| `omarchy-update-cursor` | Replaces `/opt/cursor/cursor.AppImage` from Cursor's linux-arm64 latest API when the installed version is behind. No-ops on x86_64 and when the AppImage is absent. | **Keep.** The aarch64 installer disables in-app updates (`update.mode: none`), so Omarchy has to own the AppImage. | | `omarchy-update-orphan-pkgs` | Lists orphans and prompts before removal; noninteractive mode never removes. | **Keep for now.** Safe because it is prompt-only. | | `omarchy-update-analyze-logs` | Scans `/tmp/omarchy-update.log` for known failure patterns, currently initramfs generation. | **Keep/expand.** Useful safety net; should grow only for high-signal checks. | | `omarchy-update-restart` | Prompts for reboot after kernel/Hyprland updates, restarts components with `restart-*-required` markers, and always restarts the shell. | **Keep.** Important final step; may eventually include service-restart checks. | @@ -308,10 +309,15 @@ scripts. 4. **Mise remains in the blessed update path** - `omarchy-update-mise` intentionally runs as part of `omarchy update`. -5. **Orphan cleanup stays in the update path for now** +5. **ARM Cursor AppImage is part of the blessed update path** + - `omarchy-update-cursor` runs after mise. + - It no-ops on x86_64, where Cursor is `cursor-bin`, and when `/opt/cursor/cursor.AppImage` is not installed. + - In-app Cursor updates stay disabled; Omarchy replaces the AppImage. + +6. **Orphan cleanup stays in the update path for now** - It is prompt-only and never removes packages noninteractively. -6. **Direct pacman user follow-up is based on actual migration state** +7. **Direct pacman user follow-up is based on actual migration state** - Direct `sudo pacman -Syu` no longer uses a fake user-update marker. - User notifications are shown only when `omarchy-migrate --pending` finds missing per-user migration state. diff --git a/test/shell.d/update-cursor-test.sh b/test/shell.d/update-cursor-test.sh new file mode 100755 index 00000000000..8b81c2f6559 --- /dev/null +++ b/test/shell.d/update-cursor-test.sh @@ -0,0 +1,133 @@ +#!/bin/bash + +set -euo pipefail + +source "$(dirname "$0")/base-test.sh" + +require_command jq + +test_tmp=$(mktemp -d) +trap 'rm -rf "$test_tmp"' EXIT + +stub_bin="$test_tmp/bin" +cursor_dir="$test_tmp/cursor" +curl_log="$test_tmp/curl.log" +latest_version="3.20.17" +download_url="https://downloads.cursor.com/production/deadbeef/linux/arm64/Cursor-3.20.17-aarch64.AppImage" +mkdir -p "$stub_bin" "$cursor_dir" + +cat >"$stub_bin/uname" <<'SH' +#!/bin/bash +if [[ ${1:-} == "-m" ]]; then + printf '%s\n' "${TEST_UNAME_M:-aarch64}" + exit 0 +fi +exec /usr/bin/uname "$@" +SH +chmod +x "$stub_bin/uname" + +cat >"$stub_bin/curl" <<'SH' +#!/bin/bash +printf '%s\n' "$*" >>"$CURL_LOG" + +output="" +url="" +while [[ $# -gt 0 ]]; do + case "$1" in + -o | --output) + output=$2 + shift 2 + ;; + http*) + url=$1 + shift + ;; + *) + shift + ;; + esac +done + +if [[ $url == *"/api/download"* ]]; then + json=$(printf '{"version":"%s","downloadUrl":"%s"}\n' "$CURSOR_LATEST" "$CURSOR_DOWNLOAD_URL") + if [[ -n $output ]]; then + printf '%s' "$json" >"$output" + else + printf '%s' "$json" + fi + exit 0 +fi + +if [[ $url == "$CURSOR_DOWNLOAD_URL" ]]; then + [[ -n $output ]] || exit 1 + printf 'appimage-bytes\n' >"$output" + exit 0 +fi + +echo "unexpected curl url: ${url:-none}" >&2 +exit 1 +SH +chmod +x "$stub_bin/curl" + +cat >"$stub_bin/sudo" <<'SH' +#!/bin/bash +exec "$@" +SH +chmod +x "$stub_bin/sudo" + +run_update() { + : >"$curl_log" + CURL_LOG="$curl_log" \ + CURSOR_LATEST="$latest_version" \ + CURSOR_DOWNLOAD_URL="$download_url" \ + TEST_UNAME_M="${TEST_UNAME_M:-aarch64}" \ + OMARCHY_CURSOR_DIR="$cursor_dir" \ + PATH="$stub_bin:$PATH" \ + "$ROOT/bin/omarchy-update-cursor" >"$test_tmp/out" 2>"$test_tmp/err" +} + +run_update +[[ ! -s $curl_log ]] || fail "aarch64 without an AppImage does not contact Cursor" "$(cat "$curl_log")" +pass "aarch64 without an AppImage is a no-op" + +printf 'old-bytes\n' >"$cursor_dir/cursor.AppImage" +TEST_UNAME_M=x86_64 run_update +[[ ! -s $curl_log ]] || fail "x86_64 does not contact Cursor" "$(cat "$curl_log")" +pass "x86_64 leaves Cursor to cursor-bin" + +printf '%s\n' "$latest_version" >"$cursor_dir/version" +run_update +grep -F '/api/download' "$curl_log" >/dev/null || + fail "a current AppImage still checks the latest version" "$(cat "$curl_log")" +if grep -Fq -- "$download_url" "$curl_log"; then + fail "a current AppImage is downloaded again" "$(cat "$curl_log")" +fi +[[ $(<"$cursor_dir/cursor.AppImage") == "old-bytes" ]] || + fail "a current AppImage is left in place" +pass "matching version skips the download" + +printf '2.6.20\n' >"$cursor_dir/version" +run_update +[[ $(<"$cursor_dir/cursor.AppImage") == "appimage-bytes" ]] || + fail "an older AppImage is replaced" "$(cat "$cursor_dir/cursor.AppImage")" +[[ $(<"$cursor_dir/version") == "$latest_version" ]] || + fail "the installed version file is updated" "$(cat "$cursor_dir/version")" +pass "an older AppImage is replaced" + +printf 'old-bytes\n' >"$cursor_dir/cursor.AppImage" +rm -f "$cursor_dir/version" +run_update +[[ $(<"$cursor_dir/cursor.AppImage") == "appimage-bytes" ]] || + fail "an AppImage without a version file is replaced" +[[ $(<"$cursor_dir/version") == "$latest_version" ]] || + fail "a missing version file is written after the download" +pass "a missing version file is treated as outdated" + +if grep -Fq 'VERSION="2.6.20"' "$ROOT/bin/omarchy-install-cursor"; then + fail "the aarch64 installer still pins Cursor 2.6.20" +fi +grep -Fq 'platform=linux-arm64&releaseTrack=latest' "$ROOT/bin/omarchy-install-cursor" || + fail "the aarch64 installer does not fetch the current Cursor release" +grep -Fq '$CURSOR_VERSION_FILE' "$ROOT/bin/omarchy-install-cursor" || + fail "the aarch64 installer does not record the installed version" +pass "the aarch64 installer fetches current Cursor and records its version" diff --git a/test/shell.d/update-disk-space-test.sh b/test/shell.d/update-disk-space-test.sh index 2c7368ea630..11627435b63 100644 --- a/test/shell.d/update-disk-space-test.sh +++ b/test/shell.d/update-disk-space-test.sh @@ -74,6 +74,7 @@ for command in \ omarchy-migrate \ omarchy-update-aur-pkgs \ omarchy-update-mise \ + omarchy-update-cursor \ omarchy-update-orphan-pkgs \ omarchy-hook \ omarchy-update-analyze-logs \ diff --git a/test/shell.d/update-lock-test.sh b/test/shell.d/update-lock-test.sh index 6c65428dc89..57a085eed0e 100644 --- a/test/shell.d/update-lock-test.sh +++ b/test/shell.d/update-lock-test.sh @@ -42,6 +42,7 @@ for command in \ omarchy-migrate \ omarchy-update-aur-pkgs \ omarchy-update-mise \ + omarchy-update-cursor \ omarchy-update-orphan-pkgs \ omarchy-hook \ omarchy-update-analyze-logs \ diff --git a/test/shell.d/update-sequence-test.sh b/test/shell.d/update-sequence-test.sh index 2dd62b6e43f..06f4e999913 100755 --- a/test/shell.d/update-sequence-test.sh +++ b/test/shell.d/update-sequence-test.sh @@ -26,6 +26,7 @@ steps=( omarchy-hook omarchy-update-aur-pkgs omarchy-update-mise + omarchy-update-cursor omarchy-update-orphan-pkgs omarchy-update-analyze-logs omarchy-update-status @@ -73,6 +74,7 @@ expected_steps() { omarchy-hook \ omarchy-update-aur-pkgs \ omarchy-update-mise \ + omarchy-update-cursor \ omarchy-update-orphan-pkgs \ omarchy-update-analyze-logs \ omarchy-update-status \