From a8b5346de7adf83ffef820a6fa552dcb9eab6352 Mon Sep 17 00:00:00 2001 From: ash Date: Thu, 3 Sep 2026 05:16:02 +0000 Subject: [PATCH] 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. --- .gitea/workflows/update.yml | 8 ++- README.md | 6 +- scripts/update.sh | 124 ++++++++++++++++++++++++++++++------ 3 files changed, 117 insertions(+), 21 deletions(-) diff --git a/.gitea/workflows/update.yml b/.gitea/workflows/update.yml index d55c40a..a430488 100644 --- a/.gitea/workflows/update.yml +++ b/.gitea/workflows/update.yml @@ -33,4 +33,10 @@ jobs: git add PKGBUILD .SRCINFO git commit -m "pkgbuild: Bump grok-bot-bin to ${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 diff --git a/README.md b/README.md index 10fd7fa..dec5bc2 100644 --- a/README.md +++ b/README.md @@ -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`. -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 When you do not want to wait for cron, or you are landing a bump on `develop`: ```bash -# requires curl, jq, python3 +# requires curl, python3, sha256sum; jq optional ./scripts/update.sh ``` diff --git a/scripts/update.sh b/scripts/update.sh index ccbfdfe..ef95ddd 100755 --- a/scripts/update.sh +++ b/scripts/update.sh @@ -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" != '!'* ]]; 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