Skip to content

Enable Avalonia self-update on macOS and Linux via .tar.gz#4919

Merged
Gabriel Dufresne (GabrielDuf) merged 2 commits into
mainfrom
feat/avalonia-selfupdate-macos-linux-targz
Jun 12, 2026
Merged

Enable Avalonia self-update on macOS and Linux via .tar.gz#4919
Gabriel Dufresne (GabrielDuf) merged 2 commits into
mainfrom
feat/avalonia-selfupdate-macos-linux-targz

Conversation

@GabrielDuf

Copy link
Copy Markdown
Contributor

On macOS and Linux, clicking Check for updates showed "Auto-update is not yet available on this platform" — even though releases for both platforms are now published in productinfo.json alongside Windows.

Consume the published tar.gz archives on macOS and Linux (decision: tar.gz over dmg/deb/rpm for the simplest, no-elevation, in-place replacement).

  • Selection — pick tar.gz by architecture. macOS and Linux both use Type: "tar.gz" and the schema has no OS field, so entries are disambiguated on the macos-/linux- token in the download URL.
  • Extraction — use the system tar. The managed System.Formats.Tar reader rejects these archives' PAX extended headers (InvalidDataException: extended header contains invalid records); bsdtar/GNU tar extract them cleanly and preserve permissions/symlinks.
  • macOS — extract the bundle, verify the Devolutions Team Identifier via codesign -dv (the self-contained .NET bundle cannot pass codesign --verify/spctl, so those are not used as a gate), then swap /Applications/UniGetUI.app (or the running bundle) and relaunch via open.
  • Linux — extract the self-contained tree and swap the running install directory by rename (avoids ETXTBSY on the running binary), then relaunch.
  • Safe swap — a detached helper waits for the current process to exit before touching files, rolls back on failure, and always relaunches. As on Windows, success/failure is confirmed by the relaunched copy through CheckForOrphanedUpdateAttempt() (version match), so no "finished" marker is written on the success path.
  • Dev-build guards — skip the in-place swap when running via the dotnet host or from a bin/Debug · bin/Release · obj tree, and validate the archive contents before committing to the swap.

Integrity and authenticity remain enforced by the SHA256 hash checked against productinfo.json fetched over HTTPS from a trusted host.

The auto-updater showed "Auto-update is not yet available on this
platform" on macOS/Linux because SelectInstallerFile looked for `pkg`
(macOS) and `AppImage` (Linux) artifacts, while productinfo.json now
publishes `tar.gz` (alongside dmg/deb/rpm). No matching artifact was
found, so PlatformArtifactMissingException was thrown on every check.

Consume the published `tar.gz` archives on both platforms:

- SelectInstallerFile selects `tar.gz` by architecture. macOS and Linux
  both use Type "tar.gz", so disambiguate on the `macos-`/`linux-` token
  in the URL (the schema has no OS field).
- Extract with the system `tar`: the managed System.Formats.Tar reader
  rejects the archives' PAX extended headers, whereas bsdtar/GNU tar
  extract them cleanly while preserving permissions and symlinks.
- macOS: extract the bundle, verify the Developer Team Identifier via
  `codesign -dv` (the self-contained .NET bundle cannot pass
  `codesign --verify`, so that is not used as a gate), then swap
  /Applications/UniGetUI.app (or the running bundle) and relaunch.
- Linux: extract the self-contained tree and swap the running install
  directory by rename (avoids ETXTBSY), then relaunch.
- The swap runs in a detached helper that waits for this process to exit
  before touching files, rolls back on failure, and relaunches; as on
  Windows, success/failure is confirmed by the relaunched copy via
  CheckForOrphanedUpdateAttempt(), so no "finished" marker is written.
- Guard against clobbering development builds (dotnet host, bin/Debug,
  bin/Release, obj) and validate the archive contents before swapping.

Integrity/authenticity remain enforced by the SHA256 hash checked
against productinfo.json fetched over HTTPS from a trusted host.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

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.

Pull request overview

This PR enables UniGetUI’s Avalonia self-update flow on macOS and Linux by switching those platforms to consume the published self-contained .tar.gz artifacts from productinfo.json, extracting with the system tar, then performing an in-place swap via a detached helper and relaunch.

Changes:

  • Switch macOS/Linux installer selection + cached installer naming to .tar.gz, disambiguating macOS vs Linux via URL tokens.
  • Implement .tar.gz extraction via system tar, plus platform-specific swap/relaunch logic (macOS .app bundle swap; Linux directory rename swap).
  • Add post-extraction macOS signer Team Identifier validation via codesign -dv (best-effort, hash remains the primary integrity check).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs Outdated
Comment thread src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs Outdated
Comment thread src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs
Address three robustness issues in the macOS/Linux self-update path:

- ExtractTarGz no longer hard-codes /usr/bin/tar. It prefers the standard
  FHS locations and otherwise defers to a bare `tar` so the OS resolves it
  from PATH, fixing extraction on Nix and other non-FHS layouts.
- ExtractTarGz uses a unique per-attempt staging subdirectory instead of a
  single shared one. Update checks can run concurrently (background loop +
  manual check), so the shared directory could be deleted/overwritten
  mid-extraction by a second attempt. Stale leftovers are pruned best-effort
  (only directories older than one hour, so a live attempt is never raced).
- VerifyMacAppSignature reads codesign's stdout and stderr concurrently
  instead of sequentially, so a full pipe on one stream cannot deadlock the
  updater.
@GabrielDuf Gabriel Dufresne (GabrielDuf) merged commit 4f09efa into main Jun 12, 2026
3 checks passed
@GabrielDuf Gabriel Dufresne (GabrielDuf) deleted the feat/avalonia-selfupdate-macos-linux-targz branch June 12, 2026 17:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants