Building and shipping

Platform Notes

Testing a build on a platform for the first time? Work through Smoke Test Checklist rather than clicking around — it is ordered so the most likely failures surface first.

Icons

app-icon.png in the repository root is the source of every app icon, and is the default cargo tauri icon looks for. Regenerate after changing it:

cargo tauri icon

That rewrites everything in src-tauri/icons/. It also emits android/ and ios/ sets, which Flume does not ship — delete them, or the tree grows 35 files and 1.5 MB for platforms with no build.

The source must have a real alpha channel. macOS and Linux draw the icon against whatever is behind it, so a baked-in background renders as a square tile rather than a shaped mark.

Build matrix

Target Runner Output
macOS 12+ macos-latest .dmg, .app
Windows 10/11 windows-latest .msi, .exe (NSIS)
Debian 12+ / Ubuntu 22.04+ ubuntu-22.04 .deb
Fedora 38+ / RHEL 9.4+ Fedora/RHEL 9 container .rpm

The Linux glibc floor is set by the build runner, not by our code. A binary built on Ubuntu 24.04 will not run on Debian 12. This is why CI pins ubuntu-22.04 for .deb and uses a RHEL 9-era container for .rpm.

Flume’s TLS choice helps here: because we build librqbit with rust-tls instead of native TLS, packages carry no libssl runtime dependency, which removes the most common cross-distro breakage.

macOS

Minimum: 12.0 (set in tauri.conf.json).

WebView: WKWebView, bundled with the OS. No runtime to ship.

Released builds are signed and notarized. The release workflow signs with a Developer ID certificate and submits to Apple’s notary service; the rc.2 run logged Notarizing Finished with status Accepted. Gatekeeper does not warn.

Signing is conditional on secrets.APPLE_CERTIFICATE being set — the workflow picks the signed or unsigned path from HAS_APPLE_CERT, so a fork with no certificate still builds rather than failing. Check which ran before claiming a build is signed: the job’s step list says Build and bundle (signed) or (unsigned), and one of them is always skipped.

scripts/setup-macos-signing.sh sets the four sensitive secrets. It never echoes one and never takes one as an argument.

An unsigned build — a local tauri build, or a fork’s — is blocked by Gatekeeper. Bypass via right-click → Open, or:

xattr -dr com.apple.quarantine /Applications/Flume.app

Signing requires an Apple Developer account ($99/yr), a Developer ID Application certificate, and notarization through Apple’s service. Tracked in #18.

Do not set licenseFile in tauri.conf.json

It looks like obvious good practice — ship the licence with the app — and what it actually does on macOS is turn the .dmg into a click-through EULA. Every user must accept an agreement before the disk image will open, and non-interactive tooling cannot mount it at all (hdiutil: attach canceled).

That pattern is inherited from commercial software. Apache-2.0 is permissive and applies whether or not anyone clicks Agree; the licence is already in the repository, in NOTICE, and inside the app bundle. The gate adds friction and buys nothing.

Confirm with:

hdiutil imageinfo Flume_x.y.z_aarch64.dmg | grep "Software License Agreement"

false is correct.

Do not set minimumSystemVersion in tauri.conf.json

It looks like the obvious place to declare the minimum macOS, and it breaks the release build.

Setting it makes the Tauri CLI export MACOSX_DEPLOYMENT_TARGET for the entire cargo invocation. That variable leaks into the host build of proc-macro crates, where it breaks ctor-proc-macro; the build then fails with can't find crate for ctor_proc_macro. Any value triggers it.

The minimum is declared in src-tauri/Info.plist instead, which tauri-bundler merges into the generated plist. Finder enforces LSMinimumSystemVersion either way, so the user-visible behaviour is the same.

See #22 for the full investigation.

Universal binaries (--target universal-apple-darwin) roughly double bundle size. Decision deferred.

Windows

Minimum: Windows 10 1803.

WebView: WebView2, preinstalled on Windows 11 and current Windows 10. Tauri can bundle a bootstrapper for older systems.

SmartScreen warns on unsigned executables until a download reputation builds. Windows builds are not signed — unlike macOS, which is. Code signing certificates cost real money annually; EV certificates clear SmartScreen immediately, OV ones build reputation over time.

File locking and seeding — confirmed on v9, and fixed. Windows refuses an open whose access conflicts with the sharing mode of an existing handle. FilesystemStorage opened every file read and write when allow_overwrite is set, including a complete torrent that would only ever be served, so another application holding a download with FILE_SHARE_READ blocked the add outright.

The patched librqbit Flume carries retries read-only on a sharing violation. Seeding only reads, so a completed torrent is served normally. Sent upstream as ikatson/rqbit#645; see #9.

A holder that permits no sharing at all (share_mode(0)) still cannot be read by anyone, which is Windows as documented rather than a defect. src-tauri/tests/windows_file_locking.rs covers both, and is the only place they run — the file is #[cfg(windows)].

Linux

WebView: WebKitGTK 4.1. This is the single biggest source of rendering differences from macOS and Windows — it lags on newer CSS features. Test layout changes on Linux early rather than late.

Debian / Ubuntu

sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev \
  librsvg2-dev patchelf build-essential curl wget file libxdo-dev libssl-dev

Fedora / RHEL / Rocky / Alma

sudo dnf install -y webkit2gtk4.1-devel openssl-devel curl wget file \
  libappindicator-gtk3-devel librsvg2-devel gcc gcc-c++ make

RHEL 9 derivatives may need EPEL for libappindicator-gtk3-devel. Verify webkit2gtk4.1 availability on the exact base image before relying on it.

Wayland

Tauri runs under Wayland via GTK. If rendering misbehaves, force X11:

GDK_BACKEND=x11 flume

AppImage

Optional additional output. Bundles more dependencies, so it works across more distros at the cost of size.

Flume registers itself as a handler for magnet: URIs. How that registration happens differs by platform, which matters when testing:

Platform Registered by Works in tauri dev?
macOS CFBundleURLTypes in the bundled app’s Info.plist No — needs an installed .app
Windows Registry entries written by the installer No — needs an installed build
Linux .desktop MIME entry, or at runtime Yes, via runtime registration

On macOS the app logs runtime deep-link registration unavailable (unsupported platform) at debug level on every dev start. That is expected, not a fault: macOS has no runtime registration API, so the association only exists for an installed bundle.

Consequence for testing: clicking a magnet link in a browser cannot be verified with npm run tauri:dev on macOS or Windows. Build a bundle (npm run tauri:build) and launch that instead.

Verified on macOS against a real bundle: CFBundleURLTypes registers the magnet scheme, and launching the app a second time with a magnet argument hands it to the running instance and exits, rather than starting a second engine.

If another client is installed, it may already own the magnet: default. Both apps are registered; macOS picks one. Change the default in the other client’s settings, or via a LaunchServices utility.

System tray

The tray is optional by design. Some Linux desktops ship no system tray at all, so a failure to create the icon is logged and ignored rather than being fatal — the app is perfectly usable without one.

Left-clicking the icon reveals the window on Windows and Linux; macOS opens the menu on any click, which is the platform convention and is handled by Tauri.

Sandboxing and firewalls

Flume needs:

  • Outbound TCP to peers and trackers
  • Outbound UDP for DHT
  • Inbound TCP on the listen port (42221) for incoming peers and seeding
  • Outbound UDP to the gateway for UPnP port mapping, if enabled

macOS prompts for incoming connections on first launch. Linux firewalls (ufw, firewalld) usually need an explicit rule for inbound.

Per-platform smoke checklist

Run before tagging a release:

  • Window opens and renders correctly
  • Engine reaches Ready within ~10s
  • Listen port binds; verify in status
  • Add a magnet (a Linux ISO) and confirm metadata resolves
  • Download completes and file integrity verifies
  • Seeding works with another client connected
  • Quit and relaunch; torrent resumes without full re-hash
  • Open containing folder works
  • Theme matches system setting