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 <alexis@typedr.at>
This commit is contained in:
Alexis Williams
2026-05-11 17:16:27 -07:00
committed by superherointj
parent 99b5236774
commit f6fc501041
+73 -37
View File
@@ -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 ];
};
})