diff --git a/.forgejo/workflows/desktop.yml b/.forgejo/workflows/desktop.yml index e5ff048..25a53ac 100644 --- a/.forgejo/workflows/desktop.yml +++ b/.forgejo/workflows/desktop.yml @@ -126,3 +126,60 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} run: bash desktop/packaging/publish-release.sh + + # Windows installer, CROSS-COMPILED from Linux — there is no Windows build host. + # A Windows container can't run on a Linux host (containers share the host + # kernel), so cross-compiling is the only route without Windows hardware: + # cargo-xwin + LLVM's lld-link + makensis are Linux programs that emit Windows + # PE output. That toolchain is why this needs its own image rather than ci-tauri. + # + # NSIS only. `.msi` needs WiX v3, which is a Windows program — Tauri: ".msi + # installers can only be created on Windows". It returns if a Windows node does. + # + # A separate job, so a Windows-side failure never blocks the Linux artifacts that + # are the primary product today. Tauri calls this path "not tested as much" and a + # last resort, and nothing here can LAUNCH a Windows binary — green means it + # built, not that it runs. A real-machine check stays mandatory before trusting it. + windows: + name: Windows installer (cross-compiled) + if: github.ref == 'refs/heads/dev' || github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') + runs-on: python-ci + container: + image: git.fabledsword.com/bvandeusen/ci-tauri-win:1.97 + steps: + - uses: actions/checkout@v6 + + # Same reason as the Linux job: generate_context! embeds the built frontend + # at compile time, so it must exist before cargo runs. + - name: Build the shared frontend + run: npm ci && npm run build + working-directory: frontend + + # --runner cargo-xwin swaps cargo for the cross-compiling driver (it supplies + # the MSVC CRT/SDK, pre-warmed into the image, and links with lld-link). + # Frontend already built above; skip the beforeBuildCommand rebuild. + - name: Tauri build (NSIS installer) + run: | + cargo tauri build \ + --runner cargo-xwin \ + --target x86_64-pc-windows-msvc \ + --bundles nsis \ + --config '{"build":{"beforeBuildCommand":""}}' + working-directory: desktop/src-tauri + + - name: Upload installer + continue-on-error: true + uses: actions/upload-artifact@v3 + with: + name: thoughtsync-windows + path: desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe + if-no-files-found: warn + + # Publishes to the SAME release as the Linux job. Safe to run twice: the + # script reuses an existing release (409) and nullglob means each job uploads + # only the bundles present in its own workspace. + - name: Publish release + if: startsWith(github.ref, 'refs/tags/v') + env: + GITHUB_TOKEN: ${{ github.token }} + run: bash desktop/packaging/publish-release.sh diff --git a/ci-requirements.md b/ci-requirements.md index 081daa1..d75e9bd 100644 --- a/ci-requirements.md +++ b/ci-requirements.md @@ -82,5 +82,29 @@ backend/frontend push. `pacman -U`-tested here. That step logs `.PKGINFO` + the full file listing so the package is auditable from the run log; a real Arch install is the operator's confirm. + +### Windows lane — second job, second image + +`desktop.yml` also runs a `windows` job that cross-compiles the NSIS installer. + +- **Image:** `git.fabledsword.com/bvandeusen/ci-tauri-win:1.97` (Rust + Node + + `cargo-xwin` + LLVM/`lld` + NSIS). A separate image from `ci-tauri` per + CI-Runner's `docs/process.md` fork rule — the MSVC CRT/SDK cache alone is >1 GB. + Its pins are held in lockstep with `ci-tauri`; bump them together, since both + lanes compile the same source. +- **Why cross-compile:** there is no Windows build host, and a Windows container + cannot run on a Linux host (containers share the host kernel). `cargo-xwin`, + `lld-link` and `makensis` are Linux programs that emit Windows PE output. +- **NSIS only.** `.msi` requires WiX v3, a Windows program — per Tauri, "`.msi` + installers can only be created on Windows." +- **Separate job on purpose:** a Windows failure must not block the Linux + artifacts, which are the primary product today. +- **Weakest verification of any lane.** Tauri documents this path as "not as + straight forward as compiling on Windows directly and is not tested as much", + to be used "only as a last resort" — and a Linux runner cannot execute a + Windows binary. Green means it *built*. A real Windows machine check is + mandatory before trusting a release. +- **Unsigned.** Installers will trip SmartScreen until a code-signing + certificate exists; that is a purchasing decision, not a CI one. - No Postgres lane (unchanged): the desktop app's local store + sync behavior is verified on the operator's machine, not in CI. diff --git a/desktop/packaging/publish-release.sh b/desktop/packaging/publish-release.sh index 32a1c7d..0e79bae 100755 --- a/desktop/packaging/publish-release.sh +++ b/desktop/packaging/publish-release.sh @@ -17,6 +17,7 @@ # desktop/src-tauri/target/release/bundle/appimage/*.AppImage (de-bundled) # desktop/src-tauri/target/release/bundle/deb/*.deb # desktop/src-tauri/target/release/bundle/arch/*.pkg.tar.* (prebuilt pacman) +# desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle/nsis/*.exe # # Instance-agnostic: server + repo come from the runner's github.* context # (Forgejo populates them for compatibility), so nothing is hardcoded to one host. @@ -37,13 +38,20 @@ AUTH=(-H "Authorization: token $GITHUB_TOKEN") SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" BUNDLE_ROOT="$REPO_ROOT/desktop/src-tauri/target/release/bundle" +# Cross-compiled Windows output lands under the target triple, not the host root. +WIN_BUNDLE_ROOT="$REPO_ROOT/desktop/src-tauri/target/x86_64-pc-windows-msvc/release/bundle" # --- collect the assets to upload ------------------------------------------- shopt -s nullglob +# nullglob (set above) drops the patterns that didn't match, which is what lets the +# Linux job and the Windows job each run this script against the SAME release and +# upload only what they actually built — they run in separate workspaces, so neither +# can see the other's bundles. The release is created once and reused (409 path). ASSETS=( "$BUNDLE_ROOT"/appimage/*.AppImage "$BUNDLE_ROOT"/deb/*.deb "$BUNDLE_ROOT"/arch/*.pkg.tar.* + "$WIN_BUNDLE_ROOT"/nsis/*.exe ) if [ ${#ASSETS[@]} -eq 0 ]; then echo "ERROR: no bundles under $BUNDLE_ROOT — did the tauri build run?" >&2