Packaging Software for OpenBSD
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_PATHenvironment variable, then thePKG_PATHenvironment variable. The special url ‘installpath’ refers to the contents of installurl(5). If neitherTRUSTED_PKG_PATHnorPKG_PATHare 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