From: Jakub Czajka Date: Sun, 12 Jul 2026 14:06:07 +0000 (+0200) Subject: [home] Bundle Claude Code configuration into the package. X-Git-Url: https://git.ekhem.eu.org/?a=commitdiff_plain;h=a2a8b2d037509a64bebee165c1c5e5e726f02100;p=guix.git [home] Bundle Claude Code configuration into the package. Co-Authored-By: Claude --- diff --git a/TODO b/TODO index 3c9f46e..2e425e6 100644 --- a/TODO +++ b/TODO @@ -16,10 +16,10 @@ TODO Remove the npm-install-build-system wrapper and the Node.js dependency in conf/home/pi.scm. -* Move configuration files to the Guix store and bundle them with the - binary. This requires redesigning home-program-configuration so - dotfiles are part of the package derivation rather than symlinked via - home-symlink-service-type. +* When Claude Code supports separating config from runtime state (e.g. + separate CLAUDE_STATE_DIR env var), remove the symlink and --settings + flag from the claude-code wrapper; point CLAUDE_CONFIG_DIR directly + to $out/etc. * When Paseo triggers Claude Code from mobile, get-api-key runs pass(1) which prompts for a GPG passphrase on a headless terminal — the session hangs. @@ -27,11 +27,6 @@ TODO key lives on the phone behind Paseo's relay; the key needs to cross that relay into the laptop somehow, which there is currently no mechanism for. -* Claude Code defaults to ~/.claude for its config directory but we store - it at ~/.config/claude (XDG), so CLAUDE_CONFIG_DIR must be set to - redirect it. Remove the explicit CLAUDE_CONFIG_DIR from the paseo - Shepherd service and find a generic way to set it for all daemons that - spawn Claude Code. * Use SetCwd instead of spawning a subshell with cd(1) in hooks once https://github.com/anthropics/claude-code/issues/69159 is resolved. diff --git a/bash/.config/profile.d/20-env.sh b/bash/.config/profile.d/20-env.sh index 1ad317f..07ae4e2 100644 --- a/bash/.config/profile.d/20-env.sh +++ b/bash/.config/profile.d/20-env.sh @@ -7,4 +7,3 @@ export EDITOR="emacs" export DISPLAY=":1" export PATH="${HOME}/.local/bin:${PATH}" export TERM="st-256color" -export CLAUDE_CONFIG_DIR="${HOME}/.config/claude" diff --git a/conf/home/claude.scm b/conf/home/claude.scm index d68c122..5cbe3ef 100644 --- a/conf/home/claude.scm +++ b/conf/home/claude.scm @@ -1,12 +1,7 @@ ;; Copyright (c) 2026 Jakub Czajka ;; License: GPL-3.0 or later. ;; -;; claude.scm - Claude Code package and home service with dotfiles. -;; -;; The upstream binary is prebuilt for systems with Filesystem Hierarchy -;; Standard and expects a standard linker path. GNU Guix uses non-standard store -;; paths, so we wrap the binary in a shell script that sets LD_LIBRARY_PATH to -;; the store locations of its dependencies and invokes it through ld-linux. +;; claude.scm - Claude Code package with bundled configuration. (define-module (conf home claude) #:use-module (guix packages) @@ -18,9 +13,23 @@ #:use-module (gnu packages tls) #:use-module (gnu packages compression) #:use-module (gnu packages bash) - #:use-module (conf home program) - #:use-module (gnu services) - #:export (claude-code claude-service)) + #:use-module (gnu packages elf) + #:export (claude-code)) + +(define settings-file + (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH") + "/claude/.config/claude/settings.json")) + "settings.json")) + +(define instructions-file + (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH") + "/claude/.config/claude/CLAUDE.md")) + "CLAUDE.md")) + +(define api-key-file + (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH") + "/claude/.local/bin/get-api-key")) + "get-api-key")) (define claude-code (package @@ -42,50 +51,54 @@ (use-modules (guix build utils)) (let* ((out #$output) (bin (string-append out "/bin")) - (internal-bin (string-append out "/libexec")) - (real-binary (string-append internal-bin "/claude")) - (wrapper (string-append bin "/claude")) - (bash (string-append #$bash-minimal "/bin/bash")) - (ld-linux (string-append #$glibc "/lib/ld-linux-x86-64.so.2")) - ;; Define runtime library path for the wrapper. - (lib-path (string-append #$glibc - "/lib:" - #$gcc:lib - "/lib:" - #$gcc:lib - "/lib64:" - #$openssl - "/lib:" - #$zlib - "/lib"))) - ;; 1. Copy the raw binary into an isolated internal path. - (mkdir-p internal-bin) - (copy-file #$source real-binary) - (chmod real-binary #o755) - ;; 2. Generate a shell wrapper that invokes the binary - ;; using Guix's linker. + (etc (string-append out "/etc")) + (binary (string-append bin "/claude")) + (api-helper (string-append etc "/get-api-key")) + (patchelf #$(file-append patchelf "/bin/patchelf")) + (ld-so #$(file-append glibc "/lib/ld-linux-x86-64.so.2")) + (rpath (string-append #$glibc + "/lib:" + #$gcc:lib + "/lib:" + #$gcc:lib + "/lib64:" + #$openssl + "/lib:" + #$zlib + "/lib"))) + ;; 1. Copy the binary and patch its ELF. (mkdir-p bin) - (with-output-to-file wrapper - (lambda () - (format #t "#!~a~%" bash) - (format #t "export LD_LIBRARY_PATH=\"~a:" lib-path) - (format #t "$LD_LIBRARY_PATH\"~%") - ;; Invoke the explicit linker, passing CLI args. - (format #t "exec ~a ~a \"$@\"~%" ld-linux real-binary))) - (chmod wrapper #o755))))) + (copy-file #$source binary) + (chmod binary #o755) + (invoke patchelf "--set-interpreter" ld-so binary) + (invoke patchelf "--set-rpath" rpath binary) + ;; 2. Copy and patch configuration files. + (mkdir-p etc) + (copy-file #$instructions-file + (string-append etc "/CLAUDE.md")) + (copy-file #$settings-file + (string-append etc "/settings.json")) + (substitute* (string-append etc "/settings.json") + (("\\$\\{HOME\\}/\\.local/bin/get-api-key") + api-helper)) + ;; 3. Copy the API key helper. + (copy-file #$api-key-file api-helper) + (chmod api-helper #o755) + ;; 4. Wrap the binary to set CLAUDE_CONFIG_DIR. + ;; wrap-program needs bash in PATH for the shebang. + (setenv "PATH" + (string-append #$bash-minimal "/bin")) + (wrap-program binary + `("CLAUDE_CONFIG_DIR" = + (,etc))))))) (inputs (list glibc - `(,gcc "lib") openssl zlib bash-minimal)) + `(,gcc "lib") + openssl + zlib + bash-minimal + patchelf)) (home-page "https://claude.ai") (synopsis "Agentic coding tool that lives in your terminal") (description "Claude Code is a CLI tool that understands your codebase.") ;; Claude Code is proprietary software, not end-user redistributable. (license #f))) - -(define claude-dotfiles - (list "claude/.config/claude/settings.json" - "claude/.config/claude/CLAUDE.md" "claude/.local/bin/get-api-key")) - -(define claude-service - (service home-program-service-type - (home-program-configuration (packages (list claude-code)) - (dotfiles claude-dotfiles)))) diff --git a/conf/home/paseo.scm b/conf/home/paseo.scm index e2add79..ae5e5c7 100644 --- a/conf/home/paseo.scm +++ b/conf/home/paseo.scm @@ -49,13 +49,9 @@ coding agents from desktop and mobile.") (define paseo-daemon-start #~(let* ((home (getenv "HOME")) (prog #$(file-append paseo "/bin/paseo")) - (log (string-append home "/.local/var/log/paseo-daemon.log")) - (env (cons (string-append "CLAUDE_CONFIG_DIR=" home - "/.config/claude") - (default-environment-variables)))) + (log (string-append home "/.local/var/log/paseo-daemon.log"))) (make-forkexec-constructor (list prog "daemon" "start" "--foreground") - #:log-file log - #:environment-variables env))) + #:log-file log))) (define paseo-daemon-shepherd-service (shepherd-service (provision '(paseo-daemon)) diff --git a/home.scm b/home.scm index 7e848c2..8823931 100644 --- a/home.scm +++ b/home.scm @@ -25,7 +25,7 @@ (home-environment (packages (case profile ((nonfree) - (list firefox pi select-emoji st-libxft-bgra)) + (list firefox pi claude-code select-emoji st-libxft-bgra)) ((libre) (list icecat-minimal select-emoji st-libxft-bgra)))) (services @@ -33,7 +33,6 @@ ((nonfree) (append (list autorandr-service channels-service - claude-service notify-service paseo-service paseo-daemon-service