I’m in the process of creating an Ansible playbook for configuring and maintaining all the infrastructure hosted on my server. The main benefits of using Ansible for my use-case instead of just running commands directly is that it allows for modular, self-documenting modules and it’s easy to use with version control. There are a lot of programs required by the server (stagit-hook, gitman, autocert, etc.) that aren’t officially packaged by OpenBSD, so that leads to two choices: Either automate the build and install in Ansible (effectively writing package manager in Ansible), or package the projects for OpenBSD and install them normally. While either solution would work, the latter makes use of existing systems instead of introducing new ones and is less error-prone.

The basic process of packaging software on OpenBSD is described below.

The OpenBSD Ports System

On OpenBSD, packages are built from the ports tree, which is a Makefile based infrastructure for automatically fetching, patching, and packaging third-party applications. I won’t go into too much detail, but see ports(7) and the handbook for more information.

Creating Ports and Packages

The first step is fetching the ports tree using CVS, the tarball, or the git mirror. You can either put new ports in existing subdirectories (sysutils, net, etc.) or create a new directory specifically for internal ports. For these examples I’ll create the internal subdirectory and add it as a SUBDIR in the root Makefile. This keeps internal packages distinct and allows tracking changes in git without forking the whole ports tree.

After that it’s only a matter of adding a port by creating a Makefile with all the information required to fetch and build the software, then adding a description and plist(5). It can be helpful to reference other ports initially; here’s an example Makefile for an interpreted tool:

COMMENT=        acme-client post-update hooks

V=              0.1.0

NAME=   autocert
DISTNAME=       ${NAME}-${V}

SITES=  https://dist.jacobedwards.org/projects/
EXTRACT_SUFX=   .tar.gz

CATEGORIES=     internal

HOMEPAGE=       https://jacobedwards.org/projects/${NAME}
MAINTAINER=     Jacob Edwards <jacob@jacobedwards.org>

# BSD ISC
PERMIT_PACKAGE= Yes

NO_TEST=        Yes

# for interpreted scripts
NO_BUILD=       Yes  

.include <bsd.port.mk>

The do-install target can be used if the default behavior described in bsd.port.mk(5) doesn’t properly install the application to DESTDIR.

Once the port is ready, run the makesum target to generate the port’s distinfo file containing its size and checksum. After that’s done simply run the package target to build a package suitable for use with pkg_add(1). It should generate output that looks like this:

$ make package
[...]
Create /usr/ports/packages/amd64/all/autocert-0.1.0.tgz
Creating package autocert-0.1.0
Link to /usr/ports/packages/amd64/ftp/autocert-0.1.0.tgz

When building more than a few ports, using dpb(1) is the preferred method.

Signing and Distributing Packages

After all the packages are built, use signify(1) and pkg_sign(1) to sign the packages and prepare to distribute them over HTTPS:1

$ # Generate a signify key pair
$ # (to encrypt the secret key, don't use -n)
$ signify -Gnc 'Internal packages' \
    -p /etc/signify/internal-pkg.pub \
    -s /etc/signify/internal-pkg.sec

$ # Create signed packages for distribution
$ pkg_sign -Ci \
    -s signify2 -s /etc/signify/internal-pkg.sec \
    -o /var/www/htdocs/dist.example.com \
    -S /usr/ports/packages/$(uname -m)/all

To serve packages in the previous example, add something like the example below to your httpd.conf(5) and reload httpd.

server "dist.example.com" {
        listen on * tls port 443
        tls {
                certificate "/etc/ssl/example.com.pem"
                key "/etc/ssl/private/example.com.key"
        }
        directory auto index
        root "/htdocs/dist.example.com"
}

Installing Internal Packages

All that’s left to do once the packages are being served is to add the public key to /etc/signify locally and set PKG_PATH as described in pkg_add(1):

Each package name may be specified as a filename (which normally consists of the package name itself plus the “.tgz” suffix) or a URL referring to FTP, HTTP, HTTPS, or SCP locations. If the given package names are not found in the current working directory, pkg_add will search for them in each directory (local or remote) named by the TRUSTED_PKG_PATH environment variable, then the PKG_PATH environment variable. The special url ‘installpath’ refers to the contents of installurl(5). If neither TRUSTED_PKG_PATH nor PKG_PATH are defined, pkg_add will use ‘./:installpath’ as a default.

Once this is done, internal packages can be installed like any other:2

# PKG_PATH=https://dist.example.com/:installpath \
      pkg_add internal_package

  1. pkg_add(1) supports HTTP, HTTPS, FTP, and SFTP. ↩︎

  2. Note that while these examples were kept simple for brevity, it’s a good idea to separate packages by release and architecture. The official package mirrors use /pub/OpenBSD/$(uname -r)/packages/$(uname -m)/ (or %m in pkg_add syntax). ↩︎