feat(build): cross-platform desktop packaging; bump to 0.1.0
Build & Release / Run Tests (push) Has been cancelled
Traceability Validation / Check Requirement Traces (push) Has been cancelled
Build & Release / Build Linux (push) Has been cancelled
Build & Release / Build Android (push) Has been cancelled
Publish Documentation / Build & publish docs to gitea-pages (push) Has been cancelled
🏗️ Build and Test JellyTau / Android Compile Check (push) Has been cancelled
Build & Release / Create Release (push) Has been cancelled
🏗️ Build and Test JellyTau / Run Tests (push) Has been cancelled
Build & Release / Run Tests (push) Has been cancelled
Traceability Validation / Check Requirement Traces (push) Has been cancelled
Build & Release / Build Linux (push) Has been cancelled
Build & Release / Build Android (push) Has been cancelled
Publish Documentation / Build & publish docs to gitea-pages (push) Has been cancelled
🏗️ Build and Test JellyTau / Android Compile Check (push) Has been cancelled
Build & Release / Create Release (push) Has been cancelled
🏗️ Build and Test JellyTau / Run Tests (push) Has been cancelled
Adds Docker-based packaging for Linux desktop (deb/rpm), Arch
(.pkg.tar.zst via makepkg), and Windows. Windows cross-compiles from
Linux via the official Tauri path — the x86_64-pc-windows-msvc target
driven by cargo-xwin — and produces an NSIS installer (nsis via
tauri.conf.json targets, since the CLI rejects --bundles nsis on a Linux
host). Verified end to end: builds jellytau.exe + jellytau_x64-setup.exe.
Unifies everything on one registry builder image (Dockerfile.builder):
Android SDK/NDK, rpm/file, clang(+clang-cl)/lld/llvm/nsis, cargo-xwin and
the msvc target. Packaging tools sit in a trailing layer so tool changes
rebuild in ~1min instead of ~15. Desktop stages are thin FROM
${BUILDER_IMAGE} layers; Arch uses a separate archlinux image.
CLAUDE.md: CI must install no system toolchains — everything lives in the
image. Bumps version 0.0.18 -> 0.1.0 (Windows support + webview audio +
equalizer).
TRACES: UR-003, UR-005 | DR-004
This commit is contained in:
@@ -35,6 +35,20 @@ CI runs on **Gitea Actions** (`.gitea/workflows/`), not GitHub. Use the `gh` CLI
|
|||||||
only against the mirror if one exists; the canonical remote is
|
only against the mirror if one exists; the canonical remote is
|
||||||
`gitea.tourolle.paris`.
|
`gitea.tourolle.paris`.
|
||||||
|
|
||||||
|
> **🔴 CI installs no system tools.** Never add an `apt-get`, `rustup`,
|
||||||
|
> `sdkmanager`, mingw/nsis, or any other *toolchain/system-package* install to a
|
||||||
|
> CI workflow step. Every build, test, and packaging **tool** must already live
|
||||||
|
> in the Docker image the job runs in — the unified builder (`Dockerfile.builder`
|
||||||
|
> → `gitea.tourolle.paris/dtourolle/jellytau-builder`) for Android/Linux/Windows,
|
||||||
|
> or `Dockerfile.arch` for Arch. If a job needs a tool the image lacks, **add it
|
||||||
|
> to the image, rebuild + push it** (`scripts/build-builder-image.sh`), and use
|
||||||
|
> it from CI — do not install it at job time. This keeps builds reproducible and
|
||||||
|
> fast, and is why the packaging stages are thin `FROM ${BUILDER_IMAGE}` layers.
|
||||||
|
>
|
||||||
|
> `bun install` (fetching the project's own JS deps per the lockfile) is **not**
|
||||||
|
> a violation — that's project dependencies, not a toolchain. The rule is about
|
||||||
|
> system tools, not npm/bun/cargo *packages* declared by the project.
|
||||||
|
|
||||||
## Before Committing
|
## Before Committing
|
||||||
|
|
||||||
- Frontend: `bun run check` and `bun run test` must pass.
|
- Frontend: `bun run check` and `bun run test` must pass.
|
||||||
|
|||||||
+31
@@ -1,4 +1,11 @@
|
|||||||
# Multi-stage build for JellyTau - Tauri Jellyfin client
|
# Multi-stage build for JellyTau - Tauri Jellyfin client
|
||||||
|
#
|
||||||
|
# The desktop packaging stages (desktop-linux-build, windows-cross) build FROM
|
||||||
|
# the unified registry builder image, which carries every packaging tool. Declared
|
||||||
|
# here (before the first FROM) so it's in scope for those stages' FROM lines.
|
||||||
|
# Override for local iteration: --build-arg BUILDER_IMAGE=jellytau-builder:latest
|
||||||
|
ARG BUILDER_IMAGE=gitea.tourolle.paris/dtourolle/jellytau-builder:latest
|
||||||
|
|
||||||
FROM ubuntu:24.04 AS builder
|
FROM ubuntu:24.04 AS builder
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND=noninteractive \
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
@@ -108,6 +115,30 @@ RUN cd src-tauri && cargo fetch && cd .. && \
|
|||||||
bun run tauri android build --apk true && \
|
bun run tauri android build --apk true && \
|
||||||
echo "APK build complete!"
|
echo "APK build complete!"
|
||||||
|
|
||||||
|
# Desktop packaging stages build FROM the unified registry builder image (see the
|
||||||
|
# BUILDER_IMAGE ARG at the top), which already carries every packaging tool
|
||||||
|
# (rpm/file for Linux, mingw-w64 + nsis + the x86_64-pc-windows-gnu rust target
|
||||||
|
# for Windows). ONE source of dependency truth, shared with CI — no per-stage
|
||||||
|
# apt/rustup here.
|
||||||
|
|
||||||
|
# Linux desktop packaging environment (deb + rpm; Arch is Dockerfile.arch).
|
||||||
|
# Thin layer over the builder — the actual build runs at container-run time on
|
||||||
|
# the bind-mounted source (see docker-compose.yml / scripts/build-desktop-linux.sh),
|
||||||
|
# matching the `dev` service model. Run standalone with:
|
||||||
|
# docker run --rm -v "$PWD:/app" -v "$PWD/dist:/app/dist" <img> \
|
||||||
|
# bash -c "OUTPUT_DIR=/app/dist scripts/build-desktop-linux.sh"
|
||||||
|
FROM ${BUILDER_IMAGE} AS desktop-linux-build
|
||||||
|
WORKDIR /app
|
||||||
|
CMD ["bash", "-c", "OUTPUT_DIR=/app/dist scripts/build-desktop-linux.sh"]
|
||||||
|
|
||||||
|
# Windows cross-compile environment (MSVC target via cargo-xwin). Video works via
|
||||||
|
# WebView2 and audio via the webview <audio> backend; NSIS installer is produced
|
||||||
|
# from Linux by cargo-xwin. Default bundles NSIS; override WIN_BUNDLES=none for
|
||||||
|
# exe-only. Build runs at container-run time like above.
|
||||||
|
FROM ${BUILDER_IMAGE} AS windows-cross
|
||||||
|
WORKDIR /app
|
||||||
|
CMD ["bash", "-c", "OUTPUT_DIR=/app/dist WIN_BUNDLES=${WIN_BUNDLES:-nsis} scripts/build-windows-cross.sh"]
|
||||||
|
|
||||||
# Final output stage
|
# Final output stage
|
||||||
FROM ubuntu:24.04 AS final
|
FROM ubuntu:24.04 AS final
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# JellyTau Arch Linux package builder.
|
||||||
|
#
|
||||||
|
# Tauri has no pacman bundle target, so we build a real .pkg.tar.zst with makepkg
|
||||||
|
# from packaging/arch/PKGBUILD. makepkg refuses to run as root, so we create a
|
||||||
|
# non-root `builder` user with passwordless sudo (for `makepkg -s` pacman calls).
|
||||||
|
#
|
||||||
|
# docker build -f Dockerfile.arch -t jellytau-arch .
|
||||||
|
# docker run --rm -v "$PWD/dist:/out" jellytau-arch
|
||||||
|
FROM archlinux:latest
|
||||||
|
|
||||||
|
RUN pacman -Syu --noconfirm \
|
||||||
|
base-devel git sudo \
|
||||||
|
rust cargo nodejs \
|
||||||
|
webkit2gtk-4.1 mpv gtk3 libayatana-appindicator \
|
||||||
|
libsoup3 pkgconf openssl \
|
||||||
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
|
# Bun is not in the official repos; install the upstream binary.
|
||||||
|
RUN curl -fsSL https://bun.sh/install | bash && \
|
||||||
|
ln -s /root/.bun/bin/bun /usr/local/bin/bun
|
||||||
|
|
||||||
|
# Non-root build user with passwordless sudo for makepkg's dependency step.
|
||||||
|
RUN useradd -m builder && \
|
||||||
|
echo 'builder ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/builder && \
|
||||||
|
ln -sf /root/.bun/bin/bun /usr/local/bin/bun
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
RUN chown -R builder:builder /app
|
||||||
|
|
||||||
|
USER builder
|
||||||
|
ENV OUTPUT_DIR=/out
|
||||||
|
RUN mkdir -p /out
|
||||||
|
VOLUME ["/out"]
|
||||||
|
|
||||||
|
# Default: build the package. Output lands in /out (mount it to collect the pkg).
|
||||||
|
CMD ["bash", "-c", "OUTPUT_DIR=/out scripts/build-arch.sh"]
|
||||||
+33
-1
@@ -1,5 +1,9 @@
|
|||||||
# JellyTau Builder Image
|
# JellyTau Builder Image
|
||||||
# Pre-built image with all dependencies for building and testing
|
# Pre-built image with all dependencies for building, testing, and packaging:
|
||||||
|
# - Android APK (SDK/NDK), Linux desktop (deb/rpm),
|
||||||
|
# - Windows cross via the official Tauri path: MSVC target + cargo-xwin + NSIS
|
||||||
|
# Arch packages build in a separate archlinux image (Dockerfile.arch) since
|
||||||
|
# makepkg is Arch-specific.
|
||||||
# Push to your registry: docker build -f Dockerfile.builder -t gitea.tourolle.paris/dtourolle/jellytau-builder:latest .
|
# Push to your registry: docker build -f Dockerfile.builder -t gitea.tourolle.paris/dtourolle/jellytau-builder:latest .
|
||||||
|
|
||||||
FROM ubuntu:24.04
|
FROM ubuntu:24.04
|
||||||
@@ -83,6 +87,34 @@ RUN $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --sdk_root=$ANDROID_HOME \
|
|||||||
# Set NDK environment variable
|
# Set NDK environment variable
|
||||||
ENV NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION
|
ENV NDK_HOME=$ANDROID_HOME/ndk/$NDK_VERSION
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Desktop packaging tools — kept in a trailing layer ON PURPOSE so that adding
|
||||||
|
# or changing a packaging tool doesn't invalidate the expensive apt/rust/Android
|
||||||
|
# layers above (a tool tweak becomes a ~1-2 min rebuild, not ~15). Covers Linux
|
||||||
|
# (deb/rpm) and Windows cross (MSVC via cargo-xwin + NSIS).
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
# Linux desktop packaging: rpmbuild for the .rpm bundle (deb needs nothing extra)
|
||||||
|
rpm \
|
||||||
|
file \
|
||||||
|
# Windows cross-compile (official Tauri path: MSVC target via cargo-xwin).
|
||||||
|
# clang provides clang-cl, the MSVC-compatible C compiler cc-rs uses to build
|
||||||
|
# C deps (bundled sqlite, ring, ...); lld = linker; llvm = llvm-lib/ar etc;
|
||||||
|
# nsis = installer generator.
|
||||||
|
clang \
|
||||||
|
lld \
|
||||||
|
llvm \
|
||||||
|
nsis \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
# Ubuntu's clang package ships clang but NOT the clang-cl alias that cc-rs
|
||||||
|
# invokes for MSVC targets. clang-cl is the same binary in MSVC-compat mode,
|
||||||
|
# so provide it as a symlink.
|
||||||
|
&& ln -sf /usr/bin/clang /usr/local/bin/clang-cl
|
||||||
|
|
||||||
|
# Windows rust target + cargo-xwin (downloads the MSVC CRT/SDK at build time).
|
||||||
|
RUN . $HOME/.cargo/env && \
|
||||||
|
rustup target add x86_64-pc-windows-msvc && \
|
||||||
|
cargo install --locked cargo-xwin
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/bash"]
|
ENTRYPOINT ["/bin/bash"]
|
||||||
|
|||||||
@@ -33,6 +33,58 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "5172:5172" # In case you want to run dev server
|
- "5172:5172" # In case you want to run dev server
|
||||||
|
|
||||||
|
# Linux desktop packages - deb + rpm + pacman into ./dist
|
||||||
|
desktop-linux-build:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: desktop-linux-build
|
||||||
|
args:
|
||||||
|
# Defaults to the registry builder (Dockerfile's ARG). Point at a locally
|
||||||
|
# built builder with: BUILDER_IMAGE=jellytau-builder:latest docker compose ...
|
||||||
|
BUILDER_IMAGE: ${BUILDER_IMAGE:-gitea.tourolle.paris/dtourolle/jellytau-builder:latest}
|
||||||
|
container_name: jellytau-desktop-linux-build
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- cargo-cache:/root/.cargo
|
||||||
|
- bun-cache:/root/.bun
|
||||||
|
environment:
|
||||||
|
- RUST_BACKTRACE=1
|
||||||
|
- OUTPUT_DIR=/app/dist
|
||||||
|
command: bash -c "OUTPUT_DIR=/app/dist scripts/build-desktop-linux.sh"
|
||||||
|
|
||||||
|
# Arch Linux package (.pkg.tar.zst via makepkg) into ./dist
|
||||||
|
arch-build:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.arch
|
||||||
|
container_name: jellytau-arch-build
|
||||||
|
volumes:
|
||||||
|
- ./dist:/out
|
||||||
|
environment:
|
||||||
|
- RUST_BACKTRACE=1
|
||||||
|
- OUTPUT_DIR=/out
|
||||||
|
|
||||||
|
# Windows cross-compile (MSVC via cargo-xwin). Emits NSIS installer + .exe to
|
||||||
|
# ./dist. Override WIN_BUNDLES=none for exe-only.
|
||||||
|
windows-cross:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
target: windows-cross
|
||||||
|
args:
|
||||||
|
BUILDER_IMAGE: ${BUILDER_IMAGE:-gitea.tourolle.paris/dtourolle/jellytau-builder:latest}
|
||||||
|
container_name: jellytau-windows-cross
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
- cargo-cache:/root/.cargo
|
||||||
|
- bun-cache:/root/.bun
|
||||||
|
environment:
|
||||||
|
- RUST_BACKTRACE=1
|
||||||
|
- OUTPUT_DIR=/app/dist
|
||||||
|
- WIN_BUNDLES=${WIN_BUNDLES:-nsis}
|
||||||
|
command: bash -c "OUTPUT_DIR=/app/dist WIN_BUNDLES=${WIN_BUNDLES:-nsis} scripts/build-windows-cross.sh"
|
||||||
|
|
||||||
# Development container - for interactive development
|
# Development container - for interactive development
|
||||||
dev:
|
dev:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
# Desktop packaging (Linux, Arch, Windows)
|
||||||
|
|
||||||
|
How to produce distributable desktop packages for JellyTau. All three flows can
|
||||||
|
run in Docker so no host toolchain setup is required. Outputs land in `./dist`.
|
||||||
|
|
||||||
|
## One builder image (shared with CI)
|
||||||
|
|
||||||
|
The deb/rpm and Windows-cross flows build on the **unified registry builder**
|
||||||
|
([../Dockerfile.builder](../Dockerfile.builder) →
|
||||||
|
`gitea.tourolle.paris/dtourolle/jellytau-builder`), the same image CI uses. It
|
||||||
|
carries every packaging tool: Android SDK/NDK, `rpm`/`file` (Linux bundler),
|
||||||
|
`cargo-xwin` + `lld` + `llvm` + `nsis` + the `x86_64-pc-windows-msvc` rust target
|
||||||
|
(Windows). There is **one** dependency source of truth — no per-stage tool
|
||||||
|
installs.
|
||||||
|
|
||||||
|
The desktop stages in [../Dockerfile](../Dockerfile) are thin `FROM
|
||||||
|
${BUILDER_IMAGE}` environments; the actual build runs at container-run time on
|
||||||
|
your bind-mounted source (like the `dev` service), so source edits need no image
|
||||||
|
rebuild.
|
||||||
|
|
||||||
|
**If you changed `Dockerfile.builder`** (e.g. added a tool), rebuild and push it
|
||||||
|
first, or the packaging flows use the stale registry image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build-builder-image.sh # build + push :latest to the registry
|
||||||
|
# ...or iterate locally without pushing:
|
||||||
|
docker build -f Dockerfile.builder -t jellytau-builder:latest .
|
||||||
|
BUILDER_IMAGE=jellytau-builder:latest bun run docker:build:windows
|
||||||
|
```
|
||||||
|
|
||||||
|
Arch uses a separate `archlinux` image ([../Dockerfile.arch](../Dockerfile.arch))
|
||||||
|
because `makepkg` is Arch-specific — it is not part of the unified builder.
|
||||||
|
|
||||||
|
| Target | Format | Docker command | Functional? |
|
||||||
|
|--------|--------|----------------|-------------|
|
||||||
|
| Debian/Ubuntu, Fedora | `.deb`, `.rpm` | `bun run docker:build:linux` | ✅ yes |
|
||||||
|
| Arch Linux | `.pkg.tar.zst` | `bun run docker:build:arch` | ✅ yes |
|
||||||
|
| Windows | NSIS installer + `.exe` | `bun run docker:build:windows` | ✅ yes (unsigned) |
|
||||||
|
|
||||||
|
## Linux: deb + rpm
|
||||||
|
|
||||||
|
Tauri's bundler produces these natively. The build runs on the existing Ubuntu
|
||||||
|
builder image ([../Dockerfile](../Dockerfile), `desktop-linux-build` stage):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run docker:build:linux # deb + rpm -> ./dist
|
||||||
|
# or, on a host with the Tauri Linux deps installed:
|
||||||
|
BUNDLES="deb,rpm" scripts/build-desktop-linux.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Runtime dependency: the app links libmpv (audio) and WebKitGTK (webview + HTML5
|
||||||
|
transcoded video). The deb/rpm declare these.
|
||||||
|
|
||||||
|
> Note: `appimage` is also a valid Tauri target if you want a portable bundle —
|
||||||
|
> add it to `BUNDLES`.
|
||||||
|
|
||||||
|
## Arch Linux: pacman package
|
||||||
|
|
||||||
|
**Tauri has no `pacman` bundle target** (as of tauri-cli 2.9.x — valid targets
|
||||||
|
are deb/rpm/appimage/msi/nsis/app/dmg). So we ship a hand-written PKGBUILD in
|
||||||
|
[../packaging/arch/PKGBUILD](../packaging/arch/PKGBUILD) and build it with
|
||||||
|
`makepkg` on an Arch base image ([../Dockerfile.arch](../Dockerfile.arch)):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run docker:build:arch # .pkg.tar.zst -> ./dist
|
||||||
|
```
|
||||||
|
|
||||||
|
The PKGBUILD is AUR-ready: swap its `source=()` for a release tarball/VCS URL to
|
||||||
|
publish. Runtime deps: `webkit2gtk-4.1`, `mpv`, `gtk3`, `libayatana-appindicator`.
|
||||||
|
|
||||||
|
`makepkg` refuses to run as root, so the Docker stage builds as a non-root
|
||||||
|
`builder` user. Because the image `COPY`s the source at build time, the
|
||||||
|
`arch-build` compose service does **not** bind-mount the repo — rebuild the image
|
||||||
|
to pick up source changes.
|
||||||
|
|
||||||
|
## Windows: NSIS installer cross-compiled from Linux
|
||||||
|
|
||||||
|
Produces a working (unsigned) NSIS installer + `.exe` via the official Tauri
|
||||||
|
cross-compile path — the `x86_64-pc-windows-msvc` target driven by `cargo-xwin`.
|
||||||
|
Video plays via WebView2 and audio via the webview `<audio>` backend. See
|
||||||
|
[build-windows.md](build-windows.md) for the full explanation.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run docker:build:windows # NSIS installer + .exe -> ./dist
|
||||||
|
WIN_BUNDLES=none bun run docker:build:windows # exe only, skip bundling
|
||||||
|
```
|
||||||
|
|
||||||
|
The Docker `windows-cross` stage is a thin layer over the builder, which carries
|
||||||
|
`cargo-xwin` + `lld` + `llvm` + `nsis` + the `x86_64-pc-windows-msvc` target.
|
||||||
|
Cross-compilation is Tauri's "last resort" path (less tested than building on
|
||||||
|
Windows); a `windows-latest` CI job is the fallback if it misbehaves.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Windows build
|
||||||
|
|
||||||
|
JellyTau targets Linux and Android primarily, but a working Windows build —
|
||||||
|
including an **NSIS installer cross-compiled from Linux** — is produced by the
|
||||||
|
Docker tooling. It is not yet a first-class release target (no code signing / CI
|
||||||
|
job / SMTC lockscreen), but it runs and plays media.
|
||||||
|
|
||||||
|
## How playback works on Windows
|
||||||
|
|
||||||
|
- **Video** — renders through the webview HTML5 `<video>` element (hls.js) on
|
||||||
|
*every* platform; on Windows that is WebView2 (Chromium/Edge), which plays HLS +
|
||||||
|
h264 fine. No Windows-specific code.
|
||||||
|
- **Audio-only (music)** — the native audio backends are libmpv (Linux) and
|
||||||
|
ExoPlayer (Android); neither exists on Windows. Instead
|
||||||
|
`create_player_backend()` in [../src-tauri/src/lib.rs](../src-tauri/src/lib.rs)
|
||||||
|
uses `WebviewAudioBackend` on non-Linux/non-Android targets: it hands the stream
|
||||||
|
URL to a webview `<audio>` element (see
|
||||||
|
[../src/lib/services/webviewAudio.ts](../src/lib/services/webviewAudio.ts)),
|
||||||
|
which reports state back through the same `player_report_*` round-trip the video
|
||||||
|
path uses. Pure Rust + Tauri events.
|
||||||
|
|
||||||
|
## Cross-compiling from Linux (MSVC + cargo-xwin)
|
||||||
|
|
||||||
|
We use the [official Tauri cross-compile path](https://v2.tauri.app/distribute/windows-installer/):
|
||||||
|
the **MSVC** target (`x86_64-pc-windows-msvc`) driven by
|
||||||
|
[`cargo-xwin`](https://github.com/rust-cross/cargo-xwin), which downloads the MSVC
|
||||||
|
CRT / Windows SDK headers and links with `lld`. MSVC is the target Tauri
|
||||||
|
officially supports for Windows (mingw/GNU is not), and — unlike GNU — it lets the
|
||||||
|
Tauri CLI bundle the **NSIS installer from a Linux host**.
|
||||||
|
|
||||||
|
> Why not mingw/GNU? The GNU target *does* link a valid `.exe`, but the Tauri CLI
|
||||||
|
> gates `--bundles` by the host OS unless it recognizes a real Windows build.
|
||||||
|
> `--runner cargo-xwin --target x86_64-pc-windows-msvc` is what flips it into
|
||||||
|
> Windows mode and enables the `nsis`/`msi` bundlers on Linux.
|
||||||
|
|
||||||
|
The builder image ([../Dockerfile.builder](../Dockerfile.builder)) bakes in the
|
||||||
|
whole toolchain: the `x86_64-pc-windows-msvc` rust target, `cargo-xwin`, `lld`,
|
||||||
|
`llvm`, and `nsis`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run docker:build:windows # NSIS installer + .exe -> ./dist
|
||||||
|
WIN_BUNDLES=none bun run docker:build:windows # exe only, skip bundling
|
||||||
|
```
|
||||||
|
|
||||||
|
Or directly on a host that has the toolchain:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scripts/build-windows-cross.sh # nsis installer + exe
|
||||||
|
WIN_BUNDLES=none scripts/build-windows-cross.sh # exe only
|
||||||
|
```
|
||||||
|
|
||||||
|
Under the hood the build runs:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
tauri build --runner cargo-xwin --target x86_64-pc-windows-msvc --bundles nsis
|
||||||
|
```
|
||||||
|
|
||||||
|
Outputs:
|
||||||
|
- `.exe` — `src-tauri/target/x86_64-pc-windows-msvc/release/jellytau.exe`
|
||||||
|
- NSIS installer — `.../release/bundle/nsis/*-setup.exe`
|
||||||
|
|
||||||
|
(both copied to `./dist` when `OUTPUT_DIR` is set).
|
||||||
|
|
||||||
|
## Caveats
|
||||||
|
|
||||||
|
- **Cross-compilation is a last resort** per Tauri's own docs — it's less tested
|
||||||
|
than building on Windows. If it misbehaves, a `windows-latest` CI job or a
|
||||||
|
Windows VM building natively (`tauri build --bundles nsis`) is the fallback.
|
||||||
|
- **Code signing is not wired up** — the installer is unsigned, so Windows
|
||||||
|
SmartScreen will warn on first run.
|
||||||
|
|
||||||
|
## Outstanding for a first-class Windows release
|
||||||
|
|
||||||
|
1. Gapless/crossfade + SMTC (lockscreen) — currently no-ops in the webview audio
|
||||||
|
path.
|
||||||
|
2. Downloaded (`Local` source) file playback needs `convertFileSrc` on the
|
||||||
|
frontend; streaming works today.
|
||||||
|
3. Code signing + a Windows packaging CI job.
|
||||||
+7
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "jellytau",
|
"name": "jellytau",
|
||||||
"version": "0.0.18",
|
"version": "0.1.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "bun@1.3.5",
|
"packageManager": "bun@1.3.5",
|
||||||
@@ -25,6 +25,12 @@
|
|||||||
"android:dev": "./scripts/build-and-deploy.sh",
|
"android:dev": "./scripts/build-and-deploy.sh",
|
||||||
"android:check": "./scripts/check-android.sh",
|
"android:check": "./scripts/check-android.sh",
|
||||||
"android:logs": "./scripts/logcat.sh",
|
"android:logs": "./scripts/logcat.sh",
|
||||||
|
"desktop:build:linux": "./scripts/build-desktop-linux.sh",
|
||||||
|
"desktop:build:arch": "./scripts/build-arch.sh",
|
||||||
|
"desktop:build:windows": "./scripts/build-windows-cross.sh",
|
||||||
|
"docker:build:linux": "docker compose run --rm desktop-linux-build",
|
||||||
|
"docker:build:arch": "docker compose run --rm arch-build",
|
||||||
|
"docker:build:windows": "docker compose run --rm windows-cross",
|
||||||
"clean": "./scripts/clean.sh",
|
"clean": "./scripts/clean.sh",
|
||||||
"tauri": "tauri",
|
"tauri": "tauri",
|
||||||
"traces": "bun run scripts/extract-traces.ts",
|
"traces": "bun run scripts/extract-traces.ts",
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Maintainer: Duncan Tourolle <duncan@tourolle.paris>
|
||||||
|
#
|
||||||
|
# JellyTau — a cross-platform Jellyfin client (Tauri + SvelteKit).
|
||||||
|
#
|
||||||
|
# This PKGBUILD builds from the local source tree by default (see the `dev`
|
||||||
|
# convenience below), which is what scripts/build-arch.sh uses inside the Arch
|
||||||
|
# Docker stage. For AUR distribution, replace the `source=()` line with a release
|
||||||
|
# tarball/VCS URL and drop the local-copy prepare() step.
|
||||||
|
|
||||||
|
pkgname=jellytau
|
||||||
|
pkgver=0.0.18
|
||||||
|
pkgrel=1
|
||||||
|
pkgdesc="A cross-platform Jellyfin client"
|
||||||
|
arch=('x86_64')
|
||||||
|
url="https://gitea.tourolle.paris/dtourolle/jellytau"
|
||||||
|
license=('MIT')
|
||||||
|
# Runtime: libmpv for audio, webkit2gtk for the webview + HTML5 transcoded video.
|
||||||
|
depends=('webkit2gtk-4.1' 'mpv' 'gtk3' 'libayatana-appindicator')
|
||||||
|
makedepends=('rust' 'cargo' 'bun' 'nodejs' 'pkgconf' 'libsoup3')
|
||||||
|
options=('!strip' '!lto')
|
||||||
|
|
||||||
|
# Populated from the working tree by scripts/build-arch.sh (SRC env var).
|
||||||
|
_srcdir="${JELLYTAU_SRC:-$startdir/../..}"
|
||||||
|
|
||||||
|
build() {
|
||||||
|
cd "$_srcdir"
|
||||||
|
export CARGO_HOME="${CARGO_HOME:-$srcdir/cargo-home}"
|
||||||
|
bun install --frozen-lockfile || bun install
|
||||||
|
bun run build
|
||||||
|
# Only the raw binary is needed; packaging is done in package() below so we
|
||||||
|
# control the Arch filesystem layout ourselves rather than via tauri-bundler.
|
||||||
|
(cd src-tauri && cargo build --release --locked)
|
||||||
|
}
|
||||||
|
|
||||||
|
package() {
|
||||||
|
cd "$_srcdir"
|
||||||
|
|
||||||
|
install -Dm755 "src-tauri/target/release/jellytau" \
|
||||||
|
"$pkgdir/usr/bin/jellytau"
|
||||||
|
|
||||||
|
# Desktop entry
|
||||||
|
install -Dm644 "packaging/arch/jellytau.desktop" \
|
||||||
|
"$pkgdir/usr/share/applications/jellytau.desktop"
|
||||||
|
|
||||||
|
# Icons (hicolor)
|
||||||
|
install -Dm644 "src-tauri/icons/32x32.png" \
|
||||||
|
"$pkgdir/usr/share/icons/hicolor/32x32/apps/jellytau.png"
|
||||||
|
install -Dm644 "src-tauri/icons/128x128.png" \
|
||||||
|
"$pkgdir/usr/share/icons/hicolor/128x128/apps/jellytau.png"
|
||||||
|
install -Dm644 "src-tauri/icons/128x128@2x.png" \
|
||||||
|
"$pkgdir/usr/share/icons/hicolor/256x256/apps/jellytau.png"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=JellyTau
|
||||||
|
Comment=A cross-platform Jellyfin client
|
||||||
|
Exec=jellytau
|
||||||
|
Icon=jellytau
|
||||||
|
Terminal=false
|
||||||
|
Categories=AudioVideo;Player;Audio;Video;
|
||||||
|
StartupWMClass=jellytau
|
||||||
Executable
+36
@@ -0,0 +1,36 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build an Arch Linux package (.pkg.tar.zst) for JellyTau via makepkg.
|
||||||
|
#
|
||||||
|
# Tauri's bundler has no pacman target (as of tauri-cli 2.9.x), so we ship a
|
||||||
|
# hand-written PKGBUILD in packaging/arch/ and build it with makepkg. This must
|
||||||
|
# run on an Arch host / the `arch-build` Docker stage — makepkg is Arch-specific
|
||||||
|
# and refuses to run as root, so run it as a non-root user with sudo for deps.
|
||||||
|
#
|
||||||
|
# Usage (typically inside the arch-build Docker stage as a non-root user):
|
||||||
|
# scripts/build-arch.sh
|
||||||
|
# OUTPUT_DIR=/app/dist scripts/build-arch.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
cd "$REPO_ROOT/packaging/arch"
|
||||||
|
|
||||||
|
echo "🏛️ Building JellyTau Arch package"
|
||||||
|
echo "=================================="
|
||||||
|
|
||||||
|
# Point the PKGBUILD at the working tree and give cargo/bun a writable home.
|
||||||
|
export JELLYTAU_SRC="$REPO_ROOT"
|
||||||
|
export CARGO_HOME="${CARGO_HOME:-$REPO_ROOT/.cargo-arch}"
|
||||||
|
|
||||||
|
# -s installs missing deps (needs sudo/root privileges for pacman), -f overwrites.
|
||||||
|
makepkg -sf --noconfirm
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "✅ Built Arch package(s):"
|
||||||
|
ls -1 ./*.pkg.tar.zst
|
||||||
|
|
||||||
|
if [[ -n "${OUTPUT_DIR:-}" ]]; then
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
cp -v ./*.pkg.tar.zst "$OUTPUT_DIR/"
|
||||||
|
echo ""
|
||||||
|
echo "📦 Copied Arch package(s) to $OUTPUT_DIR"
|
||||||
|
fi
|
||||||
Executable
+44
@@ -0,0 +1,44 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build Linux desktop packages (deb + rpm) for JellyTau.
|
||||||
|
#
|
||||||
|
# Produces bundles under src-tauri/target/release/bundle/{deb,rpm}.
|
||||||
|
# Runs on the existing Ubuntu builder image. NOTE: Tauri has no pacman bundle
|
||||||
|
# target — the Arch package is built separately with makepkg (scripts/build-arch.sh
|
||||||
|
# / Dockerfile.arch). `appimage` is also available if you want a portable bundle.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/build-desktop-linux.sh # deb + rpm
|
||||||
|
# BUNDLES="deb,appimage" scripts/build-desktop-linux.sh # subset / add appimage
|
||||||
|
# OUTPUT_DIR=/app/dist scripts/build-desktop-linux.sh # copy bundles out
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
BUNDLES="${BUNDLES:-deb,rpm}"
|
||||||
|
|
||||||
|
echo "🐧 Building JellyTau Linux desktop packages"
|
||||||
|
echo "==========================================="
|
||||||
|
echo "Bundles: $BUNDLES"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
bun install --frozen-lockfile 2>/dev/null || bun install
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
# --bundles overrides tauri.conf.json bundle.targets so this script controls
|
||||||
|
# exactly which Linux formats are produced (never NSIS here).
|
||||||
|
bun run tauri build --bundles "$BUNDLES"
|
||||||
|
|
||||||
|
BUNDLE_ROOT="src-tauri/target/release/bundle"
|
||||||
|
echo ""
|
||||||
|
echo "✅ Built packages:"
|
||||||
|
find "$BUNDLE_ROOT" -maxdepth 2 -type f \
|
||||||
|
\( -name '*.deb' -o -name '*.rpm' -o -name '*.AppImage' \) -print
|
||||||
|
|
||||||
|
if [[ -n "${OUTPUT_DIR:-}" ]]; then
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
find "$BUNDLE_ROOT" -maxdepth 2 -type f \
|
||||||
|
\( -name '*.deb' -o -name '*.rpm' -o -name '*.AppImage' \) \
|
||||||
|
-exec cp -v {} "$OUTPUT_DIR/" \;
|
||||||
|
echo ""
|
||||||
|
echo "📦 Copied bundles to $OUTPUT_DIR"
|
||||||
|
fi
|
||||||
Executable
+69
@@ -0,0 +1,69 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Cross-compile JellyTau for Windows from Linux, producing an NSIS installer.
|
||||||
|
#
|
||||||
|
# Uses the OFFICIAL Tauri cross-compile path (https://v2.tauri.app/distribute/
|
||||||
|
# windows-installer/): the MSVC target driven by cargo-xwin, which downloads the
|
||||||
|
# MSVC CRT/Windows SDK headers and links with lld. This is the target Tauri
|
||||||
|
# officially supports for Windows (the mingw/GNU target is not), and unlike GNU
|
||||||
|
# it can bundle the NSIS installer from a Linux host.
|
||||||
|
#
|
||||||
|
# Playback on Windows: video renders via WebView2 and audio via the webview
|
||||||
|
# <audio> backend (WebviewAudioBackend) — see docs/build-windows.md.
|
||||||
|
#
|
||||||
|
# Requirements (present in the Docker windows-cross target / unified builder):
|
||||||
|
# - rustup target x86_64-pc-windows-msvc
|
||||||
|
# - cargo-xwin (cargo install --locked cargo-xwin)
|
||||||
|
# - lld, llvm (linker + llvm-lib used by cargo-xwin)
|
||||||
|
# - nsis (makensis) (installer generator)
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/build-windows-cross.sh # exe + NSIS installer
|
||||||
|
# WIN_BUNDLES=none scripts/build-windows-cross.sh # exe only, skip bundling
|
||||||
|
# OUTPUT_DIR=/app/dist scripts/build-windows-cross.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
TARGET="x86_64-pc-windows-msvc"
|
||||||
|
WIN_BUNDLES="${WIN_BUNDLES:-nsis}"
|
||||||
|
|
||||||
|
echo "🪟 Cross-compiling JellyTau for Windows ($TARGET, via cargo-xwin)"
|
||||||
|
echo "================================================================"
|
||||||
|
echo "Video plays via WebView2; audio via the webview <audio> backend."
|
||||||
|
echo "Bundles: $WIN_BUNDLES"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
bun install --frozen-lockfile 2>/dev/null || bun install
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
# --runner cargo-xwin + the MSVC target is what makes the Tauri CLI treat this as
|
||||||
|
# a real Windows build and enable the nsis/msi bundlers on a Linux host.
|
||||||
|
#
|
||||||
|
# IMPORTANT: do NOT pass `--bundles nsis` here. tauri-cli 2.9.x validates the
|
||||||
|
# `--bundles` flag against a static clap enum gated by the HOST OS (Linux allows
|
||||||
|
# only deb/rpm/appimage) *before* it considers --target/--runner, so `--bundles
|
||||||
|
# nsis` is rejected at arg-parse time. Instead the Windows bundle targets come
|
||||||
|
# from tauri.conf.json (bundle.targets includes "nsis"), which is not subject to
|
||||||
|
# that CLI validation — the bundler then picks nsis once it knows the target is
|
||||||
|
# Windows.
|
||||||
|
if [[ "$WIN_BUNDLES" == "none" ]]; then
|
||||||
|
bun run tauri build --runner cargo-xwin --target "$TARGET" --no-bundle
|
||||||
|
else
|
||||||
|
bun run tauri build --runner cargo-xwin --target "$TARGET"
|
||||||
|
fi
|
||||||
|
|
||||||
|
BIN_DIR="src-tauri/target/$TARGET/release"
|
||||||
|
echo ""
|
||||||
|
echo "✅ Built Windows artifacts:"
|
||||||
|
find "$BIN_DIR" -maxdepth 1 -name '*.exe' -print
|
||||||
|
find "$BIN_DIR/bundle" -type f \( -name '*.exe' -o -name '*.msi' \) -print 2>/dev/null || true
|
||||||
|
|
||||||
|
if [[ -n "${OUTPUT_DIR:-}" ]]; then
|
||||||
|
mkdir -p "$OUTPUT_DIR"
|
||||||
|
find "$BIN_DIR" -maxdepth 1 -name 'jellytau.exe' -exec cp -v {} "$OUTPUT_DIR/" \;
|
||||||
|
# NSIS setup installers land in bundle/nsis/*-setup.exe; MSI in bundle/msi/*.msi.
|
||||||
|
find "$BIN_DIR/bundle" -type f \( -name '*-setup.exe' -o -name '*.msi' \) \
|
||||||
|
-exec cp -v {} "$OUTPUT_DIR/" \; 2>/dev/null || true
|
||||||
|
echo ""
|
||||||
|
echo "📦 Copied Windows artifacts to $OUTPUT_DIR"
|
||||||
|
fi
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
"$schema": "https://schema.tauri.app/config/2",
|
||||||
"productName": "jellytau",
|
"productName": "jellytau",
|
||||||
"version": "0.0.18",
|
"version": "0.1.0",
|
||||||
"identifier": "com.dtourolle.jellytau",
|
"identifier": "com.dtourolle.jellytau",
|
||||||
"build": {
|
"build": {
|
||||||
"beforeDevCommand": "bun run dev",
|
"beforeDevCommand": "bun run dev",
|
||||||
@@ -23,7 +23,7 @@
|
|||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
"targets": ["deb", "rpm"],
|
"targets": ["deb", "rpm", "nsis"],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
"icons/32x32.png",
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
|
|||||||
Reference in New Issue
Block a user