diff --git a/cmd/gui/docker/win-builder/Dockerfile b/cmd/gui/docker/win-builder/Dockerfile index 03b17d0..cdd4290 100644 --- a/cmd/gui/docker/win-builder/Dockerfile +++ b/cmd/gui/docker/win-builder/Dockerfile @@ -1,11 +1,20 @@ # ModelRouter GUI Windows builder -# Self-sufficient cross-compilation environment for the embedded llmsproxy core. -# Produces llmsproxy.exe (Go, windows/amd64, luajit tag) + lua51.dll (LuaJIT mingw build) -# inside a mounted workspace: cmd/gui/bin/{llmsproxy.exe,lua51.dll} +# Self-sufficient cross-compilation environment for the embedded llmsproxy core +# AND for packaging the Windows NSIS installer. +# +# Produces inside a mounted workspace: +# - llmsproxy.exe (Go, windows/amd64, luajit tag) + lua51.dll +# - ModelRouter Setup .exe (electron-builder --win nsis; requires wine +# so the uninstaller is generated — set USE_SYSTEM_WINE=true) # # Build: docker build -t modelrouter/win-builder cmd/gui/docker/win-builder # Usage: make gui-win-docker (or cmd/gui/scripts/dist-win-docker.sh) # +# This image is the SINGLE wine provider for the NSIS step, so a host that has +# no / broken wine (e.g. amd64-only Debian: no syswow64 -> c0000135) can still +# produce a real installer. The NSIS step MUST run inside this image; running it +# on the host was how the 1.3.0 264 KB degenerate exe got shipped. +# # LuaJIT source is fetched from gitcode.com (CN-reachable mirror) with github # fallback; pass --build-arg LUAJIT_REPO to override. @@ -13,11 +22,24 @@ FROM golang:1.25 ARG LUAJIT_REPO=https://gitcode.com/openresty/luajit2.git -RUN apt-get update -qq \ +# i386 arch is required for wine32 (the NSIS uninstaller step runs a win32 exe +# through wine's wow64 path, which needs the 32-bit runtime under /usr/lib/i386). +RUN dpkg --add-architecture i386 \ + && apt-get update -qq \ && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \ gcc-mingw-w64-x86-64 make ca-certificates git \ + # NSIS step needs node + the electron-builder toolchain + nodejs npm \ + # wine (32+64) for generating the NSIS uninstaller + wine64 wine32:i386 \ && rm -rf /var/lib/apt/lists/* +# electron-builder resolves the uninstaller via the system wine when +# USE_SYSTEM_WINE=true (or by default on Linux without a toolset). Pin it. +ENV USE_SYSTEM_WINE=true +ENV WINEDEBUG=-all +ENV ELECTRON_BUILDER_CACHE=/workspace/cmd/gui/.cache + # LuaJIT cross-compiled for Windows; keeps lua51.dll and the cgo import lib # (libluajit-5.1.dll.a -> libluajit-5.1.a in the mingw lib dir). # NOTE: $LUAJIT_REPO deliberately unquoted in "sh -c" — quoting the ARG would diff --git a/cmd/gui/scripts/dist-win-docker.sh b/cmd/gui/scripts/dist-win-docker.sh index b7609ca..527b000 100755 --- a/cmd/gui/scripts/dist-win-docker.sh +++ b/cmd/gui/scripts/dist-win-docker.sh @@ -1,23 +1,64 @@ #!/usr/bin/env bash -# One-shot Windows release build for the GUI: +# One-shot Windows release build for the GUI — ENTIRELY inside docker. +# # 1) cross-compile the embedded core in docker (llmsproxy.exe + lua51.dll) -# 2) host-side electron-builder NSIS packaging (wine required on host) +# 2) electron-builder NSIS packaging ALSO inside docker (wine for the +# uninstaller step lives in the image — see Dockerfile). This is the fix +# for the 1.3.0/1.4.0 broken installers: the host had amd64-only wine +# (no syswow64 -> c0000135), which made electron-builder silently emit a +# 264 KB degenerate exe without its 86 MB payload. +# # Outputs: cmd/build/gui-dist/"ModelRouter Setup .exe" +# cmd/build/gui-dist/modelrouter-gui--x64.nsis.7z +# +# Needs: docker (any machine; no host mingw/wine/node required) set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)" -cd "$ROOT/cmd/gui" +IMAGE="${WIN_BUILDER_IMAGE:-modelrouter/win-builder}" +PROXY="${GOPROXY:-https://goproxy.cn,direct}" +CACHE_VOL="modelrouter-eb-cache" -# 1) core (docker) -"$ROOT/cmd/gui/scripts/win-core-docker.sh" +command -v docker >/dev/null || { echo "docker required"; exit 1; } -# 2) packaging (host, needs wine for electron-builder's signtool/nsis) -command -v wine >/dev/null || { echo "wine required on host for NSIS packaging"; exit 1; } -if ! command -v npx >/dev/null; then echo "node/npm required"; exit 1; fi +if ! docker image inspect "$IMAGE" >/dev/null 2>&1; then + echo "[dist-win-docker] building image $IMAGE (first run, ~5-10 min)..." + docker build -t "$IMAGE" "$ROOT/cmd/gui/docker/win-builder" +fi -echo "[dist-win] electron-builder --win nsis ..." -# bin artifacts already refreshed by win-core-docker.sh; skip the prepare step -npx electron-builder --win nsis +docker volume create "$CACHE_VOL" >/dev/null 2>&1 || true -ls -la "$ROOT/cmd/build/gui-dist/"*.exe 2>/dev/null -echo "[dist-win] done" +# 1) embedded core + 2) NSIS packaging — both inside the image so wine and node +# are the image's, never the host's. electron-builder downloads electron + nsis +# toolchains into ~/.cache on first run; the mounted volume keeps that warm so +# subsequent builds are incremental instead of re-fetching ~400 MB. +echo "[dist-win-docker] cross-compiling core + electron-builder NSIS in docker ..." +docker run --rm \ + -v "$ROOT":/workspace \ + -v "$CACHE_VOL":/root/.cache \ + -e GOPROXY="$PROXY" \ + -w /workspace \ + "$IMAGE" bash -c ' + set -euo pipefail + cp /opt/luajit2/src/lua51.dll /workspace/cmd/gui/bin/lua51.dll + GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc \ + go build -buildvcs=false -tags luajit -trimpath \ + -o /workspace/cmd/gui/bin/llmsproxy.exe ./cmd/llmsproxy + echo " -> core built" + # wine needs one prefix initialization so its wow64 runtime (syswow64/ntdll) + # exists — the exact thing that was missing on the broken host. Use a shared + # prefix in the cache volume so it is initialized only on the first run. + export WINEPREFIX=/root/.cache/wineprefix + if [ ! -f "$WINEPREFIX/drive_c/windows/syswow64/ntdll.dll" ]; then + echo " -> initializing wine prefix (first run)" + WINEDEBUG=-all wineboot -i || true + fi + cd /workspace/cmd/gui + # node_modules ships in the repo (host npm install already ran); reuse it. + npx electron-builder --win nsis + ' + +echo "[dist-win-docker] verifying artifacts ..." +"$ROOT/packaging/verify-dist.sh" "$ROOT/cmd/build/gui-dist" + +echo "[dist-win-docker] done" \ No newline at end of file