mirror of
https://github.com/aaddrick/claude-desktop-debian.git
synced 2026-05-17 08:36:35 +03:00
* launcher: log session/IME env block at startup (#548) Adds log_session_env, called once per launch from each packaging target (deb, rpm, AppImage, Nix). Emits a single env={ ... } block covering display (XDG_SESSION_TYPE, WAYLAND_DISPLAY, DISPLAY, XDG_CURRENT_DESKTOP), IME (GTK_IM_MODULE, XMODIFIERS, QT_IM_MODULE), and Claude-specific overrides (CLAUDE_USE_WAYLAND, CLAUDE_TITLEBAR_STYLE, CLAUDE_GTK_IM_MODULE). Empty/unset values are emitted as `KEY=` (rather than omitted) so absence is unambiguous in bug reports. Pure observability — no behavior change. Closes #548 Co-Authored-By: Claude <claude@anthropic.com> * test: consolidate log_session_env BATS coverage (#548) Collapse the four log_session_env cases into two, and tighten the assertions in both: - Old test 1 (substring match per key) + old test 4 (block braces on their own lines) → one test using exact-line equality on the `lines` array. Locks block structure and per-key formatting in a single pass; substring matching could not catch a regression that re-ordered keys, dropped indentation, or merged lines. - Old test 2 (unset values are KEY=) + old test 3 (empty-string is KEY=) → one test covering both code paths. Exact-line match proves the value after `=` is truly empty; the previous `*'KEY='*` substring would have matched `KEY=value` and the old test-3 regex was fragile (depended on trailing newline being literal `$'\n'` vs end-of-string `$`). Net: 77 → 42 lines, 4 → 2 cases, stronger guarantees. No change to the helper itself or the call sites — issue #548 acceptance criteria still hold. --------- Co-authored-by: Claude <claude@anthropic.com>
294 lines
9.8 KiB
Nix
294 lines
9.8 KiB
Nix
{
|
|
lib,
|
|
stdenvNoCC,
|
|
fetchurl,
|
|
electron,
|
|
p7zip,
|
|
icoutils,
|
|
imagemagick,
|
|
nodejs,
|
|
asar,
|
|
makeDesktopItem,
|
|
python3,
|
|
bash,
|
|
getent,
|
|
node-pty,
|
|
}:
|
|
let
|
|
pname = "claude-desktop";
|
|
version = "1.5354.0";
|
|
|
|
srcs = {
|
|
x86_64-linux = fetchurl {
|
|
url = "https://downloads.claude.ai/releases/win32/x64/1.5354.0/Claude-9a9e3d5a4a368f0f49a80dc303b0ed1a18bfedad.exe";
|
|
hash = "sha256-5hnHvTtnRqcwfr7+UJv+RHoUOu2X5sf2Zmd7Nqa2ulQ=";
|
|
};
|
|
aarch64-linux = fetchurl {
|
|
url = "https://downloads.claude.ai/releases/win32/arm64/1.5354.0/Claude-9a9e3d5a4a368f0f49a80dc303b0ed1a18bfedad.exe";
|
|
hash = "sha256-v33l1sASVC/q331cqnenLfzqGyRRLpptKOAEukrioR0=";
|
|
};
|
|
};
|
|
|
|
src = srcs.${stdenvNoCC.hostPlatform.system} or (throw "Unsupported system: ${stdenvNoCC.hostPlatform.system}");
|
|
|
|
sourceRoot = lib.cleanSourceWith {
|
|
src = ./..;
|
|
filter = path: type:
|
|
let rel = lib.removePrefix (toString ./.. + "/") path;
|
|
in !(lib.hasPrefix "build-reference" rel)
|
|
&& !(lib.hasPrefix "logs" rel)
|
|
&& !(lib.hasPrefix "test-build" rel)
|
|
&& !(lib.hasPrefix "squashfs-root" rel)
|
|
&& !(lib.hasPrefix "result" rel);
|
|
};
|
|
|
|
# The unwrapped electron derivation — contains the real ELF binary
|
|
# and Chromium resources (.pak files, locales/, etc.).
|
|
electronUnwrapped = electron.passthru.unwrapped or electron;
|
|
electronDir = "${electronUnwrapped}/libexec/electron";
|
|
|
|
desktopItem = makeDesktopItem {
|
|
name = "claude-desktop";
|
|
exec = "claude-desktop %u";
|
|
icon = "claude-desktop";
|
|
type = "Application";
|
|
terminal = false;
|
|
desktopName = "Claude";
|
|
genericName = "Claude Desktop";
|
|
startupWMClass = "Claude";
|
|
categories = [ "Office" "Utility" ];
|
|
mimeTypes = [ "x-scheme-handler/claude" ];
|
|
};
|
|
in
|
|
stdenvNoCC.mkDerivation {
|
|
inherit pname version src;
|
|
|
|
nativeBuildInputs = [
|
|
p7zip
|
|
nodejs
|
|
asar
|
|
icoutils
|
|
imagemagick
|
|
bash
|
|
python3
|
|
getent
|
|
];
|
|
|
|
# The exe is not a standard archive — use manual unpack
|
|
dontUnpack = true;
|
|
|
|
buildPhase = ''
|
|
runHook preBuild
|
|
|
|
export HOME=$TMPDIR
|
|
|
|
# Copy exe to a writable location for build.sh
|
|
cp $src Claude-Setup.exe
|
|
|
|
# Run build.sh in nix mode — it handles extraction, patching, icon
|
|
# extraction, and asar repacking. --source-dir points at the repo
|
|
# root so build.sh can find scripts/.
|
|
bash ${sourceRoot}/build.sh \
|
|
--exe "$(pwd)/Claude-Setup.exe" \
|
|
--source-dir "${sourceRoot}" \
|
|
--node-pty-dir "${node-pty}/lib/node_modules/node-pty" \
|
|
--build nix \
|
|
--clean no
|
|
|
|
runHook postBuild
|
|
'';
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
|
|
#==========================================================================
|
|
# Create a custom Electron tree with app resources co-located.
|
|
#
|
|
# On NixOS, the stock electron-unwrapped lives in a read-only store
|
|
# path. Chromium computes process.resourcesPath from /proc/self/exe,
|
|
# so it always points to electron-unwrapped's resources/ dir — which
|
|
# doesn't contain the app's locale JSONs, tray icons, etc. When
|
|
# ELECTRON_FORCE_IS_PACKAGED=true, the app reads en-US.json from
|
|
# resourcesPath at module load time (before frame-fix-wrapper.js can
|
|
# correct the path), causing an ENOENT crash.
|
|
#
|
|
# Solution: copy the Electron ELF binary into our own tree so that
|
|
# /proc/self/exe resolves here, then merge both Electron's and the
|
|
# app's resources into resources/. Everything else (shared libs,
|
|
# .pak files, locales/) is symlinked to avoid duplication.
|
|
#==========================================================================
|
|
electron_tree=$out/lib/claude-desktop/electron
|
|
|
|
mkdir -p $electron_tree/resources
|
|
|
|
# Copy the ELF binary — MUST be a real copy (not symlink) so that
|
|
# /proc/self/exe resolves to our tree
|
|
cp ${electronDir}/electron $electron_tree/electron
|
|
|
|
# Symlink everything else from electron-unwrapped
|
|
for item in ${electronDir}/*; do
|
|
name=$(basename "$item")
|
|
[[ "$name" = "electron" ]] && continue
|
|
[[ "$name" = "resources" ]] && continue
|
|
ln -s "$item" "$electron_tree/$name"
|
|
done
|
|
|
|
# Populate resources/ — start with Electron's own (default_app.asar)
|
|
for item in ${electronDir}/resources/*; do
|
|
ln -s "$item" "$electron_tree/resources/$(basename "$item")"
|
|
done
|
|
|
|
# Install app.asar and unpacked resources into the merged tree
|
|
cp build/electron-app/app.asar $electron_tree/resources/
|
|
cp -r build/electron-app/app.asar.unpacked $electron_tree/resources/
|
|
|
|
# Install tray icons into resources
|
|
for tray_icon in build/electron-app/nix-resources/Tray*; do
|
|
[[ -f "$tray_icon" ]] && cp "$tray_icon" $electron_tree/resources/
|
|
done
|
|
|
|
# Install SSH helpers into resources
|
|
if [[ -d build/electron-app/nix-resources/claude-ssh ]]; then
|
|
cp -r build/electron-app/nix-resources/claude-ssh \
|
|
$electron_tree/resources/
|
|
fi
|
|
|
|
# Install cowork resources (smol-bin, plugin shim)
|
|
for cowork_res in build/electron-app/nix-resources/smol-bin.*.vhdx \
|
|
build/electron-app/nix-resources/cowork-plugin-shim.sh; do
|
|
if [[ -f "$cowork_res" ]]; then
|
|
cp "$cowork_res" $electron_tree/resources/
|
|
echo "Installed cowork resource: $(basename "$cowork_res")"
|
|
fi
|
|
done
|
|
|
|
# Install ion-dist static assets (app:// protocol handler root for
|
|
# Third-Party Inference setup — see issue #488)
|
|
if [[ -d build/electron-app/nix-resources/ion-dist ]]; then
|
|
cp -r build/electron-app/nix-resources/ion-dist \
|
|
$electron_tree/resources/
|
|
echo "Installed cowork resource: ion-dist"
|
|
fi
|
|
|
|
# Install locale JSON files into resources
|
|
for locale_json in build/claude-extract/lib/net45/resources/*-*.json; do
|
|
[[ -f "$locale_json" ]] \
|
|
&& cp "$locale_json" $electron_tree/resources/
|
|
done
|
|
|
|
# Create the electron wrapper — replicates the env setup from the
|
|
# stock electron wrapper (GIO, GTK, GDK_PIXBUF, XDG_DATA_DIRS) but
|
|
# execs our custom binary. We extract everything except the final
|
|
# exec line from the stock wrapper, then append our own exec.
|
|
head -n -1 ${electron}/bin/electron > $electron_tree/electron-wrapper
|
|
echo "exec \"$electron_tree/electron\" \"\$@\"" >> $electron_tree/electron-wrapper
|
|
chmod +x $electron_tree/electron-wrapper
|
|
|
|
# Update CHROME_DEVEL_SANDBOX to point to our tree's chrome-sandbox
|
|
substituteInPlace $electron_tree/electron-wrapper \
|
|
--replace-quiet "${electron}/libexec/electron/chrome-sandbox" \
|
|
"$electron_tree/chrome-sandbox"
|
|
|
|
#==========================================================================
|
|
# Standard install (icons, desktop file, launcher)
|
|
#==========================================================================
|
|
|
|
# Convenience symlink for resources dir (used by launcher, FHS, etc.)
|
|
ln -s $electron_tree/resources $out/lib/claude-desktop/resources
|
|
|
|
# Install icons
|
|
for size in 16 24 32 48 64 256; do
|
|
icon_dir=$out/share/icons/hicolor/"$size"x"$size"/apps
|
|
mkdir -p "$icon_dir"
|
|
icon=$(find build/ -name "claude_*''${size}x''${size}x32.png" 2>/dev/null | head -1)
|
|
if [[ -n "$icon" ]]; then
|
|
install -Dm644 "$icon" "$icon_dir/claude-desktop.png"
|
|
fi
|
|
done
|
|
|
|
# Install shared launcher library + doctor (launcher-common.sh
|
|
# sources doctor.sh at runtime, so both must live in the same dir)
|
|
install -Dm755 ${sourceRoot}/scripts/launcher-common.sh \
|
|
$out/lib/claude-desktop/launcher-common.sh
|
|
install -Dm755 ${sourceRoot}/scripts/doctor.sh \
|
|
$out/lib/claude-desktop/doctor.sh
|
|
|
|
# Install .desktop file
|
|
mkdir -p $out/share/applications
|
|
install -Dm644 ${desktopItem}/share/applications/* $out/share/applications/
|
|
|
|
# Create launcher script
|
|
mkdir -p $out/bin
|
|
cat > $out/bin/claude-desktop <<'LAUNCHER'
|
|
#!/usr/bin/env bash
|
|
# Claude Desktop launcher for NixOS
|
|
|
|
electron_exec="ELECTRON_PLACEHOLDER"
|
|
app_path="RESOURCES_PLACEHOLDER/app.asar"
|
|
|
|
source "LAUNCHER_LIB_PLACEHOLDER"
|
|
|
|
# Handle --doctor flag before anything else
|
|
if [[ "''${1:-}" == '--doctor' ]]; then
|
|
run_doctor "$electron_exec"
|
|
exit $?
|
|
fi
|
|
|
|
# Setup logging and environment
|
|
setup_logging || exit 1
|
|
setup_electron_env
|
|
cleanup_orphaned_cowork_daemon
|
|
cleanup_stale_lock
|
|
cleanup_stale_cowork_socket
|
|
|
|
# Log startup info
|
|
log_message '--- Claude Desktop Launcher Start (NixOS) ---'
|
|
log_message "Timestamp: $(date)"
|
|
log_message "Arguments: $@"
|
|
log_session_env
|
|
|
|
# Check for display
|
|
if ! check_display; then
|
|
log_message 'No display detected (TTY session)'
|
|
echo 'Error: Claude Desktop requires a graphical desktop environment.' >&2
|
|
echo 'Please run from within an X11 or Wayland session, not from a TTY.' >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Detect display backend (handles CLAUDE_USE_WAYLAND)
|
|
detect_display_backend
|
|
|
|
# Build Electron arguments
|
|
build_electron_args 'nix'
|
|
|
|
# Add app path
|
|
electron_args+=("$app_path")
|
|
|
|
# Execute Electron
|
|
log_message "Executing: $electron_exec ''${electron_args[*]} $*"
|
|
"$electron_exec" "''${electron_args[@]}" "$@" >> "$log_file" 2>&1
|
|
exit_code=$?
|
|
log_message "Electron exited with code: $exit_code"
|
|
exit $exit_code
|
|
LAUNCHER
|
|
# Substitute placeholders — electron_exec points to our custom
|
|
# wrapper (which sets GTK/GIO env then execs our merged binary)
|
|
substituteInPlace $out/bin/claude-desktop \
|
|
--replace-fail "ELECTRON_PLACEHOLDER" "$electron_tree/electron-wrapper" \
|
|
--replace-fail "RESOURCES_PLACEHOLDER" "$electron_tree/resources" \
|
|
--replace-fail "LAUNCHER_LIB_PLACEHOLDER" "$out/lib/claude-desktop/launcher-common.sh"
|
|
chmod +x $out/bin/claude-desktop
|
|
|
|
runHook postInstall
|
|
'';
|
|
|
|
meta = with lib; {
|
|
description = "Claude Desktop for Linux";
|
|
homepage = "https://github.com/aaddrick/claude-desktop-debian";
|
|
license = licenses.unfree;
|
|
platforms = [ "x86_64-linux" "aarch64-linux" ];
|
|
sourceProvenance = with sourceTypes; [ binaryNativeCode ];
|
|
mainProgram = "claude-desktop";
|
|
};
|
|
}
|