diff --git a/Makefile b/Makefile index df3b2e9..545fcb7 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,12 @@ export GUI_DIR GUI_DIR=cmd/gui VERSION ?= $(shell git describe --tags --dirty 2>/dev/null || echo "0.1.0") +# Clean env for GUI packaging: the host may have LD_LIBRARY_PATH polluted by +# third-party runtimes (e.g. /opt/cangjie) which electron-builder bakes into +# the produced binary's DT_RPATH/dep resolution -> the packaged app then +# SIGSEGVs inside ld.so on clean machines. Strip it for every pack step. +CLEAN_PACK_ENV = env -u LD_LIBRARY_PATH + all: build # ---- core binary (server users / embedded core) ---- @@ -26,23 +32,25 @@ gui-dev: # build the embedded core into the GUI, then run electron-builder # targets: deb + AppImage (linux), rpm needs system rpmbuild +# NOTE: run in a clean env (no LD_LIBRARY_PATH pollution) so the packaged +# binary does not link against host-only libs and crash on user machines. gui-dist: - @cd $(GUI_DIR) && npm run dist:linux + @cd $(GUI_DIR) && $(CLEAN_PACK_ENV) npm run dist:linux # deb only (for local sharing), rpm needs rpmbuild gui-deb: - @cd $(GUI_DIR) && npm run dist:debian + @cd $(GUI_DIR) && $(CLEAN_PACK_ENV) npm run dist:debian # windows nsis installer: host-built core (needs apt install gcc-mingw-w64-x86-64 + wine) gui-win: - @cd $(GUI_DIR) && npm run dist:win + @cd $(GUI_DIR) && $(CLEAN_PACK_ENV) npm run dist:win # windows nsis installer: full cross-build in docker (self-sufficient, no host mingw) gui-win-docker: - @cd $(GUI_DIR) && ./scripts/dist-win-docker.sh + @cd $(GUI_DIR) && $(CLEAN_PACK_ENV) ./scripts/dist-win-docker.sh gui-dist-dir: - @cd $(GUI_DIR) && npm run dist:dir + @cd $(GUI_DIR) && $(CLEAN_PACK_ENV) npm run dist:dir test: go test ./... diff --git a/cmd/gui/main.js b/cmd/gui/main.js index 3c9b954..6b025a3 100644 --- a/cmd/gui/main.js +++ b/cmd/gui/main.js @@ -47,6 +47,15 @@ const CORE_EXE = path.join( "bin", process.platform === "win32" ? "llmsproxy.exe" : "llmsproxy", ); +// Packaged window icon: must be a REAL file on disk (extraResource), not an +// asar path — Electron/GTK pulls BrowserWindow icons from the filesystem and +// crashes (SIGSEGV) when given an asar virtual path on Linux. +// Dev: cmd/gui/build/icon.png. +const WINDOW_ICON = path.join( + app.isPackaged ? process.resourcesPath : __dirname, + "build", + "icon.png", +); // default port for the embedded core (configurable via settings) const DEFAULT_PORT = 8787; @@ -508,8 +517,9 @@ function createWindow() { const menu = Menu.buildFromTemplate([]); Menu.setApplicationMenu(menu); mainWindow = new BrowserWindow({ - // Window icon (taskbar/dock): same asset as the tray and package. - icon: path.join(__dirname, "build", "icon.png"), + // Window icon (taskbar/dock). Packaged: real file under resourcesPath (see + // WINDOW_ICON) — an asar path here makes GTK segfault on Linux. + icon: WINDOW_ICON, // GNOME taskbar icons match the window's WM_CLASS (lowercase // "modelrouter-gui") against StartupWMClass in the .desktop file. backgroundColor: "#1f2937", @@ -639,6 +649,12 @@ ipcMain.handle("shell:openPath", (_, p) => { // window control from renderer uses preload -> ipcMain above // ---------- bootstrap ---------- +// Defensive GPU handling: Electron's Chromium GPU process is a frequent +// source of hard crashes (SIGSEGV) on Linux, especially on hybrid-GPU and +// Wayland setups. The GUI host is an LLM management shell — a software +// renderer is perfectly fine. Must run before app.whenReady. +app.disableHardwareAcceleration(); + const gotLock = app.requestSingleInstanceLock(); if (gotLock) { app.on("second-instance", () => {