In the past few days I looked into a proof of concept for a IPFS-hosted F-Droid repository. The background for this experiment was the idea, to get rid of more manual upgrades that my phone is currently plagued with, for example for Signal, where one has to manually approve updates, while f-droid, thanks to the privileged extension, can just install updates in background and doesn’t need to bother me about it.
While the overall experiment wasn’t as successful as I might wished, due to some other issues, the result is still worth writing about, because a complete toolchain for F-Droid repositories on IPFS were created, with the ability to create a basic F-Droid repository, hosted on your local machine, available to the internet, through public IPFS gateways.
The F-Droid-CLI hassle
The first goal was obviously to create and F-Droid repository. For that purpose, the F-Droid developers have created the
fdroid-CLI. As it turns out, a non-trivial task. While F-Droid provides an official guide which includes a container-based setup, this container doesn’t include the Android-SDK. However, in order to initialise the repository, the Android-SDK is apparently mandatory. The official recommendation is to mount the Android-SDK as volume into the container.
Long story short, the official container didn’t cut it for me, as I wasn’t about to install a full Android-SDK locally. As a result, the
fdroid-CLI was created as an own command in my “isolated development tools”-project. It’s a simple shell script, that builds a
fdroid container, using the official Debian packages for the
fdroid-CLI, hotfixes the
apksigner-binary, which fails to execute and then runs the
fdroid-CLI limited to the current working directory, so whatever happens no one steals your SSH keys. Of course, all of it in rootless podman containers and a little bit opinionated for my setup.1
After spending an entire evening on these issues, all it took were some adjustments, like the repository name and description, in the
config.py which is created by the
fdroid init command and the basic repository was set up.
Filling the repository
The next step was to get an APK to publish. For the simplicity reasons, I used the Signal APK as an example app, as it’s easy to get from their website, and not already on F-Droid. Throwing it into the
./repo directory using
wget got me set up there.2 Using the
fdroid update --use-date-from-apk --rename-apks-command all the needed meta-information were set up and another
fdroid update-command later, which is probably superficial, things were ready to deploy.
Sadly the tutorial I followed ended here. And given the sensitive nature of configuration files like the
config.py, containing passwords, and the
keystore.jks, containing the signature keys, I didn’t feel comfortable to push things anywhere. However, the official documentation could help me out and educated me about the
fdroid deploy command, which pushes the content onto a remote host or a directory. The latter was interesting to me. Creating a new directory
./deploy and configuring it as
serverwebroot in the
config.py allowed me to use
fdroid deploy -v to create the content intended to be published.
Adding IPFS into the mix
In order to publish the repository I always wanted to use IPFS. IPFS is a “peer-to-peer hypermedia protocol”. It basically allows to publish content in a content-addressable fashion onto a P2P network. Given the immutability of the content, it’s possible for each node to cache content indefinitely, which should make it trivial to set up mirrors by “pinning” the entire directory-tree or single CIDs. It also allowed to use IPFS gateways to publish content without the requirement to host an internet facing server. All-in-all very interesting for public software repositories.
In order to achieve this, one has to setup a local IPFS daemon, using the command
ipfs daemon, and “upload” the content to this local daemon using
ipfs add ./deploy. This results in a so-called CID, Content identifier,3 which is a hash of the directory object and recursively all objects within the directory, that can be used to access the repository.
However, since CIDs change every time the content of the repository changes, it’s not super useful for publishing. In order to get a static identifier for CIDs that can change over time, IPFS has IPNS. This provides a cryptographic keypair, that is used to sign a reference to a reference and publish it to peer-to-peer network. In this case, it’s the current CID for my repository. This is done by using
ìpfs name publish /ipfs/<CID>, later on
ipfs name publish --key <key id> /ipfs/<CID>. As a result, a public key as provided, that I can use to access the repository using a public gateway like
gateway.ipfs.io in form of a URL like this:
To make it a bit more accessible, IPFS has a concept called
dnslink. DNSlinks allow you to add a TXT record to an DNS entry which contains
dnslink=<IPFS reference>. Such a reference can be
/ipns/<key id>. So I created a DNSlink DNS record similar to
_dnslink.f-droid.example.com with the content
dnslink=/ipns/<key id>. As a result I can open
https://gateway.ipfs.io/ipns/f-droid.example.com and reach my repository.
The new publicly available URL now just has to be set as
repo_url. Additionally one can add further public IPFS gateways, like
mirrors in the
Beautification of the repository
Having an F-Droid repository is all sweet, but one has to add it to their phone in order to have any value out of it. Many F-Droid repositories have a landing page, allowing to add it by scanning a QR code or clicking on a link. For that I adjusted the
index.html-page, that Bitwarden uses for their repository and created a template from it, that can filled by using
Additionally I created a QR code using
qrencode -o deploy/qr.png <repository URL>, so for example
qrencode -o deploy/qr.png https://gateway.ipfs.io/ipns/f-droid.example.com.
Setup on the smartphone and a little bit of disappointment
With all this done, it was time to test things on the smartphone. Therefore opening the URL using
https://gateway.ipfs.io/ipns/f-droid.example.com in the browser, scanning the QR code and adding the pre-filled repository. First impressions: It’s very slow. This might be due to the IPFS gateways being overloaded, but more-likely it’s because the gateways have to fetch the date through my upload, before allowing to download it again.4 However, the good news is, once the repository is fetched, it works!
The other disappointment was that the Signal app, was lacking a lot of metadata including the build-date. Resulting in F-droid thinking that the app is 40 years old and doesn’t have a version number. So while the repository set up was successful, the content it was filled with, was quite bad.
I still decided to automate the setup.5 If I already took the time to figure all that out and run things over and over again, I decided to automate a few steps on the way and that’s how “F-Droid IPFS tools”6 were born. It’s a collection of Makefiles that allow to set up the entire repository described above, except of the repository settings in
config.py and, of course, the DNS link. Everything else takes just a
make publish and will magically be set up for you.
The magic itself can be found in the
_makefiles directory and thanks to the article about “Self-documenting Makefiles”
make help should also tell you all you need to know in order to use the tools.
All things considered, it was a fun project and maybe the F-droid community get something out of it. In first place it was a fun exercise for me, and finally a project where I could put IPFS in a useful context and play around with it, as until now, it was always something I wanted to do and never got done.
I think there is some potential here, to allow easier mirroring and making repository content available to people everywhere, independent on their connection to upstream servers. As long as they can reach an IPFS gateway, they should be able to pull updates. In a world with growing geoblocking, this might be an important ability.
And I also learned that some F-droid documentation needs some more details in places regarding keys, as well as providing a container that is actually plug & play and doesn’t require to figure out the Android-SDK locally.
At the end of the day, it was a lot of fun, and if you learned something from it, it’s already worth it.
However, since everything is built without strong dependencies, it should also work with your locally installed
fdroid-CLI as long as the overall commands stay the same ↩
Note: this was a playground setup, if you ever do that, validate the keys as described on Signal’s website. ↩
A concept you might know from Git, where these are commit IDs (technically speaking directory objects, but let’s not get ahead of ourselves). ↩
It might also be that IPFS in general is not super quick in responding, but I have no data to argue for or against that. ↩
Which is also the reason, this is a story and not a tutorial. ↩
Name is still subject to change. ↩