fix(core-update): support versioned tarball/zip release assets#908
fix(core-update): support versioned tarball/zip release assets#908senamakel merged 4 commits intotinyhumansai:mainfrom
Conversation
Repo migrated to pnpm via the package.json `packageManager` field in commit a96d9da, but CLAUDE.md still showed `yarn dev`, `yarn core:stage`, etc. Running those commands now errors out with "This project is configured to use pnpm". Update every command example and the lead-in sentence accordingly, and call out the migration so future readers know why.
The core-sidecar auto-update path was looking for a release asset named `openhuman-core-<triple>` (or `<triple>.exe`), but the actual release assets ship as `openhuman-core-<version>-<triple>.tar.gz` on Unix and `…<triple>.zip` on Windows. Asset matching always returned None, so the auto-update aborted with "no matching asset for platform …" and the sidecar stayed pinned to whatever stale version the bundle was built with. Even with a fixed matcher the old `download_binary` path would have written tar.gz bytes straight to the staged binary location and tried to execute them — so this also adds proper archive extraction. Changes: - `find_platform_asset` now matches `openhuman-core-…-<triple>.tar.gz` / `.zip` first (skipping `.sha256` / `.sig` siblings), falling back to the legacy raw-binary name for older releases. - New `extract_archive` reads tar.gz (flate2 + tar) and zip (zip crate), pulls the inner `openhuman-core` (or `.exe`) entry, and atomically renames it into the staged path with executable bit set on Unix. - `check_and_update_core` now downloads to an asset-named path, extracts the inner binary into the canonical `openhuman-core-<triple>` filename that `default_core_bin` discovers, and best-effort cleans up the archive. - New helpers `staged_binary_name`, `is_archive_asset`, `unique_tmp_path`, `finalize_executable` factor the shared logic. - Verbose `[core-update]` logs at every stage (download, extract, staged-path, set_core_bin, restart) so future failures are greppable. - Unit tests cover archive matching, signature/checksum filtering, legacy-fallback matching, archive-extension detection, and end-to-end tar.gz extraction. Verified end-to-end on macOS aarch64: downgraded core to 0.52.20, auto-check fired on startup, downloaded openhuman-core-0.52.26-aarch64-apple-darwin.tar.gz (16.98 MB), extracted, restarted sidecar, core.version reported 0.52.26.
📝 WalkthroughWalkthroughThis PR updates documentation commands from Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Update Initiator
participant Selector as Asset Selector
participant Downloader as Download Handler
participant Extractor as Archive Extractor
participant Stager as File Stager
participant Executor as Process Executor
Client->>Selector: Request core update (version, triple)
Selector->>Selector: Find matching asset\n(prefer versioned archive, exclude .sha256/.sig)
Selector-->>Downloader: Asset URL & filename
Downloader->>Downloader: Download to canonical download path (temp file)
Downloader-->>Extractor: Downloaded file path
alt asset is archive (.tar.gz or .zip)
Extractor->>Extractor: Extract inner `openhuman-core(.exe)`
Extractor-->>Stager: Path to extracted binary
else raw binary
Extractor-->>Stager: Path to downloaded binary
end
Stager->>Stager: Move via temp->atomic rename to staged binary path
Stager->>Stager: Set Unix exec perms (chmod 755) if applicable
Stager-->>Executor: Update process handle to staged binary
Executor-->>Client: Update complete / ready to launch
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src-tauri/src/core_update.rs`:
- Around line 172-183: The asset-matching logic uses archive_ext = ".tar.gz" so
`.tgz` archives never match; update the discovery to accept both formats by
changing the logic around archive_ext/archive_match (the variable and the
closure that filters assets in the code that sets archive_ext and computes
archive_match) to check for either ".tar.gz" or ".tgz" (or reuse the existing
is_archive_asset() predicate) and make the same change in the other occurrences
mentioned (the second match block and the test area around
is_archive_asset()/the new test). Ensure the defensive checks for ".sha256" and
".sig" remain in place.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d561ff8c-1897-4aa0-a414-e76f3debc158
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockapp/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (3)
CLAUDE.mdapp/src-tauri/Cargo.tomlapp/src-tauri/src/core_update.rs
| let archive_ext = if cfg!(windows) { ".zip" } else { ".tar.gz" }; | ||
|
|
||
| // New versioned-archive format: `openhuman-core-0.52.26-aarch64-apple-darwin.tar.gz` | ||
| let archive_match = assets.iter().find(|a| { | ||
| a.name.starts_with("openhuman-core-") | ||
| && a.name.contains(triple) | ||
| && a.name.ends_with(archive_ext) | ||
| // Defensive: avoid matching detached signatures or checksums that | ||
| // happen to share the prefix (e.g. `…tar.gz.sha256`, `…tar.gz.sig`). | ||
| && !a.name.ends_with(".sha256") | ||
| && !a.name.ends_with(".sig") | ||
| }); |
There was a problem hiding this comment.
.tgz assets are recognized too late to ever update.
Line 172 still hardcodes .tar.gz for Unix asset discovery, while is_archive_asset() and the new Line 707 test treat .tgz as supported. A release that ships openhuman-core-<version>-<triple>.tgz will still fall through to “no matching asset”.
♻️ Suggested fix
- let archive_ext = if cfg!(windows) { ".zip" } else { ".tar.gz" };
+ let matches_archive = |name: &str| {
+ if cfg!(windows) {
+ name.ends_with(".zip")
+ } else {
+ name.ends_with(".tar.gz") || name.ends_with(".tgz")
+ }
+ };
// New versioned-archive format: `openhuman-core-0.52.26-aarch64-apple-darwin.tar.gz`
let archive_match = assets.iter().find(|a| {
a.name.starts_with("openhuman-core-")
&& a.name.contains(triple)
- && a.name.ends_with(archive_ext)
+ && matches_archive(&a.name)
// Defensive: avoid matching detached signatures or checksums that
// happen to share the prefix (e.g. `…tar.gz.sha256`, `…tar.gz.sig`).
&& !a.name.ends_with(".sha256")
&& !a.name.ends_with(".sig")
});Also applies to: 208-209, 699-709
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src-tauri/src/core_update.rs` around lines 172 - 183, The asset-matching
logic uses archive_ext = ".tar.gz" so `.tgz` archives never match; update the
discovery to accept both formats by changing the logic around
archive_ext/archive_match (the variable and the closure that filters assets in
the code that sets archive_ext and computes archive_match) to check for either
".tar.gz" or ".tgz" (or reuse the existing is_archive_asset() predicate) and
make the same change in the other occurrences mentioned (the second match block
and the test area around is_archive_asset()/the new test). Ensure the defensive
checks for ".sha256" and ".sig" remain in place.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src-tauri/tauri.conf.json`:
- Around line 77-78: The updater currently skips cryptographic checks in
core_update.rs (look for functions like download_release_assets, stage_update or
apply_update that download and stage GitHub assets) so the pubkey in
tauri.conf.json is ineffective; add minisign signature verification before
staging by retrieving the corresponding .sig (or .minisig) for each binary,
verifying it against the configured public key (from tauri.conf.json) using a
minisign verification library or subprocess call, and aborting with a clear
error/log if verification fails; alternatively, enable and configure the
built-in Tauri updater by setting "active": true and wiring the pubkey in
tauri.conf.json so Tauri handles verification instead of the custom flow.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 80eedf8c-c04a-49a5-8ded-98ab7a336fc5
📒 Files selected for processing (1)
app/src-tauri/tauri.conf.json
| "active": false, | ||
| "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDc0OTREMjkxREFCNUIzRTEKUldUaHM3WGFrZEtVZEJzZWtMTlc5dGxnT0R2Q3hUTWVaclJWSm9JUFpPcVFUV2RBSG5oNFN6UjQK", | ||
| "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdDNDNENzk3QzVCN0MwNDcKUldSSHdMZkZsOWREZkpicit5c2ZWTDJtcDVhT25WMXkzbUJqcW5ucVBDdU1kN29GbUwwRDFkTkoK", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== tauri updater config =="
rg -n -C2 '"updater"|\"active\"|\"pubkey\"' app/src-tauri/tauri.conf.json
echo
echo "== core updater verification-related code (if any) =="
rg -n --type=rust -C3 'verify|signature|sig|sha256|checksum|minisign|ed25519|digest' app/src-tauri/src/core_update.rsRepository: tinyhumansai/openhuman
Length of output: 219
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Checking tauri.conf.json structure and updater config =="
cat app/src-tauri/tauri.conf.json | head -100
echo
echo "== Lines 77-78 specifically =="
sed -n '75,80p' app/src-tauri/tauri.conf.json
echo
echo "== Searching for updater-related config =="
rg -n 'updater|pubkey' app/src-tauri/tauri.conf.json
echo
echo "== Searching for verification logic in core_update.rs =="
if [ -f app/src-tauri/src/core_update.rs ]; then
wc -l app/src-tauri/src/core_update.rs
echo
rg -n 'verify|signature|checksum|hash|minisign' app/src-tauri/src/core_update.rs
else
echo "core_update.rs not found"
fiRepository: tinyhumansai/openhuman
Length of output: 3389
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Context around line 179 (signature/checksum handling) =="
sed -n '170,190p' app/src-tauri/src/core_update.rs
echo
echo "== Context around line 685 (test about skipping signatures) =="
sed -n '680,700p' app/src-tauri/src/core_update.rs
echo
echo "== Broader search: do we verify or skip signatures? =="
rg -n -B2 -A5 'checksum|signature|hash|verify|minisign' app/src-tauri/src/core_update.rs | head -80Repository: tinyhumansai/openhuman
Length of output: 2620
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Searching entire core_update.rs for verify/signature/hash operations =="
rg -n -i 'fn.*verify|fn.*check|fn.*hash|fn.*sign' app/src-tauri/src/core_update.rs
echo
echo "== Check what the actual download/swap logic does =="
rg -n -B3 -A10 'download|extract|swap|replace|binary' app/src-tauri/src/core_update.rs | head -100
echo
echo "== Check if pubkey from config is used anywhere =="
rg -n 'pubkey|minisign|ed25519' app/src-tauri/src/ --type=rustRepository: tinyhumansai/openhuman
Length of output: 4298
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== Checking Tauri updater plugin documentation/behavior =="
# Check if updater plugin is referenced anywhere in the Rust code
rg -n 'updater' app/src-tauri/src/ --type=rust
echo
echo "== Check tauri dependencies for updater plugin =="
grep -i updater app/src-tauri/Cargo.toml || echo "No updater in Cargo.toml"Repository: tinyhumansai/openhuman
Length of output: 480
Updater pubkey rotation is ineffective—the custom updater path lacks signature verification.
The Tauri updater plugin is disabled at Line 77 ("active": false), so the pubkey at Line 78 is not used. The custom update flow in core_update.rs downloads binaries from GitHub, explicitly skips signature files (.sig, .sha256), and stages them without cryptographic verification. The pubkey in the configuration provides no actual protection for the core-sidecar binary swap.
To address this security gap, add minisign verification to core_update.rs before staging binaries, or enable and configure the Tauri updater plugin to enforce it.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/src-tauri/tauri.conf.json` around lines 77 - 78, The updater currently
skips cryptographic checks in core_update.rs (look for functions like
download_release_assets, stage_update or apply_update that download and stage
GitHub assets) so the pubkey in tauri.conf.json is ineffective; add minisign
signature verification before staging by retrieving the corresponding .sig (or
.minisig) for each binary, verifying it against the configured public key (from
tauri.conf.json) using a minisign verification library or subprocess call, and
aborting with a clear error/log if verification fails; alternatively, enable and
configure the built-in Tauri updater by setting "active": true and wiring the
pubkey in tauri.conf.json so Tauri handles verification instead of the custom
flow.
Summary
openhuman-core-<triple>but the actual release assets ship asopenhuman-core-<version>-<triple>.tar.gz(Unix) /.zip(Windows). Asset matching always returnedNoneand updates aborted with "no matching asset for platform …".CLAUDE.mdstill referencedyarneverywhere despite the pnpm migration in a96d9da. Updated all command examples.Changes
app/src-tauri/src/core_update.rsfind_platform_assetnow matchesopenhuman-core-…-<triple>.tar.gz/.zipfirst (skipping.sha256/.sigsiblings), with the legacy raw-binary name kept as fallback for older releases.extract_archivereads tar.gz (flate2+tar) or zip (zip), pulls the inneropenhuman-core(or.exe) entry, and atomically renames it into the staged path with the executable bit set on Unix.check_and_update_corenow downloads to an asset-named path, extracts the inner binary into the canonicalopenhuman-core-<triple>filenamedefault_core_bindiscovers, and best-effort cleans up the archive after extraction.staged_binary_name,is_archive_asset,unique_tmp_path,finalize_executable.[core-update]logs at every stage (download / extract / staged-path /set_core_bin/ restart) for greppable diagnosis.app/src-tauri/Cargo.toml— addsflate2 = "1",tar = "0.4",zip = "2"(default-features-off, deflate-only).CLAUDE.md— yarn → pnpm throughout the commands section, plus a note about the migration.Test plan
cargo test --manifest-path app/src-tauri/Cargo.toml --no-default-features --features cef --lib core_update::— 6/6 pass (existingoutdated_detection+ 5 new tests covering archive matching, signature/checksum filtering, legacy fallback, archive-extension detection, and end-to-end tar.gz extraction with an in-memory fixture).End-to-end manual verification on macOS aarch64: downgraded core to
0.52.20via rootCargo.toml,pnpm core:stage,pnpm dev:app. Logs:Whole upgrade ran in ~14 s; old sidecar shut down cleanly, port freed, new sidecar respawned and RPC reconnected.
Notes for reviewers
extract_archivehelper, with cfg-gated branches for inner filename and archive extension.find_platform_asset. It's dead code against the current release format but cheap to keep for one or two release cycles in case anyone has older releases pinned.style:commit at the end is justcargo fmt(the pre-push hook auto-formatted on first push attempt).Summary by CodeRabbit