20 Commits

Author SHA1 Message Date
eva 1b51bbddca Merge pull request 'pkgbuild: Bump pkgrel to 3 for polkit update verify' (#18) from packaging/pkgrel-0.39.0-3 into master
Tag release after merge / tag (push) Successful in 2s
Build Arch package / build (push) Successful in 28s
pkgbuild: Bump pkgrel to 3 for polkit update verify (#18)

No shim/feature changes — verify install path for GUI auto-update.
2026-09-04 12:40:19 -05:00
eva f93409a2ac pkgbuild: Bump pkgrel to 3 for polkit update verify
No shim or feature changes — Justin installs 0.39.0-2 then menu-launches to confirm auto-update to -3.
2026-09-04 17:40:09 +00:00
linus 1001abba40 scripts: Prompt polkit for GUI grok-bot auto-update (#17)
Tag release after merge / tag (push) Successful in 2s
Build Arch package / build (push) Successful in 29s
pkexec auth_admin helper for GUI pacman -U; cancel still launches. Blake GO. Closes #16.
2026-09-04 12:30:10 -05:00
ash 56b6c046ef scripts: Prompt polkit for GUI grok-bot auto-update
Desktop Exec=grok-bot has no TTY, so interactive sudo cannot
prompt and auto-update is skipped. Try sudo -n, then pkexec of a
grok-bot-bin-only helper (auth_admin, not NOPASSWD), then
interactive sudo on a TTY. Cancel or deny still launches the
installed binary.

#16
2026-09-04 17:17:02 +00:00
linus a9697913da ci: Open bump PRs instead of pushing protected master (#15)
Tag release after merge / tag (push) Successful in 3s
Bump opens chore/bump-* PR; tag-release.yml tags after master merge. Blake GO. Closes #14.
2026-09-04 11:10:57 -05:00
ash 7d361549e1 ci: Open bump PRs instead of pushing protected master
Nightly update.yml rewrote PKGBUILD then failed with pre-receive hook
declined on protected master and tags. Push chore/bump-$pkgver and open
(or reuse) a PR instead. After merge, tag-release.yml creates v$pkgver
so build.yml still attaches the package.
2026-09-04 15:40:21 +00:00
eva 049b00de10 pkgbuild: Bump grok-bot-bin to 0.39.0 (#13)
Build Arch package / build (push) Successful in 27s
pkgbuild: Bump grok-bot-bin to 0.39.0
2026-09-04 09:55:15 -05:00
eva eb83529a0d pkgbuild: Bump grok-bot-bin to 0.39.0 2026-09-04 14:54:45 +00:00
eva ae6d65637e ci: Prefer job GITEA_TOKEN for package releases
Build Arch package / build (push) Successful in 31s
2026-09-03 16:04:51 +00:00
linus f6f3520415 ci: Replace deleted s1d3sw1ped_bot commit identity (#12)
update.yml commits as eva. Closes #11.
2026-09-03 01:44:48 -05:00
eva 4fc0fa9eaf ci: Replace deleted s1d3sw1ped_bot commit identity
Use eva for scheduled bump commits; push still via job token.
2026-09-03 06:43:41 +00:00
linus e1da61d7b7 scripts: Parse update feed with python3 if jq is missing (#10)
jq optional on stay-current path; fail closed on bad feed/download. Duplicate #9 closed.
2026-09-03 00:18:31 -05:00
ash fe03fed004 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.
2026-09-03 05:17:04 +00:00
linus 1faa355d6e pkgbuild: Bump grok-bot-bin to 0.36.0 (#8)
pkgbuild: Bump grok-bot-bin to 0.36.0

Stay-current path: fail-closed update.sh/workflow, README Updating for cron + manual force. Closes #7.
2026-09-03 00:15:14 -05:00
ash 49a15ebb2d docs: Document stay-current bump path
Tell Justin how cron runs update.sh, commits, tags, and pushes, and how
to force the same bump by hand. Align the release install example with
PKGBUILD 0.36.0 (tag v0.36.0) and keep the makepkg path until CI
publishes the asset.
2026-09-03 05:13:25 +00:00
ash 6b180acd39 ci: Harden update workflow
Fail clearly when curl/jq/python3 are missing, when the feed cannot be
parsed, or when the PKGBUILD rewrite does not stick. A scheduled bump
still commits PKGBUILD+.SRCINFO, tags v$pkgver, and pushes; a no-op is
only an already-current pkgver.
2026-09-03 05:13:25 +00:00
ash b93fe47e90 pkgbuild: Bump grok-bot-bin to 0.36.0
Cursor's linux-x64 feed moved past 0.35.0 (pkgrel 2). Keep Justin on
the current official .deb: pkgver 0.36.0, pkgrel 1, commit
9465f3ae75550511296fabbb7a4b6fc8afe9e408.
2026-09-03 05:13:25 +00:00
s1d3sw1ped_bot 34ded1b6ae chore: Promote develop to master (CONTRIBUTING)
Land CONTRIBUTING.md on default master via develop tip.
2026-09-02 11:34:00 -05:00
s1d3sw1ped_bot e65d3131ba docs: Add CONTRIBUTING
Fixes #4
2026-09-02 11:33:28 -05:00
s1d3sw1ped_bot 6184de4bd3 README: Point pacman -U example at v0.35.0-2
Docs sync: align README with current install/CLI behavior.

Co-authored-by: s1d3sw1ped_bot <s1d3sw1ped+giteabot@gmail.com>
Co-committed-by: s1d3sw1ped_bot <s1d3sw1ped+giteabot@gmail.com>
2026-09-02 10:43:48 -05:00
11 changed files with 493 additions and 47 deletions
+11 -6
View File
@@ -1,7 +1,7 @@
pkgbase = grok-bot-bin
pkgdesc = Grok Bot desktop agent (official .deb repackaged)
pkgver = 0.35.0
pkgrel = 2
pkgver = 0.39.0
pkgrel = 3
url = https://cursor.com/download/bot
arch = x86_64
license = LicenseRef-proprietary
@@ -19,6 +19,7 @@ pkgbase = grok-bot-bin
depends = xdg-utils
optdepends = libappindicator-gtk3: tray icon support
optdepends = curl: auto-update check on launch
optdepends = polkit: graphical password prompt for desktop auto-update (pkexec)
provides = grok-bot
provides = sand
conflicts = grok-bot
@@ -26,10 +27,14 @@ pkgbase = grok-bot-bin
replaces = sand
options = !strip
options = !debug
source = https://downloads.cursor.com/grokbot/stable/1c5a6ceb364c4dd53f0069f72cc4239220ed471e/linux/x64/grok-bot_0.35.0_amd64.deb
source = https://downloads.cursor.com/grokbot/stable/d8bc9c753edddb313047c9c69b480b7f8f321087/linux/x64/grok-bot_0.39.0_amd64.deb
source = grok-bot-launch.sh
noextract = grok-bot_0.35.0_amd64.deb
sha256sums = ed254e819d0f0419a1df9771009363074f65f80d8175d2ee0fc62900087ebd99
sha256sums = 1c5a3599db15816caa839fc9e088ba9798757e44fec89c066ef5f6f3c0972aee
source = pkexec-pacman-u
source = com.s1d3sw1ped.grok-bot-bin.policy
noextract = grok-bot_0.39.0_amd64.deb
sha256sums = ae498adaf71f9ff1734a786163c5807fa50165acb5aa102bb68f43ead96de4fe
sha256sums = 36e1c522f7a3920a850a6235263d82ad5990611612979447c1e9eff92548ac1e
sha256sums = e92ea4534d864d7f302a082a8717a52af9675195af41092d18f15094f74fed45
sha256sums = a9533dfe4d31332ac1505d2e582eff27771dd0107108a5f6289d0d3839431a74
pkgname = grok-bot-bin
+1 -1
View File
@@ -30,7 +30,7 @@ jobs:
- name: Attach package to Gitea release
if: startsWith(github.ref, 'refs/tags/')
env:
GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME}"
+58
View File
@@ -0,0 +1,58 @@
name: Tag release after merge
on:
push:
branches: ['master']
workflow_dispatch:
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
token: ${{ secrets.GITEA_TOKEN }}
- name: Create v$pkgver tag if missing
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
if [[ "${GITHUB_REF:-}" != "refs/heads/master" ]]; then
echo "error: refusing to tag from ${GITHUB_REF:-unset}; only master" >&2
exit 1
fi
pkgver=$(sed -n 's/^pkgver=//p' PKGBUILD | head -1)
if [[ -z "$pkgver" ]]; then
echo "error: could not read pkgver from PKGBUILD" >&2
exit 1
fi
if [[ ! "$pkgver" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z]+)*$ ]]; then
echo "error: PKGBUILD pkgver looks wrong: $pkgver" >&2
exit 1
fi
if [[ -z "${GITHUB_SHA:-}" ]]; then
echo "error: GITHUB_SHA is empty; cannot tag v${pkgver}" >&2
exit 1
fi
tag="v${pkgver}"
existing=$(git ls-remote --tags origin "refs/tags/${tag}")
if [[ -n "$existing" ]]; then
echo "Tag ${tag} already exists; nothing to do"
exit 0
fi
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "error: GITEA_TOKEN is empty; cannot push tag ${tag}" >&2
exit 1
fi
git tag "${tag}" "${GITHUB_SHA}"
if git push origin "refs/tags/${tag}"; then
echo "Pushed tag ${tag} -> ${GITHUB_SHA}"
exit 0
fi
existing=$(git ls-remote --tags origin "refs/tags/${tag}")
if [[ -n "$existing" ]]; then
echo "Tag ${tag} appeared concurrently; nothing to do"
exit 0
fi
echo "error: could not push tag ${tag} for ${GITHUB_SHA}." >&2
echo "error: Manual path: git tag ${tag} && git push origin ${tag} so build.yml can attach the release asset." >&2
exit 1
+103 -8
View File
@@ -10,20 +10,115 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Bump PKGBUILD if a new version is out
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -euo pipefail
sudo apt-get update -qq
sudo apt-get install -y -qq jq curl
before=$(sed -n 's/^pkgver=//p' PKGBUILD)
sudo apt-get install -y -qq jq curl python3
for cmd in jq curl python3; do
command -v "$cmd" >/dev/null || { echo "error: missing $cmd" >&2; exit 1; }
done
before=$(sed -n 's/^pkgver=//p' PKGBUILD | head -1)
./scripts/update.sh
after=$(sed -n 's/^pkgver=//p' PKGBUILD)
after=$(sed -n 's/^pkgver=//p' PKGBUILD | head -1)
if [[ -z "$after" ]]; then
echo "error: could not read pkgver after update.sh" >&2
exit 1
fi
if [[ "$before" == "$after" ]]; then
echo "No bump"
echo "No bump (still $after)"
exit 0
fi
git config user.name 's1d3sw1ped_bot'
git config user.email 's1d3sw1ped_bot@git.s1d3sw1ped.com'
if [[ -z "${GITEA_TOKEN:-}" ]]; then
echo "error: GITEA_TOKEN is empty; cannot open or reuse bump PR" >&2
exit 1
fi
if [[ -z "${GITHUB_REPOSITORY:-}" ]]; then
echo "error: GITHUB_REPOSITORY is empty; cannot open or reuse bump PR" >&2
exit 1
fi
git config user.name 'eva'
git config user.email 's1d3sw1ped+eva@gmail.com'
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}"
# Do not tag here and do not push protected master. Open a bump PR;
# tag-release.yml creates v$pkgver after merge so build.yml can run.
branch="chore/bump-${after}"
git fetch origin "refs/heads/${branch}:refs/remotes/origin/${branch}" || \
echo "note: ${branch} not on origin yet (creating)"
if ! git push --force-with-lease origin "HEAD:refs/heads/${branch}"; then
echo "error: could not push ${branch} (pkgver ${after})." >&2
echo "error: Manual path: commit PKGBUILD+.SRCINFO, open a PR to master, merge, then let tag-release.yml create v${after} (or push that tag yourself)." >&2
exit 1
fi
api="${GITHUB_API_URL:-https://git.s1d3sw1ped.com/api/v1}"
repo="${GITHUB_REPOSITORY}"
title="pkgbuild: Bump grok-bot-bin to ${after}"
body=$(printf '%s\n' \
"Automated PKGBUILD bump from Cursor's linux-x64 feed (${before} → ${after})." \
"" \
"This job does not create a git tag. After this PR merges to master, tag-release.yml should create \`v${after}\` so build.yml can attach the \`.pkg.tar.zst\`.")
find_pr() {
local prs
if ! prs=$(curl -fsS -H "Authorization: token ${GITEA_TOKEN}" \
"${api}/repos/${repo}/pulls?state=open&limit=50"); then
echo "error: could not list open pull requests for ${repo}" >&2
return 1
fi
if ! jq -e 'type == "array"' >/dev/null <<<"$prs"; then
echo "error: unexpected pulls list response for ${repo}" >&2
printf '%s\n' "$prs" >&2
return 1
fi
jq -r --arg h "$branch" \
'[.[] | select(.base.ref == "master" and (.head.ref == $h or ((.head.label // "") | endswith(":" + $h)))) | (.html_url // .url)] | .[0] // empty' \
<<<"$prs"
}
existing=$(find_pr) || {
echo "error: could not look up existing PR for ${branch} → master" >&2
exit 1
}
if [[ -n "$existing" ]]; then
echo "Reusing existing PR: ${existing}"
exit 0
fi
payload=$(jq -n \
--arg title "$title" \
--arg body "$body" \
--arg head "$branch" \
--arg base "master" \
'{title:$title, body:$body, head:$head, base:$base}')
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
set +e
code=$(curl -sS -o "$tmp" -w '%{http_code}' -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "$payload" \
"${api}/repos/${repo}/pulls")
curl_ec=$?
set -e
if [[ $curl_ec -ne 0 ]]; then
echo "error: curl failed creating PR ${branch} → master (exit ${curl_ec})" >&2
exit 1
fi
if [[ "$code" == "201" ]]; then
pr_url=$(jq -r '.html_url // .url // empty' "$tmp")
echo "Opened PR: ${pr_url:-created (${branch} → master)}"
exit 0
fi
if [[ "$code" == "409" || "$code" == "422" ]]; then
existing=$(find_pr) || {
echo "error: PR create returned HTTP ${code} but looking up existing PR failed" >&2
cat "$tmp" >&2 || true
exit 1
}
if [[ -n "$existing" ]]; then
echo "Reusing existing PR: ${existing}"
exit 0
fi
fi
echo "error: could not open or reuse PR ${branch} → master (HTTP ${code})" >&2
cat "$tmp" >&2 || true
exit 1
+45
View File
@@ -0,0 +1,45 @@
# Contributing
## Propose changes
Open a pull request against `develop`. Keep the default branch for releases and
stable tips; land work on `develop` first.
Point at an existing issue when one fits. Prefer a short issue that states the
symptom or request before a large PR.
## Commits
Subject form:
```
area: Imperative summary
```
- **Area** is a real package, directory, or subsystem token (`ci:`, `docs:`,
Go package name). Not a lone filename.
- **Imperative** mood: Fix, Add, Remove — not "Fixed" or "This patch…".
- No trailing period. Aim ≤ ~7075 characters for the whole subject.
- Not conventional-commits (`feat:` / `fix:` / `chore:` as types).
Body explains **why**. Establish the problem, then say what you are doing.
One logical change per commit; split fix and cleanup.
## Pull requests
Title matches the primary commit subject.
- **What** changed
- **Why** (problem and impact)
- **Test** (concrete steps; "CI green" alone is weak)
## Issues and closing
Cite leftover issues by **full URL**. Gitea closes issues when `#N` appears in
merge text, so do not put `#N` in the merge message unless that issue is actually
done. Use `Fixes #N` / `Closes #N` only when the leftover work is finished.
## License
License TBD by owner.
+15 -5
View File
@@ -8,9 +8,9 @@
# https://downloads.cursor.com/grokbot/stable/<commit>/linux/x64/grok-bot_<pkgver>_amd64.deb
pkgname=grok-bot-bin
pkgver=0.35.0
_commit=1c5a6ceb364c4dd53f0069f72cc4239220ed471e
pkgrel=2
pkgver=0.39.0
_commit=d8bc9c753edddb313047c9c69b480b7f8f321087
pkgrel=3
pkgdesc="Grok Bot desktop agent (official .deb repackaged)"
arch=('x86_64')
url="https://cursor.com/download/bot"
@@ -32,6 +32,7 @@ depends=(
optdepends=(
'libappindicator-gtk3: tray icon support'
'curl: auto-update check on launch'
'polkit: graphical password prompt for desktop auto-update (pkexec)'
)
provides=('grok-bot' 'sand')
conflicts=('grok-bot' 'sand')
@@ -40,11 +41,15 @@ options=('!strip' '!debug')
source=(
"https://downloads.cursor.com/grokbot/stable/${_commit}/linux/x64/grok-bot_${pkgver}_amd64.deb"
"grok-bot-launch.sh"
"pkexec-pacman-u"
"com.s1d3sw1ped.grok-bot-bin.policy"
)
noextract=("grok-bot_${pkgver}_amd64.deb")
sha256sums=(
'ed254e819d0f0419a1df9771009363074f65f80d8175d2ee0fc62900087ebd99'
'1c5a3599db15816caa839fc9e088ba9798757e44fec89c066ef5f6f3c0972aee'
'ae498adaf71f9ff1734a786163c5807fa50165acb5aa102bb68f43ead96de4fe'
'36e1c522f7a3920a850a6235263d82ad5990611612979447c1e9eff92548ac1e'
'e92ea4534d864d7f302a082a8717a52af9675195af41092d18f15094f74fed45'
'a9533dfe4d31332ac1505d2e582eff27771dd0107108a5f6289d0d3839431a74'
)
package() {
@@ -56,6 +61,11 @@ package() {
# Keep provides(sand) on PATH; both names run the same shim.
ln -sf grok-bot "$pkgdir/usr/bin/sand"
# pkexec helper + polkit action (auth_admin, not NOPASSWD) for desktop auto-update.
install -Dm755 "$srcdir/pkexec-pacman-u" "$pkgdir/usr/lib/grok-bot-bin/pkexec-pacman-u"
install -Dm644 "$srcdir/com.s1d3sw1ped.grok-bot-bin.policy" \
"$pkgdir/usr/share/polkit-1/actions/com.s1d3sw1ped.grok-bot-bin.policy"
# Always point the menu entry at the PATH shim. Upstream .desktop has used
# Exec="/opt/Grok Bot/grok-bot" (0.30) which bypasses auto-update; rewrite
# every build so a future /opt regression cannot ship again.
+50 -6
View File
@@ -6,6 +6,8 @@ Not affiliated with Cursor / SpaceXAI. The app binary is unchanged; this only re
## Install (build locally)
This tree packages **0.39.0** (`pkgver` / `pkgrel=2` in `PKGBUILD`). `makepkg -si` from git tip builds that version even before a release tag exists.
```bash
git clone https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin.git
cd grok-bot-bin
@@ -28,11 +30,11 @@ makepkg -si
## Install from a release package
When CI has published a `.pkg.tar.zst` on the [Releases](https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin/releases) page:
`pacman -U` installs whatever `.pkg.tar.zst` is attached on [Releases](https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin/releases), which lags git tip until the bump is tagged (see Updating). After `v0.39.0` is tagged, CI (`build.yml`) attaches `grok-bot-bin-0.39.0-1-x86_64.pkg.tar.zst`. Until a new tag's asset exists, use `makepkg -si` for git-tip `pkgver`.
```bash
curl -LO 'https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin/releases/download/v0.30.0/grok-bot-bin-0.30.0-1-x86_64.pkg.tar.zst'
sudo pacman -U grok-bot-bin-0.30.0-1-x86_64.pkg.tar.zst
curl -LO 'https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin/releases/download/v0.39.0/grok-bot-bin-0.39.0-1-x86_64.pkg.tar.zst'
sudo pacman -U grok-bot-bin-0.39.0-1-x86_64.pkg.tar.zst
```
Download first, then `pacman -U` the local file (`LocalFileSigLevel = Optional`). Do not pass the remote URL straight to pacman if signatures are required.
@@ -51,13 +53,55 @@ Exact `Server=` URL may change once the first repo DB lands; `makepkg -si` works
## Updating
`./scripts/update.sh` reads Cursor's linux-x64 update feed, downloads the matching `.deb`, and bumps `pkgver` / `_commit` / `sha256sums` in `PKGBUILD` and `.SRCINFO`.
Stay-current path when Cursor's linux-x64 Grok Bot feed moves. `PKGBUILD` is currently **0.39.0** (pkgrel 2). One updater: `./scripts/update.sh`.
Scheduled Gitea Actions will do the same and build a release package.
### Scheduled bump (Gitea Actions)
`.gitea/workflows/update.yml` runs daily (`cron: '17 6 * * *'`) and on `workflow_dispatch`. It:
1. Runs `./scripts/update.sh` against the live Cursor linux-x64 feed.
2. If `pkgver` changed, commits `PKGBUILD` and `.SRCINFO` as eva.
3. Pushes branch `chore/bump-$pkgver` (force-with-lease if that version is re-run). It does **not** push `master` or create a tag.
4. Opens a PR into `master` via the Gitea API (or reuses an open PR from that head). Title: `pkgbuild: Bump grok-bot-bin to $pkgver`.
`master` and tags stay protected; the bump job must not push those refs. A no-op is only “already at this `pkgver`” (exit 0). Branch push or PR create/reuse failures fail the job.
After that PR merges, `.gitea/workflows/tag-release.yml` (push to `master`, or `workflow_dispatch`) reads `pkgver` from `PKGBUILD`. If tag `v$pkgver` is missing, it creates and pushes it for the master commit; if the tag already exists, it is a no-op. Pushing only that tag does not re-run the PKGBUILD bump.
The tag triggers `.gitea/workflows/build.yml`, which builds the Arch package and attaches `grok-bot-bin-$pkgver-$pkgrel-x86_64.pkg.tar.zst` to the Gitea release. An upstream bump resets `pkgrel` to 1, so 0.39.0 → tag `v0.39.0`.
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.
Scheduled jobs run on the default branch (`master`). Bump PRs target `master` so merge can tag. Product PRs still land on `develop` (see CONTRIBUTING).
### Manual force path
When you do not want to wait for cron:
```bash
# requires curl, python3, sha256sum; jq optional
./scripts/update.sh
```
If the feed is newer, that rewrites `PKGBUILD` and `.SRCINFO`. Commit and open a PR into `master` (same as the scheduled job); after merge, `tag-release.yml` creates `v<pkgver>`. Or tag yourself after merge:
```bash
git add PKGBUILD .SRCINFO
git commit -m "pkgbuild: Bump grok-bot-bin to <pkgver>"
git push origin "HEAD:chore/bump-<pkgver>" # then open a PR into master
# After merge, tag-release.yml creates v<pkgver>. To tag yourself:
git tag "v<pkgver>" # e.g. v0.39.0
git push origin "v<pkgver>"
makepkg -si # install from this tree without waiting for the release asset
```
Install the new version with `makepkg -si`, or (once CI publishes the asset) `pacman -U` from Releases as in the example above.
## Notes
- `/usr/bin/grok-bot` (and `sand`) is a launch shim: unless `GROK_BOT_NO_UPDATE=1`, it checks the latest Gitea release and may `pacman -U` a newer `.pkg.tar.zst` before exec'ing `/opt/Grok Bot/…`. Network/API failures still launch the installed binary.
- Auto-update privilege order: `sudo -n` (passwordless, e.g. lab NOPASSWD) → `pkexec` / polkit when launched from the desktop (no TTY, `DISPLAY` or `WAYLAND_DISPLAY` set) → interactive `sudo` when stdin is a TTY. Cancel or deny still launches the installed binary. Passwordless sudo is **not** the default.
- Desktop / CachyOS: `polkit` (provides `pkexec`) and a session polkit authentication agent (usually already running with KDE / GNOME / Hyprland / etc.) are required for the graphical password prompt. The package installs `/usr/lib/grok-bot-bin/pkexec-pacman-u` and `com.s1d3sw1ped.grok-bot-bin.update` (`auth_admin`, not NOPASSWD). `polkit` is an optdepend.
- If the app menu still launches an old binary without updating, run `/usr/bin/grok-bot` once from a terminal, or `sudo pacman -U` the newer `.pkg.tar.zst` from Releases — the menu must call the PATH shim, not `/opt/Grok Bot/…`.
- Upstream Linux builds still have no in-app updater for the Electron app itself; `scripts/update.sh` bumps the PKGBUILD from Cursor's feed when packaging a new upstream version.
- Upstream Linux builds still have no in-app updater for the Electron app itself; `scripts/update.sh` (cron or manual) is the packaging bump from Cursor's feed.
- `chrome-sandbox` is left non-setuid because the install path contains a space; user namespaces cover sandboxing on CachyOS.
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<vendor>s1d3sw1ped</vendor>
<vendor_url>https://git.s1d3sw1ped.com/s1d3sw1ped/grok-bot-bin</vendor_url>
<action id="com.s1d3sw1ped.grok-bot-bin.update">
<description>Install a grok-bot-bin package update</description>
<message>Authentication is required to update Grok Bot</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>auth_admin</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/lib/grok-bot-bin/pkexec-pacman-u</annotate>
</action>
</policyconfig>
+38 -8
View File
@@ -5,6 +5,43 @@ set -u
REAL_DIR="/opt/Grok Bot"
API_URL='https://git.s1d3sw1ped.com/api/v1/repos/s1d3sw1ped/grok-bot-bin/releases?limit=1'
PKEXEC_HELPER=/usr/lib/grok-bot-bin/pkexec-pacman-u
# Privilege order for pacman -U:
# 1. sudo -n (NOPASSWD, e.g. lab CT113)
# 2. pkexec (graphical polkit; desktop Exec=grok-bot has no TTY)
# 3. interactive sudo (terminal TTY)
# Never default to passwordless sudo. Cancel/deny still launches the installed binary.
_install_update_pkg() {
local pkg=$1
if sudo -n true 2>/dev/null; then
if sudo -n pacman -U --noconfirm "$pkg"; then
return 0
fi
echo "grok-bot: pacman -U failed; launching installed version" >&2
return 1
fi
if [[ ! -t 0 ]] && command -v pkexec >/dev/null 2>&1 && [[ -x "$PKEXEC_HELPER" ]] \
&& [[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ]]; then
if pkexec --disable-internal-agent "$PKEXEC_HELPER" "$pkg"; then
return 0
fi
echo "grok-bot: polkit auth failed or cancelled; launching installed version" >&2
return 1
fi
if [[ -t 0 ]]; then
if sudo pacman -U --noconfirm "$pkg"; then
return 0
fi
fi
echo "grok-bot: need sudo or polkit to auto-update; run: sudo pacman -U <pkg> or launch /usr/bin/grok-bot from a terminal" >&2
echo "grok-bot: pacman -U failed; launching installed version" >&2
return 1
}
_maybe_update() {
[[ "${GROK_BOT_NO_UPDATE:-}" == "1" ]] && return 0
@@ -50,14 +87,7 @@ _maybe_update() {
return 0
fi
# Prefer passwordless sudo; fall back to interactive sudo. Fail soft either way.
if ! sudo -n pacman -U --noconfirm "$pkg" 2>/dev/null; then
if ! sudo pacman -U --noconfirm "$pkg"; then
echo "grok-bot: need sudo to auto-update; run: sudo pacman -U <pkg> or launch /usr/bin/grok-bot from a terminal" >&2
echo "grok-bot: pacman -U failed; launching installed version" >&2
return 0
fi
fi
_install_update_pkg "$pkg" || return 0
}
_maybe_update || true
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Root helper for grok-bot-bin GUI auto-update. Invoked via pkexec only.
# Runs pacman -U on a local grok-bot-bin package — not a generic pacman wrapper.
set -euo pipefail
if [[ $# -ne 1 ]]; then
echo "pkexec-pacman-u: usage: pkexec-pacman-u /path/to/grok-bot-bin-*-x86_64.pkg.tar.zst" >&2
exit 2
fi
pkg=$1
if [[ "$pkg" != /* || "$pkg" == *..* ]]; then
echo "pkexec-pacman-u: refusing path: $pkg" >&2
exit 2
fi
base=$(basename -- "$pkg")
if [[ ! "$base" =~ ^grok-bot-bin-.+-x86_64\.pkg\.tar\.zst$ ]]; then
echo "pkexec-pacman-u: refusing package name: $base" >&2
exit 2
fi
if [[ ! -f "$pkg" || ! -r "$pkg" ]]; then
echo "pkexec-pacman-u: package not found or unreadable: $pkg" >&2
exit 2
fi
# Filename is not enough; require the package metadata name.
info=$(/usr/bin/pacman -Qp -- "$pkg" 2>/dev/null) || {
echo "pkexec-pacman-u: not a pacman package: $pkg" >&2
exit 2
}
name=${info%% *}
if [[ "$name" != grok-bot-bin ]]; then
echo "pkexec-pacman-u: refusing package $name" >&2
exit 2
fi
exec /usr/bin/pacman -U --noconfirm -- "$pkg"
+115 -13
View File
@@ -1,16 +1,73 @@
#!/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")/.."
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)
need_cmd() { command -v "$1" >/dev/null 2>&1; }
if [[ -z "$version" || "$version" == "null" || ! "$commit" =~ ^[0-9a-f]{40}$ ]]; then
echo "Could not parse update feed: $resp" >&2
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'
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
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
echo "error: could not read pkgver from PKGBUILD" >&2
exit 1
fi
if [[ "$version" == "$current" ]]; then
@@ -22,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 \
@@ -33,6 +109,14 @@ sed -i \
-e "s/^pkgrel=.*/pkgrel=1/" \
PKGBUILD
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
python3 - "$deb_sha" "$shim_sha" <<'PY'
import pathlib, re, sys
deb_sha, shim_sha = sys.argv[1], sys.argv[2]
@@ -46,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")
@@ -67,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