# Claude Desktop — Nix-Wrapper-Paket # # Liefert: # $out/bin/claude-desktop — Launcher mit LD_LIBRARY_PATH # $out/bin/claude-desktop-install — Installer (kopiert Binary nach ~/.local/share) # $out/share/applications/… — Desktop-Entry # $out/share/icons/… — Icon # # Das eigentliche Binary lebt unter ~/.local/share/claude-desktop/bin/claude-desktop # (writable), damit der Auto-Updater es ersetzen kann. Nix-Store ist read-only und # waere deshalb inkompatibel mit dem Rename-Trick in apply_update(). # # Einbinden in /etc/nixos/configuration.nix: # # environment.systemPackages = [ # (import /pfad/zum/claude-desktop/nix/default.nix { inherit pkgs; }) # ]; # # Oder per home-manager: # home.packages = [ (import ./claude-desktop/nix/default.nix { inherit pkgs; }) ]; { pkgs ? import {} }: let # Alle Laufzeit-Libs, die das Tauri-Binary braucht (parallel zu shell.nix). runtimeLibs = with pkgs; [ webkitgtk_4_1 libappindicator-gtk3 librsvg gtk3 glib cairo pango gdk-pixbuf libsoup_3 at-spi2-atk openssl # GStreamer + PipeWire fuer WebKitGTK Audio (Mikrofon, getUserMedia) gst_all_1.gstreamer gst_all_1.gst-plugins-base gst_all_1.gst-plugins-good gst_all_1.gst-plugins-bad pipewire alsa-lib ]; # GStreamer-Plugins fuer WebKitGTK (Audio/Video, WebRTC, Mikrofon via PipeWire) gstPlugins = with pkgs.gst_all_1; [ gst-plugins-base gst-plugins-good gst-plugins-bad ]; gstPluginPath = pkgs.lib.concatMapStringsSep ":" (p: "${p}/lib/gstreamer-1.0") gstPlugins + ":${pkgs.gst_all_1.gstreamer.out}/lib/gstreamer-1.0" + ":${pkgs.pipewire}/lib/gstreamer-1.0"; # Laufzeit-Binaries die die App spawnen muss (node fuer claude-bridge.js, # npm fuer apply_bundle_update, tar fuer Bundle-Extract) runtimeBins = [ pkgs.nodejs_22 pkgs.gnutar pkgs.gzip pkgs.ffmpeg-headless ]; in pkgs.stdenv.mkDerivation { pname = "claude-desktop"; version = "0.1.1"; # Keine Quelldateien — Wir packen nur Wrapper + Desktop-Entry dontUnpack = true; nativeBuildInputs = [ pkgs.makeWrapper ]; installPhase = '' runHook preInstall mkdir -p $out/bin $out/share/applications $out/share/icons/hicolor/256x256/apps # 1) Launcher: startet das User-Binary mit Nix-LD_LIBRARY_PATH cat > $out/bin/claude-desktop <<'LAUNCHER' #!${pkgs.bash}/bin/bash # Claude Desktop — NixOS-Launcher set -e APP_DIR="$HOME/.local/share/claude-desktop" BIN="$APP_DIR/bin/claude-desktop" if [ ! -x "$BIN" ]; then echo "⚠️ Claude-Desktop-Binary nicht gefunden: $BIN" >&2 echo "" >&2 echo "Erst installieren (aus fertigem Build in /tmp/claude-target):" >&2 echo " claude-desktop-install" >&2 echo "" >&2 echo "Oder neu bauen im Repo:" >&2 echo " CARGO_TARGET_DIR=/tmp/claude-target \\" >&2 echo " nix-shell shell.nix --run 'npm ci && npm run tauri build'" >&2 echo " claude-desktop-install" >&2 exit 1 fi # Marker fuer update.rs: wir laufen unter Nix-Wrapper export CLAUDE_DESKTOP_NIX_WRAPPER=1 export CLAUDE_DESKTOP_BIN="$BIN" # HiDPI/Fractional-Scaling — GTK3 unter Wayland kann kein natives fractional # scaling. Workaround: Xwayland erzwingen (der Compositor skaliert das Fenster) # und GDK_DPI_SCALE aus Xft.dpi ableiten, damit Fonts scharf bleiben. # Override-bar ueber CLAUDE_DESKTOP_SCALE (z.B. "1.5") oder komplett # deaktivierbar mit CLAUDE_DESKTOP_SCALE=off. if [ "''${CLAUDE_DESKTOP_SCALE:-}" != "off" ]; then SCALE="''${CLAUDE_DESKTOP_SCALE:-}" if [ -z "$SCALE" ] && command -v xrdb >/dev/null 2>&1 && [ -n "''${DISPLAY:-}" ]; then DPI="$(xrdb -query 2>/dev/null | awk -F':\\s*' '/^Xft.dpi:/ {print $2; exit}')" if [ -n "$DPI" ] && [ "$DPI" -gt 96 ] 2>/dev/null; then # bash-only float-Division: auf 2 Stellen runden SCALE="$(awk -v d="$DPI" 'BEGIN { printf "%.2f", d/96 }')" fi fi if [ -n "$SCALE" ] && [ "$SCALE" != "1.00" ] && [ "$SCALE" != "1.0" ]; then export GDK_DPI_SCALE="$SCALE" # Unter Wayland: Xwayland nutzen, damit der Compositor-Scale greift if [ "''${XDG_SESSION_TYPE:-}" = "wayland" ]; then export GDK_BACKEND="''${GDK_BACKEND:-x11}" fi fi fi # GStreamer/PipeWire: Wenn PipeWire NICHT laeuft, pipewiresrc runterranken # damit WebKitGTK auf pulsesrc zurueckfaellt (sonst haengt getUserMedia endlos) if ! pgrep -x pipewire >/dev/null 2>&1; then export GST_PLUGIN_FEATURE_RANK="pipewiresrc:0,pipewiresink:0" fi exec "$BIN" "$@" LAUNCHER chmod +x $out/bin/claude-desktop # LD_LIBRARY_PATH + PATH dauerhaft ans Launcher-Script binden # PATH: node/npm/tar muessen fuer die Bridge und apply_bundle_update verfuegbar sein # GST_PLUGIN_SYSTEM_PATH_1_0: GStreamer-Plugins fuer WebKitGTK (Mikrofon/PipeWire) wrapProgram $out/bin/claude-desktop \ --prefix LD_LIBRARY_PATH : ${pkgs.lib.makeLibraryPath runtimeLibs} \ --prefix PATH : ${pkgs.lib.makeBinPath runtimeBins} \ --prefix GST_PLUGIN_SYSTEM_PATH_1_0 : ${gstPluginPath} # 2) Installer: kopiert ein frisch gebautes Binary an den Ziel-Ort cat > $out/bin/claude-desktop-install <<'INSTALLER' #!${pkgs.bash}/bin/bash set -e SRC="''${1:-/tmp/claude-target/release/claude-desktop}" DEST_DIR="$HOME/.local/share/claude-desktop/bin" DEST="$DEST_DIR/claude-desktop" if [ ! -x "$SRC" ]; then echo "❌ Quelle nicht gefunden oder nicht ausfuehrbar: $SRC" >&2 echo "" >&2 echo "Erst bauen:" >&2 echo " cd ; CARGO_TARGET_DIR=/tmp/claude-target \\" >&2 echo " nix-shell shell.nix --run 'npm ci && npm run tauri build'" >&2 exit 1 fi mkdir -p "$DEST_DIR" cp "$SRC" "$DEST" chmod +x "$DEST" echo "✅ Claude Desktop installiert nach $DEST" echo " Starten mit: claude-desktop (oder aus KDE-Menue)" INSTALLER chmod +x $out/bin/claude-desktop-install # 3) Desktop-Entry cp ${./claude-desktop.desktop} $out/share/applications/claude-desktop.desktop # 4) Icon cp ${../src-tauri/icons/icon.png} $out/share/icons/hicolor/256x256/apps/claude-desktop.png runHook postInstall ''; meta = with pkgs.lib; { description = "Native Desktop-App fuer Claude Code (Wrapper-Paket, Binary in ~/.local/share)"; homepage = "https://git.data-it-solution.de/data/claude-desktop"; license = licenses.mit; platforms = platforms.linux; mainProgram = "claude-desktop"; }; }