Releasing¶
Releases are cut by hand from GitHub Actions. Pushing a tag does nothing: the Release workflow creates the tag itself, after every installer has built, so a tag always points to a commit that built on all platforms. This page mirrors RELEASING.md at the root of the repository.
Cutting a Release¶
-
Bump the version in all three places (they must agree):
File Field Cargo.toml[workspace.package] version(both crates inherit it)package.json"version"src-tauri/tauri.conf.json"version"Run
cargo check(or any cargo build) after the bump so thatCargo.lockpicks up the new crate versions, then check that the three files agree: -
Merge to
mainthrough a pull request. CI runscheck-version.shtoo, so a partial bump fails the pull request. -
Run the workflow: GitHub → Actions → Release → Run workflow:
- Use workflow from:
main - tag:
v+ the version, for examplev0.2.0 - prerelease: tick it for a pre-release (it is then not marked "Latest")
- Use workflow from:
-
Wait for the run (about 20–30 minutes). When it is green, the release is live at
https://github.com/luksgrin/CytoFlow.rs/releases/tag/<tag>.
If a run fails, nothing is tagged or published. Fix the problem on main and run the workflow again with the same tag.
Tag pushed but release not published
The one exception: if the tag was pushed but publishing the release then failed, the tag now blocks a re-run. Check that no release exists for it, delete the tag, and run the workflow again:
What Validation Enforces¶
The first job (validate) stops the run before anything is built if:
- the workflow is not run from
main; - the tag does not match
^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.]+)?$; - the tag has a pre-release suffix that is not a number from 0 to 65535. The Windows MSI bundler (WiX) only accepts numeric pre-release identifiers, so use
v0.2.0-1, notv0.2.0-rc.1; - the tag is not exactly
v+ the version inCargo.toml,package.jsonandsrc-tauri/tauri.conf.json(the error names each file that does not match); - the tag already exists on the remote (
git ls-remote --tags); - a GitHub release with that tag already exists (
gh release view).
So one tag can never be released twice. To ship a fix, bump the version. The final job checks the tag and the release again just before tagging, in case either appeared while the builds ran.
What the Workflow Does¶
-
validate: the checks above, on the commit the run was started from.
-
build, in parallel, all on that commit (
cargo tauri build --target … --bundles …):Runner Target Bundles macos-15aarch64-apple-darwinapp,dmgmacos-15(cross-compiled)x86_64-apple-darwinapp,dmgwindows-latestx86_64-pc-windows-msvcmsi,nsisubuntu-22.04x86_64-unknown-linux-gnudeb,appimageEvery build job gets the updater signing key (
TAURI_SIGNING_PRIVATE_KEY,TAURI_SIGNING_PRIVATE_KEY_PASSWORD), so Tauri also writes the updater bundles and their.sigsignatures; a missing key fails the build.scripts/collect-bundles.shcopies each installer, updater bundle and signature to its release name, so the asset names do not depend on Tauri's naming rules. -
release: writes
latest.json(full releases only, see below), checks that the asset list is exactly as expected, creates the annotated tag (git tag -a <tag>) on the validated commit, pushes it, then publishes the GitHub release. The release notes list the per-platform downloads, the install instructions (including the Gatekeeper and SmartScreen steps), and the changes since the previous tag (generated by GitHub).
flowchart LR
A[Run workflow<br/>tag = vX.Y.Z] --> B[validate]
B --> C1[macOS arm64]
B --> C2[macOS x64]
B --> C3[Windows x64]
B --> C4[Linux x64]
C1 & C2 & C3 & C4 --> D[release:<br/>tag + publish]
Release assets¶
For version X.Y.Z:
| Asset | Platform |
|---|---|
CytoFlow_X.Y.Z_aarch64.dmg |
macOS, Apple Silicon |
CytoFlow_X.Y.Z_x64.dmg |
macOS, Intel |
CytoFlow_X.Y.Z_x64_en-US.msi |
Windows x64, MSI |
CytoFlow_X.Y.Z_x64-setup.exe |
Windows x64, NSIS installer (per-user, no administrator rights) |
CytoFlow_X.Y.Z_amd64.deb |
Linux x64, Debian / Ubuntu (depends on libwebkit2gtk-4.1-0 and libgtk-3-0) |
CytoFlow_X.Y.Z_amd64.AppImage |
Linux x64, portable |
CytoFlow_X.Y.Z_aarch64.app.tar.gz |
macOS, Apple Silicon: updater bundle (the compressed .app) |
CytoFlow_X.Y.Z_x64.app.tar.gz |
macOS, Intel: updater bundle |
<asset>.sig |
updater signature, next to each .app.tar.gz, .msi, -setup.exe and .AppImage (5 files) |
latest.json |
updater manifest; full releases only, not attached to pre-releases |
source.tar.gz |
git archive of the tagged commit |
The build artifacts are also kept on the workflow run for 7 days (bundle-<target-triple>).
The builds are not code-signed or notarised. Users have to bypass Gatekeeper (xattr -cr /Applications/CytoFlow.app, or right-click → Open, or System Settings → Privacy & Security → Open Anyway) and SmartScreen (More info → Run anyway); see the Installation Guide. The release notes explain this too.
Updater Signing and latest.json¶
The app updates itself with the Tauri updater (see Updating and the update_check / update_install commands in the IPC contract). Updates are signed with a minisign key pair, which is unrelated to Apple or Microsoft code signing:
- The public key is in
src-tauri/tauri.conf.json→plugins.updater.pubkey, next toplugins.updater.endpoints = ["https://github.com/luksgrin/CytoFlow.rs/releases/latest/download/latest.json"];bundle.createUpdaterArtifacts: truemakescargo tauri buildproduce the signed updater bundles. -
The private key and its password are repository secrets, passed to every build job:
Secret Content TAURI_SIGNING_PRIVATE_KEYthe private key file written by cargo tauri signer generateTAURI_SIGNING_PRIVATE_KEY_PASSWORDits password The maintainer keeps them in
~/.tauri/cytoflow-updater.keyand~/.tauri/cytoflow-updater.password. Back them up: if the private key is lost, installed copies can no longer update in place and every user has to install the next version by hand. To set the secrets again: -
To rotate the key, generate a new pair (
cargo tauri signer generate -w ~/.tauri/cytoflow-updater.key), put the new public key intauri.conf.json, update both secrets and release. Copies running an older version trust only the old key and cannot install updates signed with the new one, so their users update by hand once. - A local
cargo tauri buildwithout the key fails with "A public key has been found, but no private key"; export the two variables, or build without updater bundles (--config '{"bundle":{"createUpdaterArtifacts":false}}').
For a full release, the release job writes latest.json in the Tauri updater format (notes from GitHub's generated release notes):
{
"version": "X.Y.Z",
"notes": "…",
"pub_date": "2026-01-31T12:00:00Z",
"platforms": {
"darwin-aarch64": { "signature": "<content of the .sig file>", "url": "https://github.com/luksgrin/CytoFlow.rs/releases/download/vX.Y.Z/CytoFlow_X.Y.Z_aarch64.app.tar.gz" },
"darwin-x86_64": { "signature": "…", "url": "…/CytoFlow_X.Y.Z_x64.app.tar.gz" },
"windows-x86_64": { "signature": "…", "url": "…/CytoFlow_X.Y.Z_x64-setup.exe" },
"windows-x86_64-nsis": { "signature": "…", "url": "…/CytoFlow_X.Y.Z_x64-setup.exe" },
"windows-x86_64-msi": { "signature": "…", "url": "…/CytoFlow_X.Y.Z_x64_en-US.msi" },
"linux-x86_64": { "signature": "…", "url": "…/CytoFlow_X.Y.Z_amd64.AppImage" },
"linux-x86_64-appimage": { "signature": "…", "url": "…/CytoFlow_X.Y.Z_amd64.AppImage" }
}
}
The updater looks up <os>-<arch>-<installer> first, then <os>-<arch>, so an MSI installation updates with the MSI and an NSIS installation with the setup program. The app fetches releases/latest/download/latest.json, which GitHub resolves to the release marked Latest. Pre-releases are never marked Latest and get no latest.json, so they are never offered. The app only installs a release with the same major version as the one running; see Updating.
Continuous Integration¶
.github/workflows/ci.yml runs on every push to main and every pull request to main:
- Frontend:
scripts/check-version.sh,npm ci,npm run typecheck,npm run build - Engine (Ubuntu): fetches the large public test fixtures with
scripts/fetch-testdata.sh(cached intestdata/large, keyed on the script's contents), thencargo test -p cytoflow-coreandcargo clippy -p cytoflow-core -p cytoflow --all-targets -- -D warnings - Compile app on Ubuntu 22.04, macOS and Windows:
cargo build -p cytoflow(no installers)
Cargo builds use --locked, so commit Cargo.lock changes. To add a large fixture, edit scripts/fetch-testdata.sh; editing it also refreshes the CI cache.
Documentation¶
The documentation is deployed separately, by .github/workflows/docs.yml; see Deploying the docs.