aboutsummaryrefslogtreecommitdiff
path: root/gnu/ci.scm
blob: c5de25e70ec98283f11ba7c346fa8201ed38eb04 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2017, 2020 Jan (janneke) Nieuwenhuizen <janneke@gnu.org>
;;; Copyright © 2018, 2019 Clément Lassieur <clement@lassieur.org>
;;; Copyright © 2020 Julien Lepiller <julien@lepiller.eu>
;;; Copyright © 2020, 2021 Mathieu Othacehe <othacehe@gnu.org>
;;;
;;; This file is part of GNU Guix.
;;;
;;; 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 (gnu ci)
  #:use-module (guix channels)
  #:use-module (guix config)
  #:use-module (guix describe)
  #:use-module (guix store)
  #:use-module (guix grafts)
  #:use-module (guix profiles)
  #:use-module (guix packages)
  #:use-module (guix channels)
  #:use-module (guix config)
  #:use-module (guix derivations)
  #:use-module (guix build-system)
  #:use-module (guix monads)
  #:use-module (guix gexp)
  #:use-module (guix ui)
  #:use-module ((guix licenses)
                #:select (gpl3+ license? license-name))
  #:use-module ((guix utils) #:select (%current-system))
  #:use-module ((guix scripts system) #:select (read-operating-system))
  #:use-module ((guix scripts pack)
                #:select (lookup-compressor self-contained-tarball))
  #:use-module (gnu bootloader)
  #:use-module (gnu bootloader u-boot)
  #:use-module (gnu image)
  #:use-module (gnu packages)
  #:use-module (gnu packages gcc)
  #:use-module (gnu packages base)
  #:use-module (gnu packages gawk)
  #:use-module (gnu packages guile)
  #:use-module (gnu packages gettext)
  #:use-module (gnu packages compression)
  #:use-module (gnu packages multiprecision)
  #:use-module (gnu packages make-bootstrap)
  #:use-module (gnu packages package-management)
  #:use-module (gnu system)
  #:use-module (gnu system image)
  #:use-module (gnu system vm)
  #:use-module (gnu system install)
  #:use-module (gnu system images hurd)
  #:use-module (gnu system images novena)
  #:use-module (gnu system images pine64)
  #:use-module (gnu system images pinebook-pro)
  #:use-module (gnu tests)
  #:use-module (srfi srfi-1)
  #:use-module (srfi srfi-26)
  #:use-module (ice-9 match)
  #:export (derivation->job
            image->job

            %core-packages
            %cross-targets
            channel-source->package

            arguments->systems
            cuirass-jobs))

;;; Commentary:
;;;
;;; This file defines build jobs for Cuirass.
;;;
;;; Code:

(define* (derivation->job name drv
                          #:key
                          (max-silent-time 3600)
                          (timeout 3600))
  "Return a Cuirass job called NAME and describing DRV.

MAX-SILENT-TIME and TIMEOUT are build options passed to the daemon when
building the derivation."
  `((#:job-name . ,name)
    (#:derivation . ,(derivation-file-name drv))
    (#:inputs . ,(map (compose derivation-file-name
                               derivation-input-derivation)
                      (derivation-inputs drv)))
    (#:outputs . ,(filter-map
                   (lambda (res)
                     (match res
                       ((name . path)
                        `(,name . ,path))))
                   (derivation->output-paths drv)))
    (#:nix-name . ,(derivation-name drv))
    (#:system . ,(derivation-system drv))
    (#:max-silent-time . ,max-silent-time)
    (#:timeout . ,timeout)))

(define* (package-job store job-name package system
                      #:key cross? target)
  "Return a job called JOB-NAME that builds PACKAGE on SYSTEM."
  (let ((job-name (string-append job-name "." system)))
    (parameterize ((%graft? #f))
      (let* ((drv (if cross?
                      (package-cross-derivation store package target system
                                                #:graft? #f)
                      (package-derivation store package system
                                          #:graft? #f)))
             (max-silent-time (or (assoc-ref (package-properties package)
                                             'max-silent-time)
                                  3600))
             (timeout (or (assoc-ref (package-properties package)
                                     'timeout)
                          72000)))
        (derivation->job job-name drv
                         #:max-silent-time max-silent-time
                         #:timeout timeout)))))

(define (package-cross-job store job-name package target system)
  "Return a job called TARGET.JOB-NAME that cross-builds PACKAGE for TARGET on
SYSTEM."
  (let ((name (string-append target "." job-name "." system)))
    (package-job store name package system
                 #:cross? #t
                 #:target target)))

(define %core-packages
  ;; Note: Don't put the '-final' package variants because (1) that's
  ;; implicit, and (2) they cannot be cross-built (due to the explicit input
  ;; chain.)
  (list gcc-7 gcc-8 gcc-9 gcc-10 glibc binutils
        gmp mpfr mpc coreutils findutils diffutils patch sed grep
        gawk gnu-gettext hello guile-2.0 guile-2.2 zlib gzip xz
        %bootstrap-binaries-tarball
        %binutils-bootstrap-tarball
        (%glibc-bootstrap-tarball)
        %gcc-bootstrap-tarball
        %guile-bootstrap-tarball
        %bootstrap-tarballs))

(define (packages-to-cross-build target)
  "Return the list of packages to cross-build for TARGET."
  ;; Don't cross-build the bootstrap tarballs for MinGW.
  (if (string-contains target "mingw")
      (drop-right %core-packages 6)
      %core-packages))

(define %cross-targets
  '("mips64el-linux-gnu"
    "arm-linux-gnueabihf"
    "aarch64-linux-gnu"
    "powerpc-linux-gnu"
    "powerpc64le-linux-gnu"
    "riscv64-linux-gnu"
    "i586-pc-gnu"                                 ;aka. GNU/Hurd
    "i686-w64-mingw32"
    "x86_64-w64-mingw32"))

(define (cross-jobs store system)
  "Return a list of cross-compilation jobs for SYSTEM."
  (define (from-32-to-64? target)
    ;; Return true if SYSTEM is 32-bit and TARGET is 64-bit.  This hack
    ;; prevents known-to-fail cross-builds from i686-linux or armhf-linux to
    ;; mips64el-linux-gnuabi64.
    (and (or (string-prefix? "i686-" system)
             (string-prefix? "i586-" system)
             (string-prefix? "armhf-" system))
         (string-contains target "64")))    ;x86_64, mips64el, aarch64, etc.

  (define (same? target)
    ;; Return true if SYSTEM and TARGET are the same thing.  This is so we
    ;; don't try to cross-compile to 'mips64el-linux-gnu' from
    ;; 'mips64el-linux'.
    (or (string-contains target system)
        (and (string-prefix? "armhf" system)    ;armhf-linux
             (string-prefix? "arm" target))))   ;arm-linux-gnueabihf

  (define (pointless? target)
    ;; Return #t if it makes no sense to cross-build to TARGET from SYSTEM.
    (match system
      ((or "x86_64-linux" "i686-linux")
       (if (string-contains target "mingw")
           (not (string=? "x86_64-linux" system))
           #f))
      (_
       ;; Don't try to cross-compile from non-Intel platforms: this isn't
       ;; very useful and these are often brittle configurations.
       #t)))

  (define (either proc1 proc2 proc3)
    (lambda (x)
      (or (proc1 x) (proc2 x) (proc3 x))))

  (append-map (lambda (target)
                (map (lambda (package)
                       (package-cross-job store (job-name package)
                                          package target system))
                     (packages-to-cross-build target)))
              (remove (either from-32-to-64? same? pointless?)
                      %cross-targets)))

(define* (guix-jobs store systems #:key source commit)
  "Return a list of jobs for Guix itself."
  (define build
    (primitive-load (string-append source "/build-aux/build-self.scm")))

  (map
   (lambda (system)
     (let ((name (string->symbol
                  (string-append "guix." system)))
           (drv (run-with-store store
                  (build source #:version commit #:system system
                         #:pull-version 1
                         #:guile-version "2.2"))))
       (derivation->job name drv)))
   systems))

;; Architectures that are able to build or cross-build Guix System images.
;; This does not mean that other architectures are not supported, only that
;; they are often not fast enough to support Guix System images building.
(define %guix-system-supported-systems
  '("x86_64-linux" "i686-linux"))

(define %guix-system-images
  (list hurd-barebones-qcow2-image
        pine64-barebones-raw-image
        pinebook-pro-barebones-raw-image
        novena-barebones-raw-image))

(define (hours hours)
  (* 3600 hours))

(define* (image->job store image
                     #:key name system)
  "Return the job for IMAGE on SYSTEM.  If NAME is passed, use it as job name,
otherwise use the IMAGE name."
  (let* ((image-name (or name
                         (symbol->string (image-name image))))
         (name (string-append image-name "." system))
         (drv (run-with-store store
                (mbegin %store-monad
                  (set-guile-for-build (default-guile))
                  (lower-object (system-image image))))))
    (parameterize ((%graft? #f))
      (derivation->job name drv))))

(define (image-jobs store system)
  "Return a list of jobs that build images for SYSTEM."
  (define MiB
    (expt 2 20))

  (if (member system %guix-system-supported-systems)
      `(,(image->job store
                     (image
                      (inherit efi-disk-image)
                      (operating-system installation-os))
                     #:name "usb-image"
                     #:system system)
        ,(image->job
          store
          (image
           (inherit (image-with-label
                     iso9660-image
                     (string-append "GUIX_" system "_"
                                    (if (> (string-length %guix-version) 7)
                                        (substring %guix-version 0 7)
                                        %guix-version))))
           (operating-system installation-os))
          #:name "iso9660-image"
          #:system system)
        ;; Only cross-compile Guix System images from x86_64-linux for now.
        ,@(if (string=? system "x86_64-linux")
              (map (cut image->job store <>
                        #:system system)
                   %guix-system-images)
              '()))
      '()))

(define channel-build-system
  ;; Build system used to "convert" a channel instance to a package.
  (let* ((build (lambda* (store name inputs
                                #:key source commit system
                                #:allow-other-keys)
                  (run-with-store store
                    ;; SOURCE can be a lowerable object such as <local-file>
                    ;; or a file name.  Adjust accordingly.
                    (mlet* %store-monad ((source (if (string? source)
                                                     (return source)
                                                     (lower-object source)))
                                         (instance
                                          -> (checkout->channel-instance
                                              source #:commit commit)))
                      (channel-instances->derivation (list instance)))
                    #:system system)))
         (lower (lambda* (name #:key system source commit
                               #:allow-other-keys)
                  (bag
                    (name name)
                    (system system)
                    (build build)
                    (arguments `(#:source ,source
                                 #:commit ,commit))))))
    (build-system (name 'channel)
                  (description "Turn a channel instance into a package.")
                  (lower lower))))

(define* (channel-source->package source #:key commit)
  "Return a package for the given channel SOURCE, a lowerable object."
  (package
    (inherit guix)
    (version (string-append (package-version guix) "+"))
    (build-system channel-build-system)
    (arguments `(#:source ,source
                 #:commit ,commit))
    (inputs '())
    (native-inputs '())
    (propagated-inputs '())))

(define* (system-test-jobs store system
                           #:key source commit)
  "Return a list of jobs for the system tests."
  (define (->job test)
    (let ((name (string-append "test." (system-test-name test)
                               "." system))
          (drv (run-with-store store
                 (mbegin %store-monad
                   (set-current-system system)
                   (set-grafting #f)
                   (set-guile-for-build (default-guile))
                   (system-test-value test)))))

      (derivation->job name drv)))

  (if (member system %guix-system-supported-systems)
      ;; Override the value of 'current-guix' used by system tests.  Using a
      ;; channel instance makes tests that rely on 'current-guix' less
      ;; expensive.  It also makes sure we get a valid Guix package when this
      ;; code is not running from a checkout.
      (parameterize ((current-guix-package
                      (channel-source->package source #:commit commit)))
        (map ->job (all-system-tests)))
      '()))

(define (tarball-jobs store system)
  "Return jobs to build the self-contained Guix binary tarball."
  (define (->job name drv)
    (let ((name (string-append name "." system)))
      (parameterize ((%graft? #f))
        (derivation->job name drv))))

  ;; XXX: Add a job for the stable Guix?
  (list
   (->job "binary-tarball"
          (run-with-store store
            (mbegin %store-monad
              (set-guile-for-build (default-guile))
              (>>= (profile-derivation (packages->manifest (list guix)))
                   (lambda (profile)
                     (self-contained-tarball "guix-binary" profile
                                             #:profile-name "current-guix"
                                             #:localstatedir? #t
                                             #:compressor
                                             (lookup-compressor "xz")))))
            #:system system))))

(define job-name
  ;; Return the name of a package's job.
  package-name)

(define package->job
  (let ((base-packages
         (delete-duplicates
          (append-map (match-lambda
                        ((_ package _ ...)
                         (match (package-transitive-inputs package)
                           (((_ inputs _ ...) ...)
                            inputs))))
                      (%final-inputs)))))
    (lambda (store package system)
      "Return a job for PACKAGE on SYSTEM, or #f if this combination is not
valid."
      (cond ((member package base-packages)
             (package-job store (string-append "base." (job-name package))
                          package system))
            ((supported-package? package system)
             (let ((drv (package-derivation store package system
                                            #:graft? #f)))
               (and (substitutable-derivation? drv)
                    (package-job store (job-name package)
                                 package system))))
            (else
             #f)))))

(define (all-packages)
  "Return the list of packages to build."
  (define (adjust package result)
    (cond ((package-replacement package)
           ;; XXX: If PACKAGE and its replacement have the same name/version,
           ;; then both Cuirass jobs will have the same name, which
           ;; effectively means that the second one will be ignored.  Thus,
           ;; return the replacement first.
           (cons* (package-replacement package)   ;build both
                  package
                  result))
          ((package-superseded package)
           result)                                ;don't build it
          (else
           (cons package result))))

  (fold-packages adjust
                 (fold adjust '()                 ;include base packages
                       (match (%final-inputs)
                         (((labels packages _ ...) ...)
                          packages)))
                 #:select? (const #t)))           ;include hidden packages

(define (arguments->manifests arguments channels)
  "Return the list of manifests extracted from ARGUMENTS."
  (map (lambda (manifest)
         (any (lambda (checkout)
                (let ((path (in-vicinity checkout manifest)))
                  (and (file-exists? path)
                       path)))
              (map channel-url channels)))
       arguments))

(define (manifests->packages store manifests)
  "Return the list of packages found in MANIFESTS."
  (define (load-manifest manifest)
    (save-module-excursion
     (lambda ()
       (set-current-module (make-user-module '((guix profiles) (gnu))))
       (primitive-load manifest))))

  (delete-duplicates!
   (map manifest-entry-item
        (append-map (compose manifest-entries
                             load-manifest)
                    manifests))))

(define (arguments->systems arguments)
  "Return the systems list from ARGUMENTS."
  (match (assoc-ref arguments 'systems)
    (#f              %cuirass-supported-systems)
    ((lst ...)       lst)
    ((? string? str) (call-with-input-string str read))))


;;;
;;; Cuirass entry point.
;;;

(define (cuirass-jobs store arguments)
  "Register Cuirass jobs."
  (define subset
    (assoc-ref arguments 'subset))

  (define systems
    (arguments->systems arguments))

  (define channels
    (let ((channels (assq-ref arguments 'channels)))
      (map sexp->channel channels)))

  (define guix
    (find guix-channel? channels))

  (define commit
    (channel-commit guix))

  (define source
    (channel-url guix))

  ;; Turn off grafts.  Grafting is meant to happen on the user's machines.
  (parameterize ((%graft? #f))
    ;; Return one job for each package, except bootstrap packages.
    (append-map
     (lambda (system)
       (format (current-error-port)
               "evaluating for '~a' (heap size: ~a MiB)...~%"
               system
               (round
                (/ (assoc-ref (gc-stats) 'heap-size)
                   (expt 2. 20))))
       (invalidate-derivation-caches!)
       (match subset
         ('all
          ;; Build everything, including replacements.
          (let ((all (all-packages))
                (job (lambda (package)
                       (package->job store package system))))
            (append
             (filter-map job all)
             (cross-jobs store system))))
         ('core
          ;; Build core packages only.
          (append
           (map (lambda (package)
                  (package-job store (job-name package)
                               package system))
                %core-packages)
           (cross-jobs store system)))
         ('guix
          ;; Build Guix modules only.
          (guix-jobs store systems
                     #:source source
                     #:commit commit))
         ('hello
          ;; Build hello package only.
          (let ((hello (specification->package "hello")))
            (list (package-job store (job-name hello)
                               hello system))))
         ('images
          ;; Build Guix System images only.
          (image-jobs store system))
         ('system-tests
          ;; Build Guix System tests only.
          (system-test-jobs store system
                            #:source source
                            #:commit commit))
         ('tarball
          ;; Build Guix tarball only.
          (tarball-jobs store system))
         (('custom . modules)
          ;; Build custom modules jobs only.
          (append-map
           (lambda (module)
             (let ((proc (module-ref
                          (resolve-interface module)
                          'cuirass-jobs)))
               (proc store arguments)))
           modules))
         (('channels . channels)
          ;; Build only the packages from CHANNELS.
          (let ((all (all-packages)))
            (filter-map
             (lambda (package)
               (any (lambda (channel)
                      (and (member (channel-name channel) channels)
                           (package->job store package system)))
                    (package-channels package)))
             all)))
         (('packages . rest)
          ;; Build selected list of packages only.
          (let ((packages (map specification->package rest)))
            (map (lambda (package)
                   (package-job store (job-name package)
                                package system))
                 packages)))
         (('manifests . rest)
          ;; Build packages in the list of manifests.
          (let* ((manifests (arguments->manifests rest channels))
                 (packages (manifests->packages store manifests)))
            (map (lambda (package)
                   (package-job store (job-name package)
                                package system))
                 packages)))
         (else
          (error "unknown subset" subset))))
     systems)))
(string-join (map quote-val val) ",") "]")) (define optional-list? list?) (define optional-string? string?) (define (serialize-list field-name val) (serialize-field field-name (build-opt-list val))) (define (serialize-optional-list field-name val) (if (null? val) (format #t "# ~a = []\n" (uglify-field-name field-name)) (serialize-list field-name val))) (define (serialize-optional-string field-name val) (if (string-null? val) (format #t "# ~a = \"\"\n" (uglify-field-name field-name)) (serialize-string field-name val))) (define-configuration libvirt-configuration (libvirt (package libvirt) "Libvirt package.") (listen-tls? (boolean #t) "Flag listening for secure TLS connections on the public TCP/IP port. must set @code{listen} for this to have any effect. It is necessary to setup a CA and issue server certificates before using this capability.") (listen-tcp? (boolean #f) "Listen for unencrypted TCP connections on the public TCP/IP port. must set @code{listen} for this to have any effect. Using the TCP socket requires SASL authentication by default. Only SASL mechanisms which support data encryption are allowed. This is DIGEST_MD5 and GSSAPI (Kerberos5)") (tls-port (string "16514") "Port for accepting secure TLS connections This can be a port number, or service name") (tcp-port (string "16509") "Port for accepting insecure TCP connections This can be a port number, or service name") (listen-addr (string "0.0.0.0") "IP address or hostname used for client connections.") (mdns-adv? (boolean #f) "Flag toggling mDNS advertisement of the libvirt service. Alternatively can disable for all services on a host by stopping the Avahi daemon.") (mdns-name (string (string-append "Virtualization Host " (gethostname))) "Default mDNS advertisement name. This must be unique on the immediate broadcast network.") (unix-sock-group (string "root") "UNIX domain socket group ownership. This can be used to allow a 'trusted' set of users access to management capabilities without becoming root.") (unix-sock-ro-perms (string "0777") "UNIX socket permissions for the R/O socket. This is used for monitoring VM status only.") (unix-sock-rw-perms (string "0770") "UNIX socket permissions for the R/W socket. Default allows only root. If PolicyKit is enabled on the socket, the default will change to allow everyone (eg, 0777)") (unix-sock-admin-perms (string "0777") "UNIX socket permissions for the admin socket. Default allows only owner (root), do not change it unless you are sure to whom you are exposing the access to.") (unix-sock-dir (string "/var/run/libvirt") "The directory in which sockets will be found/created.") (auth-unix-ro (string "polkit") "Authentication scheme for UNIX read-only sockets. By default socket permissions allow anyone to connect") (auth-unix-rw (string "polkit") "Authentication scheme for UNIX read-write sockets. By default socket permissions only allow root. If PolicyKit support was compiled into libvirt, the default will be to use 'polkit' auth.") (auth-tcp (string "sasl") "Authentication scheme for TCP sockets. If you don't enable SASL, then all TCP traffic is cleartext. Don't do this outside of a dev/test scenario.") (auth-tls (string "none") "Authentication scheme for TLS sockets. TLS sockets already have encryption provided by the TLS layer, and limited authentication is done by certificates. It is possible to make use of any SASL authentication mechanism as well, by using 'sasl' for this option") (access-drivers (optional-list '()) "API access control scheme. By default an authenticated user is allowed access to all APIs. Access drivers can place restrictions on this.") (key-file (string "") "Server key file path. If set to an empty string, then no private key is loaded.") (cert-file (string "") "Server key file path. If set to an empty string, then no certificate is loaded.") (ca-file (string "") "Server key file path. If set to an empty string, then no CA certificate is loaded.") (crl-file (string "") "Certificate revocation list path. If set to an empty string, then no CRL is loaded.") (tls-no-sanity-cert (boolean #f) "Disable verification of our own server certificates. When libvirtd starts it performs some sanity checks against its own certificates.") (tls-no-verify-cert (boolean #f) "Disable verification of client certificates. Client certificate verification is the primary authentication mechanism. Any client which does not present a certificate signed by the CA will be rejected.") (tls-allowed-dn-list (optional-list '()) "Whitelist of allowed x509 Distinguished Name.") (sasl-allowed-usernames (optional-list '()) "Whitelist of allowed SASL usernames. The format for username depends on the SASL authentication mechanism.") (tls-priority (string "NORMAL") "Override the compile time default TLS priority string. The default is usually \"NORMAL\" unless overridden at build time. Only set this is it is desired for libvirt to deviate from the global default settings.") (max-clients (integer 5000) "Maximum number of concurrent client connections to allow over all sockets combined.") (max-queued-clients (integer 1000) "Maximum length of queue of connections waiting to be accepted by the daemon. Note, that some protocols supporting retransmission may obey this so that a later reattempt at connection succeeds.") (max-anonymous-clients (integer 20) "Maximum length of queue of accepted but not yet authenticated clients. Set this to zero to turn this feature off") (min-workers (integer 5) "Number of workers to start up initially.") (max-workers (integer 20) "Maximum number of worker threads. If the number of active clients exceeds @code{min-workers}, then more threads are spawned, up to max_workers limit. Typically you'd want max_workers to equal maximum number of clients allowed.") (prio-workers (integer 5) "Number of priority workers. If all workers from above pool are stuck, some calls marked as high priority (notably domainDestroy) can be executed in this pool.") (max-requests (integer 20) "Total global limit on concurrent RPC calls.") (max-client-requests (integer 5) "Limit on concurrent requests from a single client connection. To avoid one client monopolizing the server this should be a small fraction of the global max_requests and max_workers parameter.") (admin-min-workers (integer 1) "Same as @code{min-workers} but for the admin interface.") (admin-max-workers (integer 5) "Same as @code{max-workers} but for the admin interface.") (admin-max-clients (integer 5) "Same as @code{max-clients} but for the admin interface.") (admin-max-queued-clients (integer 5) "Same as @code{max-queued-clients} but for the admin interface.") (admin-max-client-requests (integer 5) "Same as @code{max-client-requests} but for the admin interface.") (log-level (integer 3) "Logging level. 4 errors, 3 warnings, 2 information, 1 debug.") (log-filters (string "3:remote 4:event") "Logging filters. A filter allows selecting a different logging level for a given category of logs The format for a filter is one of: @itemize @item x:name @item x:+name @end itemize where @code{name} is a string which is matched against the category given in the @code{VIR_LOG_INIT()} at the top of each libvirt source file, e.g., \"remote\", \"qemu\", or \"util.json\" (the name in the filter can be a substring of the full category name, in order to match multiple similar categories), the optional \"+\" prefix tells libvirt to log stack trace for each message matching name, and @code{x} is the minimal level where matching messages should be logged: @itemize @item 1: DEBUG @item 2: INFO @item 3: WARNING @item 4: ERROR @end itemize Multiple filters can be defined in a single filters statement, they just need to be separated by spaces.") (log-outputs (string "3:syslog:libvirtd") "Logging outputs. An output is one of the places to save logging information The format for an output can be: @table @code @item x:stderr output goes to stderr @item x:syslog:name use syslog for the output and use the given name as the ident @item x:file:file_path output to a file, with the given filepath @item x:journald output to journald logging system @end table In all case the x prefix is the minimal level, acting as a filter @itemize @item 1: DEBUG @item 2: INFO @item 3: WARNING @item 4: ERROR @end itemize Multiple outputs can be defined, they just need to be separated by spaces.") (audit-level (integer 1) "Allows usage of the auditing subsystem to be altered @itemize @item 0: disable all auditing @item 1: enable auditing, only if enabled on host @item 2: enable auditing, and exit if disabled on host. @end itemize ") (audit-logging (boolean #f) "Send audit messages via libvirt logging infrastructure.") (host-uuid (optional-string "") "Host UUID. UUID must not have all digits be the same.") (host-uuid-source (string "smbios") "Source to read host UUID. @itemize @item @code{smbios}: fetch the UUID from @code{dmidecode -s system-uuid} @item @code{machine-id}: fetch the UUID from @code{/etc/machine-id} @end itemize If @code{dmidecode} does not provide a valid UUID a temporary UUID will be generated.") (keepalive-interval (integer 5) "A keepalive message is sent to a client after @code{keepalive_interval} seconds of inactivity to check if the client is still responding. If set to -1, libvirtd will never send keepalive requests; however clients can still send them and the daemon will send responses.") (keepalive-count (integer 5) "Maximum number of keepalive messages that are allowed to be sent to the client without getting any response before the connection is considered broken. In other words, the connection is automatically closed approximately after @code{keepalive_interval * (keepalive_count + 1)} seconds since the last message received from the client. When @code{keepalive-count} is set to 0, connections will be automatically closed after @code{keepalive-interval} seconds of inactivity without sending any keepalive messages.") (admin-keepalive-interval (integer 5) "Same as above but for admin interface.") (admin-keepalive-count (integer 5) "Same as above but for admin interface.") (ovs-timeout (integer 5) "Timeout for Open vSwitch calls. The @code{ovs-vsctl} utility is used for the configuration and its timeout option is set by default to 5 seconds to avoid potential infinite waits blocking libvirt.")) (define* (libvirt-conf-file config) "Return a libvirtd config file." (plain-file "libvirtd.conf" (with-output-to-string (lambda () (serialize-configuration config libvirt-configuration-fields))))) (define %libvirt-accounts (list (user-group (name "libvirt") (system? #t)))) (define (%libvirt-activation config) (let ((sock-dir (libvirt-configuration-unix-sock-dir config))) #~(begin (use-modules (guix build utils)) (mkdir-p #$sock-dir)))) (define (libvirt-shepherd-service config) (let* ((config-file (libvirt-conf-file config)) (libvirt (libvirt-configuration-libvirt config))) (list (shepherd-service (documentation "Run the libvirt daemon.") (provision '(libvirtd)) (start #~(make-forkexec-constructor (list (string-append #$libvirt "/sbin/libvirtd") "-f" #$config-file) ;; For finding qemu and ip binaries. #:environment-variables (list (string-append "PATH=/run/current-system/profile/bin:" "/run/current-system/profile/sbin")))) (stop #~(make-kill-destructor)))))) (define libvirt-service-type (service-type (name 'libvirt) (extensions (list (service-extension polkit-service-type (compose list libvirt-configuration-libvirt)) (service-extension profile-service-type (lambda (config) (list (libvirt-configuration-libvirt config) qemu))) (service-extension activation-service-type %libvirt-activation) (service-extension shepherd-root-service-type libvirt-shepherd-service) (service-extension account-service-type (const %libvirt-accounts)))) (default-value (libvirt-configuration)))) (define-record-type* <virtlog-configuration> virtlog-configuration make-virtlog-configuration virtlog-configuration? (libvirt virtlog-configuration-libvirt (default libvirt)) (log-level virtlog-configuration-log-level (default 3)) (log-filters virtlog-configuration-log-filters (default "3:remote 4:event")) (log-outputs virtlog-configuration-log-outputs (default "3:syslog:virtlogd")) (max-clients virtlog-configuration-max-clients (default 1024)) (max-size virtlog-configuration-max-size (default 2097152)) ;; 2MB (max-backups virtlog-configuration-max-backups (default 3))) (define* (virtlogd-conf-file config) "Return a virtlogd config file." (plain-file "virtlogd.conf" (string-append "log_level = " (number->string (virtlog-configuration-log-level config)) "\n" "log_filters = \"" (virtlog-configuration-log-filters config) "\"\n" "log_outputs = \"" (virtlog-configuration-log-outputs config) "\"\n" "max_clients = " (number->string (virtlog-configuration-max-clients config)) "\n" "max_size = " (number->string (virtlog-configuration-max-size config)) "\n" "max_backups = " (number->string (virtlog-configuration-max-backups config)) "\n"))) (define (virtlogd-shepherd-service config) (let* ((config-file (virtlogd-conf-file config)) (libvirt (virtlog-configuration-libvirt config))) (list (shepherd-service (documentation "Run the virtlog daemon.") (provision '(virtlogd)) (start #~(make-forkexec-constructor (list (string-append #$libvirt "/sbin/virtlogd") "-f" #$config-file))) (stop #~(make-kill-destructor)))))) (define virtlog-service-type (service-type (name 'virtlogd) (extensions (list (service-extension shepherd-root-service-type virtlogd-shepherd-service))) (default-value (virtlog-configuration)))) (define (generate-libvirt-documentation) (generate-documentation `((libvirt-configuration ,libvirt-configuration-fields)) 'libvirt-configuration)) ;;; ;;; Transparent QEMU emulation via binfmt_misc. ;;; ;; Platforms that QEMU can emulate. (define-record-type <qemu-platform> (qemu-platform name family magic mask) qemu-platform? (name qemu-platform-name) ;string (family qemu-platform-family) ;string (magic qemu-platform-magic) ;bytevector (mask qemu-platform-mask)) ;bytevector (define-syntax bv (lambda (s) "Expand the given string into a bytevector." (syntax-case s () ((_ str) (string? (syntax->datum #'str)) (let ((bv (u8-list->bytevector (map char->integer (string->list (syntax->datum #'str)))))) bv))))) ;;; The platform descriptions below are taken from ;;; 'scripts/qemu-binfmt-conf.sh' in QEMU. (define %i386 (qemu-platform "i386" "i386" (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x03\x00") (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %i486 (qemu-platform "i486" "i386" (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x06\x00") (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %alpha (qemu-platform "alpha" "alpha" (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x26\x90") (bv "\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %arm (qemu-platform "arm" "arm" (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %armeb (qemu-platform "armeb" "arm" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x28") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %sparc (qemu-platform "sparc" "sparc" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x02") (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %sparc32plus (qemu-platform "sparc32plus" "sparc" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x12") (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %ppc (qemu-platform "ppc" "ppc" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x14") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %ppc64 (qemu-platform "ppc64" "ppc" (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %ppc64le (qemu-platform "ppc64le" "ppcle" (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x15\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\x00"))) (define %m68k (qemu-platform "m68k" "m68k" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x04") (bv "\xff\xff\xff\xff\xff\xff\xfe\xfe\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) ;; XXX: We could use the other endianness on a MIPS host. (define %mips (qemu-platform "mips" "mips" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %mipsel (qemu-platform "mipsel" "mips" (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %mipsn32 (qemu-platform "mipsn32" "mips" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %mipsn32el (qemu-platform "mipsn32el" "mips" (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %mips64 (qemu-platform "mips64" "mips" (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %mips64el (qemu-platform "mips64el" "mips" (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x08\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %riscv32 (qemu-platform "riscv32" "riscv" (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %riscv64 (qemu-platform "riscv64" "riscv" (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xf3\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %sh4 (qemu-platform "sh4" "sh4" (bv "\x7fELF\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %sh4eb (qemu-platform "sh4eb" "sh4" (bv "\x7fELF\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x2a") (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %s390x (qemu-platform "s390x" "s390x" (bv "\x7fELF\x02\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x16") (bv "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %aarch64 (qemu-platform "aarch64" "arm" (bv "\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\xb7\x00") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"))) (define %hppa (qemu-platform "hppa" "hppa" (bv "\x7f\x45\x4c\x46\x01\x02\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x0f") (bv "\xff\xff\xff\xff\xff\xff\xff\x00\xff\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff"))) (define %qemu-platforms (list %i386 %i486 %alpha %arm %sparc32plus %ppc %ppc64 %ppc64le %m68k %mips %mipsel %mipsn32 %mipsn32el %mips64 %mips64el %riscv32 %riscv64 %sh4 %sh4eb %s390x %aarch64 %hppa)) (define (lookup-qemu-platforms . names) "Return the list of QEMU platforms that match NAMES--a list of names such as \"arm\", \"hppa\", etc." (filter (lambda (platform) (member (qemu-platform-name platform) names)) %qemu-platforms)) (define-record-type* <qemu-binfmt-configuration> qemu-binfmt-configuration make-qemu-binfmt-configuration qemu-binfmt-configuration? (qemu qemu-binfmt-configuration-qemu (default qemu)) (platforms qemu-binfmt-configuration-platforms (default '())) ;safest default (guix-support? qemu-binfmt-configuration-guix-support? (default #t))) (define (qemu-platform->binfmt qemu platform) "Return a gexp that evaluates to a binfmt string for PLATFORM, using the given QEMU package." (define (bytevector->binfmt-string bv) ;; Return a binfmt-friendly string representing BV. Hex-encode every ;; character, in particular because the doc notes "that you must escape ;; any NUL bytes; parsing halts at the first one". (string-concatenate (map (lambda (n) (string-append "\\x" (string-pad (number->string n 16) 2 #\0))) (bytevector->u8-list bv)))) (match platform (($ <qemu-platform> name family magic mask) ;; See 'Documentation/binfmt_misc.txt' in the kernel. #~(string-append ":qemu-" #$name ":M::" #$(bytevector->binfmt-string magic) ":" #$(bytevector->binfmt-string mask) ":" #$(file-append qemu "/bin/qemu-" name) ":" ;FLAGS go here )))) (define %binfmt-mount-point (file-system-mount-point %binary-format-file-system)) (define %binfmt-register-file (string-append %binfmt-mount-point "/register")) (define qemu-binfmt-shepherd-services (match-lambda (($ <qemu-binfmt-configuration> qemu platforms) (list (shepherd-service (provision '(qemu-binfmt)) (documentation "Install binfmt_misc handlers for QEMU.") (requirement '(file-system-/proc/sys/fs/binfmt_misc)) (start #~(lambda () ;; Register the handlers for all of PLATFORMS. (for-each (lambda (str) (call-with-output-file #$%binfmt-register-file (lambda (port) (display str port)))) (list #$@(map (cut qemu-platform->binfmt qemu <>) platforms))) #t)) (stop #~(lambda (_) ;; Unregister the handlers. (for-each (lambda (name) (let ((file (string-append #$%binfmt-mount-point "/qemu-" name))) (call-with-output-file file (lambda (port) (display "-1" port))))) '#$(map qemu-platform-name platforms)) #f))))))) (define qemu-binfmt-guix-chroot (match-lambda ;; Add QEMU and its dependencies to the guix-daemon chroot so that our ;; binfmt_misc handlers work in the chroot (otherwise 'execve' would fail ;; with ENOENT.) ;; ;; The 'F' flag of binfmt_misc is meant to address this problem by loading ;; the interpreter upfront rather than lazily, but apparently that is ;; insufficient (perhaps it loads the 'qemu-ARCH' binary upfront but looks ;; up its dependencies lazily?). (($ <qemu-binfmt-configuration> qemu platforms guix?) (if guix? (list qemu) '())))) (define qemu-binfmt-service-type ;; TODO: Make a separate binfmt_misc service out of this? (service-type (name 'qemu-binfmt) (extensions (list (service-extension file-system-service-type (const (list %binary-format-file-system))) (service-extension shepherd-root-service-type qemu-binfmt-shepherd-services) (service-extension guix-service-type qemu-binfmt-guix-chroot))) (default-value (qemu-binfmt-configuration)) (description "This service supports transparent emulation of binaries compiled for other architectures using QEMU and the @code{binfmt_misc} functionality of the kernel Linux."))) ;;; ;;; Secrets for guest VMs. ;;; (define (secret-service-activation port) "Return an activation snippet that fetches sensitive material at local PORT, over TCP. Reboot upon failure." (with-imported-modules '((gnu build secret-service) (guix build utils)) #~(begin (use-modules (gnu build secret-service)) (let ((sent (secret-service-receive-secrets #$port))) (unless sent (sleep 3) (reboot)))))) (define secret-service-type (service-type (name 'secret-service) (extensions (list (service-extension activation-service-type secret-service-activation))) (description "This service fetches secret key and other sensitive material over TCP at boot time. This service is meant to be used by virtual machines (VMs) that can only be accessed by their host."))) (define (secret-service-operating-system os) "Return an operating system based on OS that includes the secret-service, that will be listening to receive secret keys on port 1004, TCP." (operating-system (inherit os) ;; Arrange so that the secret service activation snippet shows up before ;; the OpenSSH and Guix activation snippets. That way, we receive OpenSSH ;; and Guix keys before the activation snippets try to generate fresh keys ;; for nothing. (services (append (operating-system-user-services os) (list (service secret-service-type 1004)))))) ;;; ;;; The Hurd in VM service: a Childhurd. ;;; (define %hurd-vm-operating-system (operating-system (inherit %hurd-default-operating-system) (host-name "childhurd") (timezone "Europe/Amsterdam") (bootloader (bootloader-configuration (bootloader grub-minimal-bootloader) (target "/dev/vda") (timeout 0))) (packages (cons* gdb-minimal (operating-system-packages %hurd-default-operating-system))) (services (cons* (service openssh-service-type (openssh-configuration (openssh openssh-sans-x) (use-pam? #f) (port-number 2222) (permit-root-login #t) (allow-empty-passwords? #t) (password-authentication? #t))) ;; By default, the secret service introduces a pre-initialized ;; /etc/guix/acl file in the childhurd. Thus, clear ;; 'authorize-key?' so that it's not overridden at activation ;; time. (modify-services %base-services/hurd (guix-service-type config => (guix-configuration (inherit config) (authorize-key? #f)))))))) (define-record-type* <hurd-vm-configuration> hurd-vm-configuration make-hurd-vm-configuration hurd-vm-configuration? (os hurd-vm-configuration-os ;<operating-system> (default %hurd-vm-operating-system)) (qemu hurd-vm-configuration-qemu ;<package> (default qemu-minimal)) (image hurd-vm-configuration-image ;string (thunked) (default (hurd-vm-disk-image this-record))) (disk-size hurd-vm-configuration-disk-size ;number or 'guess (default 'guess)) (memory-size hurd-vm-configuration-memory-size ;number (default 512)) (options hurd-vm-configuration-options ;list of string (default `("--snapshot"))) (id hurd-vm-configuration-id ;#f or integer [1..] (default #f)) (net-options hurd-vm-configuration-net-options ;list of string (thunked) (default (hurd-vm-net-options this-record))) (secret-root hurd-vm-configuration-secret-root ;string (default "/etc/childhurd"))) (define (hurd-vm-disk-image config) "Return a disk-image for the Hurd according to CONFIG. The secret-service is added to the OS specified in CONFIG." (let* ((os (secret-service-operating-system (hurd-vm-configuration-os config))) (disk-size (hurd-vm-configuration-disk-size config)) (type (lookup-image-type-by-name 'hurd-qcow2)) (os->image (image-type-constructor type))) (system-image (image (inherit (os->image os)) (size disk-size))))) (define (hurd-vm-port config base) "Return the forwarded vm port for this childhurd config." (let ((id (or (hurd-vm-configuration-id config) 0))) (+ base (* 1000 id)))) (define %hurd-vm-secrets-port 11004) (define %hurd-vm-ssh-port 10022) (define %hurd-vm-vnc-port 15900) (define (hurd-vm-net-options config) `("--device" "rtl8139,netdev=net0" "--netdev" ,(string-append "user,id=net0" ",hostfwd=tcp:127.0.0.1:" (number->string (hurd-vm-port config %hurd-vm-secrets-port)) "-:1004" ",hostfwd=tcp:127.0.0.1:" (number->string (hurd-vm-port config %hurd-vm-ssh-port)) "-:2222" ",hostfwd=tcp:127.0.0.1:" (number->string (hurd-vm-port config %hurd-vm-vnc-port)) "-:5900"))) (define (hurd-vm-shepherd-service config) "Return a <shepherd-service> for a Hurd in a Virtual Machine with CONFIG." (let ((image (hurd-vm-configuration-image config)) (qemu (hurd-vm-configuration-qemu config)) (memory-size (hurd-vm-configuration-memory-size config)) (options (hurd-vm-configuration-options config)) (id (hurd-vm-configuration-id config)) (net-options (hurd-vm-configuration-net-options config)) (provisions '(hurd-vm childhurd))) (define vm-command #~(append (list #$(file-append qemu "/bin/qemu-system-i386") "-m" (number->string #$memory-size) #$@net-options #$@options "--hda" #+image ;; Cause the service to be respawned if the guest ;; reboots (it can reboot for instance if it did not ;; receive valid secrets, or if it crashed.) "--no-reboot") (if (file-exists? "/dev/kvm") '("--enable-kvm") '()))) (list (shepherd-service (documentation "Run the Hurd in a Virtual Machine: a Childhurd.") (provision (if id (map (cute symbol-append <> (string->symbol (number->string id))) provisions) provisions)) (requirement '(loopback networking user-processes)) (start (with-imported-modules (source-module-closure '((gnu build secret-service) (guix build utils))) #~(lambda () (let ((pid (fork+exec-command #$vm-command #:user "childhurd" ;; XXX TODO: use "childhurd" after ;; updating Shepherd #:group "kvm" #:environment-variables ;; QEMU tries to write to /var/tmp ;; by default. '("TMPDIR=/tmp"))) (port #$(hurd-vm-port config %hurd-vm-secrets-port)) (root #$(hurd-vm-configuration-secret-root config))) (catch #t (lambda _ ;; XXX: 'secret-service-send-secrets' won't complete until ;; the guest has booted and its secret service server is ;; running, which could take 20+ seconds during which PID 1 ;; is stuck waiting. (if (secret-service-send-secrets port root) pid (begin (kill (- pid) SIGTERM) #f))) (lambda (key . args) (kill (- pid) SIGTERM) (apply throw key args))))))) (modules `((gnu build secret-service) (guix build utils) ,@%default-modules)) (stop #~(make-kill-destructor)))))) (define %hurd-vm-accounts (list (user-group (name "childhurd") (system? #t)) (user-account (name "childhurd") (group "childhurd") (supplementary-groups '("kvm")) (comment "Privilege separation user for the childhurd") (home-directory "/var/empty") (shell (file-append shadow "/sbin/nologin")) (system? #t)))) (define (initialize-hurd-vm-substitutes) "Initialize the Hurd VM's key pair and ACL and store it on the host." (define run (with-imported-modules '((guix build utils)) #~(begin (use-modules (guix build utils) (ice-9 match)) (define host-key "/etc/guix/signing-key.pub") (define host-acl "/etc/guix/acl") (match (command-line) ((_ guest-config-directory) (setenv "GUIX_CONFIGURATION_DIRECTORY" guest-config-directory) (invoke #+(file-append guix "/bin/guix") "archive" "--generate-key") (when (file-exists? host-acl) ;; Copy the host ACL. (copy-file host-acl (string-append guest-config-directory "/acl"))) (when (file-exists? host-key) ;; Add the host key to the childhurd's ACL. (let ((key (open-fdes host-key O_RDONLY))) (close-fdes 0) (dup2 key 0) (execl #+(file-append guix "/bin/guix") "guix" "archive" "--authorize")))))))) (program-file "initialize-hurd-vm-substitutes" run)) (define (hurd-vm-activation config) "Return a gexp to activate the Hurd VM according to CONFIG." (with-imported-modules '((guix build utils)) #~(begin (use-modules (guix build utils)) (define secret-directory #$(hurd-vm-configuration-secret-root config)) (define ssh-directory (string-append secret-directory "/etc/ssh")) (define guix-directory (string-append secret-directory "/etc/guix")) (unless (file-exists? ssh-directory) ;; Generate SSH host keys under SSH-DIRECTORY. (mkdir-p ssh-directory) (invoke #$(file-append openssh "/bin/ssh-keygen") "-A" "-f" secret-directory)) (unless (file-exists? guix-directory) (invoke #$(initialize-hurd-vm-substitutes) guix-directory))))) (define hurd-vm-service-type (service-type (name 'hurd-vm) (extensions (list (service-extension shepherd-root-service-type hurd-vm-shepherd-service) (service-extension account-service-type (const %hurd-vm-accounts)) (service-extension activation-service-type hurd-vm-activation))) (default-value (hurd-vm-configuration)) (description "Provide a virtual machine (VM) running GNU/Hurd, also known as a @dfn{childhurd}.")))