Add note on $GPG_TTY

This commit is contained in:
Bram Schoenmakers 2023-12-14 21:39:36 +01:00
parent 9a76d39524
commit 0fae583d4e

View file

@ -776,6 +776,71 @@ Based on an answer at the [[https://emacs.stackexchange.com/a/77480/34645][Emacs
(org-set-property "URL" url))
#+end_src
* Using gpg-agent inside Emacs in Termux
:PROPERTIES:
:URL: https://emacs.ch/@bram85/111580555195721041
:END:
Getting =gpg-agent= to work properly inside #termux and have it properly accessed from #emacs is a bit tricky.
The first issue is Android related: by default the agent will spawn as a top level process. This makes the process prone to be randomly killed by Android for memory management purposes, causing you to enter your passphrase more often than you may have configured.
Since I always run Emacs anyway, I chose to execute it from the Emacs init file, and passing a shell for the =--daemon= flag. Then it becomes a child process and won't be killed at random. It occupies a hidden buffer /*gpg-agent*/.
#+begin_src elisp
(defconst my/termux-p (getenv "ANDROID_ROOT"))
(when my/termux-p
(start-process "gpg-agent" " *gpg-agent*" "gpg-agent" "--daemon" "/bin/sh"))
#+end_src
The second issue is 'knowing' the correct TTY such that =pinentry= shows up correctly inside Emacs (using =(setq epg-pinentry-mode 'loopback)=).
Outside Emacs, =pinentry= shows up in the right place because the GPG manual dictates to have your $GPG_TTY set to the output of the =tty= command, preferably from your shell initialization.
Inside Emacs, the correct TTY may change: run =tty= inside =eshell= and it may output /dev/pts/1. Open another real terminal, go back to =eshell= and run =tty= again: /dev/pts/2. So commands inside =eshell=, such as =gpg=, =ssh= and =git= cannot rely on a fixed $GPG_TTY that was set when starting Emacs. With the wrong value, the loopback pinentry fails and no passphrase is prompted from the minibuffer. Instead, the terminal that displays Emacs gets messed up.
One could fix it with the following inside =eshell=:
#+begin_src sh
tty
(setenv "GPG_TTY" "/dev/pts/2")
gpg-connect-agent updatestartuptty /bye
#+end_src
Which needs to be executed every time you're about to run something that might trigger a =pinentry= (including remote operations with Magit or TRAMP).
These steps can be performed from various hooks such that any subprocess gets the proper $GPG_TTY to which Emacs responds.
First a function to retrieve the =tty= output /synchronously/. If we don't wait, a =ssh= subprocess may have been spawned in the meantime with an outdated/incorrect $GPG_TTY.
#+begin_src elisp
(defun my/get-pty ()
(with-temp-buffer
(let* ((process-connection-type t) ; force PTY allocation
(proc (start-process "tty" (current-buffer) "tty")))
(while (process-live-p proc)
(accept-process-output proc 0.01 nil t)) ; wait for process to terminate
(car (string-lines (buffer-string)))))) ; return process output
#+end_src
And then a function we can use for hooks to actually update $GPG_TTY and make sure that SSH uses the correct display for a possible passphrase prompt.
#+begin_src elisp
(defun my/hook/set-gpg-tty ()
(setenv "GPG_TTY" (my/get-pty))
(call-process "gpg-connect-agent" nil nil nil "updatestartuptty" "/bye"))
#+end_src
Finally, I attached this hook in three places:
#+begin_src elisp
(add-hook 'find-file-hook #'my/hook/set-gpg-tty) ; for TRAMP
(add-hook 'magit-pre-start-git-hook #'my/hook/set-gpg-tty)
(add-hook 'eshell-pre-command-hook #'my/hook/set-gpg-tty 0 t)
#+end_src
Which covers my (potential) GPG/SSH usage within Emacs. Now, anytime a I perform a GPG / SSH operation, the $GPG_TTY variable is set properly and if needed, the passhprase prompt shows up in the minibuffer.
* Meta
** License