GNU bug report logs - #45006
cuirass: Add remote build support.

Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.

Package: guix-patches; Reported by: Mathieu Othacehe <othacehe@HIDDEN>; dated Wed, 2 Dec 2020 11:06:02 UTC; Maintainer for guix-patches is guix-patches@HIDDEN.

Message received at 45006 <at> debbugs.gnu.org:


Received: (at 45006) by debbugs.gnu.org; 2 Dec 2020 11:26:50 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Dec 02 06:26:50 2020
Received: from localhost ([127.0.0.1]:34536 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1kkQHa-0002RG-5h
	for submit <at> debbugs.gnu.org; Wed, 02 Dec 2020 06:26:50 -0500
Received: from mail-wr1-f49.google.com ([209.85.221.49]:44857)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <zimon.toutoune@HIDDEN>) id 1kkQHX-0002Qj-07
 for 45006 <at> debbugs.gnu.org; Wed, 02 Dec 2020 06:26:47 -0500
Received: by mail-wr1-f49.google.com with SMTP id 64so3397755wra.11
 for <45006 <at> debbugs.gnu.org>; Wed, 02 Dec 2020 03:26:46 -0800 (PST)
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025;
 h=from:to:subject:in-reply-to:references:date:message-id:mime-version
 :content-transfer-encoding;
 bh=rjlsCWayrcUa+/BHeXCTEa2eIiPGoFe/XKXUwDM0VYM=;
 b=TnAENC2nlRhI/zAiq0AOx/mhYJUNMw3fbEEB3TjIboWdGVVflzaapnXFvX735C5H51
 hPsxMzfRpjcoCZRaaCD5Y+4UnJECEgYGGQKLfWcQpT7nXWFmeJ9iNzyR6ZsmqeUEdk5j
 yXZweWmIg42OJG1QCcITNqLZXEe+E7KS0YmwHY3nRD31LOFZtJ/mU0GsO51PcJhAfiaZ
 gXnBQxnBqcQFB5t+1qwK5yuzxLCyk76IuhAMqEUjZ867ITjdYfG557bX/lEzoezahPso
 EBhw4p9QDFj5PFmi8I2vAtgt633EMnE+vMEVsH/L7VGA/ZrdlqqZPqUCX7tiH8ttWNDS
 /GOA==
X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed;
 d=1e100.net; s=20161025;
 h=x-gm-message-state:from:to:subject:in-reply-to:references:date
 :message-id:mime-version:content-transfer-encoding;
 bh=rjlsCWayrcUa+/BHeXCTEa2eIiPGoFe/XKXUwDM0VYM=;
 b=preHnnCngT8S+xVobhyt8O03cEUskT9sBH6WtvFqr/zdaT9is6L7fB5YDxrj/Y7vK7
 ReQttWkfrnpjEKtgV7DRC8At3FJJYZPhA0DdHeo2GzlcGGpMYEIfRLJtMZo+2gLgoZ8P
 qo++OLPXxg0qDBOfKa7LA4pYjPk0kPkZlSEvh9pxuOoakeQd0uVTFy3xO+Bk7l4vbzsb
 qTtBo0evzbYFKno9TTL9cEVNQzHvRkIuQKXbiQenruRfRrnOpz1FGMcitrLk0XESarGs
 hV9tNmNpPgGvrK2iNvUa+B6S9dM53qljePsb15AyaYHnPfdYUEmNp4wUNrraQQuXFdyO
 zsXg==
X-Gm-Message-State: AOAM532PMPy5nyU+0F7QeBPR2L2Ol6iFXiXnRe69Kmuuo8MMfR8Sj1gJ
 CqR9NBX7/oq+GH83P9/oHx9fUClPOhJGVw==
X-Google-Smtp-Source: ABdhPJwuurIkDfBavxyiqngj9GDYfdvolyiwm2qwPjuTUrxsn7Z1Aus3azs/QRA5mnJ/ceg+ccJbLg==
X-Received: by 2002:adf:ff8e:: with SMTP id j14mr2926670wrr.48.1606908400927; 
 Wed, 02 Dec 2020 03:26:40 -0800 (PST)
Received: from lili ([2a01:e0a:59b:9120:65d2:2476:f637:db1e])
 by smtp.gmail.com with ESMTPSA id e3sm1807286wro.90.2020.12.02.03.26.40
 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);
 Wed, 02 Dec 2020 03:26:40 -0800 (PST)
From: zimoun <zimon.toutoune@HIDDEN>
To: Mathieu Othacehe <othacehe@HIDDEN>, 45006 <at> debbugs.gnu.org
Subject: Re: [bug#45006] cuirass: Add remote build support.
In-Reply-To: <87czzso4dj.fsf@HIDDEN>
References: <87czzso4dj.fsf@HIDDEN>
Date: Wed, 02 Dec 2020 12:25:41 +0100
Message-ID: <86pn3s1mbu.fsf@HIDDEN>
MIME-Version: 1.0
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
X-Spam-Score: 0.0 (/)
X-Debbugs-Envelope-To: 45006
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>
X-Spam-Score: -1.0 (-)

Hi Mathieu,

On Wed, 02 Dec 2020 at 12:04, Mathieu Othacehe <othacehe@HIDDEN> wrote:

> Here's a patch adding remote build support to Cuirass, as presented
> during Guix Days[1]. The concept is the following:

Neat!  You implemented the =E2=80=9Cdynamic offloading=E2=80=9D in Cuirass.=
 \o/

What about the store?  And the outputs?


> This is still a bit rough on the edges, but I have tested it on berlin
> spawning ~30 workers and building ~10K derivations, it seems to work
> fine.

~30 workers on ~30 different machines?  Or are some workers running on
the same node?


All the best,
simon




Information forwarded to guix-patches@HIDDEN:
bug#45006; Package guix-patches. Full text available.

Message received at submit <at> debbugs.gnu.org:


Received: (at submit) by debbugs.gnu.org; 2 Dec 2020 11:05:03 +0000
From debbugs-submit-bounces <at> debbugs.gnu.org Wed Dec 02 06:05:03 2020
Received: from localhost ([127.0.0.1]:34503 helo=debbugs.gnu.org)
	by debbugs.gnu.org with esmtp (Exim 4.84_2)
	(envelope-from <debbugs-submit-bounces <at> debbugs.gnu.org>)
	id 1kkPwV-0001pW-Om
	for submit <at> debbugs.gnu.org; Wed, 02 Dec 2020 06:05:03 -0500
Received: from lists.gnu.org ([209.51.188.17]:53274)
 by debbugs.gnu.org with esmtp (Exim 4.84_2)
 (envelope-from <othacehe@HIDDEN>) id 1kkPwU-0001pE-CR
 for submit <at> debbugs.gnu.org; Wed, 02 Dec 2020 06:05:02 -0500
Received: from eggs.gnu.org ([2001:470:142:3::10]:37864)
 by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)
 (Exim 4.90_1) (envelope-from <othacehe@HIDDEN>) id 1kkPwR-0003yn-Rx
 for guix-patches@HIDDEN; Wed, 02 Dec 2020 06:05:02 -0500
Received: from fencepost.gnu.org ([2001:470:142:3::e]:47393)
 by eggs.gnu.org with esmtp (Exim 4.90_1)
 (envelope-from <othacehe@HIDDEN>) id 1kkPwR-0003NP-JO
 for guix-patches@HIDDEN; Wed, 02 Dec 2020 06:04:59 -0500
Received: from [2a01:e0a:19b:d9a0:35e9:f6a0:9d50:eebf] (port=50492 helo=cervin)
 by fencepost.gnu.org with esmtpsa (TLS1.2:RSA_AES_256_CBC_SHA1:256)
 (Exim 4.82) (envelope-from <othacehe@HIDDEN>) id 1kkPwQ-00052l-7h
 for guix-patches@HIDDEN; Wed, 02 Dec 2020 06:04:59 -0500
From: Mathieu Othacehe <othacehe@HIDDEN>
To: guix-patches@HIDDEN
Subject: cuirass: Add remote build support.
Date: Wed, 02 Dec 2020 12:04:56 +0100
Message-ID: <87czzso4dj.fsf@HIDDEN>
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.1 (gnu/linux)
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="=-=-="
X-Debbugs-Envelope-To: submit
X-BeenThere: debbugs-submit <at> debbugs.gnu.org
X-Mailman-Version: 2.1.18
Precedence: list
List-Id: <debbugs-submit.debbugs.gnu.org>
List-Unsubscribe: <https://debbugs.gnu.org/cgi-bin/mailman/options/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=unsubscribe>
List-Archive: <https://debbugs.gnu.org/cgi-bin/mailman/private/debbugs-submit/>
List-Post: <mailto:debbugs-submit <at> debbugs.gnu.org>
List-Help: <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=help>
List-Subscribe: <https://debbugs.gnu.org/cgi-bin/mailman/listinfo/debbugs-submit>, 
 <mailto:debbugs-submit-request <at> debbugs.gnu.org?subject=subscribe>
Errors-To: debbugs-submit-bounces <at> debbugs.gnu.org
Sender: "Debbugs-submit" <debbugs-submit-bounces <at> debbugs.gnu.org>

--=-=-=
Content-Type: text/plain


Hello,

Here's a patch adding remote build support to Cuirass, as presented
during Guix Days[1]. The concept is the following:

* Cuirass talks to a "remote server" instead of the "guix-daemon" to
build derivations when the "--build-remote" option is passed.

* The "remote server" is advertised using Avahi. It queues the received
build requests. It also starts a "publish" server.

* The "remote workers" discover the "remote server" using Avahi, connect
to it and request some builds. The "remote server" publish server is
added to the workers "guix-daemon" substitute urls list.

* On build completion, the "remote server" downloads the build outputs
as nar and narinfo files from the worker "publish" server and store them
in a cache directory. It can also add them to the store if the
"--add-to-store" option is passed.

* Cuirass is notified by the "remote server" when a build starts, fails
or completes and can update its database accordingly.

* The communication between Cuirass, the "remote server" and the "remote
workers" is done by sending SEXP over ZMQ.

This is still a bit rough on the edges, but I have tested it on berlin
spawning ~30 workers and building ~10K derivations, it seems to work
fine.

The corresponding patch and an architecture overview diagram are attached.

Thanks,

Mathieu

[1]:
https://xana.lepiller.eu/guix-days-2020/guix-days-2020-mathieu-otacehe-fixing-the-ci.mp4

--=-=-=
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: inline; filename=0001-Add-remote-build-support.patch
Content-Transfer-Encoding: quoted-printable

From 94898f67e1dca6152c434ff50e860691ce813018 Mon Sep 17 00:00:00 2001
From: Mathieu Othacehe <othacehe@HIDDEN>
Date: Wed, 2 Dec 2020 11:13:33 +0100
Subject: [PATCH] Add remote build support.

* src/cuirass/remote.scm: New file.
* src/cuirass/remote-server.scm: New file.
* src/cuirass/remote-worker.scm: New file.
* bin/remote-server.in: New file.
* bin/remote-worker.in: New file.
* Makefile.am (bin_SCRIPTS): Add new binaries,
(dist_pkgmodule_DATA): add new files,
(EXTRA_DIST): add new binaries,
(bin/remote-server, bin/remote-worker): new targets.
* .gitignore: Add new binaries.
* bin/cuirass.in (%options): Add "--build-remote" option,
(show-help): document it,
(main): honor it.
* src/cuirass/base.scm (with-build-offload-thread): New macro,
(%build-remote?, %build-offload-channel): new parameters,
(make-build-offload-thread): new procedure,
(build-derivations/offload): new procedure,
(restart-builds): use it to offload builds when %build-remote? is set,
(build-packages): ditto.
---
 .gitignore                    |   2 +
 Makefile.am                   |  16 +-
 bin/cuirass.in                | 162 ++++++-----
 bin/remote-server.in          |  29 ++
 bin/remote-worker.in          |  29 ++
 src/cuirass/base.scm          |  65 ++++-
 src/cuirass/remote-server.scm | 518 ++++++++++++++++++++++++++++++++++
 src/cuirass/remote-worker.scm | 286 +++++++++++++++++++
 src/cuirass/remote.scm        | 292 +++++++++++++++++++
 9 files changed, 1318 insertions(+), 81 deletions(-)
 create mode 100644 bin/remote-server.in
 create mode 100644 bin/remote-worker.in
 create mode 100644 src/cuirass/remote-server.scm
 create mode 100644 src/cuirass/remote-worker.scm
 create mode 100644 src/cuirass/remote.scm

diff --git a/.gitignore b/.gitignore
index beabf29..7cd0e1f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,8 @@
 /bin/cuirass
 /bin/cuirass-send-events
 /bin/evaluate
+/bin/remote-server
+/bin/remote-worker
 /build-aux/config.guess
 /build-aux/config.sub
 /build-aux/install-sh
diff --git a/Makefile.am b/Makefile.am
index 17a73f0..270c0ed 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -22,7 +22,13 @@
 # You should have received a copy of the GNU General Public License
 # along with Cuirass.  If not, see <http://www.gnu.org/licenses/>.
=20
-bin_SCRIPTS =3D bin/cuirass bin/cuirass-send-events bin/evaluate
+bin_SCRIPTS =3D                    \
+  bin/cuirass      	  	 \
+  bin/cuirass-send-events 	 \
+  bin/evaluate      		 \
+  bin/remote-server		 \
+  bin/remote-worker
+
 noinst_SCRIPTS =3D pre-inst-env
=20
 guilesitedir =3D $(datarootdir)/guile/site/@GUILE_EFFECTIVE_VERSION@
@@ -48,6 +54,9 @@ dist_pkgmodule_DATA =3D				\
   src/cuirass/http.scm				\
   src/cuirass/logging.scm			\
   src/cuirass/metrics.scm			\
+  src/cuirass/remote.scm			\
+  src/cuirass/remote-server.scm			\
+  src/cuirass/remote-worker.scm			\
   src/cuirass/send-events.scm			\
   src/cuirass/ui.scm				\
   src/cuirass/utils.scm		                \
@@ -166,6 +175,8 @@ EXTRA_DIST =3D \
   bin/cuirass.in \
   bin/cuirass-send-events.in \
   bin/evaluate.in \
+  bin/remote-server.in \
+  bin/remote-worker.in \
   bootstrap \
   build-aux/guix.scm \
   src/cuirass/config.scm.in \
@@ -226,6 +237,9 @@ generate_file =3D \
 bin/cuirass: $(srcdir)/bin/cuirass.in
 bin/cuirass-send-events: $(srcdir)/bin/cuirass-send-events.in
 bin/evaluate: $(srcdir)/bin/evaluate.in
+bin/remote-server: $(srcdir)/bin/remote-server.in
+bin/remote-worker: $(srcdir)/bin/remote-worker.in
+
 $(bin_SCRIPTS): Makefile
 	$(generate_file); chmod +x $@
 src/cuirass/config.scm: $(srcdir)/src/cuirass/config.scm.in Makefile
diff --git a/bin/cuirass.in b/bin/cuirass.in
index aef4a65..ac9811c 100644
--- a/bin/cuirass.in
+++ b/bin/cuirass.in
@@ -57,6 +57,7 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" =
"$@"
       --listen=3DHOST         Listen on the network interface for HOST
   -I, --interval=3DN          Wait N seconds between each poll
       --log-queries=3DFILE    Log SQL queries in FILE.
+      --build-remote        Use the remote build mechanism
       --use-substitutes     Allow usage of pre-built substitutes
       --record-events       Record events for distribution
       --threads=3DN           Use up to N kernel threads
@@ -74,6 +75,7 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" =
"$@"
     (port           (single-char #\p) (value #t))
     (listen                           (value #t))
     (interval       (single-char #\I) (value #t))
+    (build-remote                     (value #f))
     (use-substitutes                  (value #f))
     (threads                          (value #t))
     (fallback                         (value #f))
@@ -100,6 +102,7 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0=
" "$@"
          (%package-database (option-ref opts 'database (%package-database)=
))
          (%package-cachedir
           (option-ref opts 'cache-directory (%package-cachedir)))
+         (%build-remote? (option-ref opts 'build-remote #f))
          (%use-substitutes? (option-ref opts 'use-substitutes #f))
          (%fallback? (option-ref opts 'fallback #f))
          (%record-events? (option-ref opts 'record-events #f))
@@ -141,84 +144,87 @@ exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "=
$0" "$@"
            (lambda ()
              (with-database
                (with-queue-writer-worker
-                (and specfile
-                     (let ((new-specs (save-module-excursion
-                                       (lambda ()
-                                         (set-current-module (make-user-mo=
dule '()))
-                                         (primitive-load specfile)))))
-                       (for-each db-add-specification new-specs)))
-
-                (when queries-file
-                  (log-message "Enable SQL query logging.")
-                  (db-log-queries queries-file))
-
-                (if one-shot?
-                    (process-specs (db-get-specifications))
-                    (let ((exit-channel (make-channel)))
-                      (start-watchdog)
-                      (if (option-ref opts 'web #f)
-                          (begin
-                            (spawn-fiber
-                             (essential-task
-                              'web exit-channel
-                              (lambda ()
-                                (run-cuirass-server #:host host #:port por=
t)))
-                             #:parallel? #t)
-
-                            (spawn-fiber
-                             (essential-task
-                              'monitor exit-channel
-                              (lambda ()
-                                (while #t
-                                  (log-monitoring-stats)
-                                  (sleep 600))))))
-
-                          (begin
-                            (clear-build-queue)
-
-                            ;; If Cuirass was stopped during an evaluation,
-                            ;; abort it. Builds that were not registered
-                            ;; during this evaluation will be registered
-                            ;; during the next evaluation.
-                            (db-abort-pending-evaluations)
-
-                            ;; First off, restart builds that had not
-                            ;; completed or were not even started on a
-                            ;; previous run.
-                            (spawn-fiber
-                             (essential-task
-                              'restart-builds exit-channel
-                              (lambda ()
-                                (restart-builds))))
-
-                            (spawn-fiber
-                             (essential-task
-                              'build exit-channel
-                              (lambda ()
-                                (while #t
-                                  (process-specs (db-get-specifications))
-                                  (log-message
-                                   "next evaluation in ~a seconds" interva=
l)
-                                  (sleep interval)))))
-
-                            (spawn-fiber
-                             (essential-task
-                              'metrics exit-channel
-                              (lambda ()
-                                (while #t
-                                  (with-time-logging
-                                   "Metrics update"
-                                   (db-update-metrics))
-                                  (sleep 3600)))))
-
-                            (spawn-fiber
-                             (essential-task
-                              'monitor exit-channel
-                              (lambda ()
-                                (while #t
-                                  (log-monitoring-stats)
-                                  (sleep 600)))))))
-                      (primitive-exit (get-message exit-channel)))))))
+                 (with-build-offload-thread
+                  (and specfile
+                       (let ((new-specs (save-module-excursion
+                                         (lambda ()
+                                           (set-current-module
+                                            (make-user-module '()))
+                                           (primitive-load specfile)))))
+                         (for-each db-add-specification new-specs)))
+
+                  (when queries-file
+                    (log-message "Enable SQL query logging.")
+                    (db-log-queries queries-file))
+
+                  (if one-shot?
+                      (process-specs (db-get-specifications))
+                      (let ((exit-channel (make-channel)))
+                        (start-watchdog)
+                        (if (option-ref opts 'web #f)
+                            (begin
+                              (spawn-fiber
+                               (essential-task
+                                'web exit-channel
+                                (lambda ()
+                                  (run-cuirass-server #:host host
+                                                      #:port port)))
+                               #:parallel? #t)
+
+                              (spawn-fiber
+                               (essential-task
+                                'monitor exit-channel
+                                (lambda ()
+                                  (while #t
+                                    (log-monitoring-stats)
+                                    (sleep 600))))))
+
+                            (begin
+                              (clear-build-queue)
+
+                              ;; If Cuirass was stopped during an evaluati=
on,
+                              ;; abort it. Builds that were not registered
+                              ;; during this evaluation will be registered
+                              ;; during the next evaluation.
+                              (db-abort-pending-evaluations)
+
+                              ;; First off, restart builds that had not
+                              ;; completed or were not even started on a
+                              ;; previous run.
+                              (spawn-fiber
+                               (essential-task
+                                'restart-builds exit-channel
+                                (lambda ()
+                                  (restart-builds))))
+
+                              (spawn-fiber
+                               (essential-task
+                                'build exit-channel
+                                (lambda ()
+                                  (while #t
+                                    (process-specs (db-get-specifications))
+                                    (log-message
+                                     "next evaluation in ~a seconds" inter=
val)
+                                    (sleep interval)))))
+
+                              (spawn-fiber
+                               (essential-task
+                                'metrics exit-channel
+                                (lambda ()
+                                  (while #t
+                                    (with-time-logging
+                                     "Metrics update"
+                                     (db-update-metrics))
+                                    (sleep 3600)))))
+
+                              (spawn-fiber
+                               (essential-task
+                                'monitor exit-channel
+                                (lambda ()
+                                  (while #t
+                                    (log-monitoring-stats)
+                                    (sleep 600)))))))
+                        (primitive-exit (get-message exit-channel))))))))
=20
            ;; Most of our code is I/O so preemption doesn't matter much (it
            ;; could help while we're doing SQL requests, for instance, but=
 it
diff --git a/bin/remote-server.in b/bin/remote-server.in
new file mode 100644
index 0000000..6425d51
--- /dev/null
+++ b/bin/remote-server.in
@@ -0,0 +1,29 @@
+#!/bin/sh
+# -*- scheme -*-
+# @configure_input@
+#GUILE_LOAD_PATH=3D"@PACKAGE_LOAD_PATH@${GUILE_LOAD_PATH:+:}$GUILE_LOAD_PA=
TH"
+#GUILE_LOAD_COMPILED_PATH=3D"@PACKAGE_LOAD_COMPILED_PATH@${GUILE_LOAD_COMP=
ILED_PATH:+:}$GUILE_LOAD_COMPILED_PATH"
+exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" "$@"
+!#
+;;; remote-server.in -- Remote build server.
+;;; Copyright =C2=A9 2020 Mathieu Othacehe <othacehe@HIDDEN>
+;;;
+;;; This file is part of Cuirass.
+;;;
+;;; Cuirass is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation, either version 3 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; Cuirass is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Cuirass.  If not, see <http://www.gnu.org/licenses/>.
+
+(use-modules (cuirass remote-server))
+
+(define* (main #:optional (args (command-line)))
+  (remote-server (cdr args)))
diff --git a/bin/remote-worker.in b/bin/remote-worker.in
new file mode 100644
index 0000000..8a3830c
--- /dev/null
+++ b/bin/remote-worker.in
@@ -0,0 +1,29 @@
+#!/bin/sh
+# -*- scheme -*-
+# @configure_input@
+#GUILE_LOAD_PATH=3D"@PACKAGE_LOAD_PATH@${GUILE_LOAD_PATH:+:}$GUILE_LOAD_PA=
TH"
+#GUILE_LOAD_COMPILED_PATH=3D"@PACKAGE_LOAD_COMPILED_PATH@${GUILE_LOAD_COMP=
ILED_PATH:+:}$GUILE_LOAD_COMPILED_PATH"
+exec ${GUILE:-@GUILE@} --no-auto-compile -e main -s "$0" "$@"
+!#
+;;; remote-worker.in -- Remote build worker.
+;;; Copyright =C2=A9 2020 Mathieu Othacehe <othacehe@HIDDEN>
+;;;
+;;; This file is part of Cuirass.
+;;;
+;;; Cuirass is free software: you can redistribute it and/or modify
+;;; it under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation, either version 3 of the License, or
+;;; (at your option) any later version.
+;;;
+;;; Cuirass is distributed in the hope that it will be useful,
+;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with Cuirass.  If not, see <http://www.gnu.org/licenses/>.
+
+(use-modules (cuirass remote-worker))
+
+(define* (main #:optional (args (command-line)))
+  (remote-worker (cdr args)))
diff --git a/src/cuirass/base.scm b/src/cuirass/base.scm
index c3ce900..c526f5c 100644
--- a/src/cuirass/base.scm
+++ b/src/cuirass/base.scm
@@ -22,8 +22,10 @@
=20
 (define-module (cuirass base)
   #:use-module (fibers)
+  #:use-module (fibers channels)
   #:use-module (cuirass logging)
   #:use-module (cuirass database)
+  #:use-module (cuirass remote)
   #:use-module (cuirass utils)
   #:use-module ((cuirass config) #:select (%localstatedir))
   #:use-module (gnu packages)
@@ -36,9 +38,13 @@
   #:use-module ((guix config) #:select (%state-directory))
   #:use-module (git)
   #:use-module (ice-9 binary-ports)
+  #:use-module ((ice-9 suspendable-ports)
+                #:select (current-read-waiter
+                          current-write-waiter))
   #:use-module (ice-9 format)
   #:use-module (ice-9 match)
   #:use-module (ice-9 popen)
+  #:use-module (ice-9 ports internal)
   #:use-module (ice-9 rdelim)
   #:use-module (ice-9 receive)
   #:use-module (ice-9 regex)
@@ -65,11 +71,13 @@
             prepare-git
             process-specs
             evaluation-log-file
+            with-build-offload-thread
=20
             ;; Parameters.
             %package-cachedir
             %gc-root-directory
             %gc-root-ttl
+            %build-remote?
             %use-substitutes?
             %fallback?))
=20
@@ -102,6 +110,10 @@
    (define time-monotonic time-tai))
   (else #t))
=20
+(define %build-remote?
+  ;; Define whether to use the remote build mechanism.
+  (make-parameter #f))
+
 (define %use-substitutes?
   ;; Define whether to use substitutes
   (make-parameter #f))
@@ -110,6 +122,10 @@
   ;; Define whether to fall back to building when the substituter fails.
   (make-parameter #f))
=20
+(define %build-offload-channel
+  ;; Channel to communicate with the remote build server.
+  (make-parameter #f))
+
 (define %package-cachedir
   ;; Define to location of cache directory of this package.
   (make-parameter (or (getenv "CUIRASS_CACHEDIR")
@@ -436,6 +452,39 @@ Essentially this procedure inverts the inversion-of-co=
ntrol that
                   (raise c))
                  (x x)))))))
=20
+(define (make-build-offload-thread)
+  "Return a channel used to offload builds by communicating with the remote
+build server in a separate thread.  The spawned thread also polls for build
+events sent by the remote server and calls HANDLE-BUILD-EVENT to register =
them
+in the database."
+  (let ((channel (make-channel)))
+    (call-with-new-thread
+     (lambda ()
+       (parameterize (((@@ (fibers internal) current-fiber) #f)
+                      (current-read-waiter (lambda (port)
+                                             (port-poll port "r")))
+                      (current-write-waiter (lambda (port)
+                                              (port-poll port "w"))))
+         (let ((socket (remote-build-socket)))
+           (let loop ()
+             (remote-build-poll socket handle-build-event)
+             (match (get-message-with-timeout channel
+                                              #:seconds 1
+                                              #:retry? #f)
+               ((drvs . systems)
+                (remote-build socket drvs systems))
+               ('timeout #f))
+             (loop))))))
+    channel))
+
+(define-syntax-rule (with-build-offload-thread body ...)
+  (parameterize ((%build-offload-channel
+                  (make-build-offload-thread)))
+    body ...))
+
+(define (build-derivations/offload drvs systems)
+  (put-message (%build-offload-channel) (cons drvs systems)))
+
 
 ;;;
 ;;; Building packages.
@@ -641,7 +690,14 @@ started)."
       ;; Those in VALID can be restarted.  If some of them were built in t=
he
       ;; meantime behind our back, that's fine: 'spawn-builds' will DTRT.
       (log-message "restarting ~a pending builds" (length valid))
-      (spawn-builds store valid)
+      (if (%build-remote?)
+          (let* ((builds (map db-get-build valid))
+                 (systems (map (cut assq-ref <> #:system) builds)))
+            ;; The system could by read from the store by the remote build
+            ;; server using the derivation name, but it is far less expens=
ive
+            ;; to read it from the database.
+            (build-derivations/offload valid systems))
+          (spawn-builds store valid))
       (log-message "done with restarted builds"))))
=20
 (define (create-build-outputs build product-specs)
@@ -690,7 +746,12 @@ by PRODUCT-SPECS."
   (db-set-evaluation-status eval-id
                             (evaluation-status succeeded))
=20
-  (spawn-builds store derivations)
+  (if (%build-remote?)
+      (let* ((builds (map db-get-build derivations))
+             (systems (map (cut assq-ref <> #:system) builds)))
+        ;; See the comment above regarding system read.
+        (build-derivations/offload derivations systems))
+      (spawn-builds store derivations))
=20
   (let* ((results (filter-map (cut db-get-build <>) derivations))
          (status (map (cut assq-ref <> #:status) results))
diff --git a/src/cuirass/remote-server.scm b/src/cuirass/remote-server.scm
new file mode 100644
index 0000000..6217918
--- /dev/null
+++ b/src/cuirass/remote-server.scm
@@ -0,0 +1,518 @@
+;;; remote-server.scm -- Remote build server.
+;;; Copyright =C2=A9 2020 Mathieu Othacehe <othacehe@HIDDEN>
+;;;
+;;; This file is part of Cuirass.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (cuirass remote-server)
+  #:use-module (cuirass base)
+  #:use-module (cuirass remote)
+  #:use-module (gcrypt pk-crypto)
+  #:use-module (guix avahi)
+  #:use-module (guix base32)
+  #:use-module (guix base64)
+  #:use-module (guix config)
+  #:use-module (guix derivations)
+  #:use-module (guix records)
+  #:use-module (guix packages)
+  #:use-module (guix pki)
+  #:use-module (guix scripts)
+  #:use-module (guix store)
+  #:use-module (guix ui)
+  #:use-module (guix workers)
+  #:use-module (guix build download)
+  #:use-module (guix build syscalls)
+  #:use-module (gcrypt hash)
+  #:use-module (gcrypt pk-crypto)
+  #:use-module (simple-zmq)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 atomic)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 q)
+  #:use-module (ice-9 rdelim)
+  #:use-module (ice-9 regex)
+  #:use-module (ice-9 threads)
+
+  #:export (remote-server))
+
+;; Indicate if the process has to be stopped.
+(define %stop-process?
+  (make-atomic-box #f))
+
+;; Whether to add build items to the store.
+(define %add-to-store?
+  (make-parameter #f))
+
+(define %cache-directory
+  (make-parameter #f))
+
+(define %private-key
+  (make-parameter #f))
+
+(define %public-key
+  (make-parameter #f))
+
+(define service-name
+  "Cuirass remote server")
+
+(define (show-help)
+  (format #t (G_ "Usage: remote-server [OPTION]...
+Start a remote build server.\n"))
+  (display (G_ "
+  -a, --add-to-store        register built items to the store"))
+  (display (G_ "
+  -b, --backend-port=3DPORT   listen worker connections on PORT"))
+  (display (G_ "
+  -p, --publish-port=3DPORT   publish substitutes on PORT"))
+  (display (G_ "
+  -c, --cache=3DDIRECTORY     cache built items to DIRECTORY"))
+  (display (G_ "
+      --public-key=3DFILE     use FILE as the public key for signatures"))
+  (display (G_ "
+      --private-key=3DFILE    use FILE as the private key for signatures"))
+  (newline)
+  (display (G_ "
+  -h, --help                display this help and exit"))
+  (display (G_ "
+  -V, --version             display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  (list (option '(#\h "help") #f #f
+                (lambda _
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda _
+                  (show-version-and-exit "guix publish")))
+        (option '(#\a "add-to-store") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'add-to-store? arg result)))
+        (option '(#\b "backend-port") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'backend-port (string->number* arg) result)))
+        (option '(#\p "publish-port") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'publish-port (string->number* arg) result)))
+        (option '(#\c "cache") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'cache arg result)))
+        (option '("public-key") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'public-key-file arg result)))
+        (option '("private-key") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'private-key-file arg result)))))
+
+(define %default-options
+  `((backend-port . 5555)
+    (publish-port . 5556)
+    (public-key-file . ,%public-key-file)
+    (private-key-file . ,%private-key-file)))
+
+
+;;;
+;;; Build workers.
+;;;
+
+(define %workers
+  ;; Set of connected workers.
+  (make-hash-table))
+
+(define %build-queues
+  ;; Builds request queue.
+  (map (lambda (system)
+         (cons system (make-q)))
+       %supported-systems))
+
+(define (find-system-queues systems)
+  "Return the list of build queues for SYSTEMS that are not empty."
+  (filter-map (match-lambda
+                ((system . queue)
+                 (and (member system systems)
+                      (not (q-empty? queue))
+                      queue)))
+              %build-queues))
+
+(define (build-available? name)
+  "Return #t if there is some available work for the worker with the given
+NAME and #f otherwise."
+  (let* ((worker (hash-ref %workers name))
+         (systems (worker-systems worker))
+         (queues (find-system-queues systems)))
+    (not (null? queues))))
+
+(define (pop-random-build name)
+  "Pop randomly and return a build from all the build queues with available
+work for the worker with the given NAME."
+  (define (random-queue queues)
+    (list-ref queues (random (length queues))))
+
+  (let* ((worker (hash-ref %workers name))
+         (systems (worker-systems worker))
+         (queues (find-system-queues systems)))
+    (q-pop! (random-queue queues))))
+
+(define* (read-client-exp client exp)
+  "Read the given EXP sent by CLIENT."
+  (catch 'system-error
+    (lambda ()
+      (match (zmq-read-message exp)
+        (('build ('drv drv) ('system system))
+         (let ((system (or system
+                           (derivation-system
+                            (read-derivation-from-file drv)))))
+           ;; Push the derivation to the matching queue according to the
+           ;; targeted system. Also save the client ID in the queue to be =
able
+           ;; to send it build events later on.
+           (q-push! (assoc-ref %build-queues system)
+                    (list client drv))))))
+    (const #f)))
+
+(define* (read-worker-exp exp #:key reply-worker)
+  "Read the given EXP sent by a worker.  REPLY-WORKER is a procedure that =
can
+be used to reply to the worker."
+  (match (zmq-read-message exp)
+    (('worker-ready worker)
+     (let* ((worker* (sexp->worker worker))
+            (name (worker-name worker*)))
+       (info (G_ "Worker `~a' is ready.~%") name)
+       (hash-set! %workers name worker*)))
+    (('worker-request-work name)
+     (if (build-available? name)
+         (match (pop-random-build name)
+           ((client drv)
+            (reply-worker client (zmq-build-request-message drv))))
+         (reply-worker
+          (zmq-empty-delimiter)
+          (zmq-no-build-message))))))
+
+
+;;;
+;;; Fetch workers.
+;;;
+
+(define (zmq-fetch-workers-endpoint)
+  "inproc://fetch-workers")
+
+(define (zmq-fetch-worker-socket)
+  "Return a socket used to communicate with the fetch workers."
+  (let ((socket (zmq-create-socket %zmq-context ZMQ_DEALER))
+        (endpoint (zmq-fetch-workers-endpoint)))
+    (zmq-connect socket endpoint)
+    socket))
+
+(define (strip-store-prefix file)
+  ; Given a file name like "/gnu/store/=E2=80=A6-foo-1.2/bin/foo", return
+  ;; "/bin/foo".
+  (let* ((len  (string-length %store-directory))
+         (base (string-drop file (+ 1 len))))
+    (match (string-index base #\/)
+      (#f    base)
+      (index (string-drop base index)))))
+
+(define (publish-nar-url publish-url store-hash)
+  "Return the URL of STORE-HASH nar substitute on PUBLISH-URL."
+  (format #f "~a/nar/gzip/~a" publish-url store-hash))
+
+(define (publish-narinfo-url publish-url store-hash)
+  "Return the URL of STORE-HASH narinfo file on PUBLISH-URL."
+  (let ((hash (and=3D> (string-index store-hash #\-)
+                     (cut string-take store-hash <>))))
+    (format #f "~a/~a.narinfo" publish-url hash)))
+
+(define (nar-path cache-directory output)
+  "Return the path of the NAR file for OUTPUT in CACHE-DIRECTORY."
+  (string-append cache-directory "/" (basename output) ".nar"))
+
+(define (narinfo-path cache-directory output)
+  "Return the path of the NARINFO file for OUTPUT in CACHE-DIRECTORY."
+  (string-append cache-directory "/" (basename output) ".narinfo"))
+
+(define* (sign-narinfo! narinfo)
+  "Edit the given NARINFO file to replace the worker signature by the remo=
te
+build server signature."
+  (define (signed-string s)
+    (let* ((hash (bytevector->hash-data (sha256 (string->utf8 s))
+                                        #:key-type (key-type (%public-key)=
))))
+      (signature-sexp hash (%private-key) (%public-key))))
+
+  (define base64-encode-string
+    (compose base64-encode string->utf8))
+
+  (define lines
+    (call-with-input-file narinfo
+      (lambda (port)
+        (let loop ((line (read-line port))
+                   (lines '()))
+          (if (eof-object? line)
+              (reverse lines)
+              (loop (read-line port)
+                    (cons line lines)))))))
+  (let* ((lines
+          (filter (lambda (line)
+                    (not (string-match "^Signature:" line)))
+                  lines))
+         (info (format #f "~a~%" (string-join lines "\n")))
+         (signature (base64-encode-string
+                     (canonical-sexp->string (signed-string info)))))
+    (call-with-output-file narinfo
+      (lambda (port)
+        (format port "~aSignature: 1;~a;~a~%"
+                info (gethostname) signature)))))
+
+(define (download-nar cache-directory outputs url)
+  "Download in CACHE-DIRECTORY the OUTPUTS from the substitute server at U=
RL."
+  (for-each
+   (lambda (output)
+     (let* ((path (derivation-output-path output))
+            (store-hash (strip-store-prefix path))
+            (nar-file (nar-path cache-directory store-hash))
+            (narinfo-file (narinfo-path cache-directory store-hash))
+            (nar-url (publish-nar-url url store-hash))
+            (narinfo-url (publish-narinfo-url url store-hash)))
+       (unless (file-exists? nar-file)
+         (url-fetch nar-url nar-file))
+
+       (unless (file-exists? narinfo-file)
+         (url-fetch narinfo-url narinfo-file)
+         (sign-narinfo! narinfo-file))))
+   outputs))
+
+(define (add-to-store outputs url)
+  "Add the OUTPUTS that are available from the substitute server at URL to=
 the
+store."
+  (with-store store
+    (for-each (lambda (output)
+                (add-substitute-url store url)
+                (ensure-path store output))
+              (map derivation-output-path outputs))))
+
+(define (need-fetching? message)
+  "Return #t if the received MESSAGE implies that some output fetching is
+required and #f otherwise."
+  (match (zmq-read-message message)
+    (('build-succeeded ('drv drv) ('url url))
+     #t)
+    (else #f)))
+
+(define* (run-fetch message #:key reply)
+  "Read MESSAGE and download the corresponding build outputs.  If
+%CACHE-DIRECTORY is set, download the matching NAR and NARINFO files in th=
is
+directory.  If %ADD-TO-STORE? is set, add the build outputs to the store.
+
+REPLY is procedure used to forward MESSAGE to the client once the build
+outputs are downloaded."
+  (define (build-outputs drv)
+    (catch 'system-error
+      (lambda ()
+        (map (match-lambda
+               ((output-name . output)
+                output))
+             (derivation-outputs
+              (read-derivation-from-file drv))))
+      (const '())))
+
+  (match (zmq-read-message message)
+    (('build-succeeded ('drv drv) ('url url))
+     (let ((outputs (build-outputs drv)))
+       (when (%add-to-store?)
+         (add-to-store outputs url))
+       (when (%cache-directory)
+         (download-nar (%cache-directory) outputs url))
+       (reply message)))))
+
+(define (start-fetch-worker name)
+  "Start a fetch worker thread with the given NAME.  This worker takes car=
e of
+downloading build outputs.  It communicates with the remote server using a=
 ZMQ
+socket."
+  (define (reply socket client)
+    (lambda (message)
+      (zmq-send-msg-parts-bytevector
+       socket
+       (list client (zmq-empty-delimiter) (string->bv message)))))
+
+  (call-with-new-thread
+   (lambda ()
+     (set-thread-name name)
+     (let ((socket (zmq-fetch-worker-socket)))
+       (let loop ()
+         (match (zmq-get-msg-parts-bytevector socket '())
+           ((client empty rest)
+            (let ((message (bv->string rest)))
+              (run-fetch (bv->string rest)
+                         #:reply (reply socket client)))))
+         (loop))))))
+
+
+;;;
+;;; ZMQ connection.
+;;;
+
+(define %zmq-context
+  (zmq-create-context))
+
+(define (zmq-backend-endpoint backend-port)
+  "Return a ZMQ endpoint string allowing TCP connections on BACKEND-PORT f=
rom
+all network interfaces."
+  (string-append "tcp://*:" (number->string backend-port)))
+
+(define (zmq-start-proxy backend-port)
+  "This procedure starts a proxy between client connections from the IPC
+frontend to the workers connected through the TCP backend."
+  (define (socket-ready? items socket)
+    (find (lambda (item)
+            (eq? (poll-item-socket item) socket))
+          items))
+
+  (let* ((client-socket
+          (zmq-create-socket %zmq-context ZMQ_ROUTER))
+         (build-socket
+          (zmq-create-socket %zmq-context ZMQ_ROUTER))
+         (fetch-socket
+          (zmq-create-socket %zmq-context ZMQ_DEALER))
+         (poll-items (list
+                      (poll-item client-socket ZMQ_POLLIN)
+                      (poll-item build-socket ZMQ_POLLIN)
+                      (poll-item fetch-socket ZMQ_POLLIN))))
+
+    (zmq-bind-socket client-socket (zmq-frontend-endpoint))
+    (zmq-bind-socket build-socket (zmq-backend-endpoint backend-port))
+    (zmq-bind-socket fetch-socket (zmq-fetch-workers-endpoint))
+
+    ;; Change frontend socket permissions.
+    (chmod (zmq-frontend-socket-name) #o666)
+
+    ;; Do not use the built-in zmq-proxy as we want to edit the envelope of
+    ;; frontend messages before forwarding them to the backend.
+    (let loop ()
+      (let ((items (zmq-poll* poll-items)))
+        ;; CLIENT -> REMOTE-SERVER.
+        (when (zmq-socket-ready? items client-socket)
+          (match (zmq-get-msg-parts-bytevector client-socket)
+            ((client empty rest)
+             (read-client-exp client (bv->string rest)))))
+        ;; BUILD-WORKER -> REMOTE-SERVER.
+        (when (zmq-socket-ready? items build-socket)
+          (match (zmq-get-msg-parts-bytevector build-socket)
+            ((worker empty rest)
+             (let ((reply-worker
+                    (lambda (client message)
+                      (zmq-send-msg-parts-bytevector
+                       build-socket
+                       (list worker
+                             (zmq-empty-delimiter)
+                             client
+                             (zmq-empty-delimiter)
+                             (string->bv message))))))
+               (read-worker-exp (bv->string rest)
+                                #:reply-worker reply-worker)))
+            ((worker empty client empty rest)
+             (let ((message (list client (zmq-empty-delimiter) rest)))
+               (if (need-fetching? (bv->string rest))
+                   (zmq-send-msg-parts-bytevector fetch-socket message)
+                   (zmq-send-msg-parts-bytevector client-socket message)))=
)))
+        ;; FETCH-WORKER -> REMOTE-SERVER.
+        (when (zmq-socket-ready? items fetch-socket)
+          (let ((msg (zmq-get-msg-parts-bytevector fetch-socket)))
+            (zmq-send-msg-parts-bytevector client-socket msg)))
+
+        (loop)))))
+
+
+;;;
+;;; Entry point.
+;;;
+
+;; The PID of the publish process.
+(define %publish-pid
+  (make-atomic-box #f))
+
+;; The thread running the Avahi publish service.
+(define %avahi-thread
+  (make-atomic-box #f))
+
+(define (signal-handler)
+  "Catch SIGINT to stop the Avahi event loop and the publish process before
+exiting."
+  (sigaction SIGINT
+    (lambda (signum)
+      (let ((publish-pid (atomic-box-ref %publish-pid))
+            (avahi-thread (atomic-box-ref %avahi-thread)))
+        (atomic-box-set! %stop-process? #t)
+
+        (and publish-pid
+             (begin
+               (kill publish-pid SIGHUP)
+               (waitpid publish-pid)))
+
+        (and avahi-thread
+             (join-thread avahi-thread))
+
+        (exit 1)))))
+
+(define (remote-server args)
+  (signal-handler)
+
+  (with-error-handling
+    (let* ((opts (args-fold* args %options
+                             (lambda (opt name arg result)
+                               (leave (G_ "~A: unrecognized option~%") nam=
e))
+                             (lambda (arg result)
+                               (leave (G_ "~A: extraneous argument~%") arg=
))
+                             %default-options))
+           (add-to-store? (assoc-ref opts 'add-to-store?))
+           (backend-port (assoc-ref opts 'backend-port))
+           (publish-port (assoc-ref opts 'publish-port))
+           (cache (assoc-ref opts 'cache))
+           (public-key
+            (read-file-sexp
+             (assoc-ref opts 'public-key-file)))
+           (private-key
+            (read-file-sexp
+             (assoc-ref opts 'private-key-file))))
+
+      (parameterize ((%add-to-store? add-to-store?)
+                     (%cache-directory cache)
+                     (%public-key public-key)
+                     (%private-key private-key))
+
+        (atomic-box-set!
+         %publish-pid
+         (publish-server publish-port
+                         #:public-key public-key
+                         #:private-key private-key))
+
+        (atomic-box-set!
+         %avahi-thread
+         (avahi-publish-service-thread
+          service-name
+          #:type remote-server-service-type
+          #:port backend-port
+          #:stop-loop? (lambda ()
+                         (atomic-box-ref %stop-process?))
+          #:txt (list (string-append "publish=3D"
+                                     (number->string publish-port)))))
+
+        (for-each (lambda (number)
+                    (start-fetch-worker
+                     (string-append "fetch-worker-" (number->string number=
))))
+                  (iota 4))
+
+        (zmq-start-proxy backend-port)))))
diff --git a/src/cuirass/remote-worker.scm b/src/cuirass/remote-worker.scm
new file mode 100644
index 0000000..c253ee5
--- /dev/null
+++ b/src/cuirass/remote-worker.scm
@@ -0,0 +1,286 @@
+;;; remote-worker.scm -- Remote build worker.
+;;; Copyright =C2=A9 2020 Mathieu Othacehe <othacehe@HIDDEN>
+;;;
+;;; This file is part of Cuirass.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (cuirass remote-worker)
+  #:use-module (cuirass remote)
+  #:use-module (gcrypt pk-crypto)
+  #:use-module (guix)
+  #:use-module (guix avahi)
+  #:use-module (guix config)
+  #:use-module (guix diagnostics)
+  #:use-module (guix pki)
+  #:use-module (guix records)
+  #:use-module (guix scripts)
+  #:use-module (guix ui)
+  #:use-module (guix build syscalls)
+  #:use-module (guix scripts publish)
+  #:use-module (simple-zmq)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (srfi srfi-34)
+  #:use-module (srfi srfi-37)
+  #:use-module (ice-9 atomic)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 threads)
+
+  #:export (remote-worker))
+
+;; Indicate if the process has to be stopped.
+(define %stop-process?
+  (make-atomic-box #f))
+
+(define (show-help)
+  (format #t (G_ "Usage: remote-worker [OPTION]...
+Start a remote build worker.\n"))
+  (display (G_ "
+  -w, --workers=3DCOUNT       start COUNT parallel workers"))
+  (display (G_ "
+  -p, --publish-port=3DPORT   publish substitutes on PORT"))
+  (display (G_ "
+      --public-key=3DFILE     use FILE as the public key for signatures"))
+  (display (G_ "
+      --private-key=3DFILE    use FILE as the private key for signatures"))
+  (newline)
+  (display (G_ "
+  -h, --help                display this help and exit"))
+  (display (G_ "
+  -V, --version             display version information and exit"))
+  (newline)
+  (show-bug-report-information))
+
+(define %options
+  (list (option '(#\h "help") #f #f
+                (lambda _
+                  (show-help)
+                  (exit 0)))
+        (option '(#\V "version") #f #f
+                (lambda _
+                  (show-version-and-exit "guix publish")))
+        (option '(#\w "workers") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'workers (string->number* arg) result)))
+        (option '(#\p "publish-port") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'publish-port (string->number* arg) result)))
+        (option '("public-key") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'public-key-file arg result)))
+        (option '("private-key") #t #f
+                (lambda (opt name arg result)
+                  (alist-cons 'private-key-file arg result)))))
+
+(define %default-options
+  `((workers . 1)
+    (publish-port . 5558)
+    (public-key-file . ,%public-key-file)
+    (private-key-file . ,%private-key-file)))
+
+
+;;;
+;;; ZMQ connection.
+;;;
+
+(define %zmq-context
+  (zmq-create-context))
+
+(define (zmq-backend-endpoint address port)
+  "Return a ZMQ endpoint identifying the build server available by TCP at
+ADDRESS and PORT."
+  (string-append "tcp://" address ":" (number->string port)))
+
+(define (zmq-dealer-socket)
+  "The ZMQ socket to communicate with the worker threads."
+  (zmq-create-socket %zmq-context ZMQ_DEALER))
+
+
+;;;
+;;; Worker.
+;;;
+
+;; The port of the local publish server.
+(define %local-publish-port
+  (make-atomic-box #f))
+
+(define (server-publish-url address port)
+  "Return the server publish url at ADDRESS and PORT."
+  (string-append "http://" address ":" (number->string port)))
+
+(define (service-txt->publish-port txt)
+  "Parse the service TXT record and return the server publish port."
+  (define (parse-txt)
+    (fold (lambda (param params)
+            (match (string-split param #\=3D)
+              ((key value)
+               (cons (cons (string->symbol key) value)
+                     params))))
+          '()
+          txt))
+
+  (let ((params (parse-txt)))
+    (string->number (assq-ref params 'publish))))
+
+(define (service->publish-url service)
+  "Return the URL of the publish server corresponding to the service with =
the
+given NAME."
+  (let* ((address (avahi-service-address service))
+         (txt (avahi-service-txt service))
+         (publish-port
+          (service-txt->publish-port txt)))
+    (server-publish-url address publish-port)))
+
+(define (service->local-publish-url service)
+  "Return the URL of the local publish server."
+  (let* ((local-address (avahi-service-local-address service))
+         (port (atomic-box-ref %local-publish-port)))
+    (server-publish-url local-address port)))
+
+(define* (run-build drv service #:key reply)
+  "Build DRV and send messages upon build start, failure or completion to =
the
+build server identified by SERVICE-NAME using the REPLY procedure.
+
+The publish server of the build server is added to the list of the store
+substitutes-urls.  This way derivations that are not present on the worker=
 can
+still be substituted."
+  (with-store store
+    (let ((publish-url (service->publish-url service))
+          (local-publish-url (service->local-publish-url service)))
+      (add-substitute-url store publish-url)
+      (reply (zmq-build-started-message drv))
+      (guard (c ((store-protocol-error? c)
+                 (info (G_ "Derivation `~a' build failed: ~a~%")
+                       drv (store-protocol-error-message c))
+                 (reply (zmq-build-failed-message drv))))
+        (if (build-derivations store (list drv))
+            (reply (zmq-build-succeeded-message drv local-publish-url))
+            (reply (zmq-build-failed-message drv)))))))
+
+(define* (run-command command service #:key reply)
+  "Run COMMAND.  SERVICE-NAME is the name of the build server that sent the
+command.  REPLY is a procedure that can be used to reply to this server."
+  (match (zmq-read-message command)
+    (('build ('drv drv) ('system system))
+     (info (G_ "Building `~a' derivation.~%") drv)
+     (run-build drv service #:reply reply))
+    (('no-build)
+     #t)))
+
+(define (start-worker worker service)
+  "Start a worker thread named NAME, reading commands from the DEALER sock=
et
+and executing them.  The worker can reply on the same socket."
+  (define (reply socket client)
+    (lambda (message)
+      (zmq-send-msg-parts-bytevector
+       socket
+       (list (zmq-empty-delimiter) client
+             (zmq-empty-delimiter) (string->bv message)))))
+
+  (define (ready socket)
+    (zmq-send-msg-parts-bytevector
+     socket
+     (list (make-bytevector 0)
+           (string->bv
+            (zmq-worker-ready-message (worker->sexp worker))))))
+
+  (define (request-work socket)
+    (let ((name (worker-name worker)))
+      (zmq-send-msg-parts-bytevector
+       socket
+       (list (make-bytevector 0)
+             (string->bv (zmq-worker-request-work-message name))))))
+
+  (call-with-new-thread
+   (lambda ()
+     (set-thread-name (worker-name worker))
+     (let* ((socket (zmq-dealer-socket))
+            (address (avahi-service-address service))
+            (port (avahi-service-port service))
+            (endpoint (zmq-backend-endpoint address port)))
+       (zmq-connect socket endpoint)
+       (ready socket)
+       (let loop ()
+         (request-work socket)
+         (match (zmq-get-msg-parts-bytevector socket '())
+           ((empty client empty command)
+            (run-command (bv->string command) service
+                         #:reply (reply socket client))))
+         (sleep 1)
+         (loop))))))
+
+
+;;;
+;;; Entry point.
+;;;
+
+;; The PID of the publish process.
+(define %publish-pid
+  (make-atomic-box #f))
+
+(define (signal-handler)
+  "Catch SIGINT to stop the Avahi event loop and the publish process before
+exiting."
+  (sigaction SIGINT
+    (lambda (signum)
+      (let ((publish-pid (atomic-box-ref %publish-pid)))
+        (atomic-box-set! %stop-process? #t)
+
+        (and publish-pid
+             (begin
+               (kill publish-pid SIGHUP)
+               (waitpid publish-pid)))
+
+        (exit 1)))))
+
+(define (remote-worker args)
+  (with-error-handling
+    (let* ((opts (args-fold* args %options
+                             (lambda (opt name arg result)
+                               (leave (G_ "~A: unrecognized option~%") nam=
e))
+                             (lambda (arg result)
+                               (leave (G_ "~A: extraneous argument~%") arg=
))
+                             %default-options))
+           (workers (assoc-ref opts 'workers))
+           (publish-port (assoc-ref opts 'publish-port))
+           (public-key
+            (read-file-sexp
+             (assoc-ref opts 'public-key-file)))
+           (private-key
+            (read-file-sexp
+             (assoc-ref opts 'private-key-file))))
+
+      (atomic-box-set! %local-publish-port publish-port)
+
+      (atomic-box-set!
+       %publish-pid
+       (publish-server publish-port
+                       #:public-key public-key
+                       #:private-key private-key))
+
+      (avahi-browse-service-thread
+       (lambda (action service)
+         (case action
+           ((new-service)
+            (for-each (lambda (n)
+                        (start-worker (worker
+                                       (name (generate-worker-name))
+                                       (systems '("x86_64-linux")))
+                                      service))
+                      (iota workers)))))
+       #:types (list remote-server-service-type)
+       #:stop-loop? (lambda ()
+                      (atomic-box-ref %stop-process?))))))
diff --git a/src/cuirass/remote.scm b/src/cuirass/remote.scm
new file mode 100644
index 0000000..7a71391
--- /dev/null
+++ b/src/cuirass/remote.scm
@@ -0,0 +1,292 @@
+;;; remote.scm -- Build on remote machines.
+;;; Copyright =C2=A9 2020 Mathieu Othacehe <othacehe@HIDDEN>
+;;;
+;;; This file is part of Cuirass.
+;;;
+;;; GNU Guix is free software; you can redistribute it and/or modify it
+;;; under the terms of the GNU General Public License as published by
+;;; the Free Software Foundation; either version 3 of the License, or (at
+;;; your option) any later version.
+;;;
+;;; GNU Guix is distributed in the hope that it will be useful, but
+;;; WITHOUT ANY WARRANTY; without even the implied warranty of
+;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;;; GNU General Public License for more details.
+;;;
+;;; You should have received a copy of the GNU General Public License
+;;; along with GNU Guix.  If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (cuirass remote)
+  #:use-module (guix config)
+  #:use-module (guix derivations)
+  #:use-module (guix records)
+  #:use-module (guix store)
+  #:use-module (guix ui)
+  #:use-module (guix build download)
+  #:use-module ((guix build utils) #:select (mkdir-p))
+  #:use-module (guix scripts publish)
+  #:use-module (simple-zmq)
+  #:use-module (rnrs bytevectors)
+  #:use-module (srfi srfi-1)
+  #:use-module (srfi srfi-26)
+  #:use-module (ice-9 match)
+  #:use-module (ice-9 rdelim)
+  #:export (worker
+            worker?
+            worker-name
+            worker-systems
+            worker->sexp
+            sexp->worker
+            generate-worker-name
+
+            publish-server
+            add-substitute-url
+
+            zmq-frontend-socket-name
+            zmq-frontend-endpoint
+            zmq-poll*
+            zmq-socket-ready?
+            zmq-empty-delimiter
+
+            zmq-build-request-message
+            zmq-no-build-message
+            zmq-build-started-message
+            zmq-build-failed-message
+            zmq-build-succeeded-message
+            zmq-worker-ready-message
+            zmq-worker-request-work-message
+            zmq-read-message
+
+            remote-server-service-type
+            remote-build-socket
+            remote-build
+            remote-build-poll))
+
+
+;;;
+;;; Workers.
+;;;
+
+(define-record-type* <worker>
+  worker make-worker
+  worker?
+  (name           worker-name)
+  (systems        worker-systems))
+
+(define (worker->sexp worker)
+  "Return an sexp describing WORKER."
+  (let ((name (worker-name worker))
+        (systems (worker-systems worker)))
+    `(worker
+      (name ,name)
+      (systems ,systems))))
+
+(define (sexp->worker sexp)
+  "Turn SEXP, an sexp as returned by 'worker->sexp', into a <worker> recor=
d."
+  (match sexp
+    (('worker ('name name) ('systems systems))
+     (worker
+      (name name)
+      (systems systems)))))
+
+
+(define %seed
+  (seed->random-state
+   (logxor (getpid) (car (gettimeofday)))))
+
+(define (integer->alphanumeric-char n)
+  "Map N, an integer in the [0..62] range, to an alphanumeric character."
+  (cond ((< n 10)
+         (integer->char (+ (char->integer #\0) n)))
+        ((< n 36)
+         (integer->char (+ (char->integer #\A) (- n 10))))
+        ((< n 62)
+         (integer->char (+ (char->integer #\a) (- n 36))))
+        (else
+         (error "integer out of bounds" n))))
+
+(define (random-string len)
+  "Compute a random string of size LEN where each character is alphanumeri=
c."
+  (let loop ((chars '())
+             (len len))
+    (if (zero? len)
+        (list->string chars)
+        (let ((n (random 62 %seed)))
+          (loop (cons (integer->alphanumeric-char n) chars)
+                (- len 1))))))
+
+(define (generate-worker-name)
+  "Return the service name of the server."
+  (string-append (gethostname) "-" (random-string 4)))
+
+
+;;;
+;;; Store publishing.
+;;;
+
+(define (add-substitute-url store url)
+  "Add URL to the list of STORE substitutes-urls."
+  (set-build-options store
+                     #:use-substitutes? #t
+                     #:fallback? #f
+                     #:keep-going? #t
+                     #:print-build-trace #t
+                     #:build-verbosity 1
+                     #:substitute-urls
+                     (cons url %default-substitute-urls)))
+
+(define* (publish-server port
+                         #:key
+                         public-key
+                         private-key)
+  "This procedure starts a publishing server listening on PORT in a new
+process and returns the pid of the forked process.  Use PUBLIC-KEY and
+PRIVATE-KEY to sign narinfos."
+  (match (primitive-fork)
+    (0
+     (parameterize ((%public-key public-key)
+                    (%private-key private-key))
+       (with-store store
+         (let* ((address (make-socket-address AF_INET INADDR_ANY 0))
+                (socket-address
+                 (make-socket-address (sockaddr:fam address)
+                                      (sockaddr:addr address)
+                                      port))
+                (socket (open-server-socket socket-address)))
+           (run-publish-server socket store
+                               #:compressions
+                               (list %default-gzip-compression))))))
+    (pid pid)))
+
+
+;;;
+;;; ZMQ.
+;;;
+
+(define %zmq-context
+  (zmq-create-context))
+
+(define (zmq-frontend-socket-name)
+  "Return the name of the ZMQ frontend socket."
+  (string-append %state-directory "/remote-build-socket"))
+
+(define (zmq-frontend-endpoint)
+  "Return a ZMQ endpoint allowing client connections using the IPC transpo=
rt."
+  (string-append "ipc://" (zmq-frontend-socket-name)))
+
+(define (EINTR-safe proc)
+  "Return a variant of PROC that catches EINTR 'zmq-error' exceptions and
+retries a call to PROC."
+  (define (safe . args)
+    (catch 'zmq-error
+      (lambda ()
+        (apply proc args))
+      (lambda (key errno . rest)
+        (if (=3D errno EINTR)
+            (apply safe args)
+            (apply throw key errno rest)))))
+
+  safe)
+
+(define zmq-poll*
+  ;; Return a variant of ZMQ-POLL that catches EINTR errors.
+  (EINTR-safe zmq-poll))
+
+(define (zmq-socket-ready? items socket)
+  "Return #t if the given SOCKET is part of ITEMS, a list returned by a
+'zmq-poll' call, return #f otherwise."
+  (find (lambda (item)
+          (eq? (poll-item-socket item) socket))
+        items))
+
+(define (zmq-read-message msg)
+  (call-with-input-string msg read))
+
+(define (zmq-empty-delimiter)
+  "Return an empty ZMQ delimiter used to format message envelopes."
+  (make-bytevector 0))
+
+;; ZMQ Messages.
+(define* (zmq-build-request-message drv #:optional system)
+  "Return a message requesting the build of DRV for SYSTEM."
+  (format #f "~s" `(build (drv ,drv) (system ,system))))
+
+(define (zmq-no-build-message)
+  "Return a message that indicates that no builds are available."
+  (format #f "~s" `(no-build)))
+
+(define (zmq-build-started-message drv)
+  "Return a message that indicates that the build of DRV has started."
+  (format #f "~s" `(build-started (drv ,drv))))
+
+(define (zmq-build-failed-message drv)
+  "Return a message that indicates that the build of DRV has failed."
+  (format #f "~s" `(build-failed (drv ,drv))))
+
+(define (zmq-build-succeeded-message drv url)
+  "Return a message that indicates that the build of DRV is done."
+  (format #f "~s" `(build-succeeded (drv ,drv) (url ,url))))
+
+(define (zmq-worker-ready-message worker)
+  "Return a message that indicates that WORKER is ready."
+  (format #f "~s" `(worker-ready ,worker)))
+
+(define (zmq-worker-request-work-message name)
+  "Return a message that indicates that WORKER is requesting work."
+  (format #f "~s" `(worker-request-work ,name)))
+
+
+;;;
+;;; Remote builds.
+;;;
+
+(define remote-server-service-type
+  "_remote-server._tcp")
+
+(define (remote-build-socket)
+  "Return a socket used to communicate with the remote build server."
+  (let ((socket (zmq-create-socket %zmq-context ZMQ_DEALER))
+        (endpoint (zmq-frontend-endpoint)))
+    (zmq-connect socket endpoint)
+    socket))
+
+(define* (remote-build socket drvs systems)
+  "Builds DRVS using the remote build mechanism.  A build command is sent =
on
+SOCKET to the build server for each derivation.
+
+SYSTEMS is a list describing the systems of each derivations in the DRVS l=
ist.
+It is used for performance reasons, so that the remote server doesn't need=
 to
+call 'read-derivation-from-file' for each derivation, which can be an
+expensive operation."
+  (for-each
+   (lambda (drv system)
+     ;; We need to prefix the command with an empty delimiter
+     ;; because the DEALER socket is connected to a ROUTER
+     ;; socket. See "zmq-start-proxy" procedure.
+     (zmq-send-msg-parts-bytevector
+      socket
+      (list (make-bytevector 0)
+            (string->bv (zmq-build-request-message drv system)))))
+   drvs systems))
+
+(define* (remote-build-poll socket event-proc
+                            #:key
+                            (timeout 1000))
+  "Poll SOCKET for messages and call EVENT-PROC each time a build event is
+received, return if no event occured for TIMEOUT milliseconds."
+  (define (parse-result result)
+    (match (zmq-read-message result)
+      (('build-started ('drv drv))
+       (event-proc (list 'build-started drv)))
+      (('build-succeeded ('drv drv) ('url url))
+       (event-proc (list 'build-succeeded drv)))
+      (('build-failed ('drv drv))
+       (event-proc (list 'build-failed drv)))))
+
+  (let* ((poll-items (list
+                      (poll-item socket ZMQ_POLLIN)))
+         (items (zmq-poll* poll-items timeout)))
+    (when (zmq-socket-ready? items socket)
+      (match (zmq-get-msg-parts-bytevector socket '())
+        ((empty result)
+         (parse-result (bv->string result)))))))
--=20
2.29.2


--=-=-=
Content-Type: image/png
Content-Disposition: attachment; filename=remote.png
Content-Transfer-Encoding: base64

iVBORw0KGgoAAAANSUhEUgAAA98AAAFWCAYAAABw0y6JAAAIjHRFWHRteGZpbGUAJTNDbXhmaWxl
JTIwaG9zdCUzRCUyMmFwcC5kaWFncmFtcy5uZXQlMjIlMjBtb2RpZmllZCUzRCUyMjIwMjAtMTIt
MDJUMTAlM0E0OSUzQTAzLjk5MVolMjIlMjBhZ2VudCUzRCUyMjUuMCUyMChYMTEpJTIyJTIwdmVy
c2lvbiUzRCUyMjEzLjkuMiUyMiUyMGV0YWclM0QlMjJBS3h0Y2JVZnQyS0hkeWlKV0lmRCUyMiUy
MHR5cGUlM0QlMjJkZXZpY2UlMjIlM0UlM0NkaWFncmFtJTIwaWQlM0QlMjI5YkJIZ28tWGlDbDB3
WHFHc1J1LSUyMiUzRTdWdmZjNXM0RVA1ciUyRk5nTWt2Z2hIbU1uYVRyVHp2bWFkSHAzTHpjRVpK
c3B0and5cnUzNzYwOEVBVWFJSUR2Q2RaM2tKV2dSa3ZoMnRmdnRDZyUyRlFhTDc5eUlMbDdBdU5T
REtBVnJRZG9Kc0JoTURCbVAlMkZMSkx0Y2dqMDNGMHhaSElsT2xlQWglMkZvOElvU1drNnpnaXEx
ckhsTklralpkMVlVZ1hDeEttTlZuQUdOM1V1MDFvVXA5MUdVeEpRJTJGQVFCa2xUJTJCajJPMHBt
UUF0ZXZidHlUZURvVFUyUG81VGZtUWRGWnZNbHFGa1Iwc3lkQ3R3TTBZcFNtJTJCZFY4T3lKSkJs
NkJTJTJGN2NYY3ZkY21HTUxGS2RCMkQlMkJ3TThnV1l0M0UlMkJ0S2Q4WExNcnBlUkNUcmJ3M1Fj
RE9MVSUyRkt3RE1MczdvYXJsOHRtNlR6aExjQXZWeW1qUDhpSUpwUTlQNDNjRUpPbkNiOHppWk5r
VHg0RkJFOUNMaGNMSUN3bDI5YVhBQ1UwM0tZSW5aT1U3WGlYNGdGYm9Dbk15UlBOVGFVYnU1RE45
dFNDaXVjQ1lRJTJGVGN1Z0tNbjRoVUZNamlCUUl1Z21mWWZqRUw2YlpCU056bXBJUEs4TDRheFoz
JTJCYmhsQjhPWVIlMkI2VDY3aE56Q2VUQ1F3TllRNVJIWE5nTlVHSFdBRTZ3QVpBdHklMkZCYkQy
M0RpRnVJb2hVWmx0NHo5Y0E2R2hiN1lheUgzMVliWnR0dGx1ekNjaHhIWExmYjBBT29BSnl4NERO
dXQyUTg3aTIlMkZjQk5iRTRYRndLNGoyVTNBVTZIdU5mdEpjZ2l1czRJQVclMkJGU2JCYXhUa2lB
VXViNG9hJTJGS0dLJTJGZlFpNmQzZER4N05LZEVuVW9CcWQyTzVoNTd3QUhTTkprTVklMkY2OE9y
OEJRempHbk1KNjVVNSUyRmxYJTJGdDZmVTFja3hGZE9mY2dWWGJPUWlGSDJPWWMwTVBZN0JxNFB5
NVV4SldsajJHZmxsNkJvMlFQdTNvSCUyRmZQbnozNXZiNjglMkIzWDN2ZmdBUkVEdkZVSnVLN0hn
b01iVURYNnd3eXZzS0diQU14eHRlRCUyQiUyQnNmM3g0dkJtNEhkTk9pVWxhalJTYTRhREg1UzVD
UHZ3MCUyRmYzcTQ3eDF2amphT2JCWGVHRDRodHlmekJnVTE3TEp2MndEYW9EdSUyQiUyRkg0c0ZQ
aGVBOEhlYUNoUTVaOXZrSWRDNERiZFJGJTJCMENHaWtySmZQUkhraWVrTElOUkxXZHlwNkpCV0Z0
dE1QRmVVRDE0YzFSMFdCUmdKJTJCOFZ4VUZXcjZJcU5BSSUyRjIlMkJiR29FN1dicTNSczElMkJv
V3B0N1l0bjYlMkIlMkZjJTJCVXNvZ2paaCUyRnEzeG80RFVIYVZCbjJhS3I5JTJCViUyRnF4U3Vj
dTY5aXcxdGo2aGZmclFla2FXZjdqYUt6bFU3bXZTenN5b2xLYmJmb1Brbmk2eU15SUs1V25DMmlZ
JTJCZEE0REpKcmNXTWVSMUUyb2RKbjE3MjZDU2NNNjVwUWxGOXNoVWxCQTV3VGFoUUQzalZUdWNa
VHFrWlZPYmowMHBoOFNxc3FqYW40aUpFRHd5SVV2UjM2SjFjaVMyUHVxa1NhS0VUcW5JcUg2NWp4
VUg4cDUlMkJGT0hXNkVtbkNYWiUyQmFteXdzNjUlMkJIdnpLdVZlVFYwSiUyQnRFbDNjQlg5cHpz
dXRxNFYxY0E4RnVyOXN5NjdCcVg3QThUN0hneWx6eUVZOGxkVHFmQjN3YWo5NHFkWUNXQkQ4NElY
WFFxR05NU0JyTyUyQmklMkZiQnBFZk9TcDlCc1FHQ0pyQkdqV3FHS2NNWTBiS0dBdTZJSWZXYkx1
eGZaN2lMczdXZnZNNzFIQnQ2WU14NkVsVlBWMG42OWxlYlNEZzZuMHdjS2lUbFJlTTVFOHBwZjRP
UHF5JTJGbktSMzlmZmtLQVVOTyUyRjNEeWpmQ3FvOWhFSHlVUGNNOTFxMmNyNm03RFJwd2JCMUhq
dk55QkRGWHg5SDVXdVAlMkI4ZEZZdWFCVVpwdjZ6eXpteSUyQlVDRmI5WG1aU0ptRjlNMWYlMkZP
UER4VDVmQ3gzVjhaemxkTzBmeGJqUCUyRmN1TmtLSmVTdFhkbUt1b05YYnQ1Q2hNNXNsenVPZEZy
b2VjZnRjdXhCS2FCSkF4a0thUEtDQzJyVkduQ2tDa0pYZjJEWnIzMUFWSk5OaFRTazhZVk94OGJS
SVc4eUZYdlZMc0slMkJYZHRIVnhaME92Y1NiNDBKaXprMG1hczhib081WjdhJTJGT0t0JTJGNlhD
JTJCVEF3T1R0SXQlMkJadGtWeSUyQnFIcHlsVzNMNDlzd3lOZ1JQRzdOTGMyMHo4RE9MMlJoSUpx
TW84UnVLMmJ4WiUyRldnclYyWDEwemQwJTJCejglM0QlM0MlMkZkaWFncmFtJTNFJTNDJTJGbXhm
aWxlJTNF+aKNuwAAIABJREFUeJzsnXdUVEfDxgcE6SgqFqRYsBN7i4WILXaNJvZYYl57hxiM3aiJ
xlgSJRprxJbYG2jErqiwKiJrxbI2NDExWRO/vCnv8/1B9sqFZXdRdu9leZ5zfifHu7v3zg0zs/Nj
Zi5i5IIjIIQQQkjuwTAMwzAMkzli5IIjuP7ob0IIIYTkApRvhmEYhmGMhfJNCCGE5CKUb4ZhGIZh
jIXyTQghhOQilG+GYRiGYYyF8k0IIYTkIpRvhmEYhmGMhfJNCCGE5CKUb4ZhGIZhjIXyTQghhOQi
lG+GYRiGYYyF8k0IIYTkIpRvhmEYhmGMhfJNCCGE5CKUb4ZhGIZhjIXyTQghhOQilG+GYRiGYYyF
8k0IIYTkIpbKd1T4RtVjL/dhL4xccIQQQkgehvJNCCGE5CIjF1gu3zptmmrJiXwrXdb8QFT4RsXr
NiGEkJeH8k0IIYTkMpRvQvkmhBCSGco3IYQQkstQvgnlmxBCSGYo34QQQkguk1vy3bdHPwghjDJz
0hzotGnYvGYbXq/fCN7ehVCuTHn06zUQVxJvQqdNw5L5y2SfcXJyQoXyFbFs0SrKdx6E8k0IIXkb
yjchhBCSy+SWfK+Jisa4EREYNyICLi6uKFzIR/r37s2xWBMVDUdHRxTyLoy3O3dHw3qvw8HBAdUq
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
fmi1bc8P4pv4Rptqp/g+sWWTxE2fIkop6dH2FTmUuU5+yVkvvdq3leuvrSk1qlWV7q+0kZNbc6RD
q5aenc16f6wtp/JyJSduiTS+/+9SOTxc6tSqJZ+9+44W351at5IHGtSXmjVqyOsvtJBfctYbXzPx
rVsU333+F+G5rdWrL4hSSibOmWApvrN3Z3m+pvaNteWaa6+R3AJr8V3j6hpy/R+u99y3auNKuevu
O6V9l3aSW0B8X2qIb+vGdmyhxXfGysiAjiviGwPVjfHN+eFOR8R20+L72CH/n51OfLtA4hutaKf4
3rN6lfZy4YHdusiQnm+KUkr6dnxderVvK0opef+tCClIS5UGt98u1atWkW+SEuX45o1yS906UrNG
DRnVv588+LcGUq5cOUmLmuuJ70oVK0rnl1rL7X/+syilZNboEcbXTHzrFo/vxOwEufNvd4pSSiIT
Ii3Fd+eIztKtbzd5tsUzUq5cOXlr8FuSW2Atvv9y119EKSVP/OsJeXfsUIlMiJRN+zZ6vrYovtt1
bivd+3WX7v26y0ONHyK+y4hgHn9uucQhrktrLb7XJXxNfGNQJb45P9ziyLg3tfj+mfhG4hutaqf4
PpWXK+sXL/SE96m8XLnpxhulTq1a8k1SouxclSC1b7jes9N9/svOiwK792vt5FRermxZHiudX2ot
8TOne+7r0KqlnMrL9eyuv9Orh/H1Et++g6jIho0bXnAQKaWkYsWKMi1qmuUgikuLlceefkwqV6ns
+VnXXHuNfDRtrBZEpUl8B5+yiG+nX+IQ1+1lPb6XfUF8Y1Alvjk/3OLopT20+P7pYJJtzw/im/hG
m2r3+K545ZUloiYsLEyOb96oxfe8T8aJUkomvTe0xM8sfs130b8H9+hufL3Et+8g6tqni4ycOEI2
7F2vxfdXcV+eC6IBPXy+7Dw+c7ncesetUrFSRcnenWUpiIrM2b9JlqxeLAOGDZDyFcpL5SqVJWf/
plKDaMCwAcR3GVEW8e30SxyW9nxVi++02JnENwZV4pvzwy2OWdZTi++jP6yw7flBfBPfaFPtHt9/
/P0N8sh993ru/35dquxNTZJTeb/tfF9drZqcysuVlK+/0Ha+8xKWSq/2bWXlnJnEdxA1cc33+XaO
6CxKKfnfB4M8tzV5uokopWTmopleg6hZ6+dFKSULVs4POIiydmVKvZvqyUONH/K89Lfo54eFhUn2
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
--=-=-=--




Acknowledgement sent to Mathieu Othacehe <othacehe@HIDDEN>:
New bug report received and forwarded. Copy sent to guix-patches@HIDDEN. Full text available.
Report forwarded to guix-patches@HIDDEN:
bug#45006; Package guix-patches. Full text available.
Please note: This is a static page, with minimal formatting, updated once a day.
Click here to see this page with the latest information and nicer formatting.
Last modified: Wed, 2 Dec 2020 11:30:02 UTC

GNU bug tracking system
Copyright (C) 1999 Darren O. Benham, 1997 nCipher Corporation Ltd, 1994-97 Ian Jackson.