Skip to content

For contributors and maintainers

Releasing Agora

Agora has two independent release streams:

StreamTypical tagContents
Catalogregistry-YYYY-MM-DDSigned catalog database and web export
DesktopvX.Y.ZPlatform installers and application bundles

Do not treat a successful catalog release as proof that a desktop package was built correctly, or vice versa.

Desktop release checklist

Windows portable desktop

The Windows desktop job also publishes agora-desktop-vX.Y.Z-windows-x64-portable.zip and its .zip.sha256 checksum. This is distinct from the standalone CLI ZIP. The archive contains the signed desktop executable, portable.txt, and instructions; extract it into a writable folder and run Agora Launcher.exe. WebView2 Runtime must already be installed. The normal MSI/NSIS installers remain installed applications; portable mode is distributed separately.

Data and WebView preferences live in the adjacent data folder unless AGORA_DATA_DIR overrides it. Installed data is not automatically moved into a portable copy. A portable copy never uses the OS keyring: Microsoft and GitHub sign-ins are encrypted in the data folder so they travel with it, which means anyone who can read that folder can read them. Close any other running Agora copy before starting the portable one (the application enforces a single instance).

To update, close Agora, replace the executable from the next portable ZIP, and keep portable.txt and data. The portable update action explains this instead of running an installer. Before publishing, smoke-test extraction, first launch, moving the folder, preference persistence, and this update path on Windows. Verify the extracted executable's Authenticode signature and the ZIP checksum.

Fresh appearance preferences and Reset appearance use Civic Gold (default). Existing saved appearance choices are preserved; the previous blue preset is Agora Blue.

Windows code signing

Windows releases use Azure Artifact Signing with GitHub OIDC. The shared setup is in .github/actions/windows-signing/action.yml; the Azure identifiers there are public configuration, not secrets. The signing profile is Agora-MC-Launcher in account Agora-MC-Launcher, resource group Agora, at https://wus2.codesigning.azure.net (West US 2).

The Entra application Agora GitHub Release Signing needs the Artifact Signing Certificate Profile Signer role on this signing account. Its federated credential uses issuer https://token.actions.githubusercontent.com, audience api://AzureADTokenExchange, and this immutable GitHub subject:

repo:agora-mc@309633368/Agora-Launcher@1272711399:environment:windows-signing

Enable immutable subjects in the repository's GitHub Actions OIDC settings so the issued token matches this credential. The windows-signing GitHub environment allows selected tags matching v*. Both release build jobs use this environment; only their Windows matrix entries authenticate to Azure and invoke signing. For manual dispatch, select the actual release tag as the workflow ref, not master.

No client secret or exported code-signing private key is required. The setup downloads a version- and SHA-256-pinned Microsoft signing client and verifies its NuGet signature. It uses the signed Windows SDK SignTool installed on the hosted Windows runner.

Immediately after Azure login, the shared action obtains an access token for https://codesigning.azure.net/.default and leaves it in Azure CLI's runner-local cache, with token output suppressed. This exchange must happen while the GitHub OIDC assertion is still valid: waiting until the first signing call after Rust compilation caused AADSTS700024 because the assertion had already expired. The signing hook's AzureCliCredential then uses the cached service access token. That access token also has a finite lifetime; if a much longer build outlasts it, authenticate again and acquire a fresh service token immediately before packaging.

For desktop builds, a temporary Tauri configuration supplies a custom signing hook. Tauri signs the application and installer executables during packaging, before creating updater signatures. Tauri restores the original unsigned main executable after bundling, so the workflow separately signs and verifies target/release/agora-desktop.exe before creating the portable ZIP. The portable packager still rejects invalid signatures or missing timestamps. Each hook invocation verifies Authenticode and requires a timestamp. The Windows CLI is signed and verified before its ZIP is created, so the release checksums cover the signed binary. Signing or verification failure fails the job; it never falls back to an unsigned Windows release. Local development builds do not load this temporary signing configuration.

The existing TAURI_SIGNING_PRIVATE_KEY and optional password remain required for Tauri updater signatures; Azure Authenticode signing does not replace them.

Offline hook regression checks: pwsh -NoProfile -File scripts/test_windows_signing.ps1. These test error propagation and path handling with a fake signing tool, not Azure authentication. The first tagged release must verify the live OIDC exchange and signing permissions. Before publishing its draft, check the Windows installers and extracted CLI with Get-AuthenticodeSignature; status must be Valid, the signer must match the profile's validated identity, and a timestamp must be present. Also test a packaged update from the prior release.

References: Microsoft signing integration, immutable OIDC subjects, Tauri custom signing hook.

Before tagging

  • Version metadata agrees across package files (python scripts/set_release_version.py --check; also enforced by the version-metadata CI job).
  • Changelog or release notes describe user-visible changes.
  • Required public build variables are present in the release workflow.
  • Signing and updater configuration are available for intended platforms, including Apple Silicon and Intel macOS.
  • The in-app guide and website documentation match current labels.
  • CLI help and docs/CLI.md match current commands.
  • Migration from the previous public release has been tested with disposable data.
  • A clean installation has been tested.
  • A packaged upgrade has been tested.

Checks that gate a release build

No release artifact is built until these three workflows pass for the commit being released. The Release workflow calls them with uses:, so they are the same workflows that gate ordinary pushes and pull requests — not a second copy that can drift — and they run against this run's checkout rather than a previous run's:

Required checkCovers
CI Enforcementcargo fmt, clippy for core/CLI/desktop, Rust tests on Linux/Windows/macOS, compiler and script tests, TypeScript and Vite build, architecture boundaries, Tauri binding manifest, documentation gates, version-metadata agreement
Launch Planner CICross-platform launch-planner integration tests and the launchMode delegation-default assertion
E2E TestsThe Playwright browser suite

Two properties make "the checks passed for the released revision" a fact rather than an inference:

  • Because the checks are called rather than looked up, a stale green run on an earlier commit cannot stand in for this one, and a missing, pending, failed, or cancelled run leaves the build jobs unreachable. Path filters do not apply to a called workflow, so a change that no filter matches — a loader-only revision, for example — is still fully tested at release time.
  • The release-ref job asserts that the release tag resolves to the commit this run is building. A tag push satisfies this by construction; a manual dispatch from a branch whose HEAD is not the tagged commit fails here instead of publishing artifacts that no check ever examined. Push the tag first, then dispatch against it.

loader-manifests/, runtime-catalog/, and crash-signatures/ are include_str! inputs to agora-core, so they are also in CI Enforcement's push and pull-request path filters. A loader-refresh commit therefore gets the full suite on master, not only Launch Planner CI.

The Web Build and Nightly Compiler workflows are not release gates: they serve the catalog and website streams on their own schedules.

Build and inspect

The Release workflow in .github/workflows/release.yml runs for a pushed v* tag or a manual dispatch with a tag. Its first build step rewrites the version from that tag into every file that carries one — the workspace Cargo.toml, desktop/src-tauri/Cargo.toml, tauri.conf.json, desktop/package.json, and the Cargo.lock workspace entries — via scripts/set_release_version.py. The tag is therefore the single source of truth for installer filenames, the version the application shows, and agora --version. It rewrites the ephemeral CI checkout only; nothing is committed back. After the required checks above pass, it builds native desktop bundles on Windows, macOS, and Linux, packages standalone CLI archives, normalizes every desktop bundle name (scripts/finalize_release_assets.py), generates a SHA256SUMS covering every artifact, and assembles a draft release. The workflow never publishes the release itself; it stays a draft until a maintainer smoke-tests the artifacts and publishes explicitly.

Public desktop build variables and secret boundaries are documented once in DEVELOPMENT.md. Confirm the workflow has every required public value and protected signing credential without copying their values into release notes or logs.

Do not rely on fixed installer filenames or package sizes in documentation. Tauri and platform tooling can change both.

The workflow leaves the release as a draft with all artifacts uploaded and SHA256SUMS generated. It never publishes automatically. Inspect the draft after the workflow finishes before publishing, and cancel or re-run the workflow if any of these are wrong:

  • platform and architecture coverage;
  • both Apple Silicon and Intel macOS installers, with architecture-bearing bundle names;
  • asset names match the scheme in Release asset names;
  • version shown by the application;
  • installer identity;
  • SHA256SUMS covers every downloadable artifact and nothing else;
  • updater metadata contains signed darwin-aarch64 and darwin-x86_64 entries;
  • release notes;
  • accidental debug artifacts.

Release asset names

Every release publishes the same asset scheme:

agora-<kind>-<tag>-<platform>-<arch>[-<variant>].<ext>

KindAssets (full names start with agora-<kind>-<tag>-)
desktopwindows-x64-setup.exe (NSIS), windows-x64.msi, windows-x64-portable.zip (+ .sha256), macos-arm64.dmg, macos-x64.dmg, macos-arm64.app.tar.gz and macos-x64.app.tar.gz (updater archives), linux-x64.AppImage, linux-x64.deb, linux-x64.rpm
cliwindows-x64.zip, linux-x64.tar.gz, macos-arm64.tar.gz, macos-x64.tar.gz

Tauri names its bundles Agora.Launcher_<version>_<arch>.<ext>, which says nothing about desktop vs CLI and mixes x64, x86_64, and amd64. The assemble-release job post-processes the draft with scripts/finalize_release_assets.py before verification:

  • it renames each Agora.Launcher* bundle in place and fails on a name or an embedded version it does not recognize;
  • it deletes the updater *.sig sidecar assets — the signatures also live inline in latest.json, which is what the updater consumes;
  • it rewrites every latest.json platform URL to the renamed asset and re-uploads latest.json;
  • it writes and uploads SHA256SUMS, covering every downloadable artifact except latest.json and SHA256SUMS itself.

GitHub lists assets alphabetically (case-insensitively, so agora-cli-* sorts above agora-desktop-*) and collapses long lists behind "Show all N assets". After finalizing, assemble-release therefore rewrites the draft's release body with a direct-download table — Windows -setup.exe first — instead of relying on the asset list for discoverability. The updater notes in latest.json keep tauri-action's short summary, because the in-app update dialog shows them as plain text. Re-running the job is safe: the script leaves an already-normalized release alone, and re-running it after fresh bundles were uploaded replaces the stale renamed copies.

Packaged smoke test

Everything in this section is manual and not enforced by CI. The required checks above run against source; nothing in them installs or launches a packaged build, so a release that compiles and tests clean can still fail on first run. The one automated exception is the macOS launch check inside build-desktop, which starts the packaged .app for a few seconds to catch bundle and startup failures; it is not a substitute for the steps below, and there is no Windows or Linux equivalent.

Use the actual release artifact, not tauri dev.

Minimum test:

  1. install or run the packaged build;
    • On macOS, launch both the Apple Silicon and Intel .app packages on their matching hardware.
  2. complete first-run setup on a clean disposable profile;
  3. synchronize and verify the catalog;
  4. confirm Browse returns curated content;
  5. confirm loader and Java catalogs are available;
  6. create or import a disposable instance;
  7. run health;
  8. launch through the default delegated mode;
  9. launch directly with a test Microsoft account when that platform is supported;
  10. restart and verify settings and instances persist;
  11. exercise update detection from the previous release.

The catalog public-key check is release-critical. A package that builds successfully but lacks the expected verification key can fail only after installation. Always test catalog synchronization in the packaged artifact.

Publish

The workflow leaves the release as a draft after all build jobs succeed. The desktop and CLI artifacts and SHA256SUMS are already attached; publication is the one manual step a maintainer performs after a packaged smoke test.

After the packaged smoke test passes, publish the draft:

  • click Publish release on the draft's GitHub page, or
  • run gh release edit "$TAG" --draft=false --repo agora-mc/Agora-Launcher.

After publishing:

  • confirm the website download control selects the intended release;
  • perform one clean public download;
  • confirm update checks can see the release;
  • monitor support channels for migration, signing, and installer failures.

Catalog release checklist

  • Curated manifests validate.
  • Governance inputs validate.
  • Loader/runtime catalog inputs are current and pinned.
  • Compiler tests pass.
  • The catalog is signed with the production key.
  • The public key expected by released clients matches the signing key.
  • Catalog database and web export signatures verify.
  • Release assets use the expected names.
  • A current packaged desktop client can download and open the new catalog.

The private signing key belongs only in the protected CI environment. Never place it in documentation, issue comments, artifacts, or local shell history.

Rollback

Desktop

When a desktop release is broken:

  1. stop promoting the release;
  2. document the affected platforms and user-visible failure;
  3. preserve the failed artifacts for investigation;
  4. publish or restore a known-good release according to updater behavior;
  5. test migration from both the broken and previous good versions;
  6. explain whether users must take manual action.

Catalog

When a catalog release is invalid:

  1. do not weaken signature verification;
  2. retain the invalid release for audit unless policy requires removal;
  3. restore or republish the Last Known Good signed catalog;
  4. verify client fallback behavior;
  5. correct source manifests or compiler logic through review;
  6. publish a new signed release.

Release documentation

Release notes should lead with player impact:

  • what changed;
  • whether migration is automatic;
  • known limitations;
  • recovery advice;
  • platform-specific concerns.

Detailed compiler, signing, or governance internals belong in maintainer documents, not the first screen of player release notes.

Source: docs/RELEASING.mdEdit this page on GitHub