Skip to content

Auto-updater: stable and release candidate channels, with in-place install - #838

Closed
aidankinzett wants to merge 10 commits into
Eglantine-Community:nx2-experimentalfrom
aidankinzett:updater/tagged-channels
Closed

aidankinzett wants to merge 10 commits into
Eglantine-Community:nx2-experimentalfrom
aidankinzett:updater/tagged-channels

Conversation

@aidankinzett

@aidankinzett aidankinzett commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

The update checker inherited from Eden was switched off in CI and still pointed at Eden's servers. This makes it work for Eglantine's tagged releases.

  • Releases are read from this repo's GitHub releases. Only vX.Y.Z and vX.Y.Z-rcN tags count; daily and branch builds are ignored and do not check for updates themselves. (Branch builds will be addressed in a future PR)
  • Two update channels: Stable gets stable releases only, Release candidate gets both. The channel is a saved setting in Configure > General. The first tagged build a user runs sets it from its own tag, and after that only the user changes it, so someone who installed an rc keeps getting rcs after a stable release lands.
  • The update dialog can skip a version, and checks the download against the size and SHA-256 GitHub publishes for the asset.
  • Help > Check for Updates runs the check on demand.
  • Windows zip installs and AppImages update in place and restart. The previous version is kept and can be brought back from Help > Restore Previous Version. The portable user folder is never touched. Other packages (tar.gz, macOS) save the download to the Downloads folder.
  • CI turns the checker on for stable and rc builds only, and ships eglantine-updater.exe in the Windows zips.

The installer and the Windows helper are ported from Lichii (GPL-3.0-or-later), and the channel model follows Orchid's. Three fixes over Lichii's code: the helper now really links the C runtime statically, manifest lines like ./user/x can no longer get past the user folder protection, and an empty staging folder is refused instead of wiping the install.

Notes for reviewers:

  • The built-in news feed (builtin_news.cpp) used the same Eden release endpoints, so it now reads this repo's releases too. These are currently just download links, would be good to get more detailed release notes.
  • rc3 shipped with the checker off, so rc3 users update by hand one last time. We can release rc4 with just this merged in then people can auto-update from there?
  • The first in-place update has no manifest of the old version, so files the new version dropped are left behind. From the second update on they are removed.
  • The SHA-256 comes from the same GitHub release as the file. It catches broken downloads, not a compromised release. Nothing is signed.

Type of change

  • Bug fix
  • Game compatibility / game-support
  • Performance
  • New feature
  • Refactor / cleanup
  • CI / build
  • Documentation

Testing

  • Tested on Windows
  • Tested on Linux - see comment
  • Tested on macOS - see comment

Windows, MSVC build faked as release v0.1.0-rc2 in a portable folder:

  • offered v0.1.0-rc3 on the Release candidate channel, nothing on Stable, Skip This Version, download + unpack + restart into rc3, user folder unchanged all worked
  • Unit tests for tag parsing, channel selection and the installer pass (375 assertions in 26 cases). They were linked on their own, because the full tests target does not currently build with MSVC.
  • The Qt app builds with ENABLE_UPDATE_CHECKER both on and off.

Games tested:

Not applicable, no emulation changes.

Checklist

  • I built it locally and it compiles without new warnings
  • I did not add any copyrighted game data, keys or firmware
  • Docs updated if needed
  • I added AI Disclosure if one was used

Screenshots / logs

update available update config updated

AI Disclosure: Orchestrated by Fable 5.2 driving other Claude models

@github-actions
github-actions Bot changed the base branch from nx2 to nx2-experimental November 12, 2026 03:05
@aidankinzett
aidankinzett changed the base branch from nx2-experimental to nx2 November 12, 2026 03:05
Stable and release candidate builds are configured with
ENABLE_UPDATE_CHECKER=ON and told which repository to check
(EGLANTINE_UPDATE_REPO) and which release asset they correspond to
(EGLANTINE_RELEASE_PLATFORM). Daily and branch builds keep it off.
The update checker and the built-in news feed still pointed at Eden's
servers. Releases now come from api.github.com for the repository in
EGLANTINE_UPDATE_REPO, and each asset carries the size and SHA-256
GitHub publishes for it.

Release assets are picked by exact name from the platform the build was
made for (EGLANTINE_RELEASE_PLATFORM), with a PGO build falling back to
the standard asset.
Releases are compared by tag (vX.Y.Z or vX.Y.Z-rcN) instead of by string
inequality with the latest release. The stable channel is offered stable
releases only; the release candidate channel is offered both. A stable
release is newer than every candidate of the same version. Releases with
other tags (daily, branch) and releases without an asset for this
platform are skipped.

A failed check now says why (rate limited, HTTP error, no network).
The update channel (Stable or Release candidate) is a saved setting in
Configure > General. The first tagged release build to run sets it from
its own tag, and after that only the user changes it, so someone who
installed a release candidate keeps getting candidates after a stable
release lands.

The update dialog names the channel, can skip a version, saves to the
Downloads folder, follows GitHub's redirect, retries transient failures
and discards a download whose size or SHA-256 does not match what GitHub
published. Help > Check for Updates runs the check on demand and reports
the result either way.
UpdateInstaller installs a staged folder over an install folder: files
the previous update installed and the new one dropped are removed,
everything replaced is kept in .update-backup for one restore, a failed
step puts every file back, and the portable user folder is never
touched. eglantine-updater.exe runs it on Windows after the emulator has
closed, since a running exe and its DLLs cannot be replaced.

Changes from Lichii's code: the helper really links the C runtime
statically (the project's /MD overrode the target property), manifest
lines containing "." are ignored like ".." ones, and an empty staging
folder is refused instead of removing the whole install.
On Windows a writable install with the helper beside it downloads the
release zip, unpacks it into .update-staging and hands over to
eglantine-updater.exe, which swaps the files once the emulator has
closed and starts it again. An AppImage is replaced directly and
restarted. Anything else (tar.gz, macOS, a read-only folder) keeps
saving the download to the Downloads folder.

Help > Restore Previous Version appears once an update has left a
backup, and goes back to it.
The Windows jobs build explicit targets, so eglantine-updater.exe was
never produced. It is only named for release builds, where the update
checker (and with it the target) exists.
@aidankinzett
aidankinzett force-pushed the updater/tagged-channels branch from 6d97c4b to 1c8f3b8 Compare November 12, 2026 03:22
@aidankinzett
aidankinzett changed the base branch from nx2 to nx2-experimental November 12, 2026 03:23
@Eglantine-Community Eglantine-Community deleted a comment from github-actions Bot Nov 12, 2026
@aidankinzett
aidankinzett requested a review from kraibse November 12, 2026 03:35
"Check for updates" and "Update channel" sit together in an Updates box
instead of among the general settings. The box is hidden on builds
without the update checker.
@aidankinzett

aidankinzett commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

macOS testing

Apple Silicon, Release build with ENABLE_UPDATE_CHECKER=ON, EGLANTINE_RELEASE_PLATFORM=macos-arm64, faked as release v0.1.0-rc2 (GIT-TAG / GIT-RELEASE), run from a portable user folder and driven through the GUI.

Check Result
Offers v0.1.0-rc3 on the Release candidate channel Pass: "v0.1.0-rc3 is available. You are running v0.1.0-rc2. Update channel: Release candidate"
Nothing offered on Stable Pass: "You are running the newest version on the Stable channel"
Channel set from the build's own tag on first run Pass: update_channel=2 (Release candidate) after first launch
Skip This Version Pass: skipped_update=v0.1.0-rc3 saved, no dialog on next startup; a manual check still offers it
Help > Check for Updates Pass
Download on macOS Pass: save prompt defaults to Downloads; the .dmg size and SHA-256 match what GitHub publishes for the asset
Changing channel in Configure > General Pass: saved as Stable (update_channel=1); skipped_update was cleared
Updater unit tests (./bin/tests "[updater]") Pass: 387 assertions in 28 cases (after the compile fix below)

Not applicable / not tested: in-place install and restart, Restore Previous Version (Windows zip / AppImage only; there is no Restore entry in Help on macOS).

Notes:

  • src/tests/common/update_installer.cpp did not compile with Apple clang/libc++: Catch2 cannot stringify std::filesystem::file_time_type in the last_write_time CHECK. Fixed in a follow-up commit.
  • The full tests target still fails on macOS in src/tests/common/page_table.cpp (-Wshadow), which is unrelated to this PR. I linked the tests without it.

aidankinzett and others added 2 commits November 12, 2026 13:49
Catch2 cannot stringify std::filesystem::file_time_type with libc++, so
decomposing the comparison failed to compile on macOS. Compare inside
double parentheses so it is not decomposed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@aidankinzett

aidankinzett commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator Author

Linux testing

x86_64 Arch-based Linux (CachyOS), Release build with ENABLE_UPDATE_CHECKER=ON, EGLANTINE_RELEASE_PLATFORM=linux-x86_64, faked as release v0.1.0-rc2 (GIT-TAG / GIT-RELEASE). Tested both as an AppImage (built with linuxdeploy + the Qt plugin, as build-linux.yml does) and as a tar.gz, each with its own throwaway config/data folder, driven through the GUI. Checked against the real v0.1.0-rc3 release.

Check tar.gz AppImage
Updater unit tests (./bin/tests "[updater]") Pass: 387 assertions in 28 cases same build
Offers v0.1.0-rc3 on the Release candidate channel Pass Pass
Nothing offered on Stable Pass Pass
Channel set from the build's own tag on first run Pass Pass
Skip This Version Pass Pass
Help > Check for Updates Pass Pass
Download: size and SHA-256 match GitHub's Pass (save prompt, no in-place install) n/a (installs in place)
In-place install replaces the AppImage, old one kept n/a Pass
Restart into rc3 n/a Pass
Help > Restore Previous Version n/a Pass

@aidankinzett

Copy link
Copy Markdown
Collaborator Author

replaced with #841 so that I could stack PRs

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.

2 participants