For contributors and maintainers
Releasing Agora
Agora has two independent release streams:
| Stream | Typical tag | Contents |
|---|---|---|
| Catalog | registry-YYYY-MM-DD | Signed catalog database and web export |
| Desktop | vX.Y.Z | Platform 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 theversion-metadataCI 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.mdmatch 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 check | Covers |
|---|---|
CI Enforcement | cargo 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 CI | Cross-platform launch-planner integration tests and the launchMode delegation-default assertion |
E2E Tests | The 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-refjob 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;
SHA256SUMScovers every downloadable artifact and nothing else;- updater metadata contains signed
darwin-aarch64anddarwin-x86_64entries; - release notes;
- accidental debug artifacts.
Release asset names
Every release publishes the same asset scheme:
agora-<kind>-<tag>-<platform>-<arch>[-<variant>].<ext>
| Kind | Assets (full names start with agora-<kind>-<tag>-) |
|---|---|
desktop | windows-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 |
cli | windows-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
*.sigsidecar assets — the signatures also live inline inlatest.json, which is what the updater consumes; - it rewrites every
latest.jsonplatform URL to the renamed asset and re-uploadslatest.json; - it writes and uploads
SHA256SUMS, covering every downloadable artifact exceptlatest.jsonandSHA256SUMSitself.
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:
- install or run the packaged build;
- On macOS, launch both the Apple Silicon and Intel
.apppackages on their matching hardware.
- On macOS, launch both the Apple Silicon and Intel
- complete first-run setup on a clean disposable profile;
- synchronize and verify the catalog;
- confirm Browse returns curated content;
- confirm loader and Java catalogs are available;
- create or import a disposable instance;
- run health;
- launch through the default delegated mode;
- launch directly with a test Microsoft account when that platform is supported;
- restart and verify settings and instances persist;
- 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:
- stop promoting the release;
- document the affected platforms and user-visible failure;
- preserve the failed artifacts for investigation;
- publish or restore a known-good release according to updater behavior;
- test migration from both the broken and previous good versions;
- explain whether users must take manual action.
Catalog
When a catalog release is invalid:
- do not weaken signature verification;
- retain the invalid release for audit unless policy requires removal;
- restore or republish the Last Known Good signed catalog;
- verify client fallback behavior;
- correct source manifests or compiler logic through review;
- 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.
docs/RELEASING.mdEdit this page on GitHub