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