scripts: Parse update feed with python3 if jq is missing

Jumpboxes may lack jq; requiring it made the documented manual path
fail closed for the wrong reason. Keep python3 as the rewrite tool
and the feed parser fallback, and fail loudly when neither parser
nor curl/sha256sum is present. Reject tiny/non-deb downloads so a
failed fetch cannot look like a successful bump.
This commit is contained in:
ash
2026-09-03 05:16:02 +00:00
parent 1faa355d6e
commit fe03fed004
3 changed files with 117 additions and 21 deletions
+106 -18
View File
@@ -1,20 +1,69 @@
#!/usr/bin/env bash
# Bump PKGBUILD/.SRCINFO when Cursor publishes a newer linux-x64 Grok Bot.
# Requires curl, sha256sum, python3. jq is optional (python3 parses the feed).
# Fail loudly; do not leave a half-applied bump that looks like "up to date".
set -euo pipefail
cd "$(dirname "$0")/.."
for cmd in curl jq python3 sha256sum; do
command -v "$cmd" >/dev/null || { echo "error: missing required command: $cmd" >&2; exit 1; }
done
need_cmd() { command -v "$1" >/dev/null 2>&1; }
missing=()
need_cmd curl || missing+=(curl)
need_cmd sha256sum || missing+=(sha256sum)
need_cmd python3 || missing+=(python3)
if ((${#missing[@]})); then
echo "error: missing required tools: ${missing[*]}" >&2
exit 1
fi
if ! need_cmd jq; then
echo "note: jq not found; parsing update feed with python3" >&2
fi
FEED='https://api2.cursor.sh/updates/api/update/linux-x64/sand/0.0.0/00000000-0000-0000-0000-000000000000/stable'
resp=$(curl -fsSL "$FEED")
version=$(jq -r .version <<<"$resp")
commit=$(jq -r .url <<<"$resp" | sed -E 's#.*/stable/([^/]+)/.*#\1#')
current=$(sed -n 's/^pkgver=//p' PKGBUILD)
echo "Fetching update feed..."
if ! resp=$(curl -fsSL --retry 3 --retry-delay 2 "$FEED"); then
echo "error: failed to fetch update feed: $FEED" >&2
exit 1
fi
if [[ -z "$resp" ]]; then
echo "error: empty update feed from $FEED" >&2
exit 1
fi
if [[ -z "$version" || "$version" == "null" || ! "$commit" =~ ^[0-9a-f]{40}$ ]]; then
echo "error: could not parse update feed: $resp" >&2
feed_field() {
local field="$1"
if need_cmd jq; then
jq -er --arg f "$field" '.[$f] | select(. != null and . != "")' <<<"$resp"
else
python3 -c '
import json, sys
d = json.loads(sys.stdin.read())
v = d.get(sys.argv[1])
if not isinstance(v, (str, int, float)) or v == "":
raise SystemExit(1)
print(v)
' "$field" <<<"$resp"
fi
}
if ! version=$(feed_field version); then
echo "error: could not parse .version from update feed: $resp" >&2
exit 1
fi
if ! url=$(feed_field url); then
echo "error: could not parse .url from update feed: $resp" >&2
exit 1
fi
commit=$(sed -E 's#.*/stable/([^/]+)/.*#\1#' <<<"$url")
current=$(sed -n 's/^pkgver=//p' PKGBUILD | head -1)
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*$ ]]; then
echo "error: feed version looks wrong: $version" >&2
exit 1
fi
if [[ ! "$commit" =~ ^[0-9a-f]{40}$ ]]; then
echo "error: could not parse 40-char commit from feed url: $url" >&2
exit 1
fi
if [[ -z "$current" ]]; then
@@ -30,9 +79,28 @@ echo "Bump $current -> $version (commit $commit)"
deb_url="https://downloads.cursor.com/grokbot/stable/${commit}/linux/x64/grok-bot_${version}_amd64.deb"
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
curl -fsSL --retry 3 "$deb_url" -o "$tmp"
echo "Downloading $deb_url"
if ! curl -fL --retry 3 --retry-delay 2 -o "$tmp" "$deb_url"; then
echo "error: failed to download deb: $deb_url" >&2
exit 1
fi
sz=$(wc -c <"$tmp")
if ((sz < 1000000)); then
echo "error: downloaded deb too small (${sz} bytes): $deb_url" >&2
exit 1
fi
magic=$(head -c 8 "$tmp" | tr -d '\0')
if [[ "$magic" != '!<arch>'* ]]; then
echo "error: download is not a .deb (missing ar magic): $deb_url" >&2
exit 1
fi
deb_sha=$(sha256sum "$tmp" | cut -d' ' -f1)
shim_sha=$(sha256sum grok-bot-launch.sh | cut -d' ' -f1)
if [[ ! "$deb_sha" =~ ^[0-9a-f]{64}$ || ! "$shim_sha" =~ ^[0-9a-f]{64}$ ]]; then
echo "error: sha256sum failed (deb=$deb_sha shim=$shim_sha)" >&2
exit 1
fi
# Reset pkgrel on upstream version bumps.
sed -i \
@@ -41,9 +109,11 @@ sed -i \
-e "s/^pkgrel=.*/pkgrel=1/" \
PKGBUILD
newver=$(sed -n 's/^pkgver=//p' PKGBUILD)
if [[ "$newver" != "$version" ]]; then
echo "error: PKGBUILD pkgver rewrite failed (got ${newver@Q}, expected ${version@Q})" >&2
newver=$(sed -n 's/^pkgver=//p' PKGBUILD | head -1)
newcommit=$(sed -n 's/^_commit=//p' PKGBUILD | head -1)
newrel=$(sed -n 's/^pkgrel=//p' PKGBUILD | head -1)
if [[ "$newver" != "$version" || "$newcommit" != "$commit" || "$newrel" != "1" ]]; then
echo "error: PKGBUILD rewrite did not stick (pkgver=$newver _commit=$newcommit pkgrel=$newrel)" >&2
exit 1
fi
@@ -60,20 +130,29 @@ p.write_text(text2)
PY
python3 - "$version" "$commit" "$deb_sha" "$shim_sha" <<'PY'
import pathlib, re, sys
import pathlib, sys
version, commit, deb_sha, shim_sha = sys.argv[1:5]
p = pathlib.Path(".SRCINFO")
if not p.is_file():
raise SystemExit(".SRCINFO missing")
lines = []
saw_sha = False
saw_pkgver = saw_pkgrel = saw_source = saw_noextract = False
for line in p.read_text().splitlines(True):
if line.startswith("\tpkgver ="):
lines.append(f"\tpkgver = {version}\n"); continue
lines.append(f"\tpkgver = {version}\n"); saw_pkgver = True; continue
if line.startswith("\tpkgrel ="):
lines.append("\tpkgrel = 1\n"); continue
lines.append("\tpkgrel = 1\n"); saw_pkgrel = True; continue
if line.startswith("\tsource = https://downloads.cursor.com/grokbot/stable/"):
lines.append(f"\tsource = https://downloads.cursor.com/grokbot/stable/{commit}/linux/x64/grok-bot_{version}_amd64.deb\n"); continue
lines.append(
f"\tsource = https://downloads.cursor.com/grokbot/stable/{commit}/linux/x64/grok-bot_{version}_amd64.deb\n"
)
saw_source = True
continue
if line.startswith("\tnoextract = grok-bot_"):
lines.append(f"\tnoextract = grok-bot_{version}_amd64.deb\n"); continue
lines.append(f"\tnoextract = grok-bot_{version}_amd64.deb\n")
saw_noextract = True
continue
if line.startswith("\tsha256sums ="):
if not saw_sha:
lines.append(f"\tsha256sums = {deb_sha}\n")
@@ -81,6 +160,15 @@ for line in p.read_text().splitlines(True):
saw_sha = True
continue
lines.append(line)
missing = [n for n, ok in (
("pkgver", saw_pkgver),
("pkgrel", saw_pkgrel),
("source", saw_source),
("noextract", saw_noextract),
("sha256sums", saw_sha),
) if not ok]
if missing:
raise SystemExit(f".SRCINFO rewrite missed fields: {', '.join(missing)}")
p.write_text("".join(lines))
PY