Releasing Kinema
Every push to the release branch runs
.github/workflows/release.yml. The workflow builds
installers for macOS, Windows, Linux (AppImage, deb, rpm, tarball,
Flatpak) and FreeBSD, plus the web build, and creates or updates a
draft GitHub Release named
Kinema v<version>. Nobody sees a draft until a
maintainer publishes it.
User-facing names say Kinema. Files, binaries and
ids stay lowercase
(kinema-<version>-<platform>-<arch>.<ext>,
com.ncdev.kinema).
Cutting a release
Bump the version on
main. It lives in exactly one place,[workspace.package] versionin the rootCargo.toml; every crate inherits it and the packaging scripts read it from there:cargo xtask version # prints the current version, e.g. 0.2.1 cargo xtask version set 0.3.0 # or 0.3.0-rc.1; updates Cargo.toml and Cargo.lockCommit the change (
Cargo.toml+Cargo.lock) through the normal review flow, as aRelease: Kinema v0.3.0commit whose message says what changed for users.Merge
mainintorelease(or fast-forward it) and push. The workflow starts by itself.Wait for the draft. When every job is done (macOS notarization is the slow part), the Releases page has a draft
Kinema v0.3.0, targeting the pushed commit, with every artifact andSHA256SUMS.txt. The notes are generated from the merged pull requests.Check it. Download an installer or two and read the job summaries. A
::warning::there means a signing secret was missing and that artifact is unsigned.Publish the draft in the GitHub UI. Publishing creates the
v0.3.0tag. A version with a pre-release suffix (-rc.1) is marked as a pre-release.
Pushing to release again before you publish rebuilds the
same draft and replaces its assets. Once the draft is published, the
workflow refuses to touch that version again: bump it first.
Test runs: Actions › Release › Run workflow
runs the whole pipeline by hand. The optional version input
(such as 0.3.0-rc.1) overrides Cargo.toml for
that run only; each build job applies it with
cargo xtask version set before building. The signing jobs
(macOS, Windows) and the draft-release job run in the
release environment, which only the release
branch can use, so pick that branch for a full run. Run from any other
branch, it is a dry run: the jobs that sign nothing (Linux, Flatpak,
FreeBSD, web) build and upload their artifacts to the run, the signing
jobs are refused, and the draft-release job, which needs them, is
skipped.
What gets built
| Platform | Artifacts | Built on |
|---|---|---|
| macOS 11+ (universal: Apple silicon and Intel) | kinema-<v>-macos-universal.dmg,
kinema-cli-<v>-macos-universal.zip |
macos-15 |
| Windows x64 | kinema-<v>-windows-x64.msi,
kinema-<v>-windows-x64-portable.zip |
windows-latest |
| Windows x86 (32-bit) | kinema-<v>-windows-x86.msi,
kinema-<v>-windows-x86-portable.zip |
windows-latest |
| Windows on ARM64 | kinema-<v>-windows-arm64.msi,
kinema-<v>-windows-arm64-portable.zip |
windows-latest (cross-compiled) |
| Linux x86_64 | kinema-<v>-linux-x86_64.{AppImage,deb,rpm,tar.gz} |
ubuntu-22.04 |
| Linux aarch64 | kinema-<v>-linux-aarch64.{AppImage,deb,rpm,tar.gz} |
ubuntu-22.04-arm |
| Linux riscv64 | kinema-<v>-linux-riscv64.tar.gz |
ubuntu-24.04, cross-compiled (glibc 2.39+; CLI
smoke-tested under QEMU) |
| AppImage updates | kinema-<v>-linux-{x86_64,aarch64}.AppImage.zsync |
with the AppImage |
| Flatpak | kinema-<v>-linux-x86_64.flatpak,
kinema-<v>-linux-aarch64.flatpak |
ubuntu-24.04, ubuntu-24.04-arm (repackaged
Linux tarball) |
| FreeBSD 14 x86_64 | kinema-<v>-freebsd-x86_64.tar.gz |
FreeBSD 14.3 VM on ubuntu-latest |
| Web | kinema-web-<v>.zip, a static site (see packaging/web/README.md) |
ubuntu-latest |
kinema --version and kinema-cli --version
print the version from Cargo.toml.
Fonts. Every build job checks out craft-fonts at a
pinned commit (craft-fonts-ref in
.github/workflows/release.yml and freebsd.yml)
and builds with CRAFT_FONTS_DIR and
CRAFT_FONTS_REQUIRED=1, so releases embed its fonts and
fail rather than ship without them (the web build embeds only the UI
font, to stay small). The packages carry each embedded font's licence
(copy_font_licences in packaging/env.sh). Bump
the pins deliberately, in every job at once.
macOS
packaging/macos/package.sh builds
aarch64-apple-darwin and x86_64-apple-darwin
with MACOSX_DEPLOYMENT_TARGET=11.0, joins them with
lipo and assembles Kinema.app:
Info.plistis generated fromInfo.plist.in(bundle idcom.ncdev.kinema, the version and the build commit).- Signing uses the hardened runtime and a secure
timestamp, with the entitlements in
entitlements.plist. The Developer ID certificate is imported into a temporary keychain bypackaging/macos/import-cert.sh. - Notarization: the app is sent with
xcrun notarytool submit, then the ticket is stapled and checked withstapler validate. The app goes on a DMG (hdiutil makehybrid), which is signed and notarized too. The universalkinema-cliis signed the same way, zipped, and the zip is notarized. Its Finder window (background, icon size and positions) comes frompackaging/macos/dmg/, and its volume is namedKinemawithout the version, which the window's background needs; the DMG file name keeps the version.
Locally, without certificates, the script signs ad hoc and skips
notarization, which is enough to check the bundle and the DMG on your
own Mac (packaging/macos/package.sh).
Windows
packaging/windows/package.ps1 -Arch x64|x86|arm64 builds
with +crt-static, so neither the MSI nor the portable zip
needs the Visual C++ redistributable.
kinema.wxs(WiX) is a per-machine install into Program Files with a Start Menu shortcut and an App Paths entry (Win+Rkinema). Same-version upgrades are allowed, so release candidates replace each other.- The ARM64 build is cross-compiled on the x64 runner.
.github/workflows/windows-arm64.ymlinstalls that MSI on a Windows 11 ARM64 runner, runskinema-cli --versionnatively and uninstalls. - Signing:
packaging/windows/sign.ps1signs the executables and then the.msiwithsigntool, using whichever material is present: a.pfxcertificate (WINDOWS_CERTIFICATE, base64, andWINDOWS_CERTIFICATE_PASSWORD) or Azure Trusted Signing (theAZURE_*secrets). With neither, it warns and leaves the files unsigned, so test builds still produce installers.
Linux
packaging/linux/package.sh builds the release binaries
and packages them as an AppImage, a .deb and an
.rpm (with nfpm,
from nfpm.yaml) and a plain .tar.gz tree. The
packages install both programs, the desktop entry, the AppStream
metainfo, the icons and the licence files.
The Linux tarball includes install.sh. Extract it, open
a terminal in the extracted directory and run ./install.sh
to install both programs and their desktop integration under
~/.local (no administrator permissions needed). The
application-menu entry uses the installed binary's absolute path; add
~/.local/bin to PATH for terminal commands.
Run the installer again to update. For a system-wide installation, use
sudo ./install.sh --prefix /usr/local. A custom absolute
--prefix is also supported; its share
directory must be in the desktop's data search path to appear in the
menu. Prefixes containing =, % or control
characters are rejected for desktop-launcher compatibility.
The jobs run on ubuntu-22.04, the oldest GitHub-hosted
image, so the binaries only need glibc 2.35 or newer: Ubuntu 22.04+,
Debian 12+, Fedora 36+ and RHEL 10. The deb
(libc6 (>= 2.35)) and the rpm
(glibc >= 2.35) both declare that floor, so older
systems refuse the install. Moving the job to a newer image raises the
floor, so do it deliberately and update nfpm.yaml with it.
After packaging, the job prints the .deb's metadata and
contents, runs ldd on the binary and runs each AppImage
with --version.
AppImage updates. Each AppImage embeds update
information,
gh-releases-zsync|storytold|kinema|latest|kinema-*-linux-<arch>.AppImage.zsync,
and appimagetool writes the matching .zsync
beside it (the job installs zsync for
zsyncmake). AppImageUpdate
and AppImageLauncher then fetch only the changed blocks from the newest
published (non-pre-release) release. The job checks both with
--appimage-updateinformation and test -s.
Flatpak. The flatpak jobs (x86_64 on
ubuntu-24.04, aarch64 on ubuntu-24.04-arm) run
packaging/linux/flatpak-bundle.sh on the Linux job's
tarball: it installs the binaries with
packaging/linux/flatpak/com.ncdev.kinema.bundle.yml into a
single-file bundle,
kinema-<v>-linux-<arch>.flatpak (no Rust build,
so it takes a minute), then installs it and runs
kinema-cli --version in the sandbox. Users install it with
flatpak install --user <file>; the freedesktop
runtime comes from Flathub.
packaging/linux/flatpak/com.ncdev.kinema.yml is the
from-source manifest for a Flathub submission (its header says how to
build it). Both manifests must keep the same runtime and
finish-args, and install the same programs: the app and
kinema-cli, which runs the headless commands and the MCP
server in the sandbox
(flatpak run --command=kinema-cli com.ncdev.kinema mcp).
packaging-lint checks this.
FreeBSD
GitHub has no FreeBSD runners, so the freebsd job builds
in a FreeBSD 14.3 VM (vmactions/freebsd-vm,
pinned by commit) on ubuntu-latest.
packaging/freebsd/package.sh builds both programs and packs
a /usr/local-style tree as
kinema-<v>-freebsd-x86_64.tar.gz; install it with
tar -xzf kinema-<v>-freebsd-x86_64.tar.gz --strip-components 1 -C /usr/local.
The build needs the packages listed in the job (winit/wgpu, GTK 3 for
the file dialogs, fontconfig, and alsa-lib for audio
through cpal). .github/workflows/freebsd.yml builds and
packages the same way nightly, so a FreeBSD break shows up before a
release. package.sh --dry-run checks the tree's layout with
stub binaries on any OS (packaging-lint runs it).
Web
The web job builds apps/kinema-web with
cargo xtask web (the wasm-bindgen CLI must
match the crate's version exactly, WASM_BINDGEN in
xtask/src/main.rs) and zips the static site with
packaging/web/package.sh. packaging/web/README.md
covers hosting.
The draft release
The last job waits for every build, downloads their artifacts, writes
SHA256SUMS.txt and creates the draft
Kinema v<version> with notes generated from the
merged pull requests. If the draft already exists, it replaces its
assets and keeps it a draft. If that version is already published, the
job fails and asks for a version bump
(cargo xtask version set).
Secrets
The macOS, Windows and draft-release jobs run in the
release environment, which only the release
branch can use and which holds the signing secrets. The other jobs sign
nothing and get no secrets. Every secret is optional: a missing one
produces unsigned artifacts and a warning, never a failed build.
| Secret | Used for |
|---|---|
APPLE_CERTIFICATE,
APPLE_CERTIFICATE_PASSWORD,
KEYCHAIN_PASSWORD |
the Developer ID certificate (base64 .p12), imported
into a temporary keychain |
APPLE_ID, APPLE_PASSWORD,
APPLE_TEAM_ID |
notarization (APPLE_PASSWORD is an app-specific
password) |
WINDOWS_CERTIFICATE,
WINDOWS_CERTIFICATE_PASSWORD |
Windows signing with a .pfx |
AZURE_TENANT_ID, AZURE_CLIENT_ID,
AZURE_CLIENT_SECRET, AZURE_SIGNING_ENDPOINT,
AZURE_SIGNING_ACCOUNT, AZURE_CERT_PROFILE |
Windows signing with Azure Trusted Signing |