Personal ELPA Archive

There's been a few times over the years when I wanted to install an Emacs package that was not available on any of the common package distributers, such as GNU ELPA or MELPA. Most recently I wanted to install llvm-mode, but that package have recently been removed due to the size of the llvm repository. Previously, I would have just ended up not using the package at all or possibly adding the package to my own configuration repository if I really wanted to use it. This time however, I decided to try to figure out what it would take to setup your own Emacs package distribution server, and as it turns out, with a pre-existing webserver, there's only a few file conventions that need to be fulfilled on the host and client sides to do just that.


Lets start on the packaging end of things.

There are currently a few ways one can go about creating Elisp packages of which the simplest is probably to create a small elisp script, possibly something like this:

(load "package-x")
(let ((package-archive-upload-base (elt argv 1)))
  (package-upload-file (elt argv 2)))
(kill-emacs 0)

And then run it like this:

emacs --load package-script.el output-directory example-package.el

The way this works is setting the variable package-archive-upload-base to define were packages will end up once created, and then we call package-upload-file to create the package file(s).

This works, but there are a few caveats. For instance, it cannot easily handle multi-file packages. It is possible to make that work as well, as described in the manual, but in my opinion it seems a bit more straightforward to introduce an external dependency: MELPA itself.

As described in the wiki in the link, the only thing we really need to do is clone the MELPA git repository, remove the packages we aren't interested in and run make. The rest is handled by the MELPA build system.

Note that once we have compiled and packaged everything we no longer actually need MELPA anymore. The only thing needed to distribute the packages is a valid URL that Emacs can access so in my case my personal webserver can double as ELPA distribution server.

After you've created your packages, there's still a few things to do. First, you naturally need to add the URL to your list of package archives:

(add-to-list 'package-archives
             '("xaldew" . ""))

Second, Emacs have some security features that will prevent installation of unsigned packages, so we need to explicitly tell Emacs to allow the packages from our archive despite them being unsigned:

(add-to-list 'package-unsigned-archives "xaldew")

While it is possible avoid the previous step by signing the packages and adding the public key to the Emacs keyring using M-x package-import-keyring. I found that to be a bit of hassle, so I never actually bothered with it. Still, for the more the security minded, it might be something to consider. I've added some more details down below for how that could be done.

Once the above step has been done, you should be able to see any packages that you have uploaded in the package list shown by M-x list-packages.

ELPA Results

Signing the Packages

Signing the packages isn't actually very difficult, but you must have a PGP signing key. Creating one is a bit out of scope for this article, but there are plenty of good guides out there, such as the one GitHub provides.

On the Elisp side we don't actually need to do anything, Emacs will automatically look for an additional file called <package>.sig or <package>.tar.sig for multi-file-packages. This file must contain the PGP signature that Emacs will check. If the check passes the source of the package is verified and it will allow the package to be installed.

In my case, I created a small script to automatically sign all packages created by MELPA:

for pkg in html/packages/archive-contents html/packages/*.tar;
    gpg --detach-sign --armor -o $pkg.sig $pkg

So far I haven't found a way to sign multiple files at once, which makes this slightly annoying for many packages since you must input your PIN each time you use the key (if you set one when you created the key).


Comments powered by Disqus