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.
Packaging
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" . "https://gustafwaldemarson.com/elpa/packages/"))
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
.
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;
do
gpg --detach-sign --armor -o $pkg.sig $pkg
done
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).