Skip to content

Commit 358bfc9

Browse files
Add auto-update for desktop and CLI
Desktop uses tauri-plugin-updater to check GitHub Releases on startup, show an install banner, and expose a manual "Check for updates" button in Settings. The app version is shown in every layout header. CLI runs a fire-and-forget update check against the GitHub releases API at startup and prints an upgrade hint if a newer release exists. Release workflow now produces signed updater bundles, collects per-platform signatures, and publishes a merged latest.json alongside the release assets. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ae014bb commit 358bfc9

21 files changed

Lines changed: 1188 additions & 12 deletions

.github/workflows/release.yml

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,15 @@ jobs:
119119
- os: macos-latest
120120
target: aarch64-apple-darwin
121121
artifact: ralph-desktop-macos-arm64
122+
updater_platform: darwin-aarch64
122123
- os: windows-latest
123124
target: x86_64-pc-windows-msvc
124125
artifact: ralph-desktop-windows-x86_64
126+
updater_platform: windows-x86_64
125127
- os: ubuntu-latest
126128
target: x86_64-unknown-linux-gnu
127129
artifact: ralph-desktop-linux-x86_64
130+
updater_platform: linux-x86_64
128131

129132
runs-on: ${{ matrix.os }}
130133

@@ -161,9 +164,12 @@ jobs:
161164
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
162165
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
163166
KEYCHAIN_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
167+
# Updater signing — required to produce .sig files and latest.json.
168+
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
169+
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
164170
with:
165171
args: --target ${{ matrix.target }}
166-
includeUpdaterJson: false
172+
includeUpdaterJson: true
167173

168174
- name: Notarize and staple DMG (macOS)
169175
if: runner.os == 'macOS'
@@ -184,16 +190,65 @@ jobs:
184190
185191
rm -f "$APPLE_API_KEY_PATH"
186192
193+
- name: Collect updater metadata
194+
shell: bash
195+
env:
196+
TAG_NAME: ${{ github.ref_name }}
197+
BUNDLE_DIR: target/${{ matrix.target }}/release/bundle
198+
UPDATER_PLATFORM: ${{ matrix.updater_platform }}
199+
run: |
200+
set -euo pipefail
201+
VERSION="${TAG_NAME#release-}"
202+
# Find the updater bundle + its .sig produced by Tauri. Each OS
203+
# has exactly one updater target: .app.tar.gz (mac), .nsis .exe
204+
# (windows), .AppImage (linux).
205+
case "$UPDATER_PLATFORM" in
206+
darwin-*)
207+
BUNDLE=$(ls "$BUNDLE_DIR"/macos/*.app.tar.gz | head -n1)
208+
;;
209+
windows-*)
210+
BUNDLE=$(ls "$BUNDLE_DIR"/nsis/*-setup.exe | head -n1)
211+
;;
212+
linux-*)
213+
BUNDLE=$(ls "$BUNDLE_DIR"/appimage/*.AppImage | head -n1)
214+
;;
215+
esac
216+
SIG_FILE="${BUNDLE}.sig"
217+
SIG=$(cat "$SIG_FILE")
218+
ASSET_NAME=$(basename "$BUNDLE")
219+
URL="https://github.com/KitStream/ralph/releases/download/${TAG_NAME}/${ASSET_NAME}"
220+
mkdir -p updater-meta
221+
cat > "updater-meta/${UPDATER_PLATFORM}.json" <<EOF
222+
{
223+
"platform": "${UPDATER_PLATFORM}",
224+
"version": "${VERSION}",
225+
"signature": "${SIG//$'\n'/\\n}",
226+
"url": "${URL}",
227+
"bundle": "${BUNDLE}"
228+
}
229+
EOF
230+
187231
- name: Upload desktop artifacts
188232
uses: actions/upload-artifact@v4
189233
with:
190234
name: ${{ matrix.artifact }}
191235
path: |
192236
target/${{ matrix.target }}/release/bundle/dmg/*.dmg
193237
target/${{ matrix.target }}/release/bundle/nsis/*.exe
238+
target/${{ matrix.target }}/release/bundle/nsis/*.exe.sig
194239
target/${{ matrix.target }}/release/bundle/msi/*.msi
240+
target/${{ matrix.target }}/release/bundle/msi/*.msi.sig
195241
target/${{ matrix.target }}/release/bundle/deb/*.deb
196242
target/${{ matrix.target }}/release/bundle/appimage/*.AppImage
243+
target/${{ matrix.target }}/release/bundle/appimage/*.AppImage.sig
244+
target/${{ matrix.target }}/release/bundle/macos/*.app.tar.gz
245+
target/${{ matrix.target }}/release/bundle/macos/*.app.tar.gz.sig
246+
247+
- name: Upload updater metadata
248+
uses: actions/upload-artifact@v4
249+
with:
250+
name: updater-meta-${{ matrix.updater_platform }}
251+
path: updater-meta/*.json
197252

198253
release:
199254
needs: [build-cli, build-desktop]
@@ -209,6 +264,35 @@ jobs:
209264
with:
210265
path: artifacts
211266

267+
- name: Assemble updater latest.json
268+
run: |
269+
set -euo pipefail
270+
VERSION="${{ steps.version.outputs.version }}"
271+
PUB_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
272+
python3 - <<PY
273+
import json, glob, os
274+
platforms = {}
275+
version = None
276+
for path in glob.glob("artifacts/updater-meta-*/*.json"):
277+
with open(path) as f:
278+
meta = json.load(f)
279+
version = meta["version"]
280+
platforms[meta["platform"]] = {
281+
"signature": meta["signature"],
282+
"url": meta["url"],
283+
}
284+
out = {
285+
"version": version or "$VERSION",
286+
"notes": "See release notes on GitHub.",
287+
"pub_date": "$PUB_DATE",
288+
"platforms": platforms,
289+
}
290+
os.makedirs("latest", exist_ok=True)
291+
with open("latest/latest.json", "w") as f:
292+
json.dump(out, f, indent=2)
293+
print(json.dumps(out, indent=2))
294+
PY
295+
212296
- name: Create GitHub Release
213297
uses: softprops/action-gh-release@v2
214298
with:
@@ -219,4 +303,6 @@ jobs:
219303
The desktop app (`.dmg`) and CLI binaries are signed and notarized
220304
with a Developer ID. macOS will check Apple's notary service
221305
online on first launch — no manual `xattr` workaround needed.
222-
files: artifacts/**/*
306+
files: |
307+
artifacts/ralph-*/**/*
308+
latest/latest.json

ARCHITECTURE.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,24 @@ Events are persisted to disk as JSONL for session replay.
8383
- The frontend can reliably prefix-match tool paths against `{project_dir}/.ralph/{branch}-worktree` for display shortening
8484

8585
The frontend's `shortenPath` replaces the worktree prefix with `` for compact display. This uses case-insensitive matching with backslash normalization for Windows compatibility.
86+
87+
## Auto-update
88+
89+
Both frontends notify the user when a newer release is published:
90+
91+
- **Desktop** uses `tauri-plugin-updater`. On startup it fetches
92+
`https://github.com/KitStream/ralph/releases/latest/download/latest.json`,
93+
verifies the bundle signature against the public key in `tauri.conf.json`,
94+
and prompts the user to install. `useAppUpdate` (`src/hooks/useAppUpdate.ts`)
95+
drives the UI; `UpdateBanner` renders the top-of-window banner; `SettingsDialog`
96+
exposes a manual "Check for updates" button.
97+
- **CLI** (`crates/ralph-cli/src/update_check.rs`) does a fire-and-forget HTTPS
98+
request to the GitHub API with a 3 s timeout and prints an upgrade hint to
99+
stderr if the latest release tag is numerically greater than the compiled
100+
`CARGO_PKG_VERSION`. Failures are silent by design — the check must never
101+
affect session execution.
102+
103+
The release workflow (`.github/workflows/release.yml`) builds signed updater
104+
bundles per platform (`TAURI_SIGNING_PRIVATE_KEY` secret), collects their
105+
signatures into per-platform JSON fragments, and merges them into a single
106+
`latest.json` attached to the GitHub Release.

0 commit comments

Comments
 (0)