<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title>enge@inria</title><id>https://enge.math.u-bordeaux.fr/guix.xml</id><subtitle>Recent Posts</subtitle><updated>2026-08-26T13:50:31Z</updated><link href="https://enge.math.u-bordeaux.fr/guix.xml" rel="self" /><link href="https://enge.math.u-bordeaux.fr" /><entry><title>Tiny Build Farm for Guix, part 2</title><id>https://enge.math.u-bordeaux.fr/blog/tbfg-2.html</id><author><name>Andreas Enge</name><email>andreas.enge@inria.fr</email></author><updated>2025-10-24T00:00:00Z</updated><link href="https://enge.math.u-bordeaux.fr/blog/tbfg-2.html" rel="alternate" /><content type="html">&lt;div&gt;

&lt;h1&gt;Building science packages&lt;/h1&gt;

&lt;p&gt;
In our efforts to create a Tiny Build Farm for Guix, that is supposed
to report on the status of the packages assigned to the science team,
so far we have seen how to
&lt;a href=&quot;tbfg-1.html&quot;&gt;set up&lt;/a&gt;
the required infrastructure.
On a dedicated machine with Guix as its operating system, we have added
several Shepherd services:
the Guix Build Coordinator together with a build agent;
and the web server part of the BFFE, which enables us to follow the
activity of the builders.
For performance reasons, we have renounced at installing an instance of the
Guix Data Service, and opt instead for talking to the instance operated
by the Guix project at &lt;code&gt;https://data.guix.gnu.org/&lt;/code&gt;, which
continually evaluates the Guix master branch and creates derivations for
all packages in the distribution.
The next step is to explore how to programmatically talk to the remote
data server from a Guile script, how to extract derivations we are
interested in, and how to submit them for building to our instance of the
build coordinator.
&lt;/p&gt;


&lt;h2&gt;Getting information from the data service&lt;/h2&gt;

&lt;p&gt;
We need to install the two packages
&lt;code&gt;guix-data-service&lt;/code&gt; and (for later use)
&lt;code&gt;guix-build-coordinator&lt;/code&gt; on the TBFG machine, which contain
Guile libraries with the necessary functionality.
&lt;/p&gt;
&lt;p&gt;
⚠ If installed into a user profile, both packages pull in the
&lt;code&gt;guix&lt;/code&gt; package as a propagated input, which prevents the user
from updating it through &lt;code&gt;guix pull&lt;/code&gt;.
It is thus recommended to run
&lt;/p&gt;
&lt;pre&gt;
guix shell guile-next guix-data-service guix-build-coordinator
&lt;/pre&gt;
&lt;p&gt;
instead. At the time of writing, the &lt;code&gt;guile&lt;/code&gt; package in Guix
is at version 3.0.9, while the data service library requires
&lt;code&gt;guile-next&lt;/code&gt;, which is at version 3.0.10.
&lt;/p&gt;
&lt;p&gt;
Let us open a Guile REPL and execute the following code
(to ease copy-pasting, I omit the prompt of the REPL;
lines starting with a $ sign and a number correspond to results).
&lt;/p&gt;
&lt;pre&gt;
$ guile
(use-modules (guix-data-service client))
(define my-data-service &amp;quot;https://data.guix.gnu.org/&amp;quot;)

(define json
  (guix-data-service-request my-data-service
                             &amp;quot;repository/1/branch/master.json&amp;quot;))
json
$1 = ((&amp;quot;revisions&amp;quot; . #(((&amp;quot;data_available&amp;quot; . #f) (&amp;quot;commit-hash&amp;quot; . &amp;quot;cb47639a8081e8e2d651ad1612bbd1e482766469&amp;quot;) …
&lt;/pre&gt;
&lt;p&gt;
The call to &lt;code&gt;guix-data-service-request&lt;/code&gt;
is equivalent to opening the URL
&lt;a href=&quot;https://data.guix.gnu.org/repository/1/branch/master.json&quot;&gt;&lt;code&gt;https://data.guix.gnu.org/repository/1/branch/master.json&lt;/code&gt;&lt;/a&gt;,
which executes the same query as the URL
&lt;a href=&quot;https://data.guix.gnu.org/repository/1/branch/master&quot;&gt;&lt;code&gt;https://data.guix.gnu.org/repository/1/branch/master&lt;/code&gt;&lt;/a&gt;
without the &lt;code&gt;.json&lt;/code&gt; at the end, but it returns the result in
&lt;a href=&quot;https://en.wikipedia.org/wiki/JSON#Data_types&quot;&gt;JSON&lt;/a&gt; format.
Moreover, the function call transforms the JSON into a Guile data structure
through the
&lt;a href=&quot;https://github.com/aconchillo/guile-json&quot;&gt;guile-json&lt;/a&gt;
library; in particular, JSON arrays become Guile
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/guile.html#Vectors&quot;&gt;vectors&lt;/a&gt;
and JSON objects become Guile
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/guile.html#Association-Lists&quot;&gt;association
lists&lt;/a&gt;, or &lt;i&gt;alists&lt;/i&gt; for short (these are lists of key-value pairs,
so brace yourself for lots of parentheses in a row).
Thus parsing the result and extracting the information we are interested in
amounts to unwrapping these successive layers; in true Scheme/Lisp style
we will also usually transform the vectors into lists using the
&lt;code&gt;vector-&amp;gt;list&lt;/code&gt; function.
The JSON we asked for is an object with a unique field
&lt;code&gt;revisions&lt;/code&gt;, which contains an array of revisions, that is,
git commits on the master branch;
every revision is an object with the three fields
&lt;code&gt;date&lt;/code&gt;, &lt;code&gt;commit-hash&lt;/code&gt; (these are strings)
and &lt;code&gt;data_available&lt;/code&gt;, a boolean indicating whether the data
service has computed the derivations for this commit or not
(which corresponds to the green or grey badges on the website).
This structure can be derived by looking at and playing with the variables
in the REPL, or probably more conveniently by opening the corresponding URL
in a web browser, which should show the JSON in a special mode.
We can now write a small function (or maybe two even smaller functions)
that query the data service and return a list of revisions for which the
data service has computed the derivations:
&lt;/p&gt;
&lt;pre&gt;
(define (data-available? revision)
  ;; Given a REVISION, check whether it has been treated by the
  ;; data service.
  (assoc-ref revision &amp;quot;data_available&amp;quot;))

(define (get-revisions data-service)
  ;; Query DATA-SERVICE for the list of revisions it has successfully
  ;; treated in the master branch.
  (filter data-available?
    (vector-&amp;gt;list
      (assoc-ref
        (guix-data-service-request data-service
          &amp;quot;repository/1/branch/master.json&amp;quot;)
        &amp;quot;revisions&amp;quot;))))

(define revisions (get-revisions my-data-service))
revisions
$2 = (((&amp;quot;data_available&amp;quot; . #t) (&amp;quot;commit-hash&amp;quot; . &amp;quot; …
&lt;/pre&gt;
&lt;p&gt;
In the following, we will work with revisions in this form, although mainly
the commit hashes are of interest. We could print them as follows:
&lt;/p&gt;
&lt;pre&gt;
(define commits
  (map (lambda (revision)
         (assoc-ref revision &amp;quot;commit-hash&amp;quot;))
       revisions))
commits
$3 = (&amp;quot;b966f4007c8492ad89eedf32dd91b3352dba594e&amp;quot; &amp;quot;8a1f56cf8710fc142a2f8ef2e52be82e8aa9f53e&amp;quot; …
(length commits)
$4 = 46
(define commit (car commits))
commit
$5 = b966f4007c8492ad89eedf32dd91b3352dba594e
&lt;/pre&gt;
&lt;p&gt;
By default the data service returns 100 revisions (including those for which
no data is available), which will be amply enough for our purposes.
&lt;/p&gt;
&lt;p&gt;
The next step is to obtain the derivations for a given revision, say the
newest one with data available. Again this is most easily
reverse-engineered from the web interface of the data service:
Click on the latest revision with a green badge, then on
&lt;i&gt;View package derivations&lt;/i&gt;; this shows how the URL is to be formed.
Since we need all derivations, we also have to tick the &lt;i&gt;All results&lt;/i&gt;
checkbox; on the other hand, we may limit to one architecture, say
&lt;code&gt;x86_64-linux&lt;/code&gt; as &lt;i&gt;System&lt;/i&gt;, and not consider
cross-compilation by choosing &lt;code&gt;(no target)&lt;/code&gt; for &lt;i&gt;Target&lt;/i&gt;.
These choices add GET parameters to the query, which can be passed
as an alist for the optional third parameter of
&lt;code&gt;guix-data-service-request&lt;/code&gt;. Again adding &lt;code&gt;.json&lt;/code&gt;
to the URL (in front of the &lt;code&gt;?&lt;/code&gt;) shows the structure of the
resulting JSON.
It is then easy to end up with the following function; notice the use
of the
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/guile.html#index-quasiquote&quot;&gt;quasiquote&lt;/a&gt;
&lt;code&gt;`&lt;/code&gt; and the
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/guile.html#index-quasiquote&quot;&gt;unquote&lt;/a&gt;
&lt;code&gt;,&lt;/code&gt;:
&lt;/p&gt;
&lt;pre&gt;
(define (get-derivations data-service commit system)
  ;; Query DATA-SERVICE for the list of derivations for the given COMMIT
  ;; and SYSTEM.
  (map
    (lambda (p)
      (assoc-ref p &amp;quot;derivation&amp;quot;))
    (vector-&amp;gt;list
      (assoc-ref
        (guix-data-service-request data-service
          (string-append &amp;quot;revision/&amp;quot; commit &amp;quot;/package-derivations.json&amp;quot;)
          `((system . ,system) (target . &amp;quot;none&amp;quot;) (all_results . &amp;quot;on&amp;quot;)))
        &amp;quot;derivations&amp;quot;))))

(define derivations
  (get-derivations my-data-service commit &amp;quot;x86_64-linux&amp;quot;))
(length derivations)
$6 = 29531
(car derivations)
$7 = &amp;quot;/gnu/store/000lxmn2d17bv2v6znvf6z5vi7ndy8q4-r-janeaustenr-1.0.0.drv&amp;quot;
&lt;/pre&gt;
&lt;p&gt;
So the derivations are simply strings pointing to files in the store
(of the data service, so far they are not yet available on the TBFG
machine).
&lt;/p&gt;


&lt;h2&gt;Filtering out team packages&lt;/h2&gt;

&lt;p&gt;
29000 derivations are more than our poor tiny machine can handle; the next
step is to filter out those that correspond to packages in the science team.
The team is responsible for certain package modules (or equivalently, for
&lt;code&gt;.scm&lt;/code&gt; files in the &lt;code&gt;gnu/packages/&lt;/code&gt; directory);
which ones can be seen in the file &lt;code&gt;CODEOWNERS&lt;/code&gt; checked into the
Guix git repository, itself derived from &lt;code&gt;etc/teams.scm&lt;/code&gt;.
As it does not change very often, for simplicity we may determine the list
of modules by hand, which may require us to resolve regular expressions
(here: &lt;code&gt;fortran(-.+|)&lt;/code&gt;) into lists of actually present modules;
here we end up with the following:
&lt;/p&gt;
&lt;pre&gt;
(define my-locations
  '(&amp;quot;algebra&amp;quot; &amp;quot;astronomy&amp;quot; &amp;quot;chemistry&amp;quot; &amp;quot;fortran-check&amp;quot; &amp;quot;fortran-xyz&amp;quot;
  &amp;quot;geo&amp;quot; &amp;quot;graph&amp;quot; &amp;quot;lean&amp;quot; &amp;quot;maths&amp;quot; &amp;quot;medical&amp;quot; &amp;quot;sagemath&amp;quot; &amp;quot;statistics&amp;quot;))
&lt;/pre&gt;
&lt;p&gt;
When starting the project, I had hoped to extract the interesting packages
directly from the (strings representing) derivations, given a fixed list
of package names.
But it is a truth universally acknowledged that a programmer never has the
singularly good fortune of such simplicity, whatever their feelings or views
when first entering the neighbourhood of a problem.
Here two reasons speak against it: First of all, the packages of a team may
change over time as packages are added, removed or moved to a different
module. More immediately, though, only the &lt;i&gt;combination&lt;/i&gt; of package
name and version can be easily recovered from the derivation by removing a
fixed prefix, the hash and a fixed suffix, using the following function:
&lt;/p&gt;
&lt;pre&gt;
(define (derivation-&amp;gt;name+version derivation)
  ;; Given a DERIVATION (by a string of the form &amp;quot;/gnu/store/...&amp;quot;),
  ;; return the part of it that encodes the name and the version
  ;; of the underlying package.
  (string-drop (basename derivation &amp;quot;.drv&amp;quot;) 33))
&lt;/pre&gt;
&lt;p&gt;
Thus
&lt;code&gt;/gnu/store/000lxmn2d17bv2v6znvf6z5vi7ndy8q4-r-janeaustenr-1.0.0.drv&lt;/code&gt;
becomes
&lt;code&gt;r-janeaustenr-1.0.0&lt;/code&gt;, which is the concatenation of the package
name (which is mostly fixed over different revisions) and the package
version (which usually increases over time) with a hyphen in-between.
More often than not it is possible to guess the two components: Here they
are &lt;code&gt;r-janeaustenr&lt;/code&gt; and &lt;code&gt;1.0.0&lt;/code&gt;.
Package names often contain hyphens (like here, they serve to separate
a language part, &lt;code&gt;r&lt;/code&gt;, and the upstream name,
&lt;code&gt;janeaustenr&lt;/code&gt;, see the Guix
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Package-Naming.html&quot;&gt;naming
conventions&lt;/a&gt;); this could be handled by splitting at the last hyphen,
but versions may also contain hyphens. Both can contain alphabetic and
numeric components. Thus it would be quite possible that the above
derivation is for the flourishingly named version
&lt;code&gt;janeaustenr-1.0.0&lt;/code&gt; of the &lt;code&gt;r&lt;/code&gt; package.
&lt;/p&gt;
&lt;p&gt;
So we need more code to extract the desired information. Luckily the data
service knows about the packages in a revision, with their names and their
versions in different fields; and also about their locations, that is,
the files in which they are defined.
&lt;/p&gt;
&lt;pre&gt;
(define (get-packages data-service commit)
  ;; Query DATA-SERVICE for the list of packages for the given COMMIT.
  (vector-&amp;gt;list
    (assoc-ref
      (guix-data-service-request data-service
        (string-append &amp;quot;revision/&amp;quot; commit &amp;quot;/packages.json&amp;quot;)
        `((field . &amp;quot;version&amp;quot;) (field . &amp;quot;location&amp;quot;) (all_results . &amp;quot;on&amp;quot;)))
      &amp;quot;packages&amp;quot;)))

(define packages (get-packages my-data-service commit))
(car packages)
$8 = ((&amp;quot;location&amp;quot; (&amp;quot;column&amp;quot; . 2) (&amp;quot;line&amp;quot; . 8273) (&amp;quot;file&amp;quot; . &amp;quot;gnu/packages/games.scm&amp;quot;)) (&amp;quot;version&amp;quot; . &amp;quot;0.27.1&amp;quot;) (&amp;quot;name&amp;quot; . &amp;quot;0ad&amp;quot;))
&lt;/pre&gt;
&lt;p&gt;
It is now enough to compare the file name with our list of locations to
extract the packages we are interested in.
&lt;/p&gt;
&lt;pre&gt;
(define (location-package? package locations)
  ;; Check whether the PACKAGE comes from the list of LOCATIONS.
  (let* ((file (assoc-ref (assoc-ref package &amp;quot;location&amp;quot;) &amp;quot;file&amp;quot;))
         (module (basename file &amp;quot;.scm&amp;quot;)))
        (member module locations)))

(use-modules (srfi srfi-26))
(define (packages-name-version data-service commit locations)
  ;; Query DATA-SERVICE for a list of packages for the given COMMIT
  ;; that come from the list of LOCATIONS. Return a list of two-element
  ;; lists with the names and versions of these packages.
  (map
    (lambda (package)
      (list (assoc-ref package &amp;quot;name&amp;quot;) (assoc-ref package &amp;quot;version&amp;quot;)))
    (filter
      (cut location-package? &amp;lt;&amp;gt; locations)
      (get-packages data-service commit))))

(define team-name-versions
  (packages-name-version my-data-service commit my-locations))
(car team-name-versions)
$9 = (&amp;quot;4ti2&amp;quot; &amp;quot;1.6.12&amp;quot;)
&lt;/pre&gt;
&lt;p&gt;
Finally we &lt;i&gt;just&lt;/i&gt; need to compare the extracted team package names
and their versions with the derivations. Unfortunately this can be
quite costly; the following code presents a somewhat
optimised solution with memory usage linear in the result, but a quadratic
number of comparisons (thanks to Liliana Prikler for suggesting the
use of &lt;code&gt;filter-map&lt;/code&gt; to me):
&lt;/p&gt;
&lt;pre&gt;
(use-modules (srfi srfi-1))
(define (special-cartesian-product X Y)
  ;; Let X and Y be lists of two element lists of the form (x z) and (y z),
  ;; respectively. Return a list of all the (x y) such that there is an
  ;; element z with (x z) in X and (y z) in Y.
  (fold cons '()
        (filter-map (lambda (xz)
                      (let ((yz (find (lambda (yz)
                                        (equal? (cadr xz) (cadr yz)))
                                      Y)))
                        (if yz
                            (list (car xz) (car yz))
                            #f)))
                    X)))

(define (team-derivations data-service commit system locations)
  ;; Query DATA-SERVICE for the list of derivations for the given COMMIT
  ;; and SYSTEM, filtered by the LOCATIONS of the packages.
  ;; To memorise the computed information, return a list of two element
  ;; lists, each containing a derivation and the corresponding name.
  (let* ((derivations (get-derivations data-service commit system))
         (X (map
              (lambda (d)
                (list d (derivation-&amp;gt;name+version d)))
            derivations))
         (name-versions
           (packages-name-version data-service commit locations))
         (Y (map
              (lambda (nv)
                (list (car nv) (string-append (car nv) &amp;quot;-&amp;quot; (cadr nv))))
            name-versions)))
    (special-cartesian-product X Y)))

(define (sort-derivation-names derivation-names)
  ;; Just for the fun of it, sort DERIVATION-NAMES, a list of two element
  ;; lists containing derivations and their names, by names.
  (sort derivation-names
        (lambda (x y)
          (string&amp;lt;? (cadr x) (cadr y)))))

(define good-derivation-names
  (sort-derivation-names
    (team-derivations my-data-service commit &amp;quot;x86_64-linux&amp;quot; my-locations)))
(define derivation-name
        (find (lambda (dn)
                (equal? (cadr dn) &amp;quot;lrslib&amp;quot;))
              good-derivation-names))
derivation-name
$10 = (&amp;quot;/gnu/store/3pxq1g2java4f8nwfq7n98qjvhkr1b34-lrslib-7.2.drv&amp;quot; &amp;quot;lrslib&amp;quot;)
&lt;/pre&gt;
&lt;p&gt;
Strictly speaking, the function
&lt;code&gt;team-derivations&lt;/code&gt; is not correct; if there were
&lt;i&gt;simultaneously&lt;/i&gt; a derivation for the package
&lt;code&gt;r-jauneaustenr&lt;/code&gt; at version &lt;code&gt;1.0.0&lt;/code&gt;
&lt;i&gt;and&lt;/i&gt; a derivation for the package
&lt;code&gt;r&lt;/code&gt; at version &lt;code&gt;jauneaustenr-1.0.0&lt;/code&gt;,
then either both or none of them would match, while it is possible that
only one of the packages is covered by the science team, a situation
not yet encountered; at worst, we would capture one too many derivations.

For testing purposes during the
development of the TBFG, we additionally check whether the name equals
&lt;code&gt;lrslib&lt;/code&gt;; in this way only one derivation is returned (while
at the time of writing there are more than 700 packages covered by the
science team).
Moreover the package in question is a self-contained C program (without
any inputs), which compiles rather quickly.
&lt;/p&gt;


&lt;h2&gt;Submitting builds&lt;/h2&gt;

&lt;p&gt;
Now that we have a list of derivations, we would like to submit them from
our Guile script to the build coordinator. This is not very different from
the approach seen
&lt;a href=&quot;tbfg-1.html&quot;&gt;last time&lt;/a&gt;
for submitting from the command line.
Again it is recommended to open a browser window on the
&lt;code&gt;/activity&lt;/code&gt; page of the BFFE to see the build coordinator and
the agent in action.
&lt;/p&gt;
&lt;pre&gt;
(use-modules (guix-build-coordinator client-communication))

(define my-build-coordinator &amp;quot;http://localhost:8746&amp;quot;)
(define ignore-if-build-for-derivation-exists? #f)
(define ignore-if-build-for-outputs-exists? #f)
(define ensure-all-related-derivation-outputs-have-builds? #f)
(define priority 0)

(define (submit-build build-coordinator data-service derivation tags)
  ;; Given a DERIVATION (as a string), submit it to BUILD-COORDINATOR
  ;; together with TAGS;
  ;; DATA-SERVICE is passed through and used by the build coordinator to
  ;; obtain the derivation file and further references contained in
  ;; DERIVATION.
  (send-submit-build-request
    build-coordinator derivation (list data-service) 0 priority
    ignore-if-build-for-derivation-exists?
    ignore-if-build-for-outputs-exists?
    ensure-all-related-derivation-outputs-have-builds?
    tags))

(submit-build my-build-coordinator my-data-service (car derivation-name) '())
$11 = ((&amp;quot;build-submitted&amp;quot; . &amp;quot;8f8f1cad-fe9c-462c-bc59-3d1f87abf942&amp;quot;))
$12 = #&amp;lt;&amp;lt;response&amp;gt; …
&lt;/pre&gt;
&lt;p&gt;
The global variables, which we pass on to the &lt;code&gt;submit-build&lt;/code&gt;
function, determine the behaviour of the build coordinator.
If &lt;code&gt;ignore-if-build-for-derivation-exists?&lt;/code&gt; is true,
then the build will not be carried out a second time if it was already tried
(successfully or not) by the build coordinator before.
In production, it will thus be preferable to set it to &lt;code&gt;#t&lt;/code&gt;;
while still experimenting, we are likely to submit the same derivation
several times. Setting the value to &lt;code&gt;#f&lt;/code&gt; would also make sense
to check that rebuilding the same package works.
The variable &lt;code&gt;ignore-if-build-for-outputs-exists?&lt;/code&gt; goes a bit
further; if set to &lt;code&gt;#t&lt;/code&gt;, then the build will not be carried out
if a different derivation with the same output was already tried (a very
technical distinction; I would recommend to leave it at &lt;code&gt;#f&lt;/code&gt;).
If &lt;code&gt;ensure-all-related-derivation-outputs-have-builds?&lt;/code&gt; is
&lt;code&gt;#t&lt;/code&gt;,
then the build coordinator will recursively submit builds for all the
derivations required as inputs to a given derivation. While this sounds
reasonable at first, it can go very far, since the coordinator does not
look at the store, but at the builds it has handled itself and recorded
in its database. This means that the first build submission, when the
database is still empty, will entail a complete bootstrap of the Guix
distribution. So I would recommend to leave it also at &lt;code&gt;#f&lt;/code&gt;.
Then the build works as follows: The coordinator sends the derivation
to an agent. The agent tries to download all required inputs from a
substitute server and if successful, will build only the derivation it is
asked to build. Otherwise, it reports back to the coordinator that it has
encountered a set-up failure, together with a list of missing inputs.
This triggers a hook in the coordinator, and the default hook is to add
the missing inputs to the list of outstanding builds, as well as the
failed build itself to try it again once the inputs are available.
In this way, even if
&lt;code&gt;ensure-all-related-derivation-outputs-have-builds?&lt;/code&gt; is
&lt;code&gt;#f&lt;/code&gt;, all really missing inputs will be built recursively,
until the build succeeds or a real failure in one of its inputs is
encountered.
&lt;/p&gt;
&lt;p&gt;
The submission immediately returns
&lt;a href=&quot;https://www.gnu.org/software/guile/manual/guile.html#Multiple-Values&quot;&gt;two
values&lt;/a&gt;,
without waiting for the package build to finish. The first return value
can be used to link the submitted derivation to the shown UUID of the
build, which is a key in the build coordinator database. The second
return value is the HTTP response, which we will ignore from now on.
&lt;/p&gt;
&lt;p&gt;
Tags can be added in a parenthesis rich format; the parameter is a list
of tags, where each tag is a two element list (not a pair!), in which both
elements are pairs. The first one pairs the keyword &lt;code&gt;key&lt;/code&gt;
to a value, the second one pairs the keyword &lt;code&gt;value&lt;/code&gt; to a
value (the values are used to construct the URL and can be strings or
numbers). So the following would work:
&lt;/p&gt;
&lt;pre&gt;
(define tags `(((key . &amp;quot;commit&amp;quot;)(value . ,commit))
               ((key . &amp;quot;name&amp;quot;)(value . ,(cadr derivation-name)))
               ((key . &amp;quot;build&amp;quot;)(value . 2))))
(submit-build my-build-coordinator my-data-service (car derivation-name) tags)
$13 ((&amp;quot;build-submitted&amp;quot; . &amp;quot;82a56cac-1e93-4b4a-926f-d8762f919219&amp;quot;))
$14 = #&amp;lt;&amp;lt;response&amp;gt; …
&lt;/pre&gt;
&lt;p&gt;
The tags are shown in the activity window and are also recorded in the
build coordinator database; as shown here, they can encode arbitrary
additional information of a build, such as the commit it comes from, the
package name or the submission count for a given derivation.
&lt;/p&gt;


&lt;h2&gt;Code&lt;/h2&gt;

&lt;p&gt;
For ease of use, the code developed in this post is made available, under
GPLv3 or later, in a dedicated
&lt;a href=&quot;https://codeberg.org/enge/tbfg&quot;&gt;git repository&lt;/a&gt;
on
&lt;a href=&quot;https://codeberg.org/&quot;&gt;Codeberg&lt;/a&gt;.
More precisely, it is collected in the file
&lt;a href=&quot;https://codeberg.org/enge/tbfg/src/commit/51eb5c6d45c66d15b7c14340ec3af0732b5b66fd/tbfg.scm&quot;&gt;tbfg.scm&lt;/a&gt;
at commit 51eb5c6d45c66d15b7c14340ec3af0732b5b66fd.
&lt;/p&gt;


&lt;h2&gt;Outlook&lt;/h2&gt;

&lt;p&gt;
We have queried the data service and used the resulting information on
packages and derivations to submit build jobs to the build coordinator.
But so far we have no programmatical access to the build results; we only
saw the builds flicker by on the BFFE website.
It would be nice to record success or failure, and more generally to keep
track of the builds; this will be our next step.
Since we do not want to operate a substitute server, but rather follow the
state of the packages under the responsibility of the science team, unlike
the official build farms we are not necessarily interested in obtaining the
build results. These are sent from the build agents to the build coordinator;
on the bordeaux build farm the
&lt;a href=&quot;https://codeberg.org/guix/nar-herder&quot;&gt;nar herder&lt;/a&gt;
shovels them to a separate substitute server.
For us everything is on the same machine, which will thus contain
successfully built packages in its store (at least until the next
&lt;code&gt;guix gc&lt;/code&gt; run). If desired, these could be made available using
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Invoking-guix-publish.html&quot;&gt;&lt;code&gt;guix
publish&lt;/code&gt;&lt;/a&gt;.
&lt;/p&gt;

&lt;/div&gt;</content></entry><entry><title>Tiny Build Farm for Guix, part 1</title><id>https://enge.math.u-bordeaux.fr/blog/tbfg-1.html</id><author><name>Andreas Enge</name><email>andreas.enge@inria.fr</email></author><updated>2025-08-27T00:00:00Z</updated><link href="https://enge.math.u-bordeaux.fr/blog/tbfg-1.html" rel="alternate" /><content type="html">&lt;div&gt;

&lt;h1&gt;Setting up scores of services&lt;/h1&gt;

&lt;p&gt;
One of the oft-cited reasons people give for not switching to
&lt;a href=&quot;https://guix.gnu.org/&quot;&gt;Guix&lt;/a&gt; is that their favourite software
is too outdated, and a look at
&lt;a href=&quot;https://repology.org/&quot;&gt;Repology&lt;/a&gt; shows that they are not wrong.
Now the number of active committers in the Guix project is amazingly small,
and even counting all contributors I am impressed by what these few people
actually achieve. Nevertheless I wondered how I could improve the situation
at least a little bit for packages I am interested in, that is, for the
science team; and the first step is to get an account of what actually
builds and what does not.
So I decided to set up my own little build farm, limited to the packages
in the scope of the science team, using the same technology that powers the
&lt;a href=&quot;https://bordeaux.guix.gnu.org/&quot;&gt;bordeaux&lt;/a&gt; build farm.
I call it the &lt;i&gt;Tiny Build Farm for Guix&lt;/i&gt;, or &lt;i&gt;TBFG&lt;/i&gt; for short,
and this post is the first one in (hopefully) a series of blog posts
about the topic; at the time of starting this series, the TBFG does not
actually exist yet, so wish me luck.
&lt;/p&gt;


&lt;h2&gt;Motivation&lt;/h2&gt;

&lt;p&gt;
Before trying to solve a technical problem, let me digress a little bit:
Is there actually a problem? And if yes, why?
As has become my conviction over the years, the really difficult and major
problems in a project such as Guix are actually social and not technical.
They are rooted in the structure of Guix as a loosely coupled group of
volunteers who work on a common goal, mostly in their spare time; but when
I speak about a common goal, every volunteer has in fact their own goals,
and arriving at a coherent whole is partially due to the internal
structuring of the social project, and partially an emerging property
of a complex system.
Concretely, it happens often that contributors propose a package for a
software project they like; if it concerns free software and follows our
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Packaging-Guidelines.html&quot;&gt;packaging
guidelines&lt;/a&gt;, it usually ends up being committed to the software
distribution. The original contributor may leave, the package may bitrot
and stop being buildable due to changes made in other parts of the
distribution. Sometimes people submit a bug report, sometimes committers
without interest in the actual software provide a fix, but maybe nobody
uses the software anymore, and it happens that over several years nobody
notices it is broken. This is problematic since even broken packages use
resources on the build farms. And when introducing, say, an update to a
library package, it becomes difficult to say whether the failure of a
dependency is a new phenomenon due to the change or whether it was already
present. So there are good reasons to strive for a distribution that is
100% buildable at all times.
And while I alone certainly cannot reach this for the currently more than
&lt;a href=&quot;https://repology.org/repository/gnuguix&quot;&gt;28000 packages&lt;/a&gt; in
Guix, doing it only for the science team, or maybe only the
&lt;code&gt;algebra&lt;/code&gt; and &lt;code&gt;maths&lt;/code&gt; modules, in which I am
particularly interested, appears to be a reachable goal.
&lt;/p&gt;
&lt;p&gt;
But is a tiny build farm really needed? The honest answer is “no”, since
the information is already out there in the big build farms.
For historical reasons, Guix has two of them.
One is
&lt;a href=&quot;https://ci.guix.gnu.org/&quot;&gt;CI&lt;/a&gt;, also called &lt;i&gt;berlin&lt;/i&gt; for
the location of most of its build machines; it relies on
&lt;a href=&quot;https://codeberg.org/guix/cuirass&quot;&gt;Cuirass&lt;/a&gt;,
a continuous integration system written purposefully for Guix.
It shows the
&lt;a href=&quot;https://ci.guix.gnu.org/jobset/master&quot;&gt;state of the master
branch&lt;/a&gt; and provides a dashboard from which the desired information
could certainly be extracted automatically.
On the other hand there is the
&lt;a href=&quot;https://bordeaux.guix.gnu.org/&quot;&gt;bordeaux&lt;/a&gt; build farm,
named after the location of its head node; it runs a suite of continuous
integration tools written purposefully for Guix by
&lt;a href=&quot;https://www.cbaines.net/&quot;&gt;Christopher Baines&lt;/a&gt;.
One of its parts is the
&lt;a href=&quot;https://data.guix.gnu.org/&quot;&gt;Guix Data Service&lt;/a&gt;,
and its REST API with a JSON frontend makes it again possible to extract
the information I am interested in – a system that knows about &lt;i&gt;all&lt;/i&gt;
packages in Guix by definition knows about the packages in the realm
of the science team.
&lt;/p&gt;
&lt;p&gt;
So setting up my TBFG is mainly an educational project – I would like to
learn how our technology works. But the TBFG can also be used to obtain
information about a collection of packages that is not part of Guix proper,
or to look at the impact of local changes.
Many people and projects have successfully set up Cuirass to manage their
local package collection; this is, for instance, the case for the
&lt;a href=&quot;https://codeberg.org/guix-science/guix-science&quot;&gt;Guix Science&lt;/a&gt;
and
&lt;a href=&quot;https://hpc.guix.info/&quot;&gt;Guix HPC&lt;/a&gt; projects through the
&lt;a href=&quot;https://guix.bordeaux.inria.fr/&quot;&gt;build server&lt;/a&gt;
at INRIA Bordeaux.
The software behind the bordeaux build farm is more complex and consists
of several interconnected
&lt;a href=&quot;https://www.gnu.org/software/shepherd/&quot;&gt;Shepherd&lt;/a&gt; services,
so that it may in fact be less suited for a personal project.
However, not least because I host part of that build farm at home,
I am more interested in understanding this software stack, which is also
less documented. So I am going to build the TBFG on top of this technology.
Before diving in, I take the opportunity to thank Christopher Baines for
his precious help during a Guix/Nix hackers' meeting; without him, I would
not have been able to launch myself into this endeavour.
&lt;/p&gt;


&lt;h2&gt;Build coordinator and agent&lt;/h2&gt;

&lt;p&gt;
As a first step, we need to install the Guix Build Coordinator and one or
more build agents. For a really tiny TBFG, I will keep everything on only
one machine set aside for the purpose; it is called &lt;i&gt;bedok&lt;/i&gt; and is
one of the &lt;a href=&quot;https://foundation.guix.info/assets/index.html&quot;&gt;Lenovo
Thinkpad X1 Gen9&lt;/a&gt; with a four core
11th Gen Intel Core i7-1165G7 processor running at 2.80GHz graciously
donated by &lt;a href=&quot;https://www.tweag.io/&quot;&gt;Tweag&lt;/a&gt;.
For the build coordinator, this is trivial; simply add the two lines
&lt;/p&gt;
&lt;pre&gt;
(service guix-build-coordinator-service-type
  (guix-build-coordinator-configuration))
&lt;/pre&gt;
&lt;p&gt;
to the services configuration of the Guix system declaration and reconfigure
the machine. For a start, the default configuration options explained in
more detail in the
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Guix-Services.html#index-guix_002dbuild_002dcoordinator_002dservice_002dtype&quot;&gt;manual&lt;/a&gt;
are appropriate (things will become more complicated if anything is to be
done with the build results, which would require to set the
&lt;code&gt;hooks&lt;/code&gt; field of the
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Guix-Services.html#index-guix_002dbuild_002dcoordinator_002dservice_002dtype&quot;&gt;configuration
record&lt;/a&gt;).
See also the documentation in the
&lt;a href=&quot;https://codeberg.org/guix/build-coordinator&quot;&gt;git repository&lt;/a&gt;
of the project.
&lt;/p&gt;
&lt;p&gt;
Next we need the &lt;code&gt;guix-build-coordinator&lt;/code&gt; package, which provides
a command line interface to the coordinator. It could be installed into an
arbitrary profile since the security model of the build coordinator is very
basic: It is assumed that the coordinator runs on a server of its own,
and anybody with access to the machine has control over the service.
&lt;/p&gt;
&lt;p&gt;
⚠ However installing the package into a user profile currently has a big
drawback: It propagates the guix package, and the corresponding guix takes
precedence over the one in
&lt;code&gt;$HOME/.config/guix/current/bin&lt;/code&gt;.
The latter one is updated by &lt;code&gt;guix pull&lt;/code&gt;, but the former one
is not; so without a special approach, this prevents the user from
updating Guix.
Instead one can start a Guix shell as follows:
&lt;/p&gt;
&lt;pre&gt;
guix shell guix-build-coordinator
&lt;/pre&gt;
&lt;p&gt;
Running
&lt;/p&gt;
&lt;pre&gt;
$ guix-build-coordinator agent list
&lt;/pre&gt;
&lt;p&gt;
shows nothing, so the next step is to set up a build agent, which requires
some preparations on the machine where the build server is running.
Executing
&lt;/p&gt;
&lt;pre&gt;
$ guix-build-coordinator agent new
e092df28-3418-4f94-b7f0-a214b03291ee
&lt;/pre&gt;
&lt;p&gt;
creates and prints a new random version 4
&lt;a href=&quot;https://en.wikipedia.org/wiki/Universally_unique_identifier&quot;&gt;UUID&lt;/a&gt;
and stores it in the agents table in its internal database.
The next step is to set up authentication; for a small number of build
agents, a
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Guix-Services.html#index-guix_002dbuild_002dcoordinator_002dagent_002dpassword_002dfile_002dauth&quot;&gt;password
file&lt;/a&gt; is a suitable approach, otherwise an
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Guix-Services.html#index-guix_002dbuild_002dcoordinator_002dagent_002ddynamic_002dauth&quot;&gt;authentication
token&lt;/a&gt;,
which may be shared among several agents, can be used.
So we create a password for the agent by running
&lt;/p&gt;
&lt;pre&gt;
$ guix-build-coordinator agent e092df28-3418-4f94-b7f0-a214b03291ee password new
new password: IUwGrsEklfu0kVf_QquCOdzfa6-P52qPlcwBd5YB
&lt;/pre&gt;
&lt;p&gt;
which again prints the password and saves it into the coordinator
database.
&lt;/p&gt;
&lt;p&gt;
To simplify things for the human brain, we can give the agent a name; since
there is not yet a command line argument for this, we take it as a pretense
to look more closely at the SQLite database structure. So install the
&lt;code&gt;sqlite&lt;/code&gt; package into the &lt;code&gt;root&lt;/code&gt; profile, launch
&lt;code&gt;sqlite3&lt;/code&gt; on the coordinator database, and run the following
commands:
&lt;/p&gt;
&lt;pre&gt;
# sqlite3 /var/lib/guix-build-coordinator/guix_build_coordinator.db
sqlite&amp;gt; .tables
agent_passwords
agent_tags
agents
…
builds
…
tags
…
sqlite&amp;gt; select * from agent_passwords;
1|e092df28-3418-4f94-b7f0-a214b03291ee|IUwGrsEklfu0kVf_QquCOdzfa6-P52qPlcwBd5YB|2025-08-13 00:00:00
sqlite&amp;gt; .schema agents
CREATE TABLE agents (
       id TEXT PRIMARY KEY,
       description TEXT
, name TEXT, active NOT NULL DEFAULT 1);
&lt;/pre&gt;
&lt;p&gt;
There are quite a few tables, but for now we are mainly interested in
those related to the agents. We can recover the password in case we forgot
to write it down (alternatively we could run
&lt;code&gt;guix-build-coordinator agent e092df28-3418-4f94-b7f0-a214b03291ee password&lt;/code&gt;),
and we see that agents can have a name and a description, and be active
or not.
So still in SQLite run
&lt;/p&gt;
&lt;pre&gt;
sqlite&amp;gt; update agents set name='bedok', description='TBFG agent' where id='e092df28-3418-4f94-b7f0-a214b03291ee';
sqlite&amp;gt; select * from agents;
e092df28-3418-4f94-b7f0-a214b03291ee|TBFG agent|bedok|1
&lt;/pre&gt;
&lt;p&gt;
Alternatively, to check that everything has gone well, run (not necessarily
as root anymore):
&lt;/p&gt;
&lt;pre&gt;
$ guix-build-coordinator agent list
e092df28-3418-4f94-b7f0-a214b03291ee: bedok
  description:
  TBFG agent  active?: true
  0 allocated builds:
  requested systems:
  tags:
&lt;/pre&gt;
&lt;p&gt;
Now it is finally time to really set up the build agent! On the machine
where it is supposed to run (in our case, this is &lt;i&gt;bedok&lt;/i&gt; again, but
it could be an arbitrary machine somewhere on the Internet, since all
communication would take place over https), we need to handle the password.
As usual in Guix, secrets are not saved in configuration that is publicly
visible in the store, but rather as separate state; so as &lt;code&gt;root&lt;/code&gt;
create a file &lt;code&gt;/etc/guix-build-coordinator/agent-bedok-passwd&lt;/code&gt;
containing the password
&lt;code&gt;IUwGrsEklfu0kVf_QquCOdzfa6-P52qPlcwBd5YB&lt;/code&gt;
created above by the coordinator.
Then add the following snippet to the server part of the operating system
configuration:
&lt;/p&gt;
&lt;pre&gt;
(service guix-build-coordinator-agent-service-type
  (guix-build-coordinator-agent-configuration
    (authentication
      (guix-build-coordinator-agent-password-file-auth
        (uuid &amp;quot;e092df28-3418-4f94-b7f0-a214b03291ee&amp;quot;)
        (password-file
          &amp;quot;/etc/guix-build-coordinator/agent-bedok-passwd&amp;quot;)))
    (derivation-substitute-urls
      '(&amp;quot;https://data.guix.gnu.org&amp;quot;))
    (non-derivation-substitute-urls
      '(&amp;quot;https://bordeaux.guix.gnu.org&amp;quot;))
    (systems '(&amp;quot;x86_64-linux&amp;quot; &amp;quot;i686-linux&amp;quot;))
    (max-parallel-builds 4)
    (max-parallel-uploads 2)
    (max-1min-load-average 6)))
&lt;/pre&gt;
&lt;p&gt;
and reconfigure the machine.
Concerning the different parameters, see the
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Guix-Services.html#index-guix_002dbuild_002dcoordinator_002dagent_002dservice_002dtype&quot;&gt;documentation&lt;/a&gt;.
For authentication, we need to provide our &lt;code&gt;uuid&lt;/code&gt; and the
location of the &lt;code&gt;password-file&lt;/code&gt;.
Since the agent runs on the same machine as the coordinator, I kept the
&lt;code&gt;coordinator&lt;/code&gt; field at its default
&lt;code&gt;&amp;quot;http://localhost:8745&amp;quot;&lt;/code&gt;; otherwise &lt;code&gt;localhost&lt;/code&gt; needs
to be replaced by the host name of the coordinator, and the protocol should
be set to &lt;code&gt;https&lt;/code&gt;.
The &lt;code&gt;derivation-substitute-urls&lt;/code&gt; field has no default; we will
discuss it in the next section.
The &lt;code&gt;non-derivation-substitute-urls&lt;/code&gt; field also needs to be set
to avoid compiling each and every package input locally; here I chose to
only use the bordeaux build farm, but one could add
&lt;code&gt;&amp;quot;https://ci.guix.gnu.org&amp;quot;&lt;/code&gt; to also fetch packages from berlin.
If the &lt;code&gt;system&lt;/code&gt; field is not set, then only packages for the
system on which the agent is running (most likely &lt;code&gt;x86_64-linux&lt;/code&gt;)
are handled; here it is useful to add &lt;code&gt;i686-linux&lt;/code&gt;.
Or on an ARM machine, the combo
&lt;code&gt;'(&amp;quot;aarch64-linux&amp;quot; &amp;quot;armhf-linux&amp;quot;)&lt;/code&gt; makes sense.
The numerical parameters can be left at their defaults; here I am trying
to limit the load on my four core processor.
&lt;/p&gt;
&lt;p&gt;
If all goes well, we should see lines in the agent logfile
&lt;code&gt;/var/log/guix-build-coordinator/agent.log&lt;/code&gt;
looking like
&lt;/p&gt;
&lt;pre&gt;
2025-08-13 00:00:00 (INFO ): starting agent e092df28-3418-4f94-b7f0-a214b03291ee
2025-08-13 00:00:00 (INFO ): connecting to coordinator http://localhost:8745
2025-08-13 00:00:00 (INFO ): running 0 threads, currently allocated 0 builds
2025-08-13 00:00:00 (INFO ): starting 0 new builds
&lt;/pre&gt;
&lt;p&gt;
and running
&lt;/p&gt;
&lt;pre&gt;
$ guix-build-coordinator agent list
&lt;/pre&gt;
&lt;p&gt;
on the coordinator machine again should now print two entries
beneath &lt;code&gt;requested systems&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
As a last step, get back to the
&lt;code&gt;/etc/guix-build-coordinator/agent-bedok-passwd&lt;/code&gt; file,
which is probably world-readable. Starting the build agent has created
a user &lt;code&gt;guix-build-coordinator-agent&lt;/code&gt;, and I would recommend
to have the file be owned by that user, and remove permissions from all
other users.
&lt;/p&gt;


&lt;h2&gt;Data service&lt;/h2&gt;

&lt;p&gt;
This is the elephant in the room, almost literally. When starting my project
of the TBFG, I had intended to set up the full stack of software needed
to run the bordeaux build farm, and the
&lt;a href=&quot;https://codeberg.org/guix/data-service/&quot;&gt;Guix Data Service&lt;/a&gt;
is a very important part of it. But the code base is massive (more than
30000 lines of Scheme code at the time of writing), and also the required
ressources are massive: The service spends its time polling the git
repository of Guix, compiling the sources and computing all the derivations
for all the packages, which are then stored in a database. Rinse and repeat
for the next commit. (In reality, the data service does even more, in
particular it also queries build servers and stores information about
builds; but the above functionality is everything we need for the TBFG.)
As can be seen, not even the
&lt;a href=&quot;https://data.guix.gnu.org/repository/1/branch/master&quot;&gt;official
data service&lt;/a&gt;, running continuously on a powerful server, manages to
do that for all commits: Some of them are marked as green, others, the
grey ones, are skipped, in particular at times of high commit activity.
&lt;/p&gt;
&lt;p&gt;
So I have decided to rely on the central Guix data service by configuring
the corresponding &lt;code&gt;derivation-substitute-urls&lt;/code&gt; field of the
build coordinator as &lt;code&gt;'(https://data.guix.gnu.org)&lt;/code&gt;.
All information obtained by clicking through the data service website
can also be obtained as JSON through its REST API, which we will use in our
scripts to determine the derivations of science team packages to be built.
&lt;/p&gt;
&lt;p&gt;
For this to work, we also need the Guix daemon to accept the signing key
of the data service; so the &lt;code&gt;services&lt;/code&gt; field of the operating
system declaration should look like this:
&lt;/p&gt;
&lt;pre&gt;
(services
  (append
    (modify-services %base-services
      (guix-service-type config =&amp;gt;
        (guix-configuration
          (substitute-urls '(&amp;quot;https://bordeaux.guix.gnu.org&amp;quot;))
          (authorized-keys
            (list
              (local-file &amp;quot;keys/guix/bordeaux.guix.gnu.org-export.pub&amp;quot;)
              (local-file &amp;quot;keys/guix/data.guix.gnu.org.pub&amp;quot;)))
          (max-silent-time (* 24 3600))
          (timeout (* 48 3600)))))
    (list
      (service guix-build-coordinator-service-type
        (guix-build-coordinator-configuration))
      …)))
&lt;/pre&gt;
&lt;p&gt;
where the key files are copy-pasted into the local
&lt;code&gt;keys/guix&lt;/code&gt; subdirectory from the
&lt;a href=&quot;https://codeberg.org/guix/maintenance/src/commit/master/hydra/keys/guix&quot;&gt;corresponding
place&lt;/a&gt;
in the
&lt;a href=&quot;https://codeberg.org/guix/maintenance&quot;&gt;guix/maintenance&lt;/a&gt;
git repository.
&lt;/p&gt;


&lt;h2&gt;BFFE – Build Farm Front End&lt;/h2&gt;

&lt;p&gt;
We could start submitting build jobs now, but to visualise what is
happening, we need another service, &lt;code&gt;bffe&lt;/code&gt;. The
&lt;i&gt;build farm frontend&lt;/i&gt; actually serves two purposes:
On one hand on the bordeaux build farm, it submits build jobs for the
master branch to ensure continuous substitute availability (and a
different service, &lt;code&gt;qa-frontpage&lt;/code&gt;, submits build jobs for
testing branches and pull requests to the same build coordinator instance).
On the other hand, it provides a web server that connects to the
build coordinator and shows information about its status.
We will only need the second functionality. For this, add the following
snippet to the service configuration of the TBFG machine:
&lt;/p&gt;
&lt;pre&gt;
(service bffe-service-type
  (bffe-configuration
    (arguments
      #~(list
        #:web-server-args
          '(#:event-source &amp;quot;http://localhost:8746&amp;quot;
            #:controller-args (#:title &amp;quot;Science team build farm&amp;quot;))))))
&lt;/pre&gt;
&lt;p&gt;
We use the &lt;code&gt;#:web-server-args&lt;/code&gt; argument and provide as
&lt;code&gt;event-source&lt;/code&gt; the local build coordinator instance, which
communicates with clients on port 8746 (while communication with the
agents runs on port 8745 as seen above).
For more details on the optional &lt;code&gt;#:build&lt;/code&gt; argument, see
the
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Guix-Services.html#index-bffe_002dservice_002dtype&quot;&gt;documentation&lt;/a&gt;
in the Guix manual or the
&lt;a href=&quot;https://codeberg.org/guix/bffe&quot;&gt;source code&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;
Reconfigure the system and open the BFFE website, either at
&lt;a href=&quot;http://localhost:8767&quot;&gt;&lt;code&gt;http://localhost:8767&lt;/code&gt;&lt;/a&gt;
locally on the TBFG machine, or at
&lt;a href=&quot;http://192.168.1.80:8767&quot;&gt;&lt;code&gt;http://192.168.1.80:8767&lt;/code&gt;&lt;/a&gt;
from another machine in your local network, where you have to adapt the
IP address to your situation.
The result is a rather empty web page, but it should at least show the title
we have chosen.
For our purposes, we are interested in the page obtained by appending
&lt;a href=&quot;http://192.168.1.80:8767/activity&quot;&gt;&lt;code&gt;/activity&lt;/code&gt;&lt;/a&gt;
to the URL.
This shows a box &lt;i&gt;Recent activity&lt;/i&gt;, which is rightfully empty;
and a list of agents per architecture and their current occupation,
which should also be void, except possibly for the percentage giving the
CPU load in case the agent also has other business.
Clicking on the name of an agent sends us to yet another page with
more details (among which the description we provided previously) .
&lt;/p&gt;


&lt;h2&gt;Submitting a build&lt;/h2&gt;

&lt;p&gt;
After all these preparations, we can finally submit our first build job!
For this, keep the &lt;code&gt;/activity&lt;/code&gt; page open, and run
&lt;/p&gt;
&lt;pre&gt;
$ DRV=`guix build hello --derivations`
$ guix-build-coordinator build --derivation-substitute-urls=https://data.guix.gnu.org $DRV
build submitted as 7bdd2249-1214-431a-aa61-de4d907f1b32
&lt;/pre&gt;
&lt;p&gt;
Providing a URL from which to fetch derivations is necessary because the
build coordinator receives only the store path of the derivation and not
the actual file; the same then recursively holds for inputs referenced
in the submitted derivation. This could be a global parameter of the
&lt;code&gt;guix-build-coordinator-configuration&lt;/code&gt;, but currently needs
to be specified by hand and can thus vary over time or depending on the
build.
&lt;/p&gt;
&lt;p&gt;
If all goes well, a UUID for the build is printed in the terminal, and
three lines appear on the BFFE web page in the &lt;i&gt;Recent activity&lt;/i&gt; box:
&lt;i&gt;Build submitted&lt;/i&gt;, &lt;i&gt;Build started&lt;/i&gt; and &lt;i&gt;Build succeeded&lt;/i&gt;.
The &lt;code&gt;/var/log/guix-build-coordinator/coordinator.log&lt;/code&gt; and
&lt;code&gt;/var/log/guix-build-coordinator/agent.log&lt;/code&gt; files should also
contain matching information.
And you can go to the
&lt;code&gt;http://192.168.1.80:8767/build/7bdd2249-1214-431a-aa61-de4d907f1b32&lt;/code&gt;
URL to see more detailed information on the build, with potentially a link
to the build log.
&lt;/p&gt;
&lt;p&gt;
This link unfortunately does not work out of the box, and some more
configuration is required to make the build logs available.
&lt;/p&gt;


&lt;h2&gt;Nginx for the build logs&lt;/h2&gt;

&lt;p&gt;
One possibility for accessing the build logs is by directly looking them
up in the place where they are stored by the build coordinator, the
directory &lt;code&gt;/var/lib/guix-build-coordinator/build-logs/&lt;/code&gt;.
Each build corresponds to a subdirectory named after its UUID and containing
the file &lt;code&gt;log.gz&lt;/code&gt;.
&lt;/p&gt;
&lt;p&gt;
Alternatively, we can imitate the
&lt;a href=&quot;https://codeberg.org/guix/maintenance/src/commit/bc7f188a027313437f0afb977bfe802d307d8dd3/hydra/bayfront.scm#L902-L918&quot;&gt;behaviour&lt;/a&gt;
of the bordeaux build farm and set up a separate
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Web-Services.html#index-nginx_002dservice_002dtype&quot;&gt;nginx&lt;/a&gt;
web server using the following service snippet:
&lt;/p&gt;
&lt;pre&gt;
(service nginx-service-type
  (nginx-configuration
    (server-blocks
      (list
	(nginx-server-configuration
	  (listen '(&amp;quot;80&amp;quot; &amp;quot;[::]:80&amp;quot;))
	  (locations
	    (list
	      (nginx-location-configuration
		(uri &amp;quot;~ \&amp;quot;\\/build\\/([a-z0-9-]{36})/log$\&amp;quot;&amp;quot;)
                (body '(&amp;quot;alias /var/lib/guix-build-coordinator/build-logs/$1/log;&amp;quot;
                        &amp;quot;add_header Content-Type 'text/plain; charset=UTF-8';&amp;quot;
                        &amp;quot;gzip_static always;&amp;quot;
                        &amp;quot;gunzip on;&amp;quot;)))
              (nginx-location-configuration
                (uri &amp;quot;/&amp;quot;)
                (body '(&amp;quot;proxy_pass http://localhost:8767;&amp;quot;
                        &amp;quot;proxy_http_version 1.1;&amp;quot;
                        &amp;quot;proxy_set_header Connection \&amp;quot;\&amp;quot;;&amp;quot;)))
              (nginx-location-configuration
                (uri &amp;quot;/events&amp;quot;)
                (body '(&amp;quot;proxy_pass http://localhost:8767;&amp;quot;
                        &amp;quot;proxy_http_version 1.1;&amp;quot;
                        &amp;quot;proxy_buffering off;&amp;quot;
                        &amp;quot;proxy_set_header Connection \&amp;quot;\&amp;quot;;&amp;quot;))))))))))
&lt;/pre&gt;
&lt;p&gt;
The first
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/Web-Services.html#index-nginx_002dlocation_002dconfiguration&quot;&gt;&lt;code&gt;nginx-location-configuration&lt;/code&gt;&lt;/a&gt;
serves the build logs, while the other two are reverse proxies towards
the pages provided by BFFE at port &lt;code&gt;8767&lt;/code&gt;.
If the activity page is now accessed through the nginx web server at the
standard port through the URL
&lt;a href=&quot;http://192.168.1.80/activity&quot;&gt;&lt;code&gt;http://192.168.1.80/activity&lt;/code&gt;&lt;/a&gt;,
it presents links to builds and their log files, which can be clicked on,
and the uncompressed log files are shown directly in the browser.
&lt;/p&gt;


&lt;h2&gt;Outlook&lt;/h2&gt;

&lt;p&gt;
This was a lot of work for setting up the necessary services!
But hopefully it was also a good occasion to understand how the different
components interact.
In the
&lt;a href=&quot;tbfg-2.html&quot;&gt;next installment&lt;/a&gt;
we will start writing our own scripts to communicate with these services;
in particular we will work with the data service to retrieve information
about the packages we are interested in.
&lt;/p&gt;

&lt;/div&gt;</content></entry><entry><title>Wireguard VPN with Guix</title><id>https://enge.math.u-bordeaux.fr/blog/wireguard.html</id><author><name>Andreas Enge</name><email>andreas.enge@inria.fr</email></author><updated>2025-08-07T00:00:00Z</updated><link href="https://enge.math.u-bordeaux.fr/blog/wireguard.html" rel="alternate" /><content type="html">&lt;div&gt;

&lt;h2&gt;Needing a VPN&lt;/h2&gt;

&lt;p&gt;
Recently I changed my ISP, and the new one uses
&lt;a href=&quot;https://en.wikipedia.org/wiki/Carrier-grade_NAT&quot;&gt;Carrier-grade NAT&lt;/a&gt;,
or CGNAT, by default. While this sounds fancy and professional, it is in
fact even worse than conventional NAT: Not only do all my devices share the
same IPv4, but I share one IPv4 with several other customers!
Apparently I am only assigned a few out of the 65535 ports, and this
assignment may change from day to day, which implies that I cannot connect
from the outside to any of my home devices.
However, I do have a separate IPv4 of my own for a
&lt;a href=&quot;https://www.aquilenet.fr/services/h%C3%A9bergement-serveur/&quot;&gt;virtual
machine&lt;/a&gt;
at &lt;a href=&quot;https://www.aquilenet.fr/&quot;&gt;Aquilenet&lt;/a&gt;, and it should be
possible to use this as a trampoline to access my home through a virtual
private network.
We are already employing
&lt;a href=&quot;https://en.wikipedia.org/wiki/WireGuard&quot;&gt;WireGuard&lt;/a&gt;
for one of the
&lt;a href=&quot;https://guix.gnu.org/&quot;&gt;Guix&lt;/a&gt; build farms, so it felt like
a natural choice.
Guix provides the &lt;code&gt;wireguard-service-type&lt;/code&gt;, which is
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/VPN-Services.html&quot;&gt;documented&lt;/a&gt;
with all its options in the manual; but without an explanation of the
general concepts behind the service it is a bit difficult to set up.
The &lt;a href=&quot;https://guix.gnu.org/cookbook/en/html_node/&quot;&gt;Guix Cookbook&lt;/a&gt;
has an
&lt;a href=&quot;https://guix.gnu.org/cookbook/en/guix-cookbook.html#Connecting-to-Wireguard-VPN&quot;&gt;entry&lt;/a&gt;
on WireGuard, but it is concerned with kernel modules and connecting to an
existing WireGuard VPN, while my goal was to set one up in the first place.
This turned out to be surprisingly easy.
&lt;/p&gt;

&lt;p&gt;
The &lt;code&gt;wireguard-tools&lt;/code&gt; package comes with an executable
&lt;code&gt;wg&lt;/code&gt;, and running &lt;code&gt;wg --help&lt;/code&gt; is enough to guess
how WireGuard works; essentially we need the following two subcommands:
&lt;/p&gt;
&lt;pre&gt;
genkey: Generates a new private key and writes it to stdout
pubkey: Reads a private key from stdin and writes a public key to stdout
&lt;/pre&gt;
&lt;p&gt;
Unlike other VPN, WireGuard appears to be more symmetric in the
sense that it does not distinguish between servers and clients; to talk to
each other, two participants just need to create a pair of public and
private keys each, and then to be made aware of the other's public key.
In our asymmetric situation in which only one of them has a public IPv4,
we will nevertheless distinguish the &lt;i&gt;server&lt;/i&gt;, which is reachable
from everywhere thanks to its IP, and the &lt;i&gt;clients&lt;/i&gt; hidden behind
the CGNAT.
&lt;/p&gt;


&lt;h2&gt;Creating key pairs&lt;/h2&gt;

&lt;p&gt;
In a first step, we create a &lt;i&gt;private&lt;/i&gt; key for the server.
In Guix, secrets are (so far) not handled through
the world-readable store, but as state directly on the machine, and
&lt;code&gt;wireguard-service-type&lt;/code&gt; expects by default the private key in
the file &lt;code&gt;/etc/wireguard/private.key&lt;/code&gt;. So we connect as root
to the server machine and execute
&lt;/p&gt;
&lt;pre&gt;
mkdir /etc/wireguard
umask 077
wg genkey &amp;gt; /etc/wireguard/private.key
&lt;/pre&gt;
&lt;p&gt;
The call to &lt;code&gt;umask&lt;/code&gt; is needed (at least with my shell settings)
to placate the WireGuard warning that the private key file is
world-readable, which indeed defies its purpose. The file contains a short
&lt;a href=&quot;https://en.wikipedia.org/wiki/Base64&quot;&gt;base64&lt;/a&gt;
encoded number such as
&lt;code&gt;GEhlpFGslXfo9We9jhrXham4LztmqSmpdE4ivML4qXc=&lt;/code&gt;.
Given the size (or rather lack thereof) of this number, it looks like
WireGuard uses elliptic curve cryptography with a fixed elliptic curve
and a fixed basepoint of 256 bits, so with a security level of 128 bits.
&lt;/p&gt;
&lt;p&gt;
In a second step, we need to create the corresponding public key using
the command
&lt;/p&gt;
&lt;pre&gt;
wg pubkey &amp;lt; /etc/wireguard/private.key
&lt;/pre&gt;
&lt;p&gt;
This outputs a base64 encoded number of similar size; in our example,
&lt;code&gt;AFL8UecS3GFX3hK8e6yWOK4s5RVrTpvTq2A0pdGuylQ=&lt;/code&gt;.
This public key is in fact not needed by the server, but only by the clients
wishing to connect to it (following a basic principle of asymmetric
cryptography), so we need to stow it away in a file. But since the process
of deriving the public key from a private key is deterministic, we may
actually forget the public key and recreate it when needed. And notice that
the public key is so short that it could even be exchanged on a postcard
or over the phone.
&lt;/p&gt;
&lt;p&gt;
This process of creating a key pair needs to be repeated on each client,
or more generally, each participant in the VPN. Let us assume we have one
client with private key
&lt;code&gt;0G4uhLLeY5NYmg/FobRB0p75wMrGwmmzhuoAdfX243I=&lt;/code&gt; in
&lt;code&gt;/etc/wireguard/private.key&lt;/code&gt; and corresponding public key
&lt;code&gt;BgMzEZPUGAtbSqVPRdzgdLVhAPMLaOzHe7uNFAMVLCk=&lt;/code&gt;.
&lt;/p&gt;


&lt;h2&gt;Setting up the server&lt;/h2&gt;

&lt;p&gt;
Each participant in the WireGuard network uses a private IPv4,
usually from the &lt;code&gt;10.0.0.0/8&lt;/code&gt; range.
We give &lt;code&gt;10.0.0.1&lt;/code&gt; to the server and &lt;code&gt;10.0.0.2&lt;/code&gt;
to the client, and can now follow the
&lt;a href=&quot;https://guix.gnu.org/manual/devel/en/html_node/VPN-Services.html&quot;&gt;documentation&lt;/a&gt;
(you need to scroll down a bit) of &lt;code&gt;wireguard-service-type&lt;/code&gt;
to write the corresponding block in the Guix operating system configuration
of the server:
&lt;/p&gt;
&lt;pre&gt;
(service wireguard-service-type
  (wireguard-configuration
    (addresses '(&amp;quot;10.0.0.1/32&amp;quot;))
    (peers
      (list
        (wireguard-peer
          (name &amp;quot;client&amp;quot;)
          (public-key &amp;quot;BgMzEZPUGAtbSqVPRdzgdLVhAPMLaOzHe7uNFAMVLCk=&amp;quot;)
          (allowed-ips '(&amp;quot;10.0.0.2/32&amp;quot;)))))))
&lt;/pre&gt;
&lt;p&gt;
The &lt;code&gt;addresses&lt;/code&gt; field is in fact the default;
there is also an optional &lt;code&gt;port&lt;/code&gt; field with default 51820.
The &lt;code&gt;peers&lt;/code&gt; field is a list of, well, peers in the VPN which are
allowed to connect to the server (so it is in theory possible to create
strange connection graphs); in this case, we register only one client peer
with an arbitrary name, its public key created above, and its assigned
private IP address.
That is all! Now we can &lt;code&gt;guix system reconfigure&lt;/code&gt;, and
the server is ready.
&lt;/p&gt;


&lt;h2&gt;Setting up the client&lt;/h2&gt;

&lt;p&gt;
As stated above, in principle there does not seem to be a distinction
between clients and server in WireGuard, so the operating system
declaration on the client is similar to that on the server. But I think
that nevertheless, it is necessary to bootstrap the network topology.
And in our case, the inherent distinction between the server machine which
is, say, publicly reachable on the IPv4 &lt;code&gt;198.51.100.0&lt;/code&gt; under the
name &lt;code&gt;vpn.example.org&lt;/code&gt;, and the client hidden by CGNAT needs to
be taken into account. So we need the client to punch a hole into the NAT
and to reach out to the server, which leads to the following service block:
&lt;/p&gt;
&lt;pre&gt;
(service wireguard-service-type
  (wireguard-configuration
    (addresses '(&amp;quot;10.0.0.2/32&amp;quot;))
    (peers
      (list
        (wireguard-peer
          (name &amp;quot;server&amp;quot;)
          (public-key &amp;quot;AFL8UecS3GFX3hK8e6yWOK4s5RVrTpvTq2A0pdGuylQ=&amp;quot;)
          (allowed-ips '(&amp;quot;10.0.0.1/32&amp;quot;))
          (endpoint &amp;quot;198.51.100.0:51820&amp;quot;)
          (keep-alive 60))))))
&lt;/pre&gt;
&lt;p&gt;
The first fields are symmetric to the corresponding fields on the server.
But the additional &lt;code&gt;endpoint&lt;/code&gt; and &lt;code&gt;keep-alive&lt;/code&gt; fields
tell the client to connect to the server on its public IPv4 address (and
the default port 51820) for the initial handshake establishing the session,
and to keep it alive by reconnecting every 60 seconds.
I have tried to use the host name &lt;code&gt;vpn.example.org&lt;/code&gt; instead of
the IPv4, but this ended up being resolved to an IPv6, which did not work.
So &lt;code&gt;guix system reconfigure&lt;/code&gt; the client, wait for at most one
minute, and the VPN is running!
&lt;/p&gt;


&lt;h2&gt;Looking behind the scenes&lt;/h2&gt;

&lt;p&gt;
The following is not necessary for setting up the VPN, but it may be
helpful for trouble shooting; and I was curious to see how the VPN
manifested itself.
Running &lt;code&gt;ifconfig&lt;/code&gt; as root on the client, say, shows a new
interface
&lt;/p&gt;
&lt;pre&gt;
wg0 Link encap:(hwtype unknown)
    inet addr:10.0.0.2  P-t-P:10.0.0.2  Mask:255.255.255.255
…
&lt;/pre&gt;
&lt;p&gt;
and running &lt;code&gt;wg&lt;/code&gt; (again as root) shows the information about
the VPN that we entered into the service description:
&lt;/p&gt;
&lt;pre&gt;
interface: wg0
  public key: BgMzEZPUGAtbSqVPRdzgdLVhAPMLaOzHe7uNFAMVLCk=
  private key: (hidden)
  listening port: 51820

peer: AFL8UecS3GFX3hK8e6yWOK4s5RVrTpvTq2A0pdGuylQ=
  endpoint: 198.51.100.0:51820
  allowed ips: 10.0.0.1/32
  latest handshake: 1 minute, 15 seconds ago
  transfer: 555.77 KiB received, 1.38 MiB sent
  persistent keepalive: every 1 minute
&lt;/pre&gt;


&lt;h2&gt;Finally, connecting from the outside!&lt;/h2&gt;

&lt;p&gt;
But let us not get carried away by the beauty of technology (and
cryptography), but get back to our initial concern: Connect from the
outside to the machine in the home network, which we know under the name
of &lt;i&gt;client&lt;/i&gt;.
This is now just a matter of two hops with &lt;code&gt;ssh&lt;/code&gt;:
First do an &lt;code&gt;ssh vpn.example.org&lt;/code&gt;, and once on the VPN server
machine, a second &lt;code&gt;ssh 10.0.0.2&lt;/code&gt;.
This can be automated by the following entry in &lt;code&gt;.ssh/config&lt;/code&gt;
on the machine from which we try to connect:
&lt;/p&gt;
&lt;pre&gt;
Host client
  Hostname 10.0.0.2
  ProxyJump vpn.example.org
&lt;/pre&gt;
&lt;p&gt;
so that from now on, &lt;code&gt;ssh client&lt;/code&gt; will send us into the
home network.
Voilà, problem solved!
&lt;/p&gt;


&lt;h2&gt;Epilogue&lt;/h2&gt;

&lt;p&gt;
After installing my WireGuard VPN, I talked with a fellow geek from
Aquilenet, who has the same ISP, and who suggested an alternative
solution to me. I should call the hotline and pronounce the magic words
that I would like an “IP rollback” so that servers at home become
accessible. This helps passing the barrier of the first support level.
The next level then initiates the “rollback”, which means going from
IPv6 (plus IPv4 with CGNAT) back to regular IPv4 (without IPv6). After a
few hours or days, the new more or less static (not guaranteed to be so,
but actually not changing) IPv4 address is established, and IPv6 is disabled
in the wireless router provided by the ISP. One can then reenable IPv6
in the router and lives in the best of all worlds – with a static IPv4
address, IPv6 and a WireGuard VPN on top of it all.
&lt;/p&gt;

&lt;/div&gt;</content></entry></feed>