Leiningen, Release Me!

Apr 15, 2016

A mystical journey in search of the magic release crystal

I work on a Macbook Pro.

I use Leiningen for project automation and Git-Flow (AVH) for a sane Git branching model.

I do not sign my releases, I don’t need to for our internal projects.

I assume that’s a pretty normal setup, so why did it suddenly become so damn hard to release a project when I upgraded Leiningen to the latest version? It felt like falling into a series of holes while on a quest to retrieve a magic release crystal from an underwater Pixie King. It should not be this hard.

Skip to the TL;DR to avoid an overwrought tale of woe, and for the specifics of configuring Leiningen to use gnupg, gpg-agent, and pinentry-mac correctly to enable you sign releases and/or tags on a mac.

An Unexpected Party

My misadventure began not with a company of dwarves, but when I upgraded Leiningen.

$ brew upgrade leiningen

All I wanted was have tests in .cljc files recognized by the test runner. The wheels fell off.

Leiningen has always had the ability to sign releases. I don’t use it.

 :repositories [["releases" {:sign-releases false

The new Leiningen release process signs tags. This requires a series of tools to be installed and configured correctly, can’t be hard.

My first thought was ‘bugger that I don’t want to sign tags’, turns out I’m not the only one. Trumpets sound, our hero arrives. Turns out it’s my old colleague Daniel Compton with a patch to Leiningen that disables the signing of tags. Good man.

Short quest that. Unfortunately the ability to –no-sign tags requires explicitly defining the release tasks for each project. I choose not to do that and forge onwards.

A Series of Holes

The Leiningen GPG guide gives perfectly good advice on installing gnupg, on creating a key-pair, and on configuring the public key for signing releases.

$ brew install gnupg

Done! I run my release process and it prompts me to enter my passphrase, easy beans. But wait, no input is accepted from the command-line but the enter-key? I repeatedly fail to enter my passphrase and sulk.

I climb from my hole to find some assistance within the Leiningen Deploy guide:

Due to a bug in gpg you currently need to use gpg-agent and have already unlocked your key

$ brew install gpg-agent

Done! With a little configuration (see: below) gpg-agent is configured and running.

Wait, gpg-agent isn’t working? After configuring the error logs I note that it can’t launch pinentry, after some digging I find that GPG-TTY must be set.

Done! Nearly. It turns out that the reason Leiningen requires gpg-agent is there is a bug that makes it hard to enter credentials via the same terminal window the release task is running in (I did manage it once).

At this point I verged off on a misadventure starting gpg-agent in a lenient mode and presetting the password (so that I would never be prompted, no pinentry required).

$ eval $(gpg-agent --daemon --allow-preset-passphrase)
$ /usr/local/opt/gpg-agent/libexec/gpg-preset-passphrase --passphrase my-passphrase --preset 81440C12501230BFDC9728FC3BFD789684305A6EE76

That works, but it’s quite unpleasant. A better solution is to install pinentry-mac (which launches an application window) and configure gpg-agent to use that instead.

brew install pinentry-mac

Done! Now I am successfully signing tags as I release. Unfortunately at this point my release process failed because Leiningen and Git Flow were both generating tags that conflicted with each other, fortunately there’s a straight forward solution to that (again, below).

TL;DR, Save Your Tears

Install gnupg, gpg-agent, pinentry-mac.

$ brew install gnupg
$ brew install gpg-agent
$ brew install pinentry-mac

Configure gnupg to use an agent (remove the leading #).

$ vi ~/.gnupg/gpg.conf

---

# We support the old experimental passphrase agent protocol as well as
# the new Assuan based one (currently available in the "newpg" package
# at ftp.gnupg.org/gcrypt/alpha/aegypten/).  To make use of the agent,
# you have to run an agent as daemon and use the option
#
use-agent

Configure the gpg-agent, most importantly to use pinentry-mac

$ vi ~/.gnupg/gpg-agent.conf

--- 

default-cache-ttl 600
max-cache-ttl 7200
pinentry-program /usr/local/bin/pinentry-mac
write-env-file /Users/derek/.gnupg/gpg-agent-info
log-file /Users/derek/.gnupg/gpg-agent.log

Create your key-per as per the Leiningen guide, configure your key per project if required.

Start the agent

$ eval $(gpg-agent --daemon)

Confirm you are in a happy place (a mac window will pop up asking for your password)

% echo "test" | gpg -ase                                                                                                                                                                           !10003

You need a passphrase to unlock the secret key for
user: "Derek Troy-West <derek@troywest.com>"
2048-bit RSA key, ID 55A6EE76, created 2016-04-15

At this point Leiningen will happily release, signing your tags (and more) via gnupg using gpg-agent.

The latest version of the Leiningen release task creates tags that conflict with Git Flow Release, you can configure Leiningen to prefix tags, or name Git Flow tags differently, but who wants to tag a release twice?

A better solution, provided by my colleague Mark Derricut, is to tell Git Flow to release without a tag.

$ git flow release start 1.0.18
$ lein release
$ git flow release finish -n