Toggle diff (444 lines)
diff --git a/doc/guix.texi b/doc/guix.texi
index ac17f91f7d..04a6bf2bcd 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -22,7 +22,7 @@
@set SUBSTITUTE-URLS https://@value{SUBSTITUTE-SERVER-1} https://@value{SUBSTITUTE-SERVER-2}
@copying
-Copyright @copyright{} 2012-2023 Ludovic Courtès@*
+Copyright @copyright{} 2012-2024 Ludovic Courtès@*
Copyright @copyright{} 2013, 2014, 2016 Andreas Enge@*
Copyright @copyright{} 2013 Nikita Karetnikov@*
Copyright @copyright{} 2014, 2015, 2016 Alex Kost@*
@@ -1297,6 +1297,11 @@ Build Environment Setup
@file{/homeless-shelter}. This helps to highlight inappropriate uses of
@env{HOME} in the build scripts of packages.
+All this usually enough to ensure details of the environment do not
+influence build processes. In some exceptional cases where more control
+is needed---typically over the date, kernel, or CPU---you can resort to
+a virtual build machine (@pxref{build-vm, virtual build machines}).
+
You can influence the directory where the daemon stores build trees
@i{via} the @env{TMPDIR} environment variable. However, the build tree
within the chroot is always called @file{/tmp/guix-build-@var{name}.drv-0},
@@ -36081,6 +36086,138 @@ Virtualization Services
@end deftp
+@anchor{build-vm}
+@subsubheading Virtual Build Machines
+
+@cindex virtual build machines
+@cindex build VMs
+@cindex VMs, for offloading
+@dfn{Virtual build machines} or ``build VMs'' let you offload builds to
+a fully controlled environment. ``How can it be more controlled than
+regular builds? And why would it be useful?'', you ask. Good
+questions.
+
+Builds spawned by @code{guix-daemon} indeed run in a controlled
+environment; specifically the daemon spawns build processes in separate
+namespaces and in a chroot, such as that build processes only see their
+declared dependencies and a well-defined subset of the file system tree
+(@pxref{Build Environment Setup}, for details). A few aspects of the
+environments are not controlled though: the operating system kernel, the
+CPU model, and the date. Most of the time, these aspects have no impact
+on the build process: the level of isolation @code{guix-daemon} provides
+is ``good enough''.
+
+@cindex time traps
+However, there are occasionally cases where those aspects @emph{do}
+influence the build process. A typical example is @dfn{time traps}:
+build processes that stop working after a certain date@footnote{The most
+widespread example of time traps is test suites that involve checking
+the expiration date of a certificate. Such tests exists in TLS
+implementations such as OpenSSL and GnuTLS, but also in high-level
+software such as Python.}. Another one is software that optimizes for
+the CPU microarchitecture it is built on or, worse, bugs that manifest
+only on specific CPUs.
+
+To address that, @code{virtual-build-machine-service-type} lets you add
+a virtual build machine on your system, as in this example:
+
+@lisp
+(use-modules (gnu services virtualization))
+
+(operating-system
+ ;; @dots{}
+ (services (append (list (service virtual-build-machine-service-type))
+ %base-services)))
+@end lisp
+
+By default, you have to explicitly start the build machine when you need
+it, at which point builds may be offloaded to it (@pxref{Daemon Offload
+Setup}):
+
+@example
+herd start build-vm
+@end example
+
+With the default setting shown above, the build VM runs with its clock
+set to a date several years in the past, and on a CPU model that
+corresponds to that date---a model possibly older than that of your
+machine. This lets you rebuild today software from the past that would
+otherwise fail to build due to a time trap or other issues in its build
+process.
+
+You can configure the build VM, as in this example:
+
+@lisp
+(service virtual-build-machine-service-type
+ (virtual-build-machine
+ (cpu "Westmere")
+ (cpu-count 8)
+ (memory-size (* 1 1024))
+ (auto-start? #t)))
+@end lisp
+
+The available options are shown below.
+
+@defvar virtual-build-machine-service-type
+This is the service type to run @dfn{virtual build machines}. Virtual
+build machines are configured so that builds are offloaded to them when
+they are running.
+@end defvar
+
+@deftp {Data Type} virtual-build-machine
+This is the data type specifying the configuration of a build machine.
+It contains the fields below:
+
+@table @asis
+@item @code{name} (default: @code{'build-vm})
+The name of this build VM. It is used to construct the name of its
+Shepherd service.
+
+@item @code{image}
+The image of the virtual machine (@pxref{System Images}). This notably
+specifies the virtual disk size and the operating system running into it
+(@pxref{operating-system Reference}). The default value is a minimal
+operating system image.
+
+@item @code{qemu} (default: @code{qemu-minimal})
+The QEMU package to run the image.
+
+@item @code{cpu}
+The CPU model being emulated as a string denoting a model known to QEMU.
+
+The default value is a model that matches @code{date} (see below). To
+see what CPU models are available, run, for example:
+
+@example
+qemu-system-x86_64 -cpu help
+@end example
+
+@item @code{cpu-count} (default: @code{4})
+The number of CPUs emulated by the virtual machine.
+
+@item @code{memory-size} (default: @code{2048})
+Size in mebibytes (MiB) of the virtual machine's main memory (RAM).
+
+@item @code{date} (default: a few years ago)
+Date inside the virtual machine when it starts; this must be a SRFI-19
+date object (@pxref{SRFI-19 Date,,, guile, GNU Guile Reference Manual}).
+
+@item @code{port-forwardings} (default: 11022 and 11004)
+TCP ports of the virtual machine forwarded to the host. By default, the
+SSH and secrets ports are forwarded into the host.
+
+@item @code{systems} (default: @code{(list (%current-system))})
+List of system types supported by the build VM---e.g.,
+@code{"x86_64-linux"}.
+
+@item @code{auto-start?} (default: @code{#f})
+Whether to start the virtual machine when the system boots.
+@end table
+@end deftp
+
+In the next section, you'll find a variant on this theme: GNU/Hurd
+virtual machines!
+
@anchor{hurd-vm}
@subsubheading The Hurd in a Virtual Machine
diff --git a/gnu/services/virtualization.scm b/gnu/services/virtualization.scm
index 5b8566f600..907d641c6a 100644
--- a/gnu/services/virtualization.scm
+++ b/gnu/services/virtualization.scm
@@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2017 Ryan Moe <ryan.moe@gmail.com>
-;;; Copyright © 2018, 2020-2023 Ludovic Courtès <ludo@gnu.org>
+;;; Copyright © 2018, 2020-2024 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2020, 2021, 2023 Janneke Nieuwenhuizen <janneke@gnu.org>
;;; Copyright © 2021 Timotej Lazar <timotej.lazar@araneo.si>
;;; Copyright © 2022 Oleg Pykhalov <go.wigust@gmail.com>
@@ -43,6 +43,8 @@ (define-module (gnu services virtualization)
#:use-module (gnu system hurd)
#:use-module (gnu system image)
#:use-module (gnu system shadow)
+ #:autoload (gnu system vm) (linux-image-startup-command
+ virtualized-operating-system)
#:use-module (gnu system)
#:use-module (guix derivations)
#:use-module (guix gexp)
@@ -55,12 +57,20 @@ (define-module (gnu services virtualization)
#:autoload (guix self) (make-config.scm)
#:autoload (guix platform) (platform-system)
+ #:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-19)
#:use-module (srfi srfi-26)
#:use-module (rnrs bytevectors)
#:use-module (ice-9 match)
- #:export (%hurd-vm-operating-system
+ #:export (virtual-build-machine
+ virtual-build-machine-service-type
+
+ %virtual-build-machine-operating-system
+ %virtual-build-machine-default-vm
+
+ %hurd-vm-operating-system
hurd-vm-configuration
hurd-vm-configuration?
hurd-vm-configuration-os
@@ -1064,6 +1074,459 @@ (define* (secret-service-operating-system os
(inherit config)
(generate-substitute-key? #f))))))))
+
+;;;
+;;; Offloading-as-a-service.
+;;;
+
+(define-record-type* <virtual-build-machine>
+ virtual-build-machine make-virtual-build-machine
+ virtual-build-machine?
+ this-virtual-build-machine
+ (name virtual-build-machine-name
+ (default 'build-vm))
+ (image virtual-build-machine-image
+ (thunked)
+ (default
+ (virtual-build-machine-default-image
+ this-virtual-build-machine)))
+ (qemu virtual-build-machine-qemu
+ (default qemu-minimal))
+ (cpu virtual-build-machine-cpu
+ (thunked)
+ (default
+ (qemu-cpu-model-for-date
+ (virtual-build-machine-systems this-virtual-build-machine)
+ (virtual-build-machine-date this-virtual-build-machine))))
+ (cpu-count virtual-build-machine-cpu-count
+ (default 4))
+ (memory-size virtual-build-machine-memory-size ;integer (MiB)
+ (default 2048))
+ (date virtual-build-machine-date
+ (default (make-date 0 0 00 00 01 01 2020 0)))
+ (port-forwardings virtual-build-machine-port-forwardings
+ (default
+ `((,%build-vm-ssh-port . 22)
+ (,%build-vm-secrets-port . 1004))))
+ (systems virtual-build-machine-systems
+ (default (list (%current-system))))
+ (auto-start? virtual-build-machine-auto-start?
+ (default #f)))
+
+(define %build-vm-ssh-port
+ ;; Default host port where the guest's SSH port is forwarded.
+ 11022)
+
+(define %build-vm-secrets-port
+ ;; Host port to communicate secrets to the build VM.
+ ;; FIXME: Anyone on the host can talk to it; use virtio ports or AF_VSOCK
+ ;; instead.
+ 11044)
+
+(define %x86-64-intel-cpu-models
+ ;; List of release date/CPU model pairs representing Intel's x86_64 models.
+ ;; The list is taken from
+ ;; <https://en.wikipedia.org/wiki/List_of_Intel_CPU_microarchitectures>.
+ ;; CPU model strings are those found in 'qemu-system-x86_64 -cpu help'.
+ (letrec-syntax ((cpu-models (syntax-rules ()
+ ((_ (date model) rest ...)
+ (alist-cons (date->time-utc
+ (string->date date "~Y-~m-~d"))
+ model
+ (cpu-models rest ...)))
+ ((_)
+ '()))))
+ (reverse
+ (cpu-models ("2006-01-01" "core2duo")
+ ("2010-01-01" "Westmere")
+ ("2008-01-01" "Nehalem")
+ ("2011-01-01" "SandyBridge")
+ ("2012-01-01" "IvyBridge")
+ ("2013-01-01" "Haswell")
+ ("2014-01-01" "Broadwell")
+ ("2015-01-01" "Skylake-Client")))))
+
+(define (qemu-cpu-model-for-date systems date)
+ "Return the QEMU name of a CPU model for SYSTEMS that was current at DATE."
+ (if (any (cut string-prefix? "x86_64-" <>) systems)
+ (let ((time (date->time-utc date)))
+ (any (match-lambda
+ ((release-date . model)
+ (and (time<? release-date time)
+ model)))
+ %x86-64-intel-cpu-models))
+ ;; TODO: Add models for other architectures.
+ "host"))
+
+(define (virtual-build-machine-ssh-port config)
+ "Return the host port where CONFIG has its VM's SSH port forwarded."
+ (any (match-lambda
+ ((host-port . 22) host-port)
+ (_ #f))
+ (virtual-build-machine-port-forwardings config)))
+
+(define (virtual-build-machine-secrets-port config)
+ "Return the host port where CONFIG has its VM's secrets port forwarded."
+ (any (match-lambda
+ ((host-port . 1004) host-port)
+ (_ #f))
+ (virtual-build-machine-port-forwardings config)))
+
+(define %minimal-vm-syslog-config
+ ;; Minimal syslog configuration for a VM.
+ (plain-file "vm-syslog.conf" "\
+# Log most messages to the console, which goes to the serial
+# output, allowing the host to log it.
+*.info;auth.notice;authpriv.none -/dev/console
+
+# The rest.
+*.=debug -/var/log/debug
+authpriv.*;auth.info /var/log/secure
+"))
+
+(define %virtual-build-machine-operating-system
+ (operating-system
+ (host-name "build-machine")
+ (bootloader (bootloader-configuration ;unused
+ (bootloader grub-minimal-bootloader)
+ (targets '("/dev/null"))))
+ (file-systems (list (file-system ;unused
+ (mount-point "/")
+ (device "none")
+ (type "tmpfs"))))
+ (users (cons (user-account
+ (name "offload")
+ (group "users")
+ (supplementary-groups '("kvm"))
+ (comment "Account used for offloading"))
+ %base-user-accounts))
+ (services (cons* (service static-networking-service-type
+ (list %qemu-static-networking))
+ (service openssh-service-type
+ (openssh-configuration
+ (openssh openssh-sans-x)))
+
+ (modify-services %base-services
+ ;; By default, the secret service introduces a
+ ;; pre-initialized /etc/guix/acl file in the VM. Thus,
+ ;; clear 'authorize-key?' so that it's not overridden
+ ;; at activation time.
+ (guix-service-type config =>
+ (guix-configuration
+ (inherit config)
+ (authorize-key? #f)))
+ (syslog-service-type config =>
+ (syslog-configuration
+ (config-file
+ %minimal-vm-syslog-config)))
+ (delete mingetty-service-type)
+ (delete console-font-service-type))))))
+
+(define (virtual-build-machine-default-image config)
+ (let* ((type (lookup-image-type-by-name 'mbr-raw))
+ (base (os->image %virtual-build-machine-operating-system
+ #:type type)))
+ (image (inherit base)
+ (name (symbol-append 'build-vm-
+ (virtual-build-machine-name config)))
+ (format 'compressed-qcow2)
+ (partition-table-type 'mbr)
+ (shared-store? #f)
+ (size (* 10 (expt 2 30))))))
+
+(define (virtual-build-machine-account-name config)
+ (string-append "build-vm-"
+ (symbol->string
+ (virtual-build-machine-name config))))
+
+(define (virtual-build-machine-accounts config)
+ (let ((name (virtual-build-machine-account-name config)))
+ (list (user-group (name name) (system? #t))
+ (user-account
+ (name name)
+ (group name)
+ (supplementary-groups '("kvm"))
+ (comment "Privilege separation user for the virtual build machine")
+ (home-directory "/var/empty")
+ (shell (file-append shadow "/sbin/nologin"))
+ (system? #t)))))
+
+(define (build-vm-shepherd-services config)
+ (define transform
+ (compose secret-service-operating-system
+ operating-system-with-locked-root-account
+ operating-system-with-offloading-account
+ (lambda (os)
+ (virtualized-operating-system os #:full-boot? #t))))
+
+ (define transformed-image
+ (let ((base (virtual-build-machine-image config)))
+ (image
+ (inherit base)
+ (operating-system
+ (transform (image-operating-system base))))))
+
+ (define command
+ (linux-image-startup-command transformed-image
+ #:qemu
+ (virtual-build-machine-qemu config)
+ #:cpu
+ (virtual-build-machine-cpu config)
+ #:cpu-count
+ (virtual-build-machine-cpu-count config)
+ #:memory-size
+ (virtual-build-machine-memory-size config)
+ #:port-forwardings
+ (virtual-build-machine-port-forwardings
+ config)
+ #:date
+ (virtual-build-machine-date config)))
+
+ (define user
+ (virtual-build-machine-account-name config))
+
+ (list (shepherd-service
+ (documentation "Run the build virtual machine service.")
+ (provision (list (virtual-build-machine-name config)))
+ (requirement '(user-processes))
+ (modules `((gnu build secret-service)
+ (guix build utils)
+ ,@%default-modules))
+ (start
+ (with-imported-modules (source-module-closure
+ '((gnu build secret-service)
+ (guix build utils)))
+ #~(lambda arguments
+ (let* ((pid (fork+exec-command (append #$command arguments)
+ #:user #$user
+ #:group "kvm"
+ #:environment-variables
+ ;; QEMU tries to write to /var/tmp
+ ;; by default.
+ '("TMPDIR=/tmp")))
+ (port #$(virtual-build-machine-secrets-port config))
+ (root #$(virtual-build-machine-secret-root config))
+ (address (make-socket-