This page looks best with JavaScript enabled

Poudriere: Binary package server on FreeBSD

 ·  🎃 kr0m

We all know the advantages of using custom compiled binaries:

  • Lower RAM consumption
  • Smaller binaries
  • More modern software versions
  • Lower attack surface since unnecessary binary functionalities have been removed

The only drawback is the time required for compilation, but what if we had a more powerful computer to perform the compilation and use these binaries on our server?

Poudriere precisely takes care of this task, compiling custom binaries from ports to be used by other computers.

All the information in this article has been collected from the following sources:


To be able to generate binaries using Poudriere, we will have to install Portmaster, Nginx, and ccache:

pkg install poudriere portmaster nginx ccache

When we build packages with Poudriere, we will sign them with a private key, thus ensuring that no one can impersonate our package server. We create the necessary directories:

mkdir -p /usr/local/etc/ssl/{keys,certs}
chmod 0600 /usr/local/etc/ssl/keys

We generate the private key:

openssl genrsa -out /usr/local/etc/ssl/keys/poudriere.key 4096

We generate the certificate that will be included in the package signature:

openssl rsa -in /usr/local/etc/ssl/keys/poudriere.key -pubout -out /usr/local/etc/ssl/certs/poudriere.cert

If our server does not support ZFS:

vi /usr/local/etc/poudriere.conf

NO_ZFS=yes

Otherwise, we indicate the pool and the rootfs:

ZPOOL=zroot  
NO_ZFS=no  
ZROOTFS=/poudriere

Poudriere mounts a jail where it will compile the ports, with the FREEBSD_HOST parameter we indicate where it should download the jail image from:

FREEBSD_HOST=ftp://ftp.freebsd.org

We indicate where it should generate the necessary files for its operation:

POUDRIERE_DATA=${BASEFS}/data

We indicate if we want to use RAM as a file system, several levels of use can be defined, in my case I have 32Gb of RAM so I leave it configured as all:

USE_TMPFS=all  

CHECK_CHANGED_OPTIONS indicates whether packages should be recompiled if the compilation options have changed. CHECK_CHANGED_DEPS indicates whether packages should be recompiled if the dependencies have changed since the last compilation.

CHECK_CHANGED_OPTIONS=verbose  
CHECK_CHANGED_DEPS=yes

We tell Poudriere where the key is that it should use to sign the packages:

PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key 

The directory where to save the cached object files from previous compilations:

CCACHE_DIR=/var/cache/ccache

We allow each job to use all available cores in the system:
ALLOW_MAKE_JOBS=yes

Finally, we indicate the URL with which clients will have access to the packages:

URL_BASE=http://poudriere.alfaexploit.com/

We create the object caching directory and the downloaded file caching directory:

mkdir -p /var/cache/ccache
mkdir /usr/ports/distfiles

My entire configuration looks like this:

egrep -v '^($|[[:space:]]*#|;)' /usr/local/etc/poudriere.conf

ZPOOL=zroot
NO_ZFS=no
ZROOTFS=/poudriere
FREEBSD_HOST=ftp://ftp.freebsd.org
RESOLV_CONF=/etc/resolv.conf
BASEFS=/usr/local/poudriere
POUDRIERE_DATA=${BASEFS}/data
USE_PORTLINT=no
USE_TMPFS=all
DISTFILES_CACHE=/usr/ports/distfiles
CHECK_CHANGED_OPTIONS=verbose
CHECK_CHANGED_DEPS=yes
PKG_REPO_SIGNING_KEY=/usr/local/etc/ssl/keys/poudriere.key
CCACHE_DIR=/var/cache/ccache
ALLOW_MAKE_JOBS=yes
URL_BASE=http://poudriere.alfaexploit.com/

If we consider that Poudriere is consuming too many resources, we can limit the number of PARALLEL_JOBS, modify the ALLOW_MAKE_JOBS parameter, or reconfigure the USE_TMPFS parameter.

For example, we could choose to compile 8 ports, with each port using 1 core:

PARALLEL_JOBS=8
ALLOW_MAKE_JOBS=no

Or compile 1 port and use all the cores of the system:

PARALLEL_JOBS=1
ALLOW_MAKE_JOBS=yes

Now that we have Poudriere configured, we need to set up our compilation environment. As previously indicated, Poudriere will set up a jail for this purpose, and the only requirement is that the jail must be the same version or lower than the parent system.

We create the jail by specifying the name and version:

poudriere jail -c -j freebsd_12-1x64 -v 12.1-RELEASE

NOTE: It is not recommended to use “.” in the jail name as it can cause problems with certain tools.

poudriere jail -l

JAILNAME        VERSION         ARCH  METHOD TIMESTAMP           PATH
freebsd_12-1x64 12.1-RELEASE-p5 amd64 ftp    2020-06-05 20:24:50 /usr/local/poudriere/jails/freebsd_12-1x64

We install the ports tree, and later we will tell the jail to use it:

poudriere ports -c -p latest -m git+https -B main

NOTE: We can have several jails with different OS versions and versions of the ports.

poudriere ports -l

PORTSTREE METHOD    TIMESTAMP            PATH  
latest    git+https 2020-06-05 21:05:18 /usr/local/poudriere/ports/latest

We generate the list of ports to compile. The best way to do this is to get the list of packages currently installed on the servers that will use our repository. Before generating the list, we do a little cleaning to make the list as small as possible.

pkg autoremove

We get the list of packages we installed explicitly without dependencies:

pkg prime-origins > /root/port-list

We copy the list to the Poudriere server.

scp SERVER:/root/port-list /usr/local/etc/poudriere.d/port-list

This process must be done on all servers, and the list must be unified on the compilation server.

If necessary, we can add additional ports to the list:

vi /usr/local/etc/poudriere.d/port-list

NOTE: Don’t worry about dependencies since they will be compiled automatically.

If we want to parameterize the make.conf, we must indicate it. In my case, I’m interested in compiling everything related to PHP with version 7.4 and binaries without X or ipv6 support:

vi /usr/local/etc/poudriere.d/freebsd_12-1x64-make.conf

OPTIONS_UNSET+= X11 IPV6  
DEFAULT_VERSIONS+= php=7.4

We tell Poudriere which options to use to compile the ports. The configuration dialog will only appear if it has not been previously configured in the /usr/local/etc/poudriere.d/freebsd_12-1x64-latest-options/PORTNAME/options file. Once the configuration is done, it will not ask again in subsequent compilations. It is important to choose the minimum options to have fewer dependencies and faster compilation.

poudriere options -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

The Poudriere web interface shows us why each port is being compiled. We can know who each of them depends on and thus find out incorrect configuration parameters.

If later we want to be asked again for the compilation options, we will execute the same command but with the -c option:

poudriere options -c -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

If we only want to reconfigure the options for one of the ports, the easiest way is to delete the associated configuration:

rm /usr/local/etc/poudriere.d/freebsd_12-1x64-latest-options/www_nginx/options
poudriere options -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

NOTE: If we have doubts about the correct parameters, we can always consult them on another server where the binary is installed from the FreeBSD repositories. For example, for php7.4: pkg info php74

Finally, we can start building ports, but first, we make sure to have the jail and the port tree updated.

poudriere jail -u -j freebsd_12-1x64
poudriere ports -u -p latest

We start the compilation in a screen session:

screen -S PoudriereCompilation
poudriere bulk -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

We get information about the compilation process:

CTRL-t

load: 6.80  cmd: sh 96156 [nanslp] 669.81r 0.21u 0.84s 0% 3852k  
[freebsd_12-1x64-latest] [2020-06-05_20h46m50s] [parallel_build:] Queued: 325 Built: 59  Failed: 0   Skipped: 0   Ignored: 0   Tobuild: 266  Time: 00:11:10  
 [01]: lang/tcl86                | tcl86-8.6.10              fetch           (00:00:17 / 00:00:19)  
 [02]: devel/nspr                | nspr-4.25                 package         (00:00:01 / 00:00:12)  
 [03]: lang/perl5.30             | perl5-5.30.3              build           (00:02:45 / 00:03:26)  
 [04]: databases/db5             | db5-5.3.28_7              build           (00:00:28 / 00:00:39)  
 [05]: dns/libidn2               | libidn2-2.3.0_1           fetch           (00:00:02 / 00:00:04)  
 [06]: devel/swig30              | swig30-3.0.12_1           configure       (00:00:02 / 00:00:26)  
 [07]: graphics/giflib           | giflib-5.2.1              fetch           (00:00:51 / 00:00:52)  
 [08]: devel/gettext-tools       | gettext-tools-0.20.2      build           (00:01:36 / 00:02:09)

Now that we have the binaries compiled, we just need to serve them through Nginx, which will also serve to consult the Poudriere compilation statistics.

We enable the service at startup:

sysrc nginx_enable="yes"

Through the Nginx configuration, we will have access to the Poudriere interface (/), logs (/data), and packages (/packages).

vi /usr/local/etc/nginx/nginx.conf

#http context
. . .
    server {
        listen 80 default;
        server_name poudriere.alfaexploit.com;
        root /usr/local/share/poudriere/html;

        location /data {
            alias /usr/local/poudriere/data/logs/bulk;
            autoindex on;
        }

        location /packages {
            root /usr/local/poudriere/data;
            autoindex on;
        }
    }
}

To be able to view the logs directly from the web interface and not force us to download the file, we will have to modify the Nginx mime.types file.

vi /usr/local/etc/nginx/mime.types

text/plain                          txt log;

We check the configuration:

service nginx configtest

Performing sanity check on nginx configuration:  
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok  
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

We start Nginx:

service nginx start

If we have any configured fw, we adapt it, and we can already access the Poudriere web interface.
http://poudriere.alfaexploit.com


If we want to see the packages, we must access /packages:
http://poudriere.alfaexploit.com/packages

Only the client configuration remains, just as it is not recommended to mix binary packages with ports compiled by the different compilation options they may have, neither is it recommended to mix packages from the official FreeBSD repositories with our own Poudriere repositories. To exclusively use our repository, we will only have to omit the priority configuration parameter and disable the official ones.

mkdir -p /usr/local/etc/pkg/repos

vi /usr/local/etc/pkg/repos/poudriere.conf

poudriere: {  
    url: "http://poudriere.alfaexploit.com/packages/freebsd_12-1x64-latest/",  
    mirror_type: "http",  
    signature_type: "pubkey",  
    pubkey: "/usr/local/etc/ssl/certs/poudriere.cert",  
    enabled: yes,  
}
vi /usr/local/etc/pkg/repos/freebsd.conf
FreeBSD: {  
    enabled: no  
}

We dump the content of the poudriere.cert pubkey into the client configuration.

ssh POUDRIERE_SERVER
cat /usr/local/etc/ssl/certs/poudriere.cert

On the client:
If the poudriere server is a server on our LAN, we modify the client’s /etc/hosts. If it is a public server, this step is unnecessary.

vi /etc/hosts
192.168.69.4		poudriere.alfaexploit.com

mkdir -p /usr/local/etc/ssl/{keys,certs}

vi /usr/local/etc/ssl/certs/poudriere.cert

We manually check that the client can access the packages:

<html>  
<head><title>Index of /packages/</title></head>  
<body>  
<h1>Index of /packages/</h1><hr><pre><a href="../">../</a>  
<a href="freebsd_12-1x64-latest/">freebsd_12-1x64-latest/</a>                              05-Jun-2020 18:46                   -  
</pre><hr></body>  
</html>

We force the reinstallation of all packages, but this time from our repository. This way, we make sure that no package from the FreeBSD repositories is left:

pkg upgrade -f

Now we can install packages from our binary package server ☺️


Every time we want to update our servers, we must first compile the latest binaries in Poudriere. The steps to follow are as follows.

We update the build jail:

poudriere jail -u -j freebsd_12-1x64

We update the port tree:

poudriere ports -u -p latest

We compile:

screen -S PoudriereCompilation
poudriere options -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list
poudriere bulk -r -t -J 1:8 -j freebsd_13-0x64 -p latest lang/rust lang/gcc10
poudriere bulk -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list
poudriere pkgclean -y -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

NOTE: Some ports such as lang/rust or lang/gcc may have problems with multi-threaded compilations. For this reason, they are compiled with a separate command indicating that only one core is used.

The “poudriere pkgclean” command will remove old packages that are no longer listed in /usr/local/etc/poudriere.d/port-list. If we want to regenerate all packages, we can delete them with the following command:

poudriere pkgclean -A -j freebsd_12-1x64 -p latest

We can see the build progress on the web interface:
http://poudriere.alfaexploit.com

We update the servers with:

pkg upgrade

For the laziest, here’s a quick script:

vi updatePoudriere.sh

#!/usr/local/bin/bash
poudriere jail -u -j freebsd_13-0x64
poudriere ports -u -p latest
poudriere options -j freebsd_13-0x64 -p latest -f /usr/local/etc/poudriere.d/port-list

# Compile lang/rust devel/llvm and lang/gcc using only 1 core because these ports frecuently crash with parallel compilation:
# man poudriere-bulk
poudriere bulk -r -t -J 1:8 -j freebsd_13-0x64 -p latest lang/rust lang/gcc10 

poudriere bulk -j freebsd_13-0x64 -p latest -f /usr/local/etc/poudriere.d/port-list
poudriere pkgclean -y -j freebsd_13-0x64 -p latest -f /usr/local/etc/poudriere.d/port-list

# poudriere distclean -a -p latest
# poudriere logclean -a
# poudriere pkgclean -A -j freebsd_13-0x64 -p latest
chmod 700 updatePoudriere.sh

UPDATE

If we switch from one version of FreeBSD to another, there will be old jails left. To remove them, we will proceed as follows:

poudriere jail -d -j freebsd_12-1x64
poudriere distclean -a -p latest
poudriere logclean -a -j freebsd_12-1x64 -p latest
poudriere pkgclean -A -j freebsd_12-1x64 -p latest

rm -rf /usr/local/poudriere/data/packages/freebsd_12-2x64-latest/
rm -rf /usr/local/etc/poudriere.d/freebsd_12-2x64-latest-options
rm /usr/local/etc/poudriere.d/freebsd_12-2x64-make.conf
vi /usr/local/poudriere/data/logs/bulk/.data.json


TROUBLESHOOTING

If we have problems compiling, we can try:

Creating the jail again and a new ports tree.

poudriere jail -d -j freebsd_12-1x64
poudriere ports -d -p latest
poudriere jail -c -j freebsd_12-1x64 -v 12.1-RELEASE
poudriere ports -c -p latest

Deleting the cache:

rm -rf /var/cache/ccache/*

Manually adding packages to the list. To know the name of the port, we must first search the ports system:

cd /usr/ports
make fetchindex
make search name=NAME

Now that we know the name of the port, we can add it to the list:

vi /usr/local/etc/poudriere.d/port-list

CATEGORY/NAME

We can also delete previously compiled ports and recompile them, with the time it takes:

poudriere bulk -c -j freebsd_12-1x64 -p latest -f /usr/local/etc/poudriere.d/port-list

If we see that a port is causing problems, as is the case with rust.
dmesg:

pid 17034 (rustc), jid 18, uid 0, was killed: out of swap space

Poudriere logs:

RuntimeError: failed to run: /wrkdirs/usr/ports/lang/rust/work/rustc-1.53.0-src/build/bootstrap/debug/bootstrap build –jobs=8

We will try to compile the port before the rest of the ports but with only one core:

poudriere bulk -r -t -J 1:8 -j freebsd_13-0x64 -p latest lang/rust

If you liked the article, you can treat me to a RedBull here