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
+7 -1
View File
@@ -33,4 +33,10 @@ jobs:
git add PKGBUILD .SRCINFO git add PKGBUILD .SRCINFO
git commit -m "pkgbuild: Bump grok-bot-bin to ${after}" git commit -m "pkgbuild: Bump grok-bot-bin to ${after}"
git tag "v${after}" git tag "v${after}"
git push origin "HEAD:${GITHUB_REF_NAME}" "v${after}" # Proven on this forge: run 1190 pushed master + tag v0.35.0.
# Still fail the job if push is denied rather than reporting a landed bump.
if ! git push origin "HEAD:${GITHUB_REF_NAME}" "v${after}"; then
echo "error: could not push commit/tag to ${GITHUB_REF_NAME} (v${after})." >&2
echo "error: Manual path: ./scripts/update.sh, commit, PR/merge, then tag v${after} and push the tag so build.yml can attach the release asset." >&2
exit 1
fi
+4 -2
View File
@@ -68,14 +68,16 @@ The tag triggers `.gitea/workflows/build.yml`, which builds the Arch package and
Actions **can** push and tag on this forge. [Run 1190](https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin/actions/runs/1190) (schedule on `master`) bumped 0.30.0 → 0.35.0 and pushed `master` plus tag `v0.35.0`. Actions **can** push and tag on this forge. [Run 1190](https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin/actions/runs/1190) (schedule on `master`) bumped 0.30.0 → 0.35.0 and pushed `master` plus tag `v0.35.0`.
If the script cannot parse the feed, or `curl` / `jq` / `python3` is missing, the job fails. A no-op is only “already at this `pkgver`”. If the script cannot parse the feed, the `.deb` download fails, or `curl` / `python3` / `sha256sum` is missing, the job fails (it is not a silent “up to date”). `jq` is optional; without it the script parses the feed with `python3`. The workflow still `apt-get install`s `jq` for the run. A no-op is only “already at this `pkgver`”.
Scheduled jobs run on the default branch (`master`). Product PRs land on `develop`; promote or run the manual path below if `develop` is ahead of `master`.
### Manual force path ### Manual force path
When you do not want to wait for cron, or you are landing a bump on `develop`: When you do not want to wait for cron, or you are landing a bump on `develop`:
```bash ```bash
# requires curl, jq, python3 # requires curl, python3, sha256sum; jq optional
./scripts/update.sh ./scripts/update.sh
``` ```
+106 -18
View File
@@ -1,20 +1,69 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Bump PKGBUILD/.SRCINFO when Cursor publishes a newer linux-x64 Grok Bot. # 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 set -euo pipefail
cd "$(dirname "$0")/.." cd "$(dirname "$0")/.."
for cmd in curl jq python3 sha256sum; do need_cmd() { command -v "$1" >/dev/null 2>&1; }
command -v "$cmd" >/dev/null || { echo "error: missing required command: $cmd" >&2; exit 1; }
done 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' FEED='https://api2.cursor.sh/updates/api/update/linux-x64/sand/0.0.0/00000000-0000-0000-0000-000000000000/stable'
resp=$(curl -fsSL "$FEED") echo "Fetching update feed..."
version=$(jq -r .version <<<"$resp") if ! resp=$(curl -fsSL --retry 3 --retry-delay 2 "$FEED"); then
commit=$(jq -r .url <<<"$resp" | sed -E 's#.*/stable/([^/]+)/.*#\1#') echo "error: failed to fetch update feed: $FEED" >&2
current=$(sed -n 's/^pkgver=//p' PKGBUILD) 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 feed_field() {
echo "error: could not parse update feed: $resp" >&2 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 exit 1
fi fi
if [[ -z "$current" ]]; then 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" deb_url="https://downloads.cursor.com/grokbot/stable/${commit}/linux/x64/grok-bot_${version}_amd64.deb"
tmp=$(mktemp) tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT 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) deb_sha=$(sha256sum "$tmp" | cut -d' ' -f1)
shim_sha=$(sha256sum grok-bot-launch.sh | 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. # Reset pkgrel on upstream version bumps.
sed -i \ sed -i \
@@ -41,9 +109,11 @@ sed -i \
-e "s/^pkgrel=.*/pkgrel=1/" \ -e "s/^pkgrel=.*/pkgrel=1/" \
PKGBUILD PKGBUILD
newver=$(sed -n 's/^pkgver=//p' PKGBUILD) newver=$(sed -n 's/^pkgver=//p' PKGBUILD | head -1)
if [[ "$newver" != "$version" ]]; then newcommit=$(sed -n 's/^_commit=//p' PKGBUILD | head -1)
echo "error: PKGBUILD pkgver rewrite failed (got ${newver@Q}, expected ${version@Q})" >&2 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 exit 1
fi fi
@@ -60,20 +130,29 @@ p.write_text(text2)
PY PY
python3 - "$version" "$commit" "$deb_sha" "$shim_sha" <<'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] version, commit, deb_sha, shim_sha = sys.argv[1:5]
p = pathlib.Path(".SRCINFO") p = pathlib.Path(".SRCINFO")
if not p.is_file():
raise SystemExit(".SRCINFO missing")
lines = [] lines = []
saw_sha = False saw_sha = False
saw_pkgver = saw_pkgrel = saw_source = saw_noextract = False
for line in p.read_text().splitlines(True): for line in p.read_text().splitlines(True):
if line.startswith("\tpkgver ="): 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 ="): 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/"): 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_"): 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 line.startswith("\tsha256sums ="):
if not saw_sha: if not saw_sha:
lines.append(f"\tsha256sums = {deb_sha}\n") lines.append(f"\tsha256sums = {deb_sha}\n")
@@ -81,6 +160,15 @@ for line in p.read_text().splitlines(True):
saw_sha = True saw_sha = True
continue continue
lines.append(line) 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)) p.write_text("".join(lines))
PY PY