Repository navigation
Description
Problem
Asterism ships one download: a signed, notarized macOS DMG. #165 scoped Windows out ("each is its own toolchain and its own certificate story; v0.1.0's download is the macOS build"), and release.yml repeats that under "What is deliberately absent". That was the right call for getting one platform to open cleanly, but it now leaves out the people the app is for: the users of local AI image generation tooling, many of whom are not on a Mac. A reader who cannot run the app never gets far enough to judge whether it does what it says.
This issue reverses the Windows part of that scope-out. Linux packaging and an updater stay out, for the reasons #165 gives.
Desired outcome
A tagged release carries a Windows installer next to the DMG, and on a clean Windows machine that installer opens the app and gets as far as importing a folder of images.
What the tree assumes today
The work is more than adding a bundle target. These are the places that assume macOS, found by reading the tree:
- The ffmpeg sidecar's video encoder is Apple-only.
scripts/build-ffmpeg-sidecar.shbuilds an LGPL ffmpeg whose only H.264 encoder ish264_videotoolbox, and it fails the build if that encoder is missing.preview_ffmpeg.rstrieslibx264, thenh264_videotoolbox(ENCODERS, line 40).libx264is GPL and is left out on purpose, so a Windows sidecar needs a different LGPL-compatible H.264 encoder, andFFMPEG-NOTICE.md, which ships inside the app, has to describe that build too. - The sidecar build script is written for macOS. It uses Apple-specific configure flags and checks what the binary links against in a macOS-specific way. The Windows build needs its own toolchain (MSVC or MinGW) and its own link check.
- Thumbnails take a different path.
jobs/mod.rscompilesthumb_macosandthumb_videoonly on macOS.handlers.rsfalls back to a single decode slot (THUMB_DECODE_SLOTS) and a JPEG encoder path everywhere else. That fallback compiles, but it has never shipped. - Unix-only branches.
asterism-exporter-filerejects symlink mode when not on unix.asterism-server/src/core_init.rshas acfg(not(unix))branch for file permissions. Keychain signing indisclosure.rsis macOS-only, andcore_init.rsalready logs an error when it is requested elsewhere. Each one needs a decision about what Windows does, not just whether it compiles. - CI never builds for Windows.
check.ymlandrelease.ymlboth run onmacos-latest. The Justfile recipes thatrelease.ymlrelies on (dogfood-build,ffmpeg-sidecar) are bash.
Constraints
- No GPL in the bundle. The LGPL discipline that the sidecar script and
FFMPEG-NOTICE.mdhold for macOS holds for Windows too. The release attaches the matching ffmpeg source and build script for each platform it ships. - One job per platform, not a matrix.
release.ymlalready says that the file "should grow a job per platform rather than a matrix that pretends they are the same". - Releases stay drafts. A tag creates a draft release and a person publishes it, as now.
- The macOS release path must not regress. That includes the notarization and Gatekeeper checks Sign and notarize the macOS build: the download opens like an app #165 added.
Acceptance criteria
- A
v*tag produces a draft release carrying a Windows installer, the Windows sidecar's ffmpeg source and build script, and the.cratesources the notices promise, next to the existing macOS assets. - The Windows bundle contains the ffmpeg sidecar, and the build fails if it does not, the same way
dogfood-build's trailing assert does for macOS. - On a clean Windows machine, the installed app starts, imports a folder of images, and shows thumbnails. A video in that folder gets a preview rendition.
workflow_dispatchruns a rehearsal of the Windows job that produces a workflow artifact and touches no release, as the macOS job does.
Open
Cost these first, by reading release.yml, tauri.conf.json, tauri.bundle.conf.json and the sidecar script, before any job is written:
- The Windows H.264 encoder. Candidates are
h264_mf(Media Foundation, the Windows system component that plays the role VideoToolbox plays on macOS) andlibopenh264. Each has licensing and distribution consequences for the notice. - Code signing. Decide whether the first Windows release is unsigned (so SmartScreen warns on first launch) or Authenticode-signed, and if signed, which certificate or signing service. This is the Windows counterpart of Sign and notarize the macOS build: the download opens like an app #165's Developer ID and notarization work.
- Installer format. Tauri v2 bundles NSIS (
.exe) and MSI (WiX). Decide whether to ship one or both, and how the installer handles the WebView2 runtime. - Decide whether
check.ymlgains a Windows build, and whether that is a compile check or a test run, so thatcfg(not(target_os = "macos"))code stops being untested until release day. - This issue does not yet cite how the audience splits across operating systems. Add those figures once they can be sourced publicly.
Refs #165
Activity
ynishi commentedon Sep 28, 2026
Open 5: how the audience splits across operating systems
Measured on 2026-09-28 from public GitHub release data. For each tool, the download counts of every release asset were summed across all releases and grouped by operating system using the asset's file name.
.dmgormacin the name counts as macOS,.exe,.msiorwincounts as Windows, and.AppImage,.deb,.rpmorlinuxcounts as Linux. Updater manifests (latest*.yml),.blockmapfiles, checksums and source archives are excluded.The first two are the closest to what Asterism does, since they open and read the images these tools produce. Across all five, macOS accounts for 10.8–16.0% of downloads and Windows for 73–88%. Image MetaHub, the closest of them, ships Windows, macOS and Linux builds.
Asterism's DMG is built on
macos-latest, which is an Apple Silicon runner, with no universal target. So today's download reaches only part of that macOS share.Limits of these numbers:
To reproduce:
ynishi commentedon Sep 28, 2026
Open 1–3: cost and a recommended choice for each
Researched on 2026-09-28 against
mainat e56cbb9, from upstream source and vendor documentation. Claims that could not be confirmed on a primary source are marked unverified.h264_mf(Media Foundation), built statically with MSYS2 UCRT64 in the Windows jobbundle.windows.signCommandinstallMode: currentUser, WebView2 left on the defaultdownloadBootstrapper1. Encoder:
h264_mfconfigurelistsmediafoundationas an autodetected external library, and it is on none of the GPL, nonfree or version3 lists (configure, release/8.0). The build stays LGPL-2.1 with--enable-mediafoundation --enable-encoder=h264_mfadded after--disable-everything. Media Foundation is part of Windows and is loaded at runtime, so it adds nothing to ship and nothing to offer source for.h264_mfonly looks for software encoders, and it looks for hardware ones only when-hw_encoding 1is passed (mf_utils.c). So it uses Microsoft's built-in software H.264 encoder, which supports Baseline, Main and High (Microsoft).mfenc.cloadsmfplat.dllat runtime, so on those editions ffmpeg still starts and thumbnails still work, and only the preview job fails. Its error message should name the Media Feature Pack. The same Microsoft page says the encoder does not support Windows Server editions.preview_ffmpeg.rspasses-q:v 55to every encoder exceptlibx264. The ffmpeg CLI turns that into aglobal_qualityof 118 × 55 = 6490 (ffmpeg_mux_init.c).mfenc.chands that value to Media Foundation as a QP, and Microsoft documents a QP range of 16–51. What the encoder does with 6490 is unverified.h264_mfneeds its own arguments:-rate_control quality -quality 55.libopenh264. Cisco pays the patent licence only for its own binary, which the user downloads separately and can switch on and off (FAQ, binary licence). A copy we bundle or build ourselves has no patent coverage. Using Cisco's binary would mean adding a runtime download and a settings toggle just to get a fallback.nvenc/qsv/amf. Each one needs its vendor's GPU and driver.msys2/setup-msys2provides MSYS2 on GitHub Actions. Cross-compiling from Linux would split the Windows release across two jobs. BtbN's prebuilt "lgpl" binaries add--enable-version3, so they do not match this project's LGPL-2.1 build (variant).h264_mfbuild, whether static linking removeslibwinpthread-1.dll, and how long the build takes. No source gives a build time.2. Signing
.pfxstored as a GitHub secret is no longer possible for a new certificate. The key has to sit in a CA's cloud signing service. Certificates issued from 2026-03-01 are valid for at most 460 days (Ballot CSC-31), so one has to be reissued about every 15 months.bundle.windows.signCommandruns the CA's CLI (Tauri). The bundler signs the external binaries, the main exe and the NSIS installer with it, so one command covers everything Smart App Control checks.workflow_dispatchrehearsal. Publishing an unsigned release with a note about Smart App Control is the maintainer's decision, not the default.3. Installer
currentUserinstalls into%LOCALAPPDATA%without an admin prompt. MSI only helps with enterprise deployment, and that is not who this build is for.downloadBootstrapper. It adds almost nothing to the installer's size.offlineInstallerwould add about 127 MB andfixedRuntimeabout 180 MB (WebviewInstallMode). Windows 11 already includes the runtime, and the bootstrapper installs it on the few Windows 10 machines that lack it (Microsoft).externalBinentry stays as it is. The script only has to writetarget/ffmpeg-sidecar/ffmpeg-x86_64-pc-windows-msvc.exe(resources.rs). Unverified: that the installer places it beside the main exe asffmpeg.exe, as the bundler templates suggest.Changes this issue did not list
Found by reading the tree:
thumb_ffmpeg.rs(sidecar_beside, and the PATH probe) looks for a file namedffmpeg. The tree does not usestd::env::consts::EXE_SUFFIXanywhere, andimport_launcher.rsjoinsBINARY_NAMEwithout.exe.CREATE_NO_WINDOW, and the release app is built as a GUI app (windows_subsystem = "windows"). Whether a console window actually flashes without the flag is unverified.about.tomlgeneratesTHIRD-PARTY-NOTICES.mdforaarch64-apple-darwinonly. A Windows build links a different set of crates, so the notice has to cover that target too.FFMPEG-NOTICE.mddescribes a single ffmpeg build, and it would have to describe both.zsh, anddogfood-buildhard-codes the macOS bundle paths.open.mcp_proxy.rsstarts the app withopen, so auto-launch fails on Windows.gh release view || gh release create, so whichever finishes second fails on "already exists". Either upload afterneeds: macos, or add a final job that creates the draft release.build-ffmpeg-sidecar.shusessysctl,shasumandotool -L. The Windows build needs a sibling script that checks linked DLLs withobjdump -pand an allow-list of system DLLs.ynishi commentedon Sep 28, 2026
Open 4: decided —
check.ymlgains a Windows jobDecision:
check.ymlgets a Windows job by default. If it runs into problems, those problems get weighed then, and the decision is revisited here with them in hand. It is not argued out in advance.The reason is the one Open 4 gave. Today every
cfg(not(target_os = "macos"))path compiles for the first time on release day. That covers the single-slot thumbnail decode and JPEG path injobs/handlers.rs, the non-unix branches inasterism-exporter-fileandcore_init.rs, and the Windows code this issue adds (theEXE_SUFFIXlookup,CREATE_NO_WINDOW, theh264_mfarm). A Windows job incheck.ymlmakes each pull request answer for that code instead.What the job has to deal with. All of this comes from reading
check.ymland theJustfileas they are now:Justfileshell.set shell := ["zsh", "-cu"]has no zsh to run on a Windows runner. Either the recipes the job calls get a Windows shell, or the job calls cargo directly. If it calls cargo directly, it keeps a second copy of what green means, which is what the top ofcheck.ymlargues against.brew install ffmpegbecause three tests panic without an ffmpeg binary. The Windows job needs its own source, orASTERISM_FFMPEGpointed at one.docs/aidoc/stays with the macOS job. Its regeneration step is tied to the macOS target (cargo-aidoc records the target, and two job modules are macOS-only). The Windows job must not regenerate or push it.collation-jscstays out, for the reasoncheck.ymlalready gives: it passes as a skip whereverjscis missing.If a problem turns out to be large, the options, in order, are these. Narrow the job to
cargo clippy/cargo checkfor the Windows target. Keep it off pull requests and run it only onmain. Drop it. Whichever is chosen gets recorded here, with the run that prompted it.Give file export's symlink mode one function per platform
Make the symlink and aidoc-guard comments say what the code does
Run every gate but the Rust tests in the Windows job
Say the Windows job leaves the Rust tests to macOS
Make the Windows job's comments say what it runs and why
Say the Windows test failures are partly production code
ynishi commentedon Sep 29, 2026
Open 4: the Windows job is narrowed to every gate except the Rust tests
This records the fallback Open 4 said would be chosen if the job ran into problems, together with the run that prompted it.
Run 36512441186, on #308 at cc59011, is the second run of the Windows job.
cargo clippy --workspace -D warningspassed.check-sharedgate 5204 passed.The first run had already found one Windows-only compile error, the symlink arm in
asterism-exporter-file, which dce86c5 fixed.The test failures are not just tests written against unix. Some come from production code the Windows build would ship:
resolve_output_dirrefuses any export directory that does not start with/.teams-infra's blob store open a directory as a file to fsync it, and Windows refuses that with os error 5.Test-only failures (paths joined or compared with
/) are part of the same run. #309 lists every failure, grouped by cause.Decision: the Windows job runs
check-sharedand clippy, and leaves the Rust tests to the macOS job. It still compiles and lints everycfg(windows)andcfg(not(target_os = "macos"))path on each pull request, which was the point of Open 4. The tests come back to the Windows job, with an ffmpeg install, once #309 is done.This is the first of Open 4's three fallbacks, taken more broadly than it was worded. Open 4 said "
cargo clippy/cargo check", but the job keeps every other gate as well, because all of them passed on Windows.