Skip to content

docs: update feedback pages from alpha to beta/GA policy#5483

Closed
leaanthony wants to merge 27 commits into
masterfrom
docs/feedback-beta-ga
Closed

docs: update feedback pages from alpha to beta/GA policy#5483
leaanthony wants to merge 27 commits into
masterfrom
docs/feedback-beta-ga

Conversation

@leaanthony

@leaanthony leaanthony commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Remove all "Alpha" references from feedback pages across all 8 locales (en, ja, ru, pt, zh-tw, de, ko, fr)
  • Move bug reporting to GitHub Issues (with bug report template) as the primary channel
  • Move feature suggestions to GitHub Discussions as the primary channel
  • Keep Discord #v3 as a secondary/community discussion channel
  • Remove the [v3 alpha] / [v3 alpha test] PR title prefix requirements; link to CONTRIBUTING.md instead
  • Remove the alpha-era "Things we are looking for feedback on" questionnaire section
  • Update upvoting guidance to use GitHub reactions (👍) instead of Discord emoji

Test plan

  • Verify the docs site builds without errors
  • Spot-check rendered output in each locale for correct links and formatting

Summary by CodeRabbit

  • New Features

    • Added in-app self-update functionality supporting multiple providers (GitHub Releases, keygen.sh, Sparkle AppCast)
    • Added built-in update notification UI window with customization options
    • Added artifact verification with digest and signature support
  • Documentation

    • Updated contribution guidelines to direct feedback and reports to GitHub issues/discussions instead of Discord
    • Added comprehensive updater feature documentation and usage examples
    • Updated contribution guidelines across all language versions
  • Examples

    • Added updater example application demonstrating in-app update workflow

Review Change Stack

leaanthony added 27 commits May 15, 2026 11:07
Introduces app.Updater — a singleton update orchestrator wired into
application.App. End users configure it once via Init then drive it with
Check / DownloadAndInstall / CheckAndInstall.

The Provider interface (Name / Check / Download) lets any release source
back the updater — GitHub Releases, keygen.sh, AppCast feeds, custom — with
verification, atomic writes, swap and the default window all owned by the
Updater rather than the provider.

Verification is built in: Ed25519, Ed25519ph (pre-hash, SHA-512) and
ECDSA-P256 over SHA-256, with digest-only mode supported. The internal
verifier registry is unexported in v1 so we can promote it to a public
RegisterVerifier later without breaking callers.

Subscribe to lifecycle events through the standard Wails event bus — both
Go and JavaScript use the same `updater:*` event names. Event constants
exported on the package with payload types documented inline.

This commit lands the interface, types, verification, atomic download with
streaming digest, and a fake-Provider test suite (19 tests). Window
template, binary swap, restart, GitHub/keygen.sh/AppCast providers and
docs follow in subsequent commits.
Add the swap-and-relaunch step that completes the update flow without
asking the user to deal with overwriting-a-running-binary on any platform.

Implements the inconshreveable/go-update pattern: when the Updater calls
Restart, the app re-execs itself with sentinel env vars. application.New
detects those vars at startup, hands control to runHelperSwap, and the
parent exits. The helper-mode process waits for the parent PID to die,
backs up the running binary, retries an atomic rename of the new artifact
into place (Windows file-lock safety net), launches the replaced binary,
and self-destructs the backup.

Handles macOS .app bundles correctly via recursive copy/symlink-aware
traversal, falls back to a single-file path for plain executables on
Linux and Windows, and rolls back to the backup if either the swap or
the relaunch fails so a broken update never leaves the app unbootable.

No extra binary shipped: the helper IS the app, switched into helper
mode by env vars. Cross-builds clean for darwin/linux/windows. Seven new
swap-logic tests plus the existing 19 keep the package at 26 green.
Built-in HTML/CSS/JS template that listens to updater:* events and emits
updater:user:* actions back to Go — no framework-specific tags, no custom
elements, just plain Wails events through the runtime. Dark-mode CSS
variables, status / progress / release-notes panels, four action buttons
(Install / Skip / Remind / Cancel) plus a Restart button visible only in
the ready state. Override the whole template via BuiltinWindow.HTML, layer
extra CSS via BuiltinWindow.CSS, tweak chrome (frameless/size/AOT) via
BuiltinWindow.Options.

Window option types: nil → defaults, *BuiltinWindow → overrides, any value
satisfying updater.WindowHandle (e.g. *application.WebviewWindow) → BYO,
updater.WindowNone → headless.

Host interface gains OpenWindow + OnEvent so the updater package can drive
real Wails windows without importing application. The application package
supplies an adapter (newUpdaterHost) that wraps *EventManager and
*WindowManager.NewWithOptions, plus a small updaterWindowHandle that
trims the fluent return values off SetHTML/Show/Hide.

User-action listeners are wired the moment a session opens, dispatching
updater:user:install → DownloadAndInstall, updater:user:cancel →
closeWindow, updater:user:skip → SkipVersion + close, updater:user:remind
→ close, updater:user:restart → Restart. Skipped versions short-circuit
subsequent Checks to up-to-date until cleared.

8 new lifecycle tests on top of the prior 26; full suite 34 green. App
package tests still green.
First provider implementation: hits api.github.com (or a configurable base
URL for GitHub Enterprise), picks an asset by GOOS/GOARCH heuristics, and
optionally pulls a sibling SHA256SUMS-style checksum file to populate
Release.Verification — letting the framework's verifier authenticate the
download with zero extra config from the user.

Asset matching defaults to substring-of-platform AND substring-of-arch on
the filename, skipping .sig / .asc / *sum* sidecars. Recognises the common
arch aliases (amd64 ↔ x86_64/x64, arm64 ↔ aarch64, 386 ↔ i386/x86/ia32).
Custom selection via Config.AssetMatcher.

Auth via Bearer PAT raises rate limits and unlocks private repos. Download
follows the asset's signed redirect and strips Authorization on the
cross-host hop so the token never reaches the storage backend.

Eleven httptest tests cover the happy path, up-to-date / 404, prerelease
toggle, asset filter, missing match, checksum-sidecar verification, the
redirect-and-strip flow, validation, and API-error surfacing.
Port the keygen.sh integration written previously into the Wails v3
updater Provider shape. Key adaptation: keygen's per-artifact base64
checksum (SHA-512) and signature (Ed25519ph) fields now flow into
Release.Verification, so the framework's verifier authenticates the
download automatically once the consumer has set Config.PublicKey on the
Updater — no provider-specific verification path.

Handles keygen.sh quirks discovered while building the original library:
the /upgrade endpoint silently drops ?include=artifacts (fallback fetch
to /releases/{id}/artifacts), the artifact download 303s to Cloudflare R2
on a different host (CheckRedirect wrapper drops Authorization on cross-
origin hops), and 404 from /upgrade means "no newer release" rather than
a hard error.

Auth supports either Token (admi-/prod-/envi-/user- prefixed values) or
LicenseKey, with Token winning when both are set. Typed APIError surfaces
the keygen JSON error envelope (title / detail / code / status) for
debuggable failures.

Eleven httptest tests cover the happy path with verification mapping,
404=up-to-date, both auth schemes, error surfacing, no-matching artifact,
the include=artifacts fallback, the redirect-strip flow, validation, and
the Provider-interface assertion.
Parses the canonical Sparkle XML vocabulary so existing Sparkle/WinSparkle
feeds drop in unchanged: items are picked by sparkle:shortVersionString
(falling back to sparkle:version), the download comes from <enclosure>,
and Sparkle 2's sparkle:edSignature flows into Release.Verification as an
Ed25519 signature for the framework's verifier.

Item selection reconciles Sparkle's loose OS naming ("macos"/"mac"/"osx"
all map to Go's "darwin"), and a Config.Channel filter narrows to one
sparkle:channel when the feed publishes multiple. Sparkle 1's
sparkle:dsaSignature is intentionally not supported — projects on DSA
should rotate to EdDSA per Sparkle 2's guidance.

Ten httptest tests cover the happy path with verification, Windows
selection, up-to-date short-circuit, no-platform-match, 404=up-to-date,
500=error, channel filtering, download streaming with progress,
URL-required validation, and the Provider interface assertion.
Docs cover the 30-second quick start, the three built-in providers
(GitHub Releases / keygen.sh / Sparkle AppCast) with config knobs,
fallback chain semantics, cryptographic verification (Ed25519, Ed25519ph,
ECDSA-P256, digest-only), default-window theming through CSS variables,
full template replacement, window chrome override, BYO via
application.Window, headless mode, the full event taxonomy for both Go
and JavaScript, how to write your own provider, and how the same-binary
helper-mode swap works under the hood.

Example app exercises the GitHub provider end-to-end: APP_VERSION +
GH_REPOSITORY env vars, "Check for Updates…" menu item that drives
CheckAndInstall, a small JS-side log mirroring updater:* events
alongside the framework's default window.
- Drop dead import-stabilising stubs: `var _ = url.Parse` + `net/url`
  import in github.go, `var _ = errors.New` in github_test.go, `var _
  = io.EOF` in appcast_test.go.
- Drop dead `sessionMu` type from window_lifecycle.go (never used —
  Updater uses a plain sync.Mutex named sessMu).
- Remove the `req.Header.Set(\"Accept\", req.Header.Get(\"Accept\"))`
  no-op round-trip in github.Provider.setAuth.
- Replace hand-rolled path concatenation with filepath.Join in
  download.go.
- Drop the unused `target` parameter and vestigial `_ = path` /
  `_ = target` blanks from openHelperLog.
- Use filepath.Dir instead of hand-rolled \"/\"-based parentOf in
  updater_test.go (broke on Windows).
- Drop the package-level mockHost in github_test.go in favour of a
  test-local host variable (so future t.Parallel() doesn't race).
- writeFile uses 0o644 for files instead of 0o755.
Each invocation of CheckAndInstall registered 5 fresh user-action
listeners under sessMu but never closed any previous session. A
periodic-check timer firing alongside a manual click (or two clicks)
would leak listeners and windows; stale callbacks would still fire on
the next user action, double-invoking DownloadAndInstall or Restart.

Close any active session before opening the new one, and add a
regression test that exercises three back-to-back CheckAndInstall
calls and asserts the host's listener map is empty afterwards.
The download flow created an os.MkdirTemp(\"\", \"wails-update-*\")
directory and staged the artifact inside. Two leak paths left orphan
directories under os.TempDir across update attempts:

- success path: after Restart spawned the helper and the helper renamed
  the staged binary into place, the (now-empty) directory persisted.
- error paths: \`_ = os.Remove(tmpPath)\` removed the staged file but
  left the enclosing dir.

Plus, when digestHasher errored before any download bytes flowed, the
freshly-created directory was leaked outright (flagged by CodeRabbit).

Three coordinated changes:

1. download() now returns (path, dir, err) and tears the directory
   down on every error path via a single deferred RemoveAll guarded by
   a success bool — including the hasher-setup branch.
2. Updater tracks stagingDir alongside resolved; DownloadAndInstall
   removes any stale dir before downloading again and RemoveAll's the
   current dir on verify/install failures.
3. runHelperSwap removes the parent directory of newPath after a
   successful rename, guarded by the \`wails-update-\` prefix so a
   caller-supplied path outside the temp area can never be deleted by
   accident.

Adds a TempDir-counting regression test to keep this from regressing.
The previous composeHTML appended the override <style> tag after the
closing </html> of the default template, producing a malformed
document that HTML linters and CSP-strict configurations reject.

Splice the extra <style> before </head> when one is present so the
result stays well-formed. Custom-HTML templates without </head> fall
back to appending at the document end with a comment.

Tightens the existing CSS-injection test to assert the override sits
before both </head> and </html>.
\`/repos/{r}/releases\` includes draft releases when the request is
authenticated, and \`per_page=1\` plus a recently-created draft would
surface that draft as an available update instead of the actual
latest prerelease. Asymmetric with the non-prerelease path, which
hits \`/releases/latest\` and is already draft-free.

Fetch up to 10 items and return the first non-draft, adding a
regression test that injects a draft at the top of the list.
asn1.Unmarshal returns the unconsumed tail of its input; the previous
splitECDSASig discarded it, accepting non-canonical signatures with
arbitrary trailing data. That is a known signature-malleability
hazard and conforming signers never emit such data.

Require asn1.Unmarshal to consume the entire signature and return an
error otherwise. Includes a regression test that appends four bytes
of junk to a valid signature and asserts verification fails closed.
The github and appcast providers each shipped a near-byte-identical
isNewer/compareSemver/splitNumeric. Beyond duplication the home-rolled
comparator had three correctness problems flagged in review:

- prereleases sorted *newer* than the corresponding release
  (\"1.2.3-rc.1\" > \"1.2.3\" instead of <)
- build metadata (\"+build.456\") was parsed as a numeric run rather
  than ignored, so two releases that differ only in metadata compared
  as different
- non-semver inputs returned monotonic-ish ordering that depended on
  digit-run alignment

Introduce v3/pkg/updater/internal/semver wrapping golang.org/x/mod/semver
(already an indirect dep — go mod tidy promotes it to direct).
Providers call IsNewer / TrimPrefix; tests cover the prerelease,
numeric-segment, and build-metadata cases that the previous
implementation got wrong.
The default window template emits updater:window:ready on load to ask
the host for a state snapshot — useful when the window opens after a
periodic-check timer already advanced the flow, e.g. straight into
StateAvailable, so it can't rely on the live event stream alone.

openSession previously registered listeners for the five user-action
events but ignored EventWindowReady, so the window stayed in its
initial \"looking for updates...\" state until the next event fired.

Subscribe to EventWindowReady and replay the lifecycle event matching
the current State (update-available, download-started, verifying,
installing, update-ready, or no-update). Idle / unconfigured / error
have nothing useful to replay and are skipped.
The Windows helper's waitForPID polled isAlive, which called
os.Process.Signal(nil). On Windows that always errors (EWINDOWS for
live processes, ErrProcessDone for dead ones), so isAlive reported
every pid as dead. Result: the helper never actually waited for the
parent to release the binary on Windows — it spun straight into the
swap-retry loop and grinding the file lock.

Split isAlive's per-platform probe into a platformIsAlive helper.

- Unix path: unchanged — proc.Signal(syscall.Signal(0)).
- Windows path: OpenProcess + GetExitCodeProcess. Pulling the exit
  code directly from the kernel distinguishes STILL_ACTIVE (alive)
  from any terminated state without requiring x/sys/windows.
The previous test raced a 10ms time.Sleep against listener
registration in openSession: a slow CI runner could fire
EventUserCancel before openSession installed its handler, so the
cancel was dropped and the 500ms poll loop quietly timed out.

The full CheckAndInstall flow is synchronous when the fake provider
returns immediately, and StateReady leaves the window open by design.
Drive it to completion first (listeners now guaranteed wired), then
fire the cancel emit. fakeHost dispatches callbacks on the calling
goroutine so closeWindow finishes before Emit returns — no polling,
no sleep, no time import.

20x -count run stays green.
github:
- followAndStrip now preserves a caller-supplied CheckRedirect by
  wrapping it (matches keygen / appcast) and uses
  strings.EqualFold on host comparison.
- isChecksumName is anchored to suffixes / whole tokens so primary
  artifacts whose names contain a hash algorithm (e.g.
  \"myapp-sha256-amd64.zip\") are no longer mis-classified as
  sidecars and silently skipped.

keygen:
- Upgrade response body is now wrapped in io.LimitReader(8 MiB) —
  previously an unbounded io.ReadAll on a misbehaving / compromised
  endpoint could OOM the host.
- Check stashes the chosen artifact's keygen.sh ID under
  rel.Metadata[\"keygen.artifact.id\"]; Download prefers that over
  the filename so releases that ship two artifacts with the same
  filename across platforms are unambiguous.
- pickArtifact normalises operator-defined aliases via case-
  insensitive matchers — \"macos\" matches darwin, \"x86_64\"/\"x64\"
  matches amd64, \"aarch64\" matches arm64, \"i386\"/\"x86\" matches
  386.

appcast:
- Download explicitly clears any Authorization header on the
  enclosure request — even though Check never sets one, a caller-
  supplied default on the http.Request would otherwise leak
  cross-origin on the initial CDN hop (the redirect wrapper only
  covers subsequent hops).

Regression tests cover the github false-positive case, the keygen
ID stash + download-by-ID flow, and the keygen alias matching.
- appcast.pickBestItem: an explicit Config.Channel no longer matches
  items that ship without a sparkle:channel. Sparkle's contract is
  that items opt *in* to a channel, so the previous
  "channel mismatch only if both sides non-empty" check could leak
  unlabelled (stable-default) items into a non-stable filter.
- github.containsArch: a 386 request no longer false-positive-matches
  x86_64 / amd64 / x64 assets through the bare "x86" alias. 64-bit
  aliases on the filename now veto a 386 match outright.
- window_test custom-HTML assertion: guard the 60-char preview slice
  so a short InitialHTML doesn't panic and mask the real failure.

Regression tests cover both provider fixes.
Resolve v3/go.mod conflict from the dependabot bumps in #5447:
take master's bumped versions for x/image / x/mod / x/net / x/sync /
x/text and keep golang.org/x/mod as a direct dependency (the
internal/semver helper introduced by da8fa93 imports it).

go mod tidy clean, all updater tests green.
The existing TestDefaultAssetMatcher_386_DoesNotMatchX86_64 covers
this in a dedicated test, but adding the case to the table-driven
TestDefaultAssetMatcher keeps the alias check next to the other
matcher cases — where future readers (and reviewers) naturally look
when expanding the matrix. Suggested in CodeRabbit's review nitpick.
The previous default of wailsapp/wails fails because Wails' own
release artifacts don't match the platform-naming heuristic the
updater's DefaultAssetMatcher expects ("darwin"/"arm64" tokens in
the filename, no `wails*` suffix etc.) — running the example
out-of-the-box surfaces "no asset for darwin/arm64" instead of
demoing the actual flow.

Point at wailsapp/updater-demo, a dedicated repo whose releases
publish updater-demo_<goos>_<goarch>.<ext> archives plus a
SHA256SUMS sidecar. The example also opts into ChecksumAsset
verification by default so digest-mismatch is exercised on first
run. Env-var overrides still work for users pointing at their own
repos.
The GitHub-provider example config used \`Repository: \"wailsapp/wails\"\`
which suggests pointing at the wails repository itself — but wails's
own release artifacts don't match the updater's filename heuristics
("darwin"/"arm64" tokens, no \`wails*\` prefix). A first-time user
copy/pasting hits "no asset for darwin/arm64" instead of seeing the
flow work.

Use \`myorg/myapp\` to match the Quick Start at the top of the page,
and call out the runnable example + its dedicated demo repo right
under the snippet so readers know where to look for a working
end-to-end target.
The README's Verification section talked only about
publicKey/signature verification, but the example now ships
ChecksumAsset: \"SHA256SUMS\" by default — so digest verification
runs out of the box against the demo repo's sidecar. Lead with
that (matching what the example actually does), then describe
the publicKey path as the next step up.
Four fixes from @atterpac's Arch Linux testing on the PR:

- SetHTML on webkit2gtk loads into the about:blank context which
  has no Wails runtime, so the bound JS could not Emit events
  back. Pass the HTML at construction via
  WebviewWindowOptions.HTML instead; drop SetHTML from the
  WindowHandle interface (it was dead surface).

- Wails' EventProcessor.Emit dispatches to listeners + JS on fresh
  goroutines, so events arrive at the frontend out of source
  order (\"installing → verifying → update-ready →
  download-complete\" was observed). Fixing the bus is out of
  scope for this PR; instead make the default window
  state-monotonic — rank the lifecycle states, reject backward
  transitions, gate side effects on setState() returning true,
  and only paint download progress while we're still in the
  downloading state.

- Restart() spawned the helper and returned, leaving the caller
  responsible for app.Quit() — which everyone missed, so the
  helper sat blocked on waitForPID until its 30s timeout. Add
  Quit() to the Host interface, have Restart() call it after
  the spawn so the helper handoff actually completes. Regression
  test mocks the spawn via new export_test.go seams.

- The downloaded artifact is opened with os.Create which masks
  0o666 against umask, dropping the executable bit on Unix. The
  swap renamed it into place and the relaunch then failed with
  EACCES. Snapshot the original target's mode before the swap
  and chmod the new file to match after.
TestRestart_QuitsAfterSpawn used /bin/true which doesn't exist on
Windows. Replace with os.Executable + -test.run=^$ so the spawned
command is the test binary itself, running no tests, and exits cleanly
on all platforms.

Restart() passed the raw os.Executable path as WAILS_UPDATER_HELPER_TARGET.
On macOS, swapping the binary inside a .app bundle invalidates the code
signature and Gatekeeper blocks relaunch. bundleTarget() now walks the
path for a .app segment and returns the bundle root on darwin; on all
other platforms it is a no-op passthrough.

Also replace the hand-rolled constantTimeEqual with crypto/subtle.ConstantTimeCompare
to handle the length-mismatch case in constant time.
Replace alpha-era Discord-centric feedback workflow with standard
GitHub Issues (bugs) and GitHub Discussions (suggestions) as primary
channels. Remove alpha PR title prefix requirements and the
'things we are looking for feedback on' section. Applied to all
8 locales (en, ja, ru, pt, zh-tw, de, ko, fr).
Copilot AI review requested due to automatic review settings May 20, 2026 11:22
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6b86bd96-0247-4168-ba6c-d8a4f769818e

📥 Commits

Reviewing files that changed from the base of the PR and between caf07c3 and 2ce3c48.

📒 Files selected for processing (46)
  • docs/src/content/docs/de/feedback.mdx
  • docs/src/content/docs/feedback.mdx
  • docs/src/content/docs/fr/feedback.mdx
  • docs/src/content/docs/guides/updater.mdx
  • docs/src/content/docs/ja/feedback.mdx
  • docs/src/content/docs/ko/feedback.mdx
  • docs/src/content/docs/pt/feedback.mdx
  • docs/src/content/docs/ru/feedback.mdx
  • docs/src/content/docs/zh-tw/feedback.mdx
  • v3/examples/updater/README.md
  • v3/examples/updater/assets/index.html
  • v3/examples/updater/main.go
  • v3/go.mod
  • v3/pkg/application/application.go
  • v3/pkg/application/updater_host.go
  • v3/pkg/updater/assets/window.html
  • v3/pkg/updater/config.go
  • v3/pkg/updater/doc.go
  • v3/pkg/updater/download.go
  • v3/pkg/updater/events.go
  • v3/pkg/updater/export_test.go
  • v3/pkg/updater/helper.go
  • v3/pkg/updater/helper_test.go
  • v3/pkg/updater/helper_unix.go
  • v3/pkg/updater/helper_windows.go
  • v3/pkg/updater/internal/semver/semver.go
  • v3/pkg/updater/internal/semver/semver_test.go
  • v3/pkg/updater/providers/appcast/appcast.go
  • v3/pkg/updater/providers/appcast/appcast_test.go
  • v3/pkg/updater/providers/github/github.go
  • v3/pkg/updater/providers/github/github_test.go
  • v3/pkg/updater/providers/keygen/keygen.go
  • v3/pkg/updater/providers/keygen/keygen_test.go
  • v3/pkg/updater/spawn.go
  • v3/pkg/updater/spawn_unix.go
  • v3/pkg/updater/spawn_windows.go
  • v3/pkg/updater/types.go
  • v3/pkg/updater/updater.go
  • v3/pkg/updater/updater_darwin.go
  • v3/pkg/updater/updater_darwin_test.go
  • v3/pkg/updater/updater_notdarwin.go
  • v3/pkg/updater/updater_test.go
  • v3/pkg/updater/verify.go
  • v3/pkg/updater/window.go
  • v3/pkg/updater/window_lifecycle.go
  • v3/pkg/updater/window_test.go

Walkthrough

This PR introduces Wails v3's in-app self-update system with pluggable release providers, atomic binary swapping, cryptographic verification, and an integrated UI window. It includes GitHub/keygen/AppCast provider implementations, updates all localized feedback documentation to direct users to GitHub workflows, and adds a complete feature guide with working example.

Changes

Feedback Documentation Localization

Layer / File(s) Summary
Feedback page rewrites across 8 languages
docs/src/content/docs/{de,feedback,fr,ja,ko,pt,ru,zh-tw}/feedback.mdx
All feedback pages updated from alpha-specific Discord/post guidance to Wails v3 GitHub-centric workflows (issues, discussions, PRs) with wails doctor output expectations and v3/examples update guidance.

User-Facing Documentation and Example

Layer / File(s) Summary
Updater feature guide
docs/src/content/docs/guides/updater.mdx
Comprehensive guide covering app.Updater initialization, provider interface, built-in providers, cryptographic verification with public keys, default UI customization, headless mode, event catalog, custom provider implementation, and binary swap mechanics.
Working updater example
v3/examples/updater/{README.md,assets/index.html,main.go}
Complete Wails v3 app demonstrating GitHub releases with checksum and signature verification, menu-driven CheckAndInstall, environment-variable overrides, event subscriptions, and updater event logging.

Core Updater Implementation

Layer / File(s) Summary
Data types and provider contract
v3/pkg/updater/{types.go,config.go,doc.go,events.go}
Defines lifecycle states (unconfigured → checking/downloading/verifying/installing → ready/error), release/artifact/verification structures, progress and error payloads, Config with CurrentVersion/Providers/PublicKey, and the Provider interface contract.
Updater orchestration
v3/pkg/updater/{updater.go,updater_test.go,updater_darwin*.go}
Main Updater singleton with Init/Check/DownloadAndInstall/CheckAndInstall/Restart methods, provider fallback chain, state machine transitions, skip version tracking, macOS .app bundle target normalization, and comprehensive test coverage with in-memory fakes.
Download and verification pipeline
v3/pkg/updater/{download.go,verify.go}
Streams artifact downloads with progress throttling, optional digest hashing during transfer, stores digests for verification without re-reads, supports Ed25519/Ed25519ph/ECDSA P-256 signature verification with public-key parsing (raw, PEM, PKIX).
Helper mode and binary swap
v3/pkg/updater/{helper.go,helper_test.go,helper_unix.go,helper_windows.go,spawn*.go}
Platform-specific helper process for atomic binary replacement: validates paths, backs up target, retries swap with delays, preserves executable bits, handles .app bundles, cleans staging directories, provides process liveness polling and timestamped logging.
Window lifecycle and built-in UI
v3/pkg/updater/{window.go,window_lifecycle.go,window_test.go,assets/window.html}
Built-in HTML/CSS/JS update window with light/dark theming, progress display, action buttons; session-based lifecycle wrapping check/install flows; skip version persistence; state snapshot replay on window ready.
GitHub Releases provider
v3/pkg/updater/providers/github/{github.go,github_test.go}
Queries GitHub API for latest/prerelease, matches assets by platform/arch with default matcher (skips sidecars), optional SHA-256 checksum sidecar verification, PAT authentication, cross-host redirect auth scrubbing.
Keygen.sh provider
v3/pkg/updater/providers/keygen/{keygen.go,keygen_test.go}
Keygen.sh REST API integration: upgrade endpoint queries, artifact selection with platform/arch/filetype filtering, SHA-512 and Ed25519ph verification mapping, token/license-based auth, typed API error decoding.
Sparkle AppCast provider
v3/pkg/updater/providers/appcast/{appcast.go,appcast_test.go}
Sparkle RSS/XML feed parsing with namespace support, item/enclosure selection by channel/OS/semver, Ed25519 signature decoding, content-length fallback for progress, HTTP redirect auth handling.
Semver and platform utilities
v3/pkg/updater/{internal/semver/,spawn*.go,export_test.go,updater_darwin*.go}
Internal semver comparison wrapper, platform-specific detached process spawning (Unix session isolation, Windows process detachment), test-only executable and command overrides.

Application Integration

Layer / File(s) Summary
App struct and adapter integration
v3/pkg/application/{application.go,updater_host.go}, v3/go.mod
Adds Updater field to App, calls updater.HandleHelperMode() early in New(), creates updaterHost adapter forwarding events/window creation to App, updates golang.org/x/mod as direct dependency.

🐰 The updater hops in with code to spare,
GitHub releases now float through the air,
With checksums and signatures locked down tight,
Binary swaps happen smooth as the night,
A window it shows, progress in sight—
Wails v3 updates done right!


🎯 4 (Complex) | ⏱️ ~60 minutes

  • wailsapp/wails#5356: Directly overlaps with German feedback documentation rewrite targeting Wails v3 contribution guidance.
  • wailsapp/wails#4359: App restructuring that introduced Events and Windows managers; this PR's updaterHost adapter integrates updater into those new App APIs for event emission and window creation.

Documentation, v3-alpha, Implemented in v3, size:XXL, lgtm

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch docs/feedback-beta-ga
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch docs/feedback-beta-ga

@leaanthony

Copy link
Copy Markdown
Member Author

Closing in favour of #5484 which is correctly based on master.

@leaanthony leaanthony closed this May 20, 2026
@leaanthony leaanthony removed the request for review from Copilot May 20, 2026 11:45
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