bram85's Blog

bram85's Avatar Image
Tooting mostly about #emacs.
← All posts

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/.

,—-
| (defconst my/termux-p (getenv “ANDROID_ROOT”))
|
| (when my/termux-p
| (start-process “gpg-agent” “ gpg-agent” “gpg-agent” “–daemon” “/bin/sh”))
`—-

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 thetty’ command, preferably from your shell initialization.

Inside Emacs, the correct TTY may change: run tty' insideeshell’ and it may output /dev/pts/1. Open another real terminal, go back to eshell' and runtty’ again: /dev/pts/2. So commands inside eshell', such asgpg’, ssh' andgit’ 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’:

,—-
| tty
| (setenv “GPG_TTY” “/dev/pts/2”)
| gpg-connect-agent updatestartuptty /bye
`—-

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, assh’ subprocess may have been spawned in the meantime with an outdated/incorrect $GPG_TTY.

,—-
| (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
`—-

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.

,—-
| (defun my/hook/set-gpg-tty ()
| (setenv “GPG_TTY” (my/get-pty))
| (call-process “gpg-connect-agent” nil nil nil “updatestartuptty” “/bye”))
`—-

Finally, I attached this hook in three places:

,—-
| (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)
`—-

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.

To like or reply, open original post on Emacs.ch