In-app updater (check releases, show changelog, download + install) #4

Merged
debont80 merged 3 commits from feat/app-updater into main 2026-06-25 05:45:38 -04:00
Collaborator

Adds a Software update card in Settings so users can update AeroFetch from inside the app.

Flow: Check for updates ? see the new version + its release notes ? Update now (downloads the installer with a progress bar and launches it) or View release.

How it works

  • src/main/updater.ts reads the configured Gitea repo's latest release via the public REST API, compares the tag to app.getVersion(), and streams the installer to the temp dir, then launches it and quits.
  • Security: only the trusted host over HTTPS is downloaded from, and only a freshly-downloaded .exe in our temp dir is ever run - the release JSON can't redirect us elsewhere. No token is shipped; the source repo must be anonymously readable.
  • IPC: app:update-check / -download / -run + a progress push channel (exposed through preload).

Heads-up: the update source is a constant at the top of updater.ts (currently debont80/AeroFetch). Downloads only work once that repo's releases are anonymously accessible (public repo + anonymous API/asset access on the Gitea instance) - until then the check reports it couldn't reach the server.

Branched from your current main (v0.4.0). Typecheck + 140 tests pass; full electron-vite build clean; UI verified in the browser preview.

Adds a **Software update** card in Settings so users can update AeroFetch from inside the app. **Flow:** Check for updates ? see the new version + its release notes ? **Update now** (downloads the installer with a progress bar and launches it) or **View release**. **How it works** - `src/main/updater.ts` reads the configured Gitea repo's latest release via the public REST API, compares the tag to `app.getVersion()`, and streams the installer to the temp dir, then launches it and quits. - **Security:** only the trusted host over HTTPS is downloaded from, and only a freshly-downloaded `.exe` in our temp dir is ever run - the release JSON can't redirect us elsewhere. **No token is shipped**; the source repo must be anonymously readable. - IPC: `app:update-check` / `-download` / `-run` + a progress push channel (exposed through preload). **Heads-up:** the update source is a constant at the top of `updater.ts` (currently `debont80/AeroFetch`). Downloads only work once that repo's releases are anonymously accessible (public repo + anonymous API/asset access on the Gitea instance) - until then the check reports it couldn't reach the server. Branched from your current `main` (v0.4.0). Typecheck + 140 tests pass; full `electron-vite build` clean; UI verified in the browser preview.
wayne added 1 commit 2026-06-24 22:39:06 -04:00
A new Settings → "Software update" card lets users update AeroFetch from inside
the app:

- src/main/updater.ts — queries the configured Gitea repo's latest release over
  the public REST API, compares the tag to app.getVersion(), and (on request)
  streams the installer asset to the OS temp dir with progress events, then
  launches it and quits so it can replace the app.
- Security: only the trusted update host over HTTPS is ever downloaded from, and
  only a freshly-downloaded .exe inside our temp dir is ever executed — the
  download URL from the release JSON can't redirect us elsewhere. No token is
  ever shipped; the source repo must be anonymously readable (public).
- IPC: app:update-check / -download / -run + an -progress push channel, exposed
  via preload (checkForAppUpdate / downloadAppUpdate / runAppUpdate /
  onAppUpdateProgress).
- UI: "Check for updates" → shows the new version and its release notes, then
  "Update now" (with a download progress bar) or "View release". Up-to-date and
  error states handled. Preview mock added so the flow is exercisable in `npm run ui`.

Note: the update source repo/host are constants at the top of updater.ts
(currently debont80/AeroFetch). Downloads work once the release repo is public
and the Gitea instance allows anonymous API + asset access.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
debont80 added 1 commit 2026-06-25 05:09:28 -04:00
Review follow-ups on the in-app updater:

- Download via Electron net.request with redirect:'manual', re-validating the
  host on EVERY hop. fetch() followed redirects automatically and undici hides
  the Location header, so the trusted-host pin only covered the first URL — a
  302 from the trusted host could bounce the download to an arbitrary server.
- Reject truncated downloads (received !== Content-Length) and an 'aborted'
  mid-stream so a partial installer is never launched; clean up the temp file
  on any failure.
- runAppUpdate: compare the parent dir by equality instead of startsWith(),
  which a sibling like `<temp>_evil\x.exe` defeated.
- checkForAppUpdate: 15s AbortController timeout (mirrors the yt-dlp calls) and
  pin htmlUrl to the trusted host before handing it to the OS browser.
- parseVersion: ignore -prerelease/+build suffixes so a prerelease never sorts
  above its final release.
- SettingsView: funnel a failed check into the single error slot so two error
  messages can't render at once.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
debont80 added 1 commit 2026-06-25 05:41:54 -04:00
Integrity + defence-in-depth for the in-app updater:

- Require and verify a SHA-256 checksum before running an installer. Each
  release must attach `<installer>.sha256`; downloadAppUpdate fetches it from
  the host-pinned origin (deriving the URL itself, not trusting the renderer),
  hashes the stream as it writes, and refuses to run on mismatch/missing/
  unparseable. Fails closed. NB: same-host hash is integrity, not protection
  against a fully compromised host — only Authenticode signing covers that.
- fetchTrustedText helper GETs the checksum with the same per-hop host
  re-validation as the installer download, plus a size cap and timeout.
- finish() is now the single teardown point and aborts the request on failure,
  so a write error can't leave Electron pulling bytes into a dead stream;
  removed three now-redundant explicit abort() calls.
- Clear the idle/stall timer once the body is fully received.
- Export isTrustedDownloadUrl/compareVersions, extract extractSha256, and add
  14 unit tests (host pin incl. userinfo spoof + wrong port, checksum parsing
  incl. sha512 non-slice, prerelease never outranking its release).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
debont80 merged commit 97e725c774 into main 2026-06-25 05:45:38 -04:00
Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: debont80/AeroFetch#4