Releasing Audio
Every push to the release branch runs
.github/workflows/release.yml. The workflow builds signed
installers for macOS, Windows, Linux, FreeBSD and the web, then creates
or updates a draft GitHub Release named
Audio v<version>. Nobody sees a draft until a
maintainer publishes it.
This is Audio's implementation of the shared release playbook. User-facing names say
Audio. Files, binaries and ids stay lowercase
(audio-<version>-<platform>-<arch>.<ext>,
com.ncdev.audio).
Cutting a release
Bump the version on
main. The only place it lives is[workspace.package] versionin the rootCargo.toml; the app reads it fromCARGO_PKG_VERSION(About Audio,audio --version,audio-cli --version).cargo xtask version # prints the current version, e.g. 0.1.0 cargo xtask version set 0.2.0 # or 0.2.0-rc.1; updates Cargo.toml and Cargo.lockCommit the change (
Cargo.toml+Cargo.lock) through the normal review flow.Merge
mainintorelease(or fast-forward it) and push. The workflow starts by itself.Wait for the draft. After about 30 to 60 minutes (notarization and the FreeBSD VM are the slow parts), the Releases page has a draft
Audio v0.2.0, taggedv0.2.0on the pushed commit, with every artifact andSHA256SUMS.txt. The notes are generated from the merged PRs.Check it. Download an installer or two and look at the job summaries. Any
::warning::there means a signing secret was missing and that artifact is unsigned.Publish the draft in the GitHub UI. Publishing creates the
v0.2.0tag. Versions with a pre-release suffix (-rc.1) are marked as pre-releases.
Pushing to release again before you publish rebuilds the
same draft and replaces its assets. After the draft is published, the
workflow refuses to touch that version again, so bump it first.
Test runs: Actions → Release → Run workflow
runs the whole pipeline by hand. The optional version input
(such as 0.2.0-rc.1) overrides Cargo.toml for
that run only. The jobs apply it with
cargo xtask version set before building, so the binaries
report it too. Only the signing jobs (macOS, Windows) and the
draft-release job use the release environment, which only
the release branch can use. On any other branch the run is
a dry run: Linux, Flatpak, FreeBSD and web build and are checked, the
signing jobs are refused, and no release is drafted.
What gets built
| Platform | Artifacts | Built on |
|---|---|---|
| macOS 11+ (universal: Apple silicon + Intel) | audio-<v>-macos-universal.dmg,
audio-cli-<v>-macos-universal.zip |
macos-15 |
| Windows 10+ x64 | audio-<v>-windows-x64.msi,
audio-<v>-windows-x64-portable.zip |
windows-latest |
| Windows 10+ x86 (32-bit) | audio-<v>-windows-x86.msi,
audio-<v>-windows-x86-portable.zip |
windows-latest |
| Windows 11 ARM64 | audio-<v>-windows-arm64.msi,
audio-<v>-windows-arm64-portable.zip |
windows-latest (cross-compiled;
windows-arm64.yml installs and runs it on ARM64) |
| Linux x86_64 | audio-<v>-linux-x86_64.{AppImage,deb,rpm,tar.gz} |
ubuntu-22.04 |
| Linux aarch64 | audio-<v>-linux-aarch64.{AppImage,deb,rpm,tar.gz} |
ubuntu-22.04-arm |
| Linux riscv64 | audio-<v>-linux-riscv64.tar.gz (glibc 2.39+) |
ubuntu-24.04 (cross-compiled; CLI smoke-tested under
QEMU) |
| AppImage updates | audio-<v>-linux-{x86_64,aarch64}.AppImage.zsync |
with the AppImage |
| Flatpak x86_64, aarch64 | audio-<v>-linux-{x86_64,aarch64}.flatpak |
ubuntu-24.04, ubuntu-24.04-arm (repackages
the Linux tarball) |
| FreeBSD 14 x86_64 | audio-<v>-freebsd-x86_64.tar.gz (a
/usr/local tree) |
FreeBSD 14.3 VM on ubuntu-latest |
| Web | audio-web-<v>.zip (static site; see packaging/web/README.md) |
ubuntu-latest |
The from-source Flatpak manifest
(packaging/linux/flatpak/com.ncdev.audio.yml) is kept ready
for a Flathub submission; the release's .flatpak bundles
come from com.ncdev.audio.bundle.yml (see Linux below).
Audio libraries
Audio plays and records through cpal: CoreAudio on macOS, WASAPI on Windows, ALSA on Linux and FreeBSD (PulseAudio and PipeWire through their ALSA plugins), and WebAudio in the browser.
- Linux builds need the ALSA headers
(
libasound2-devandpkg-configon Ubuntu). The binary linkslibasound.so.2, so the.debdepends onlibasound2 | libasound2t64and the.rpmonalsa-lib; the AppImage and tarball expect it on the host (every desktop distro has it). - FreeBSD builds need
alsa-lib(pkg install alsa-lib); at runtimealsa-plugins-ossroutes ALSA to the OSSsound(4)devices. Without a device the player falls back to its silent clock, so the transport still runs. - Flatpak: alsa-lib and its PulseAudio plugin come
with the freedesktop runtime; the manifests grant
--socket=pulseaudio(PipeWire's pulse server answers on it). No raw ALSA devices and no JACK: Audio doesn't use either. - Web: browsers start audio only after a user gesture, so the first click in the page may be needed before sound plays. The web build has no recording.
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 Audio.app:
Info.plistis generated fromInfo.plist.in. The bundle id iscom.ncdev.audio. It declares the.aprojsession type (com.ncdev.audio.session, MIMEapplication/x-audio-session, role Editor, rank Owner) and lists WAV, AIFF/AIFC, FLAC, MP3 and MIDI as Viewer/Alternate, so Audio appears under Open With without taking over the default player.NSMicrophoneUsageDescriptionexplains the recording prompt.- Signing goes inside-out with the hardened runtime
and a secure timestamp. The executable is signed first, then the bundle.
There's no
--deepon the final signature.entitlements.plistholds a single exception,com.apple.security.device.audio-input, without which the hardened runtime blocks recording. - Notarization: the app is zipped and sent with
xcrun notarytool submit --wait, then the ticket is stapled to the app. The app goes on a DMG (hdiutil, with anApplicationslink). The DMG is signed, notarized and stapled too. Its Finder window (background, icon size and positions) comes frompackaging/macos/dmg/, and its volume is namedAudiowithout the version, which the window's background needs; the DMG file name keeps the version. - CLI: the universal
audio-cliis signed with the hardened runtime, zipped and notarized.
Locally, without certificates, the script signs ad-hoc
(codesign -s -) and skips notarization:
packaging/macos/package.sh # universal; needs both rustup targets
packaging/macos/package.sh --arch aarch64 # quicker, host-only
open dist/release/audio-*-macos-*.dmg
Windows
packaging/windows/package.ps1 -Arch x64|x86|arm64 builds
with -C target-feature=+crt-static (in
CARGO_TARGET_<TRIPLE>_RUSTFLAGS, so host build
scripts aren't affected), so neither the MSI nor the portable zip needs
the Visual C++ redistributable.
apps/audio/build.rsembeds the icon (assets/app-icon/audio.ico) and VERSIONINFO withwinresource, only when targeting Windows. Release packaging setsAUDIO_REQUIRE_WINRES=1so a missing resource compiler fails the build.- The script checks both PE headers: the machine type matches
-Arch,audio.exeis a GUI app (no console window) andaudio-cli.exea console app. audio.wxs(WiX v5) is a per-machine install into Program Files with an advertised Start Menu shortcut. Audio becomes the default app for.aprojand is listed under Open with for.wav,.aif,.aiff,.flac,.mp3,.midand.midi. It also registers App Paths (Win+Raudio). The MSI version is the numericX.Y.Z; same-version upgrades let release candidates replace each other. The upgrade codeEE53D3E5-D0AE-40F9-808D-9417FDEBF61Ais fixed forever.- Signing:
packaging/windows/sign.ps1signs both.exefiles and then the.msiwithsigntool(SHA-256, RFC 3161 timestamp), using a.pfx(WINDOWS_CERTIFICATE*) if present, otherwise Azure Trusted Signing (all sixAZURE_*), otherwise nothing (a warning).
Locally on Windows:
dotnet tool install -g wix --version 5.0.2, then
pwsh packaging/windows/package.ps1 -Arch x64.
Linux
packaging/linux/package.sh stages one FHS tree and makes
every format from it: both binaries,
com.ncdev.audio.desktop (with MimeType= for
sessions, WAV, AIFF, FLAC, MP3 and MIDI), hicolor icons from 16 px to
512 px plus a scalable SVG, AppStream metainfo, and a shared-mime-info
file declaring application/x-audio-session
(*.aproj).
- AppImage (maintained
AppImage/appimagetool, static runtime: no libfuse2 needed). Each embeds the update informationgh-releases-zsync|storytold|soundcraft|latest|soundcraft-*-linux-<arch>.AppImage.zsync, and the.zsyncnext to it (written whenzsyncmakefrom thezsyncpackage is installed, as in CI) lets AppImageUpdate fetch only the changed blocks of the newest non-pre-release. - .deb and .rpm by nfpm from one
nfpm.yaml; thepostinst.shhook refreshes the desktop, MIME and icon caches. - .tar.gz for people who manage their own
/optor~/.local. - Flatpak: the
flatpakjob runspackaging/linux/flatpak-bundle.shon the Linux job's tarball (no Rust build) withflatpak/com.ncdev.audio.bundle.yml, then installs the bundle and runsaudio-cli --versionin the sandbox. Install withflatpak install --user audio-<v>-linux-<arch>.flatpak. Both manifests use the freedesktop 25.08 runtime with Wayland/X11,dri, PulseAudio andxdg-music; other files go through the portals. For the VST3 and CLAP hosts they grant~/.vst3and~/.clapread-only and mount Flathub'sorg.freedesktop.LinuxAudio.Pluginsextensions (pointed to byVST3_PATHandCLAP_PATH); system plugin folders aren't visible in the sandbox.com.ncdev.audio.ymlbuilds from source for Flathub and needs vendored crates (cargo-sources.jsonfromflatpak-cargo-generator.py); the commands are in its header.
Binaries are built on Ubuntu 22.04 and need glibc ≥ 2.35. X11, Wayland, xkbcommon, Vulkan and EGL are loaded at runtime, so the packages declare them as dependencies or recommendations.
Locally (on Linux): install nfpm, then
packaging/linux/package.sh (or
--formats "deb tar").
FreeBSD
GitHub has no FreeBSD runners, so the freebsd job builds
inside a FreeBSD 14.3 VM (vmactions/freebsd-vm, pinned by
commit) and runs packaging/freebsd/package.sh, which stages
a /usr/local-style tree (binaries, desktop entry, MIME
type, metainfo, icons, docs) as a .tar.gz. Install with
tar -xzf audio-*-freebsd-x86_64.tar.gz --strip-components 1 -C /usr/local.
.github/workflows/freebsd.yml builds and tests the
workspace there on every push to main.
Web
packaging/web/package.sh runs
trunk build --release in apps/audio-web
(output in dist/web, public_url = "./") and
zips the site with sample _headers and
.htaccess files and the hosting guide.
apps/audio-web runs the same SoundApp as the
desktop app on the synthesised demo session, rendering with wgpu
(WebGPU, or WebGL2 with ?webgl) and playing through
WebAudio.
Secrets
All secrets live in the repository's release
environment (Settings → Environments → release),
restricted to the release branch. Only the jobs that sign
(macOS, Windows) and the draft-release job declare
environment: release; the others get no secrets. Each
secret is optional: if one is missing, that platform's artifacts are
unsigned and the run shows a ::warning::. Scripts never
echo secret values; certificates are written with umask 077
and deleted in always()/finally steps.
| Secret | Used for |
|---|---|
APPLE_CERTIFICATE |
base64 .p12 with "Developer ID Application:
|
APPLE_CERTIFICATE_PASSWORD |
password for that .p12 |
KEYCHAIN_PASSWORD |
password for the temporary CI keychain (random if unset) |
APPLE_ID |
Apple ID used by notarytool |
APPLE_PASSWORD |
app-specific password for that Apple ID |
APPLE_TEAM_ID |
DJ6XS33FX8 |
WINDOWS_CERTIFICATE /
WINDOWS_CERTIFICATE_PASSWORD |
base64 .pfx (unused: Windows uses Azure) |
AZURE_TENANT_ID, AZURE_CLIENT_ID,
AZURE_CLIENT_SECRET |
service principal for Azure Trusted Signing |
AZURE_SIGNING_ENDPOINT,
AZURE_SIGNING_ACCOUNT, AZURE_CERT_PROFILE |
Trusted Signing endpoint, account and profile |
GITHUB_TOKEN creates the release. Only the final job
gets contents: write. Provisioning the environment, ruleset
and secrets for a new repo: craftrules
release/signing-setup.md.
Icons
assets/app-icon/icon.svg is the canonical icon: a
placeholder singing nightingale, drawn as original SVG
in the three-colour icon palette (ink, paper, Audio teal
#14a9c4); see assets/app-icon/README.md.
packaging/icons.sh regenerates the 1024 px PNG, the macOS
512 px window icon, the .icns, the .ico
(packed by cargo xtask ico) and the hicolor PNGs from it.
It needs resvg, plus iconutil on macOS. The
outputs are committed, so packaging never needs those tools.
apps/audio/src/main.rs sets the runtime window/Dock/taskbar
icon from them.
Checks
.github/workflows/packaging-lint.yml runs in seconds on
any change to packaging/, the workflows or the icons:
actionlint, shellcheck, a PowerShell parse, xmllint (WiX, plists,
AppStream, MIME), desktop-file-validate,
appstreamcli validate, and a check that the two Flatpak
manifests agree on runtime, finish-args and plugin extensions.