Skip to content

fix(core-update): support versioned tarball/zip release assets#908

Merged
senamakel merged 4 commits intotinyhumansai:mainfrom
senamakel:feat/test-update
Apr 25, 2026
Merged

fix(core-update): support versioned tarball/zip release assets#908
senamakel merged 4 commits intotinyhumansai:mainfrom
senamakel:feat/test-update

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented Apr 25, 2026

Summary

  • The core-sidecar auto-update was looking for a release asset named openhuman-core-<triple> but the actual release assets ship as openhuman-core-<version>-<triple>.tar.gz (Unix) / .zip (Windows). Asset matching always returned None and updates aborted with "no matching asset for platform …".
  • Even with a fixed matcher the old code wrote downloaded bytes straight into the staged binary path — for an archive that meant trying to execute a tarball. This PR adds proper extraction.
  • Drive-by: CLAUDE.md still referenced yarn everywhere despite the pnpm migration in a96d9da. Updated all command examples.

Changes

app/src-tauri/src/core_update.rs

  • find_platform_asset now matches openhuman-core-…-<triple>.tar.gz / .zip first (skipping .sha256 / .sig siblings), with the legacy raw-binary name kept as fallback for older releases.
  • New extract_archive reads tar.gz (flate2 + tar) or zip (zip), pulls the inner openhuman-core (or .exe) entry, and atomically renames it into the staged path with the 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 default_core_bin discovers, and best-effort cleans up the archive after extraction.
  • New small helpers: staged_binary_name, is_archive_asset, unique_tmp_path, finalize_executable.
  • Verbose [core-update] logs at every stage (download / extract / staged-path / set_core_bin / restart) for greppable diagnosis.

app/src-tauri/Cargo.toml — adds flate2 = "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 (existing outdated_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.20 via root Cargo.toml, pnpm core:stage, pnpm dev:app. Logs:

    [core-update] running core version: 0.52.20 (minimum: 0.52.28)
    [core-update] sidecar is OLDER than this app build
    [core-update] latest release: 0.52.26
    [core-update] updating core 0.52.20 → 0.52.26 (force: false)
    [core-update] found asset: openhuman-core-0.52.26-aarch64-apple-darwin.tar.gz
    [core-update] downloaded asset to …/binaries/openhuman-core-0.52.26-aarch64-apple-darwin.tar.gz
    [core-update] extracted core binary to …/binaries/openhuman-core-aarch64-apple-darwin
    [core] set_core_bin: updating core binary path …
    [core] spawning dedicated core binary …
    [core] OpenHuman core is ready — listening on http://127.0.0.1:7788 (version 0.52.26)
    [core-update] core updated from 0.52.20 to 0.52.26 and restarted
    

    Whole upgrade ran in ~14 s; old sidecar shut down cleanly, port freed, new sidecar respawned and RPC reconnected.

Notes for reviewers

  • Linux x86_64 / Windows extraction paths are exercised by the matcher tests but not end-to-end manually — the zip path uses the same extract_archive helper, with cfg-gated branches for inner filename and archive extension.
  • I have not removed the legacy raw-binary fallback in 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.
  • Drive-by style: commit at the end is just cargo fmt (the pre-push hook auto-formatted on first push attempt).

Summary by CodeRabbit

  • Bug Fixes
    • Improved updater: reliably handles compressed release archives (.tar.gz on Unix, .zip on Windows), selects correct assets with legacy fallback, and stages updates safely for atomic replacement.
  • Documentation
    • Updated developer/workflow instructions from yarn to pnpm across repo guidance.
  • Chores
    • Added archive handling tooling and updated updater signing key in configuration.

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.
@senamakel senamakel requested a review from a team April 25, 2026 00:17
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

📝 Walkthrough

Walkthrough

This PR updates documentation commands from yarn to pnpm, adds archive extraction dependencies to the Tauri app, and refactors the core updater to select, download, and extract versioned release assets (archives or raw binaries) with atomic staging and permission handling.

Changes

Cohort / File(s) Summary
Documentation
CLAUDE.md
Replaced yarn usages with pnpm across repo-root workflow instructions (dev, tauri dev/build, typecheck, lint, format, Vitest coverage, mock backend, E2E build/flow, Rust test-with-mock).
Tauri dependencies
app/src-tauri/Cargo.toml
Added archive handling crates: flate2, tar, and zip to enable .tar.gz and .zip extraction.
Core update implementation & tests
app/src-tauri/src/core_update.rs, app/src-tauri/src/...tests...
Refactored updater to: select versioned assets (prefer openhuman-core-<ver>-<triple> archives, exclude .sha256/.sig), download to canonical download path, detect archives vs raw binaries, extract inner openhuman-core(.exe) from archives or move raw binaries, perform temp-then-atomic rename to staged binary, set Unix exec perms, update process handle to staged path. Added helpers (unique_tmp_path, finalize_executable, extract_archive, download_to_file, staged_binary_name, is_archive_asset) and tests covering asset matching, signature/checksum skipping, archive detection, and tar.gz extraction.
Tauri config
app/src-tauri/tauri.conf.json
Updated updater plugin pubkey value; active remains false.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through archives, zip and tar,
I peek inside releases near and far,
Extract the core with a tidy spin,
Stage it safe, then let updates begin —
A nibble of bytes, a joyous win! 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding support for versioned tarball and zip release assets in the core-update flow, which is the primary objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 080deae and f8576f7.

⛔ Files ignored due to path filters (2)
  • Cargo.lock is excluded by !**/*.lock
  • app/src-tauri/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • CLAUDE.md
  • app/src-tauri/Cargo.toml
  • app/src-tauri/src/core_update.rs

Comment on lines +172 to +183
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")
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

.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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between f8576f7 and 7aa9b83.

📒 Files selected for processing (1)
  • app/src-tauri/tauri.conf.json

Comment on lines 77 to +78
"active": false,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDc0OTREMjkxREFCNUIzRTEKUldUaHM3WGFrZEtVZEJzZWtMTlc5dGxnT0R2Q3hUTWVaclJWSm9JUFpPcVFUV2RBSG5oNFN6UjQK",
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDdDNDNENzk3QzVCN0MwNDcKUldSSHdMZkZsOWREZkpicit5c2ZWTDJtcDVhT25WMXkzbUJqcW5ucVBDdU1kN29GbUwwRDFkTkoK",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 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.rs

Repository: 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"
fi

Repository: 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 -80

Repository: 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=rust

Repository: 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.

@senamakel senamakel merged commit c191a1f into tinyhumansai:main Apr 25, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant