diff --git a/emacs/.config/doom/config.el b/emacs/.config/doom/config.el index edef826..5d4283f 100644 --- a/emacs/.config/doom/config.el +++ b/emacs/.config/doom/config.el @@ -8,6 +8,12 @@ (after! envrc (envrc-global-mode)) +;; Force systemd user ssh-agent socket for all Emacs subprocesses. +(let* ((xdg (format "/run/user/%d" (user-uid))) + (sock (expand-file-name "ssh-agent.socket" xdg))) + (setenv "XDG_RUNTIME_DIR" xdg) + (setenv "SSH_AUTH_SOCK" sock)) + ;; load pinentry (when (require 'pinentry nil t) (pinentry-start)) @@ -444,35 +450,55 @@ Always open the result in `eww`." (global-set-key (kbd "C-c l") #'life-scrobble-url) (after! magit - (defvar my/ssh-key-injector-script - (expand-file-name "~/.local/bin/load_keys")) + (defvar my/ssh-key-injector-script (expand-file-name "~/.local/bin/load_keys")) - (defun my/ssh-agent-has-keys-p (&rest _ignore) - "Non-nil if ssh-agent currently has at least one identity loaded." - (eq 0 (call-process "ssh-add" nil nil nil "-l"))) + (defun my/systemd-ssh-auth-sock () + (format "/run/user/%d/ssh-agent.socket" (user-uid))) + + (defun my/ssh-add-status () + "ssh-add -l exit code: 0=has keys, 1=no keys, 2=no agent." + (call-process "ssh-add" nil nil nil "-l")) + + (defun my/run-ssh-key-injector () + (let* ((buf (get-buffer-create "*ssh-key-injector*")) + (sock (my/systemd-ssh-auth-sock)) + (process-connection-type t) ;; PTY for pinentry-curses + (process-environment (copy-sequence process-environment))) + (setenv "SSH_AUTH_SOCK" sock) + (setenv "XDG_RUNTIME_DIR" (format "/run/user/%d" (user-uid))) + + (unless (file-executable-p my/ssh-key-injector-script) + (user-error "SSH injector script not executable: %s" my/ssh-key-injector-script)) + + (with-current-buffer buf + (erase-buffer) + (insert (format "Emacs SSH_AUTH_SOCK=%s\n" (getenv "SSH_AUTH_SOCK"))) + (insert (format "Emacs XDG_RUNTIME_DIR=%s\n\n" (getenv "XDG_RUNTIME_DIR")))) + + (let ((proc (make-process + :name "ssh-key-injector" + :buffer buf + :stderr buf + :command (list my/ssh-key-injector-script) + :noquery t))) + (while (process-live-p proc) + (accept-process-output proc 0.05)) + (let ((exit (process-exit-status proc))) + (unless (eq exit 0) + (display-buffer buf) + (user-error "SSH key injection failed (exit %d). See *ssh-key-injector*." exit)))))) (defun my/ensure-ssh-keys-loaded (&rest _ignore) - "Ensure ssh-agent has keys loaded; if not, run injector script." - (unless (file-executable-p my/ssh-key-injector-script) - (user-error "SSH injector script not executable: %s" my/ssh-key-injector-script)) - (let ((buf (get-buffer-create "*ssh-key-injector*"))) - (with-current-buffer buf (erase-buffer)) - (let ((exit (call-process-shell-command my/ssh-key-injector-script nil buf t))) - (unless (eq exit 0) - (display-buffer buf) - (user-error "SSH key injection failed (see *ssh-key-injector*)"))))) + (pcase (my/ssh-add-status) + (0 nil) ;; already has identities + (1 (my/run-ssh-key-injector)) ;; agent reachable but empty + (2 (user-error "No reachable ssh-agent. SSH_AUTH_SOCK=%s" + (or (getenv "SSH_AUTH_SOCK") ""))) + (_ (my/run-ssh-key-injector)))) - ;; IMPORTANT: remove then re-add, so we don't keep an old advised function object around - (advice-remove 'magit-status #'my/ensure-ssh-keys-loaded) - (advice-add 'magit-status :before #'my/ensure-ssh-keys-loaded) - - ;; optional: - (advice-remove 'magit-fetch #'my/ensure-ssh-keys-loaded) - (advice-add 'magit-fetch :before #'my/ensure-ssh-keys-loaded) - (advice-remove 'magit-push #'my/ensure-ssh-keys-loaded) - (advice-add 'magit-push :before #'my/ensure-ssh-keys-loaded) - (advice-remove 'magit-pull #'my/ensure-ssh-keys-loaded) - (advice-add 'magit-pull :before #'my/ensure-ssh-keys-loaded)) + (dolist (fn '(magit-status magit-fetch magit-push magit-pull)) + (advice-remove fn #'my/ensure-ssh-keys-loaded) + (advice-add fn :before #'my/ensure-ssh-keys-loaded))) (defun my/disable-apheleia-in-certain-projects () (when-let ((root (and (fboundp 'project-root)