]> git.ekhem.eu.org Git - guix.git/commitdiff
[home] Bundle Claude Code configuration into the package.
authorJakub Czajka <jakub@ekhem.eu.org>
Sun, 12 Jul 2026 14:06:07 +0000 (16:06 +0200)
committerJakub Czajka <jakub@ekhem.eu.org>
Mon, 13 Jul 2026 17:10:12 +0000 (17:10 +0000)
Co-Authored-By: Claude <noreply@anthropic.com>
TODO
bash/.config/profile.d/20-env.sh
conf/home/claude.scm
conf/home/paseo.scm
home.scm

diff --git a/TODO b/TODO
index 3c9f46e1e45242283bd291c41c8dac13b9259bd0..2e425e683e59465eabd6ac290b6aafe32870be23 100644 (file)
--- 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.
index 1ad317f0106d528b4f0ff3e18f1bf583bb71c81b..07ae4e22ae9e60e9e349f0942526770d760530eb 100644 (file)
@@ -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"
index d68c1221abc4355f64fd0d82da4d64fb6732a534..5cbe3efecbb91d7b12c7a683827dd99ae1ba584f 100644 (file)
@@ -1,12 +1,7 @@
 ;; Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
 ;; 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)
   #: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
           (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))))
index e2add79228e5144b35a2d5a87e03607a6ea6e568..ae5e5c720720d12d6323a36951a9359e8361af63 100644 (file)
@@ -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))
index 7e848c2cbc108330f9fcf727394e62b90c10c8fc..8823931e537482371e3336fdfaca45efc32a8254 100644 (file)
--- 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