Creating signed debian apt repository

Update: 2014.03.19 Use freight

Recently had the pleasure(?) of having to resetup an apt repository. It had been managed by reprepro, but there apparently the underlying concept of a single version per repository that is central to the implementation. Seems kinda weird to me, as was it to some of the users of the repository. In an effort to resolve this and simplify our repository management I did some research and came up with a very simple solution.

Using the following references:

We already had a GPG key, so I didn't have to create a key, i just followed the instructions in How to use GnuPG in an automated environment to generate a passwordless signing subkey using the existing gpg key. If you don't have a key already there are lots of resources available if you just search around a bit. Here is one.

Once the private key was created I removed the original gpg keypair for that user off the system, storing it some place safe, and replaced it with the subkey's files I just created. This allows us to revoke the subkey if it or the server is compromised. Then we can generate a new subkey using the original key and the consumers of the repository won't need to change their installed key.

Next, we need to lay down a two things. A directory and a config file. In my example the directory will be the root of your domain on a web server. I'm not going to go into how to setup a web server here.

mkdir -p /srv/

The config file is for apt-ftparchive to generate the Releases file. Following the instructions from Automatic Debian Package Repository HOWTO we generate /srv/

APT::FTPArchive::Release::Origin "Your name or organization";
APT::FTPArchive::Release::Label "Descriptive label";
APT::FTPArchive::Release::Suite "stable";
APT::FTPArchive::Release::Codename "debian";
APT::FTPArchive::Release::Architectures "noarch";
APT::FTPArchive::Release::Components "main";
APT::FTPArchive::Release::Description "More detailed description";

In my repository we only have noarch packages. No source packages, no architecture specific. Adjust that field as is necessary for your repository. We also only have one component section. I don't even think it ends up being relevant to this simple of a repository, the same as Suite. What matters is that the codename matches your directory. I used debian for both, because ours is not specific to a Debian release such as "squeeze" or "wheezy".

With these files in place I created the following script to build the repository, and saved it as /usr/local/bin/

## Assign basic configuration
GPGKEY=<insert id of the subkey here just to be specific>
## Do the actual work
rm -f ${TARGET}/Release.gpg
rsync -a ${SOURCE}/*.deb ${TARGET}/
dpkg-scanpackages --multiversion ${TARGET} /dev/null > ${TARGET}/Packages
gzip -9c ${TARGET}/Packages > ${TARGET}/Packages.gz
apt-ftparchive -c=${TARGET}/Release.conf release ${TARGET} > ${TARGET}/Release
gpg -a --homedir ${GPGDIR} --default-key ${GPGKEY} -o ${TARGET}/Release.gpg ${TARGET}/Release

This script assumes that the packages are being uploaded to the RELEASES directory of the user bob.

I create both Packages and Packages.gz because i didn't like seeing the Ign message when I did an apt-get update. You could consolidate it down to the one line and have just a single file.

Now for the fun part. With Incron you can configure the execution of a command/script based on filesystem events. Since packages are being pushed into /home/bob/RELEASES we want to monitor that directory. We have a large number of file event types we could trigger from, as can be seen under inotify events of this man page. For our purposes IN_CREATE was what I used. I need to verify as IN_CLOSE_WRITE might be better for larger files. To configure this I run:

incrontab -e

Then added the following entry:

/home/bob/RELEASES IN_CREATE /usr/local/bin/

That's it! Now if you push a deb package into /home/bob/RELEASES the repository will get generated. The resulting repository can be accessed by configuring the following source on a client system.

deb debian/

Extra credit: add your public key to the /srv/ directory so that users can add it to their local apt-key ring, which will otherwise complain.

wget -O - | sudo apt-key add -