Skip to the content.

How a release is cut. The Windows assets (signed executables, MSI, zip, checksums) are built and signed on a release machine (a Windows PC that holds the code-signing certificate); the executables themselves are built the tested way, from the Linux containers. Linux (and later macOS) assets are built on their own machines and attached to the same release. Treat this file as a checklist: each numbered step is a step, not a suggestion (STD-003 rule 3).

0. Before anything

1. Merge and tag

  1. Merge the PR into main (squash or merge commit; keep the review log).
  2. Tag the merge commit: git tag -a vX.Y.Z -m "kvm-it X.Y.Z" and push the tag.

2. Build the Windows executables (on any machine with podman/docker)

git checkout vX.Y.Z
scripts/rs.sh windows    # -> desktop/target/x86_64-pc-windows-gnu/release/{kvmit.exe,kvmit-gui.exe}

This is the build that was tested, so this is the build that ships. Copy those two files to the release machine (for example into C:\release\exes\). Building natively on Windows (x86_64-pc-windows-msvc) is not what was tested; if you ever switch, re-run the Windows checks first and say so in the notes.

The standard build bundles no DLLs: everything it imports is a Windows system DLL (the C runtime is linked statically). A software OpenGL (Mesa) is only needed on a machine without a GPU, such as a VM; it is not shipped.

3. Prepare the release machine (once)

3b. CI signing (preferred)

.github/workflows/release.yml does steps 2, 4 and the attach half of 6 on GitHub: it cross-builds the executables with scripts/rs.sh windows, signs them and the MSI with Azure Artifact Signing, verifies every signature (valid, timestamped, expected signer), runs verify-release.py --require-signed, and attaches the assets. It runs when a release is published, or by hand (workflow_dispatch with the tag) to rebuild an existing one. No key, PFX or password exists anywhere (STD-006): the job authenticates with a GitHub OIDC federated credential, and Artifact Signing issues three-day certificates, which is why every signature is RFC 3161 timestamped.

One-time setup, from a checkout of spoolsmith (the script is repo-agnostic; every repo gets its own app registration, so access is revocable per repo):

az login
./scripts/setup-signing.ps1 -AccountName jdspille -ResourceGroup RG0 -ProfileName primary-profile -Repo spilloid/kvm-it

It creates kvm-it-release-signing, trusts only repo:spilloid/kvm-it:environment:release, grants Artifact Signing Certificate Profile Signer on the certificate profile only, and creates the release environment (restricted to main and v* tags) with SIGNING_* variables and the three Azure identifiers. The workflow refuses to run, rather than ship unsigned, if any of them is missing. Steps 5 (smoke test) and the release notes stay manual.

3c. Linux AppImage and the macOS .dmg (CI)

4. Build the assets

./scripts/build-release.ps1 -Tag vX.Y.Z -ExeDir C:\release\exes
python scripts/verify-release.py dist vX.Y.Z --require-signed   # drop --require-signed for an unsigned release

build-release.ps1 refuses to run unless VERSION and desktop/Cargo.toml match the tag. It signs the two executables, builds the MSI from them (and signs it), zips the signed executables with README.md, LICENSE, THIRD_PARTY_NOTICES.md (the MPL-2.0 notice for serialport) and CHANGELOG.md, and writes a .sha256 for each asset plus SHA256SUMS and SIGNATURES.txt. verify-release.py checks the hashes, the exact zip contents and CRCs, that both executables are 64-bit PE files with the right subsystem (kvmit.exe console, kvmit-gui.exe GUI), that the version is stamped into the binaries, that the README and CHANGELOG agree with the tag, and reports whether a signature blob is present (validity itself is what SIGNATURES.txt records, straight from Get-AuthenticodeSignature).

5. Smoke-test what you are about to ship

On a clean Windows 11 machine or VM, from the assets (not from the build tree):

  1. msiexec /i kvmit-vX.Y.Z-windows-x64.msi /qn installs kvm-it to C:\Program Files\kvm-it, a Start menu entry and kvmit on the PATH.
  2. kvmit --version prints the tag’s version; kvmit-gui.exe opens a window (or the OpenGL dialog on a GPU-less VM).
  3. msiexec /x kvmit-vX.Y.Z-windows-x64.msi /qn removes everything.
  4. Unzip the .zip elsewhere and run kvmit.exe --version from it.

6. Publish

  1. Create the GitHub release for the tag. The notes are the CHANGELOG entry for this version, plus an explicit line stating whether the Windows binaries are code-signed (copy it from SIGNATURES.txt) and what was verified where. If unsigned, say Windows SmartScreen will warn.
  2. Attach: kvmit-vX.Y.Z-windows-x64.zip, .msi, both .sha256 files, SHA256SUMS and SIGNATURES.txt. Attach the Linux (and later macOS) assets built on their machines, each with its checksum. The Linux AppImage: scripts/build-appimage.sh from a checkout of the tag (podman; an Ubuntu 22.04 container; the script fails if the binaries need a glibc newer than 2.35, or if it cannot inspect them; the AppImage tool and its runtime are pinned by checksum, the base image by digest, file times by the commit, but apt packages in the builder float, so the build is not bit-for-bit reproducible, and the host’s window-system/GL libraries are deliberately not bundled) writes dist-linux/kvm-it-X.Y.Z-x86_64.AppImage and its .sha256; smoke-test it (... cli --version, the GUI starts) and gh release upload vX.Y.Z <both files>.
  3. Optional: a GitHub build attestation for the assets (gh attestation / actions/attest-build-provenance).

7. After