soundcraft/docs/releasing.md

12 KiB

Releasing SoundCraft

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 SoundCraft v<version>. Nobody sees a draft until a maintainer publishes it.

This is SoundCraft's implementation of the shared release playbook. User-facing names say SoundCraft. Files, binaries and ids stay lowercase (soundcraft-<version>-<platform>-<arch>.<ext>, ai.storyteller.soundcraft).

Cutting a release

  1. Bump the version on main. The only place it lives is [workspace.package] version in the root Cargo.toml; the app reads it from CARGO_PKG_VERSION (About SoundCraft, soundcraft --version, soundcraft-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.lock
    

    Commit the change (Cargo.toml + Cargo.lock) through the normal review flow.

  2. Merge main into release (or fast-forward it) and push. The workflow starts by itself.

  3. 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 SoundCraft v0.2.0, tagged v0.2.0 on the pushed commit, with every artifact and SHA256SUMS.txt. The notes are generated from the merged PRs.

  4. 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.

  5. Publish the draft in the GitHub UI. Publishing creates the v0.2.0 tag. 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. The run still needs the release environment, which only the release branch can use, so pick that branch in the dialog.

What gets built

Platform Artifacts Built on
macOS 11+ (universal: Apple silicon + Intel) soundcraft-<v>-macos-universal.dmg, soundcraft-cli-<v>-macos-universal.zip macos-15
Windows 10+ x64 soundcraft-<v>-windows-x64.msi, soundcraft-<v>-windows-x64-portable.zip windows-latest
Windows 10+ x86 (32-bit) soundcraft-<v>-windows-x86.msi, soundcraft-<v>-windows-x86-portable.zip windows-latest
Windows 11 ARM64 soundcraft-<v>-windows-arm64.msi, soundcraft-<v>-windows-arm64-portable.zip windows-latest (cross-compiled; windows-arm64.yml installs and runs it on ARM64)
Linux x86_64 soundcraft-<v>-linux-x86_64.{AppImage,deb,rpm,tar.gz} ubuntu-22.04
Linux aarch64 soundcraft-<v>-linux-aarch64.{AppImage,deb,rpm,tar.gz} ubuntu-22.04-arm
FreeBSD 14 x86_64 soundcraft-<v>-freebsd-x86_64.tar.gz (a /usr/local tree) FreeBSD 14.3 VM on ubuntu-latest
Web soundcraft-web-<v>.zip (static site; see packaging/web/README.md) ubuntu-latest

The Flatpak manifest (packaging/linux/flatpak/ai.storyteller.soundcraft.yml) is kept ready for a Flathub submission and validated in CI, but not built there (see Linux below).

Audio libraries

SoundCraft 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-dev and pkg-config on Ubuntu). The binary links libasound.so.2, so the .deb depends on libasound2 | libasound2t64 and the .rpm on alsa-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 runtime alsa-plugins-oss routes ALSA to the OSS sound(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 manifest grants --socket=pulseaudio.
  • 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 SoundCraft.app:

  • Info.plist is generated from Info.plist.in. The bundle id is ai.storyteller.soundcraft. It declares the .scraft session type (ai.storyteller.soundcraft.session, MIME application/x-soundcraft-session, role Editor, rank Owner) and lists WAV, AIFF/AIFC, FLAC, MP3 and MIDI as Viewer/Alternate, so SoundCraft appears under Open With without taking over the default player. NSMicrophoneUsageDescription explains 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 --deep on the final signature. entitlements.plist holds 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 an Applications link). The DMG is signed, notarized and stapled too.
  • CLI: the universal soundcraft-cli is 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/soundcraft-*-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/soundcraft/build.rs embeds the icon (assets/app-icon/soundcraft.ico) and VERSIONINFO with winresource, only when targeting Windows. Release packaging sets SOUNDCRAFT_REQUIRE_WINRES=1 so a missing resource compiler fails the build.
  • The script checks both PE headers: the machine type matches -Arch, soundcraft.exe is a GUI app (no console window) and soundcraft-cli.exe a console app.
  • soundcraft.wxs (WiX v5) is a per-machine install into Program Files with an advertised Start Menu shortcut. SoundCraft becomes the default app for .scraft and is listed under Open with for .wav, .aif, .aiff, .flac, .mp3, .mid and .midi. It also registers App Paths (Win+R soundcraft). The MSI version is the numeric X.Y.Z; same-version upgrades let release candidates replace each other. The upgrade code EE53D3E5-D0AE-40F9-808D-9417FDEBF61A is fixed forever.
  • Signing: packaging/windows/sign.ps1 signs both .exe files and then the .msi with signtool (SHA-256, RFC 3161 timestamp), using a .pfx (WINDOWS_CERTIFICATE*) if present, otherwise Azure Trusted Signing (all six AZURE_*), 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, ai.storyteller.soundcraft.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-soundcraft-session (*.scraft).

  • AppImage (maintained AppImage/appimagetool, static runtime: no libfuse2 needed).
  • .deb and .rpm by nfpm from one nfpm.yaml; the postinst.sh hook refreshes the desktop, MIME and icon caches.
  • .tar.gz for people who manage their own /opt or ~/.local.
  • Flatpak: packaging/linux/flatpak/ai.storyteller.soundcraft.yml builds from source on the freedesktop 25.08 runtime with Wayland/X11, dri, PulseAudio and xdg-music; other files go through the portals. A real build needs vendored crates (cargo-sources.json from flatpak-cargo-generator.py); the commands are in the manifest's 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 soundcraft-*-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/soundcraft-web (output in dist/web, public_url = "./") and zips the site with sample _headers and .htaccess files and the hosting guide. apps/soundcraft-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. Every job in release.yml declares environment: release. 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: Learning Machines LLC (DJ6XS33FX8)"
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, SoundCraft 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/soundcraft/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 YAML check of the Flatpak manifest.