From f6fc5010414d0f2c1facba0fb941cdfba2fb4666 Mon Sep 17 00:00:00 2001 From: Alexis Williams Date: Mon, 11 May 2026 17:16:27 -0700 Subject: [PATCH] opencode-desktop: fix empty window and missing desktop entry - Patch prebuilt .node files (node-pty, @parcel/watcher) with autoPatchelfHook so they can dlopen libstdc++. Without this, the sidecar exits on startup because glibc does not consult electron's DT_RUNPATH transitively for dlopened libraries, and the renderer shows an empty window with no backend. - Ignore unresolved libc.musl-* SONAMEs in the prebuilt .node files so autoPatchelfHook accepts them on glibc systems. The musl-targeting bindings are dead code at runtime on the host's libc anyway. - Pass --config.asarUnpack='**/*.node' to electron-builder so every prebuilt native binding lands on the filesystem (not embedded in app.asar where dlopen cannot find them). - Disable the auto-updater at source via postPatch. With the package now correctly identifying as packaged, the updater would otherwise try to download and run upstream binaries that aren't patched for Nix. - Generate a .desktop entry via makeDesktopItem. electron-builder --dir skips the deb/AppImage/rpm stages that normally produce one, so copyDesktopItems on its own was a no-op. - Install icons into share/icons/hicolor/{30,32,44,64,71,89,107,128, 142,150,284,310}x*/apps/ mirroring upstream's deb layout, so desktop environments find them. Previously a single bare PNG at share/icons/opencode-desktop.png (not part of any icon theme path). - Set ELECTRON_FORCE_IS_PACKAGED=1 in the wrapper. With --inherit-argv0, Electron's app.isPackaged heuristic returns false, which routes the app to the dev XDG dir and breaks the WM-class match with StartupWMClass=OpenCode. - On darwin, symlink Contents/MacOS/OpenCode into $out/bin and set mainProgram accordingly so lib.getExe works on both platforms. - Drop the unused opencode-cli sidecar copy; the bundled chunk hosts its own server and never spawns the CLI. Assisted-by: OpenCode:claude-opus-4-7 Co-authored-by: typedrat --- pkgs/by-name/op/opencode-desktop/package.nix | 110 ++++++++++++------- 1 file changed, 73 insertions(+), 37 deletions(-) diff --git a/pkgs/by-name/op/opencode-desktop/package.nix b/pkgs/by-name/op/opencode-desktop/package.nix index 9ca30437f227..bfc0cf9c482c 100644 --- a/pkgs/by-name/op/opencode-desktop/package.nix +++ b/pkgs/by-name/op/opencode-desktop/package.nix @@ -1,12 +1,15 @@ { + autoPatchelfHook, bun, copyDesktopItems, electron_41, lib, makeBinaryWrapper, + makeDesktopItem, models-dev, nodejs, opencode, + stdenv, stdenvNoCC, writableTmpDirAsHomeHook, @@ -30,9 +33,20 @@ stdenvNoCC.mkDerivation (finalAttrs: { nodejs # for patchShebangs node_modules makeBinaryWrapper writableTmpDirAsHomeHook + ] + ++ lib.optionals stdenvNoCC.hostPlatform.isLinux [ + autoPatchelfHook copyDesktopItems ]; + buildInputs = lib.optionals stdenvNoCC.hostPlatform.isLinux [ + (lib.getLib stdenv.cc.cc) + ]; + + # The musl prebuilts ship libc.musl-*.so.1 SONAMEs that autoPatchelfHook can't + # resolve on glibc systems. They aren't loaded at runtime on the host libc anyway. + autoPatchelfIgnoreMissingDeps = [ "libc.musl-*.so.*" ]; + strictDeps = true; env = { @@ -42,6 +56,13 @@ stdenvNoCC.mkDerivation (finalAttrs: { OPENCODE_DISABLE_MODELS_FETCH = true; }; + postPatch = '' + # The auto-updater would try to download and run an upstream binary that + # isn't patched for Nix. Disable it at source. + substituteInPlace packages/desktop/src/main/constants.ts \ + --replace-fail 'app.isPackaged && CHANNEL !== "dev"' 'false' + ''; + configurePhase = '' runHook preConfigure @@ -69,10 +90,6 @@ stdenvNoCC.mkDerivation (finalAttrs: { # Build with electron-vite node_modules/.bin/electron-vite build - # Copy opencode CLI as sidecar - sidecar_name="opencode-cli" - install -D ${lib.getExe opencode} "resources/$sidecar_name" - # Package with electron-builder (unpacked directory mode) cp -r "${electron.dist}" $HOME/.electron-dist chmod -R u+w $HOME/.electron-dist @@ -80,53 +97,72 @@ stdenvNoCC.mkDerivation (finalAttrs: { node_modules/.bin/electron-builder --dir \ --config=electron-builder.config.ts \ --config.electronDist="$HOME/.electron-dist" \ - --config.electronVersion=${electron.version} + --config.electronVersion=${electron.version} \ + --config.asarUnpack='**/*.node' cd ../.. runHook postBuild ''; - installPhase = '' - runHook preInstall - '' - + lib.optionalString stdenvNoCC.hostPlatform.isDarwin '' - mkdir -p $out/Applications - mv packages/desktop/dist/mac-*/OpenCode.app "$out/Applications/OpenCode.app" - '' - + lib.optionalString stdenvNoCC.hostPlatform.isLinux '' - mkdir -p $out/opt/opencode-desktop - ${ - if stdenvNoCC.hostPlatform.isAarch64 then - '' - appDir="packages/desktop/dist/linux-arm64-unpacked" - '' - else - '' - appDir="packages/desktop/dist/linux-unpacked" - '' - } - [ -d "$appDir" ] || { echo "no electron-builder output dir found: $appDir"; exit 1; } - cp -r "$appDir/resources" $out/opt/opencode-desktop/ + desktopItems = lib.optional stdenvNoCC.hostPlatform.isLinux (makeDesktopItem { + name = "opencode-desktop"; + desktopName = "OpenCode"; + exec = "opencode-desktop %U"; + icon = "opencode-desktop"; + startupWMClass = "OpenCode"; + categories = [ "Development" ]; + mimeTypes = [ "x-scheme-handler/opencode" ]; + }); - install -Dm644 packages/desktop/resources/icons/icon.png $out/share/icons/opencode-desktop.png + installPhase = + let + appDir = if stdenvNoCC.hostPlatform.isAarch64 then "linux-arm64-unpacked" else "linux-unpacked"; + in + lib.concatLines [ + '' + runHook preInstall + '' + (lib.optionalString stdenvNoCC.hostPlatform.isDarwin '' + mkdir -p $out/Applications $out/bin + mv packages/desktop/dist/mac-*/OpenCode.app "$out/Applications/OpenCode.app" + ln -s "$out/Applications/OpenCode.app/Contents/MacOS/OpenCode" $out/bin/OpenCode + '') + (lib.optionalString stdenvNoCC.hostPlatform.isLinux '' + mkdir -p $out/opt/opencode-desktop + appDir="packages/desktop/dist/${appDir}" + [ -d "$appDir" ] || { echo "no electron-builder output dir found: $appDir"; exit 1; } + cp -r "$appDir/resources" $out/opt/opencode-desktop/ - makeWrapper ${lib.getExe electron} $out/bin/opencode-desktop \ - --inherit-argv0 \ - --add-flags $out/opt/opencode-desktop/resources/app.asar \ - --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true --wayland-text-input-version=3}}" \ - --add-flags ${lib.escapeShellArg commandLineArgs} - '' - + '' - runHook postInstall - ''; + for size in 32 64 128; do + install -Dm644 \ + packages/desktop/resources/icons/''${size}x''${size}.png \ + $out/share/icons/hicolor/''${size}x''${size}/apps/opencode-desktop.png + done + for size in 30 44 71 89 107 142 150 284 310; do + install -Dm644 \ + packages/desktop/resources/icons/Square''${size}x''${size}Logo.png \ + $out/share/icons/hicolor/''${size}x''${size}/apps/opencode-desktop.png + done + + makeWrapper ${lib.getExe electron} $out/bin/opencode-desktop \ + --inherit-argv0 \ + --set ELECTRON_FORCE_IS_PACKAGED 1 \ + --add-flags $out/opt/opencode-desktop/resources/app.asar \ + --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform-hint=auto --enable-features=WaylandWindowDecorations --enable-wayland-ime=true --wayland-text-input-version=3}}" \ + --add-flags ${lib.escapeShellArg commandLineArgs} + '') + '' + runHook postInstall + '' + ]; meta = { description = "AI coding agent desktop client"; homepage = "https://opencode.ai"; inherit (opencode.meta) platforms; license = lib.licenses.mit; - mainProgram = "OpenCode"; + mainProgram = if stdenvNoCC.hostPlatform.isDarwin then "OpenCode" else "opencode-desktop"; maintainers = with lib.maintainers; [ xiaoxiangmoe ]; }; })