Custom Repositories in MacPorts

In order to distribute custom tools among friends but also to some degree at work I’ve created a custom tap in Homebrew. This is pretty straight forward as it only requires a Git repository where the audience can access it. Does MacPorts have something similar?

While MacPorts focuses heavily on the main port tree you can specify multiple sources for that tree. You do that by editing /opt/local/etc/macports/sources.conf:

rsync:// [default]

Any custom source should be added before the “default” entry in there as MacPorts will go through each source in the order they are specified here. In order to demonstrate this feature, I’ve created a custom Portfile for exa which we are now going to distribute through a custom source.

Signed archives

While MacPorts doesn’t strictly require the ports tree to be distributed as archive, this is the only way where it also enforces the use of signatures. Since that road is only a bit longer, let’s take that extra step here.

In order to sign anything, we first need a key-pair (consisting of a private and public key).

# Generate the private key
$ openssl genrsa -des3 -out privkey.pem 2048

# Derive the public key
$ openssl rsa -in privkey.pem -pubout -out pubkey.pem

I’ve then placed the public key file (pubkey.pem) inside /Users/zerok/src/ Next, MacPorts has to know that the public key can be used to verify artefacts. This is done by adding it to the /opt/local/etc/macports/pubkeys.conf:

# MacPorts system-wide public key configuration file.


A new source

Now that MacPorts knows how it can verify our new source, we need to create an archive to distribute. For my exa Portfile I’ve created the following repository:

This contains the following files:

$ exa -l --tree
drwxr-xr-x    - zerok  4 May 10:05 .
.rw-r--r--  562 zerok  4 May  9:59 ├── Makefile
.rw-r--r-- 1.8k zerok  4 May  9:32 ├── privkey.pem
.rw-r--r--  451 zerok  4 May  9:32 ├── pubkey.pem
.rw-r--r--  566 zerok  4 May 10:01 ├── pubkey.pem.sig
.rw-r--r--  316 zerok  4 May 10:01 ├──
drwxr-xr-x    - zerok  4 May  9:26 └── sysutils
drwxr-xr-x    - zerok  4 May  9:26    └── exa
.rw-r--r-- 1.2k zerok  4 May  9:26       └── Portfile

The Makefile is mostly there to automate the creation of the ports-tree archive and its signing:

all: dist/ports.tar.bz2 dist/ports.tar.bz2.rmd160

	mkdir -p dist

dist/ports.tar.bz2: dist
	mkdir dist/ports && \
	cp -R sysutils dist/ports/ && \
	cd dist && tar -cjvf ports.tar.bz2 ports && \
	rm -rf ports

	openssl dgst -ripemd160 -sign privkey.pem -out dist/ports.tar.bz2.rmd160 dist/ports.tar.bz2.rmd160

	rm -rf dist

	rsync -avz dist/* && \
	rsync -avz pubkey.*

.PHONY: all clean release

Running make clean all release now generates an archive, signs it, and uploads it including the public key (and its signature) to

Hurray, we now have a ports archive that we can finally use. Finally, update the sources.conf inside /opt/local/etc/macports/ to look like this:
rsync:// [default]

This tells MacPorts that it should look first inside the source for a port before falling back to the default one.

# Download the sources:
$ sudo port sync

# Show the details of the new exa port:
$ port info exa
exa @0.8.0 (sysutils)

Description:          exa is a replacement for ls with additional features like color-coding for file types, Git integration, and extended attributes.

Extract Dependencies: unzip
Platforms:            darwin
License:              MIT
Maintainers:          Email:

The good and the not so good

While MacPorts approach for offering multiple sources of packages is a bit more complicated to set up than Homebrew’s, I really like that archives have to be signed (unless you just expose a folder somewhere). Personally, I would have preferred GnuPG signatures over openssl/rmd160 simply because they are a bit more modern and include more meta-data about the owner of the signature. At least for the distribution of the source’s public key I’d still opt to sign that with GnuPG which is why there is also a pubkey.pem.sig in my example repository.

I’m relatively new to MacPorts so I don’t know if what I’ve described here even remotely follows best practices. You’ve been warned 😅

You can also find this post linked to from the following pages: