summaryrefslogtreecommitdiff
path: root/gnu
diff options
context:
space:
mode:
authorMathieu Othacehe <m.othacehe@gmail.com>2020-04-28 14:15:28 +0200
committerMathieu Othacehe <m.othacehe@gmail.com>2020-05-05 16:13:53 +0200
commitf19cf27c2b9ff92e2c0fd931ef7fde39c376adaa (patch)
tree6d4805a1c7496e3e1e7c55084b84bd7ece348805 /gnu
parentf8fd1157174fd523d36dcfa756c965a54c30d5ae (diff)
image: Add a new API.
Raw disk-images and ISO9660 images are created in a Qemu virtual machine. This is quite fragile, very slow, and almost unusable without KVM. For all these reasons, add support for host image generation. This implies the use new image generation mechanisms. - Raw disk images: images of partitions are created using tools such as mke2fs and mkdosfs depending on the partition file-system type. The partition images are then assembled into a final image using genimage. - ISO9660 images: the ISO root directory is populated within the store. GNU xorriso is then called on that directory, in the exact same way as this is done in (gnu build vm) module. Those mechanisms are built upon the new (gnu image) module. * gnu/image.scm: New file. * gnu/system/image.scm: New file. * gnu/build/image: New file. * gnu/local.mk: Add them. * gnu/system/vm.scm (system-disk-image): Rename to system-disk-image-in-vm. * gnu/ci.scm (qemu-jobs): Adapt to new API. * gnu/tests/install.scm (run-install): Ditto. * guix/scripts/system.scm (system-derivation-for-action): Ditto.
Diffstat (limited to 'gnu')
-rw-r--r--gnu/build/image.scm273
-rw-r--r--gnu/build/install.scm1
-rw-r--r--gnu/ci.scm45
-rw-r--r--gnu/image.scm76
-rw-r--r--gnu/local.mk3
-rw-r--r--gnu/system/image.scm532
-rw-r--r--gnu/system/vm.scm17
-rw-r--r--gnu/tests/install.scm22
8 files changed, 925 insertions, 44 deletions
diff --git a/gnu/build/image.scm b/gnu/build/image.scm
new file mode 100644
index 00000000000..fe8e11aa1b4
--- /dev/null
+++ b/gnu/build/image.scm
@@ -0,0 +1,273 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
3;;; Copyright © 2016 Christopher Allan Webber <cwebber@dustycloud.org>
4;;; Copyright © 2016, 2017 Leo Famulari <leo@famulari.name>
5;;; Copyright © 2017 Marius Bakke <mbakke@fastmail.com>
6;;; Copyright © 2020 Tobias Geerinckx-Rice <me@tobias.gr>
7;;; Copyright © 2020 Mathieu Othacehe <m.othacehe@gmail.com>
8;;;
9;;; This file is part of GNU Guix.
10;;;
11;;; GNU Guix is free software; you can redistribute it and/or modify it
12;;; under the terms of the GNU General Public License as published by
13;;; the Free Software Foundation; either version 3 of the License, or (at
14;;; your option) any later version.
15;;;
16;;; GNU Guix is distributed in the hope that it will be useful, but
17;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;;; GNU General Public License for more details.
20;;;
21;;; You should have received a copy of the GNU General Public License
22;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24(define-module (gnu build image)
25 #:use-module (guix build store-copy)
26 #:use-module (guix build syscalls)
27 #:use-module (guix build utils)
28 #:use-module (guix store database)
29 #:use-module (gnu build bootloader)
30 #:use-module (gnu build install)
31 #:use-module (gnu build linux-boot)
32 #:use-module (gnu image)
33 #:use-module (gnu system uuid)
34 #:use-module (ice-9 ftw)
35 #:use-module (ice-9 match)
36 #:use-module (srfi srfi-19)
37 #:use-module (srfi srfi-34)
38 #:use-module (srfi srfi-35)
39 #:export (make-partition-image
40 genimage
41 initialize-efi-partition
42 initialize-root-partition
43
44 make-iso9660-image))
45
46(define (sexp->partition sexp)
47 "Take SEXP, a tuple as returned by 'partition->gexp', and turn it into a
48<partition> record."
49 (match sexp
50 ((size file-system label uuid)
51 (partition (size size)
52 (file-system file-system)
53 (label label)
54 (uuid uuid)))))
55
56(define (size-in-kib size)
57 "Convert SIZE expressed in bytes, to kilobytes and return it as a string."
58 (number->string
59 (inexact->exact (ceiling (/ size 1024)))))
60
61(define (estimate-partition-size root)
62 "Given the ROOT directory, evalute and return its size. As this doesn't
63take the partition metadata size into account, take a 25% margin."
64 (* 1.25 (file-size root)))
65
66(define* (make-ext4-image partition target root
67 #:key
68 (owner-uid 0)
69 (owner-gid 0))
70 "Handle the creation of EXT4 partition images. See 'make-partition-image'."
71 (let ((size (partition-size partition))
72 (label (partition-label partition))
73 (uuid (partition-uuid partition))
74 (options "lazy_itable_init=1,lazy_journal_init=1"))
75 (invoke "mke2fs" "-t" "ext4" "-d" root
76 "-L" label "-U" (uuid->string uuid)
77 "-E" (format #f "root_owner=~a:~a,~a"
78 owner-uid owner-gid options)
79 target
80 (format #f "~ak"
81 (size-in-kib
82 (if (eq? size 'guess)
83 (estimate-partition-size root)
84 size))))))
85
86(define* (make-vfat-image partition target root)
87 "Handle the creation of VFAT partition images. See 'make-partition-image'."
88 (let ((size (partition-size partition))
89 (label (partition-label partition)))
90 (invoke "mkdosfs" "-n" label "-C" target "-F" "16" "-S" "1024"
91 (size-in-kib
92 (if (eq? size 'guess)
93 (estimate-partition-size root)
94 size)))
95 (for-each (lambda (file)
96 (unless (member file '("." ".."))
97 (invoke "mcopy" "-bsp" "-i" target
98 (string-append root "/" file)
99 (string-append "::" file))))
100 (scandir root))))
101
102(define* (make-partition-image partition-sexp target root)
103 "Create and return the image of PARTITION-SEXP as TARGET. Use the given
104ROOT directory to populate the image."
105 (let* ((partition (sexp->partition partition-sexp))
106 (type (partition-file-system partition)))
107 (cond
108 ((string=? type "ext4")
109 (make-ext4-image partition target root))
110 ((string=? type "vfat")
111 (make-vfat-image partition target root))
112 (else
113 (format (current-error-port)
114 "Unsupported partition type~%.")))))
115
116(define* (genimage config target)
117 "Use genimage to generate in TARGET directory, the image described in the
118given CONFIG file."
119 ;; genimage needs a 'root' directory.
120 (mkdir "root")
121 (invoke "genimage" "--config" config
122 "--outputpath" target))
123
124(define* (register-closure prefix closure
125 #:key
126 (deduplicate? #t) (reset-timestamps? #t)
127 (schema (sql-schema)))
128 "Register CLOSURE in PREFIX, where PREFIX is the directory name of the
129target store and CLOSURE is the name of a file containing a reference graph as
130produced by #:references-graphs.. As a side effect, if RESET-TIMESTAMPS? is
131true, reset timestamps on store files and, if DEDUPLICATE? is true,
132deduplicates files common to CLOSURE and the rest of PREFIX."
133 (let ((items (call-with-input-file closure read-reference-graph)))
134 (register-items items
135 #:prefix prefix
136 #:deduplicate? deduplicate?
137 #:reset-timestamps? reset-timestamps?
138 #:registration-time %epoch
139 #:schema schema)))
140
141(define* (initialize-efi-partition root
142 #:key
143 bootloader-package
144 #:allow-other-keys)
145 "Install in ROOT directory, an EFI loader using BOOTLOADER-PACKAGE."
146 (install-efi-loader bootloader-package root))
147
148(define* (initialize-root-partition root
149 #:key
150 bootcfg
151 bootcfg-location
152 (deduplicate? #t)
153 references-graphs
154 (register-closures? #t)
155 system-directory
156 #:allow-other-keys)
157 "Initialize the given ROOT directory. Use BOOTCFG and BOOTCFG-LOCATION to
158install the bootloader configuration.
159
160If REGISTER-CLOSURES? is true, register REFERENCES-GRAPHS in the store. If
161DEDUPLICATE? is true, then also deduplicate files common to CLOSURES and the
162rest of the store when registering the closures. SYSTEM-DIRECTORY is the name
163of the directory of the 'system' derivation."
164 (populate-root-file-system system-directory root)
165 (populate-store references-graphs root)
166
167 (when register-closures?
168 (for-each (lambda (closure)
169 (register-closure root
170 closure
171 #:reset-timestamps? #t
172 #:deduplicate? deduplicate?))
173 references-graphs))
174
175 (when bootcfg
176 (install-boot-config bootcfg bootcfg-location root)))
177
178(define* (make-iso9660-image xorriso grub-mkrescue-environment
179 grub bootcfg system-directory root target
180 #:key (volume-id "Guix_image") (volume-uuid #f)
181 register-closures? (references-graphs '())
182 (compression? #t))
183 "Given a GRUB package, creates an iso image as TARGET, using BOOTCFG as
184GRUB configuration and OS-DRV as the stuff in it."
185 (define grub-mkrescue
186 (string-append grub "/bin/grub-mkrescue"))
187
188 (define grub-mkrescue-sed.sh
189 (string-append (getcwd) "/" "grub-mkrescue-sed.sh"))
190
191 ;; Use a modified version of grub-mkrescue-sed.sh, see below.
192 (copy-file (string-append xorriso
193 "/bin/grub-mkrescue-sed.sh")
194 grub-mkrescue-sed.sh)
195
196 ;; Force grub-mkrescue-sed.sh to use the build directory instead of /tmp
197 ;; that is read-only inside the build container.
198 (substitute* grub-mkrescue-sed.sh
199 (("/tmp/") (string-append (getcwd) "/"))
200 (("MKRESCUE_SED_XORRISO_ARGS \\$x")
201 (format #f "MKRESCUE_SED_XORRISO_ARGS $(echo $x | sed \"s|/tmp|~a|\")"
202 (getcwd))))
203
204 ;; 'grub-mkrescue' calls out to mtools programs to create 'efi.img', a FAT
205 ;; file system image, and mtools honors SOURCE_DATE_EPOCH for the mtime of
206 ;; those files. The epoch for FAT is Jan. 1st 1980, not 1970, so choose
207 ;; that.
208 (setenv "SOURCE_DATE_EPOCH"
209 (number->string
210 (time-second
211 (date->time-utc (make-date 0 0 0 0 1 1 1980 0)))))
212
213 ;; Our patched 'grub-mkrescue' honors this environment variable and passes
214 ;; it to 'mformat', which makes it the serial number of 'efi.img'. This
215 ;; allows for deterministic builds.
216 (setenv "GRUB_FAT_SERIAL_NUMBER"
217 (number->string (if volume-uuid
218
219 ;; On 32-bit systems the 2nd argument must be
220 ;; lower than 2^32.
221 (string-hash (iso9660-uuid->string volume-uuid)
222 (- (expt 2 32) 1))
223
224 #x77777777)
225 16))
226
227 (setenv "MKRESCUE_SED_MODE" "original")
228 (setenv "MKRESCUE_SED_XORRISO" (string-append xorriso "/bin/xorriso"))
229 (setenv "MKRESCUE_SED_IN_EFI_NO_PT" "yes")
230
231 (for-each (match-lambda
232 ((name . value) (setenv name value)))
233 grub-mkrescue-environment)
234
235 (apply invoke grub-mkrescue
236 (string-append "--xorriso=" grub-mkrescue-sed.sh)
237 "-o" target
238 (string-append "boot/grub/grub.cfg=" bootcfg)
239 root
240 "--"
241 ;; Set all timestamps to 1.
242 "-volume_date" "all_file_dates" "=1"
243
244 `(,@(if compression?
245 '(;; ‘zisofs’ compression reduces the total image size by
246 ;; ~60%.
247 "-zisofs" "level=9:block_size=128k" ; highest compression
248 ;; It's transparent to our Linux-Libre kernel but not to
249 ;; GRUB. Don't compress the kernel, initrd, and other
250 ;; files read by grub.cfg, as well as common
251 ;; already-compressed file names.
252 "-find" "/" "-type" "f"
253 ;; XXX Even after "--" above, and despite documentation
254 ;; claiming otherwise, "-or" is stolen by grub-mkrescue
255 ;; which then chokes on it (as ‘-o …’) and dies. Don't use
256 ;; "-or".
257 "-not" "-wholename" "/boot/*"
258 "-not" "-wholename" "/System/*"
259 "-not" "-name" "unicode.pf2"
260 "-not" "-name" "bzImage"
261 "-not" "-name" "*.gz" ; initrd & all man pages
262 "-not" "-name" "*.png" ; includes grub-image.png
263 "-exec" "set_filter" "--zisofs"
264 "--")
265 '())
266 "-volid" ,(string-upcase volume-id)
267 ,@(if volume-uuid
268 `("-volume_date" "uuid"
269 ,(string-filter (lambda (value)
270 (not (char=? #\- value)))
271 (iso9660-uuid->string
272 volume-uuid)))
273 '()))))
diff --git a/gnu/build/install.scm b/gnu/build/install.scm
index 59a118e9058..b18654f1cc9 100644
--- a/gnu/build/install.scm
+++ b/gnu/build/install.scm
@@ -25,7 +25,6 @@
25 #:export (install-boot-config 25 #:export (install-boot-config
26 evaluate-populate-directive 26 evaluate-populate-directive
27 populate-root-file-system 27 populate-root-file-system
28 register-closure
29 install-database-and-gc-roots 28 install-database-and-gc-roots
30 populate-single-profile-directory)) 29 populate-single-profile-directory))
31 30
diff --git a/gnu/ci.scm b/gnu/ci.scm
index fb2596c809c..0430cf594b7 100644
--- a/gnu/ci.scm
+++ b/gnu/ci.scm
@@ -38,6 +38,7 @@
38 #:select (lookup-compressor self-contained-tarball)) 38 #:select (lookup-compressor self-contained-tarball))
39 #:use-module (gnu bootloader) 39 #:use-module (gnu bootloader)
40 #:use-module (gnu bootloader u-boot) 40 #:use-module (gnu bootloader u-boot)
41 #:use-module (gnu image)
41 #:use-module (gnu packages) 42 #:use-module (gnu packages)
42 #:use-module (gnu packages gcc) 43 #:use-module (gnu packages gcc)
43 #:use-module (gnu packages base) 44 #:use-module (gnu packages base)
@@ -49,6 +50,7 @@
49 #:use-module (gnu packages make-bootstrap) 50 #:use-module (gnu packages make-bootstrap)
50 #:use-module (gnu packages package-management) 51 #:use-module (gnu packages package-management)
51 #:use-module (gnu system) 52 #:use-module (gnu system)
53 #:use-module (gnu system image)
52 #:use-module (gnu system vm) 54 #:use-module (gnu system vm)
53 #:use-module (gnu system install) 55 #:use-module (gnu system install)
54 #:use-module (gnu tests) 56 #:use-module (gnu tests)
@@ -209,32 +211,23 @@ system.")
209 (expt 2 20)) 211 (expt 2 20))
210 212
211 (if (member system %guixsd-supported-systems) 213 (if (member system %guixsd-supported-systems)
212 (if (member system %u-boot-systems) 214 (list (->job 'usb-image
213 (list (->job 'flash-image 215 (run-with-store store
214 (run-with-store store 216 (mbegin %store-monad
215 (mbegin %store-monad 217 (set-guile-for-build (default-guile))
216 (set-guile-for-build (default-guile)) 218 (system-image
217 (system-disk-image 219 (image
218 (operating-system (inherit installation-os) 220 (inherit efi-disk-image)
219 (bootloader (bootloader-configuration 221 (size (* 1500 MiB))
220 (bootloader u-boot-bootloader) 222 (operating-system installation-os))))))
221 (target #f)))) 223 (->job 'iso9660-image
222 #:disk-image-size 224 (run-with-store store
223 (* 1500 MiB)))))) 225 (mbegin %store-monad
224 (list (->job 'usb-image 226 (set-guile-for-build (default-guile))
225 (run-with-store store 227 (system-image
226 (mbegin %store-monad 228 (image
227 (set-guile-for-build (default-guile)) 229 (inherit iso9660-image)
228 (system-disk-image installation-os 230 (operating-system installation-os)))))))
229 #:disk-image-size
230 (* 1500 MiB)))))
231 (->job 'iso9660-image
232 (run-with-store store
233 (mbegin %store-monad
234 (set-guile-for-build (default-guile))
235 (system-disk-image installation-os
236 #:file-system-type
237 "iso9660"))))))
238 '())) 231 '()))
239 232
240(define channel-build-system 233(define channel-build-system
diff --git a/gnu/image.scm b/gnu/image.scm
new file mode 100644
index 00000000000..b05fc69dc57
--- /dev/null
+++ b/gnu/image.scm
@@ -0,0 +1,76 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2020 Mathieu Othacehe <m.othacehe@gmail.com>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu image)
20 #:use-module (guix records)
21 #:export (partition
22 partition?
23 partition-device
24 partition-size
25 partition-file-system
26 partition-label
27 partition-uuid
28 partition-flags
29 partition-initializer
30
31 image
32 image-name
33 image-format
34 image-size
35 image-operating-system
36 image-partitions
37 image-compression?
38 image-volatile-root?
39 image-substitutable?))
40
41
42;;;
43;;; Partition record.
44;;;
45
46(define-record-type* <partition> partition make-partition
47 partition?
48 (device partition-device (default #f))
49 (size partition-size)
50 (file-system partition-file-system (default "ext4"))
51 (label partition-label (default #f))
52 (uuid partition-uuid (default #f))
53 (flags partition-flags (default '()))
54 (initializer partition-initializer (default #f)))
55
56
57;;;
58;;; Image record.
59;;;
60
61(define-record-type* <image>
62 image make-image
63 image?
64 (format image-format) ;symbol
65 (size image-size ;size in bytes as integer
66 (default 'guess))
67 (operating-system image-operating-system ;<operating-system>
68 (default #f))
69 (partitions image-partitions ;list of <partition>
70 (default '()))
71 (compression? image-compression? ;boolean
72 (default #t))
73 (volatile-root? image-volatile-root? ;boolean
74 (default #t))
75 (substitutable? image-substitutable? ;boolean
76 (default #t)))
diff --git a/gnu/local.mk b/gnu/local.mk
index daf6bd0306e..4e0521baa59 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -62,6 +62,7 @@ GNU_SYSTEM_MODULES = \
62 %D%/bootloader/u-boot.scm \ 62 %D%/bootloader/u-boot.scm \
63 %D%/bootloader/depthcharge.scm \ 63 %D%/bootloader/depthcharge.scm \
64 %D%/ci.scm \ 64 %D%/ci.scm \
65 %D%/image.scm \
65 %D%/packages.scm \ 66 %D%/packages.scm \
66 %D%/packages/abduco.scm \ 67 %D%/packages/abduco.scm \
67 %D%/packages/abiword.scm \ 68 %D%/packages/abiword.scm \
@@ -606,6 +607,7 @@ GNU_SYSTEM_MODULES = \
606 %D%/system.scm \ 607 %D%/system.scm \
607 %D%/system/accounts.scm \ 608 %D%/system/accounts.scm \
608 %D%/system/file-systems.scm \ 609 %D%/system/file-systems.scm \
610 %D%/system/image.scm \
609 %D%/system/install.scm \ 611 %D%/system/install.scm \
610 %D%/system/keyboard.scm \ 612 %D%/system/keyboard.scm \
611 %D%/system/linux-container.scm \ 613 %D%/system/linux-container.scm \
@@ -626,6 +628,7 @@ GNU_SYSTEM_MODULES = \
626 %D%/build/activation.scm \ 628 %D%/build/activation.scm \
627 %D%/build/bootloader.scm \ 629 %D%/build/bootloader.scm \
628 %D%/build/cross-toolchain.scm \ 630 %D%/build/cross-toolchain.scm \
631 %D%/build/image.scm \
629 %D%/build/file-systems.scm \ 632 %D%/build/file-systems.scm \
630 %D%/build/install.scm \ 633 %D%/build/install.scm \
631 %D%/build/linux-boot.scm \ 634 %D%/build/linux-boot.scm \
diff --git a/gnu/system/image.scm b/gnu/system/image.scm
new file mode 100644
index 00000000000..571b7af5f35
--- /dev/null
+++ b/gnu/system/image.scm
@@ -0,0 +1,532 @@
1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2020 Mathieu Othacehe <m.othacehe@gmail.com>
3;;;
4;;; This file is part of GNU Guix.
5;;;
6;;; GNU Guix is free software; you can redistribute it and/or modify it
7;;; under the terms of the GNU General Public License as published by
8;;; the Free Software Foundation; either version 3 of the License, or (at
9;;; your option) any later version.
10;;;
11;;; GNU Guix is distributed in the hope that it will be useful, but
12;;; WITHOUT ANY WARRANTY; without even the implied warranty of
13;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14;;; GNU General Public License for more details.
15;;;
16;;; You should have received a copy of the GNU General Public License
17;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
18
19(define-module (gnu system image)
20 #:use-module (guix gexp)
21 #:use-module (guix modules)
22 #:use-module (guix monads)
23 #:use-module (guix records)
24 #:use-module (guix store)
25 #:use-module (guix ui)
26 #:use-module (guix utils)
27 #:use-module ((guix self) #:select (make-config.scm))
28 #:use-module (gnu bootloader)
29 #:use-module (gnu bootloader grub)
30 #:use-module (gnu image)
31 #:use-module (gnu services)
32 #:use-module (gnu services base)
33 #:use-module (gnu system)
34 #:use-module (gnu system file-systems)
35 #:use-module (gnu system uuid)
36 #:use-module (gnu system vm)
37 #:use-module (guix packages)
38 #:use-module (gnu packages base)
39 #:use-module (gnu packages bootloaders)
40 #:use-module (gnu packages cdrom)
41 #:use-module (gnu packages disk)
42 #:use-module (gnu packages gawk)
43 #:use-module (gnu packages genimage)
44 #:use-module (gnu packages guile)
45 #:autoload (gnu packages gnupg) (guile-gcrypt)
46 #:use-module (gnu packages linux)
47 #:use-module (gnu packages mtools)
48 #:use-module ((srfi srfi-1) #:prefix srfi-1:)
49 #:use-module (srfi srfi-11)
50 #:use-module (srfi srfi-26)
51 #:use-module (srfi srfi-35)
52 #:use-module (rnrs bytevectors)
53 #:use-module (ice-9 match)
54 #:export (esp-partition
55 root-partition
56
57 efi-disk-image
58 iso9660-image
59
60 find-image
61 system-image))
62
63
64;;;
65;;; Images definitions.
66;;;
67
68(define esp-partition
69 (partition
70 (size (* 40 (expt 2 20)))
71 (label "GNU-ESP") ;cosmetic only
72 ;; Use "vfat" here since this property is used when mounting. The actual
73 ;; FAT-ness is based on file system size (16 in this case).
74 (file-system "vfat")
75 (flags '(esp))
76 (initializer (gexp initialize-efi-partition))))
77
78(define root-partition
79 (partition
80 (size 'guess)
81 (label "Guix_image")
82 (file-system "ext4")
83 (flags '(boot))
84 (initializer (gexp initialize-root-partition))))
85
86(define efi-disk-image
87 (image
88 (format 'disk-image)
89 (partitions (list esp-partition root-partition))))
90
91(define iso9660-image
92 (image
93 (format 'iso9660)
94 (partitions
95 (list (partition
96 (size 'guess)
97 (label "GUIX_IMAGE")
98 (flags '(boot)))))
99 ;; XXX: Temporarily disable compression to speed-up the tests.
100 (compression? #f)))
101
102
103;;
104;; Helpers.
105;;
106
107(define not-config?
108 ;; Select (guix …) and (gnu …) modules, except (guix config).
109 (match-lambda
110 (('guix 'config) #f)
111 (('guix rest ...) #t)
112 (('gnu rest ...) #t)
113 (rest #f)))
114
115(define (partition->gexp partition)
116 "Turn PARTITION, a <partition> object, into a list-valued gexp suitable for
117'make-partition-image'."
118 #~'(#$@(list (partition-size partition))
119 #$(partition-file-system partition)
120 #$(partition-label partition)
121 #$(and=> (partition-uuid partition)
122 uuid-bytevector)))
123
124(define gcrypt-sqlite3&co
125 ;; Guile-Gcrypt, Guile-SQLite3, and their propagated inputs.
126 (srfi-1:append-map
127 (lambda (package)
128 (cons package
129 (match (package-transitive-propagated-inputs package)
130 (((labels packages) ...)
131 packages))))
132 (list guile-gcrypt guile-sqlite3)))
133
134(define-syntax-rule (with-imported-modules* gexp* ...)
135 (with-extensions gcrypt-sqlite3&co
136 (with-imported-modules `(,@(source-module-closure
137 '((gnu build vm)
138 (gnu build image)
139 (guix store database))
140 #:select? not-config?)
141 ((guix config) => ,(make-config.scm)))
142 #~(begin
143 (use-modules (gnu build vm)
144 (gnu build image)
145 (guix store database)
146 (guix build utils))
147 gexp* ...))))
148
149
150;;
151;; Disk image.
152;;
153
154(define* (system-disk-image image
155 #:key
156 (name "disk-image")
157 bootcfg
158 bootloader
159 register-closures?
160 (inputs '()))
161 "Return as a file-like object, the disk-image described by IMAGE. Said
162image can be copied on a USB stick as is. BOOTLOADER is the bootloader that
163will be installed and configured according to BOOTCFG parameter.
164
165Raw images of the IMAGE partitions are first created. Then, genimage is used
166to assemble the partition images into a disk-image without resorting to a
167virtual machine.
168
169INPUTS is a list of inputs (as for packages). When REGISTER-CLOSURES? is
170true, register INPUTS in the store database of the image so that Guix can be
171used in the image."
172
173 (define genimage-name "image")
174
175 (define (image->genimage-cfg image)
176 ;; Return as a file-like object, the genimage configuration file
177 ;; describing the given IMAGE.
178 (define (format->image-type format)
179 ;; Return the genimage format corresponding to FORMAT. For now, only
180 ;; the hdimage format (raw disk-image) is supported.
181 (case format
182 ((disk-image) "hdimage")
183 (else
184 (raise (condition
185 (&message
186 (message
187 (format #f (G_ "Unsupported image type ~a~%.") format))))))))
188
189 (define (partition->dos-type partition)
190 ;; Return the MBR partition type corresponding to the given PARTITION.
191 ;; See: https://en.wikipedia.org/wiki/Partition_type.
192 (let ((flags (partition-flags partition)))
193 (cond
194 ((member 'esp flags) "0xEF")
195 (else "0x83"))))
196
197 (define (partition-image partition)
198 ;; Return as a file-like object, an image of the given PARTITION. A
199 ;; directory, filled by calling the PARTITION initializer procedure, is
200 ;; first created within the store. Then, an image of this directory is
201 ;; created using tools such as 'mke2fs' or 'mkdosfs', depending on the
202 ;; partition file-system type.
203 (let* ((os (image-operating-system image))
204 (schema (local-file (search-path %load-path
205 "guix/store/schema.sql")))
206 (graph (match inputs
207 (((names . _) ...)
208 names)))
209 (root-builder
210 (with-imported-modules*
211 (let* ((initializer #$(partition-initializer partition)))
212 (sql-schema #$schema)
213
214 ;; Allow non-ASCII file names--e.g., 'nss-certs'--to be
215 ;; decoded.
216 (setenv "GUIX_LOCPATH"
217 #+(file-append glibc-utf8-locales "/lib/locale"))
218 (setlocale LC_ALL "en_US.utf8")
219
220 (initializer #$output
221 #:references-graphs '#$graph
222 #:deduplicate? #f
223 #:system-directory #$os
224 #:bootloader-package
225 #$(bootloader-package bootloader)
226 #:bootcfg #$bootcfg
227 #:bootcfg-location
228 #$(bootloader-configuration-file bootloader)))))
229 (image-root
230 (computed-file "partition-image-root" root-builder
231 #:options `(#:references-graphs ,inputs)))
232 (type (partition-file-system partition))
233 (image-builder
234 (with-imported-modules*
235 (let ((inputs '#$(list e2fsprogs dosfstools mtools)))
236 (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
237 (make-partition-image #$(partition->gexp partition)
238 #$output
239 #$image-root)))))
240 (computed-file "partition.img" image-builder)))
241
242 (define (partition->config partition)
243 ;; Return the genimage partition configuration for PARTITION.
244 (let ((label (partition-label partition))
245 (dos-type (partition->dos-type partition))
246 (image (partition-image partition)))
247 #~(format #f "~/partition ~a {
248 ~/~/partition-type = ~a
249 ~/~/image = \"~a\"
250 ~/}" #$label #$dos-type #$image)))
251
252 (let* ((format (image-format image))
253 (image-type (format->image-type format))
254 (partitions (image-partitions image))
255 (partitions-config (map partition->config partitions))
256 (builder
257 #~(begin
258 (let ((format (@ (ice-9 format) format)))
259 (call-with-output-file #$output
260 (lambda (port)
261 (format port
262 "\
263image ~a {
264~/~a {}
265~{~a~^~%~}
266}~%" #$genimage-name #$image-type (list #$@partitions-config))))))))
267 (computed-file "genimage.cfg" builder)))
268
269 (let* ((substitutable? (image-substitutable? image))
270 (builder
271 (with-imported-modules*
272 (let ((inputs '#$(list genimage coreutils findutils)))
273 (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
274 (genimage #$(image->genimage-cfg image) #$output))))
275 (image-dir (computed-file "image-dir" builder)))
276 (computed-file name
277 #~(symlink
278 (string-append #$image-dir "/" #$genimage-name)
279 #$output)
280 #:options `(#:substitutable? ,substitutable?))))
281
282
283;;
284;; ISO9660 image.
285;;
286
287(define (has-guix-service-type? os)
288 "Return true if OS contains a service of the type GUIX-SERVICE-TYPE."
289 (not (not (srfi-1:find (lambda (service)
290 (eq? (service-kind service) guix-service-type))
291 (operating-system-services os)))))
292
293(define* (system-iso9660-image image
294 #:key
295 (name "iso9660-image")
296 bootcfg
297 bootloader
298 register-closures?
299 (inputs '())
300 (grub-mkrescue-environment '()))
301 "Return as a file-like object a bootable, stand-alone iso9660 image.
302
303INPUTS is a list of inputs (as for packages). When REGISTER-CLOSURES? is
304true, register INPUTS in the store database of the image so that Guix can be
305used in the image. "
306 (define root-label
307 (match (image-partitions image)
308 ((partition)
309 (partition-label partition))))
310
311 (define root-uuid
312 (match (image-partitions image)
313 ((partition)
314 (uuid-bytevector (partition-uuid partition)))))
315
316 (let* ((os (image-operating-system image))
317 (bootloader (bootloader-package bootloader))
318 (compression? (image-compression? image))
319 (substitutable? (image-substitutable? image))
320 (schema (local-file (search-path %load-path
321 "guix/store/schema.sql")))
322 (graph (match inputs
323 (((names . _) ...)
324 names)))
325 (root-builder
326 (with-imported-modules*
327 (sql-schema #$schema)
328
329 ;; Allow non-ASCII file names--e.g., 'nss-certs'--to be decoded.
330 (setenv "GUIX_LOCPATH"
331 #+(file-append glibc-utf8-locales "/lib/locale"))
332 (setlocale LC_ALL "en_US.utf8")
333
334 (initialize-root-partition #$output
335 #:references-graphs '#$graph
336 #:deduplicate? #f
337 #:system-directory #$os)))
338 (image-root
339 (computed-file "image-root" root-builder
340 #:options `(#:references-graphs ,inputs)))
341 (builder
342 (with-imported-modules*
343 (let* ((inputs '#$(list parted e2fsprogs dosfstools xorriso
344 sed grep coreutils findutils gawk)))
345 (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
346 (make-iso9660-image #$xorriso
347 '#$grub-mkrescue-environment
348 #$bootloader
349 #$bootcfg
350 #$os
351 #$image-root
352 #$output
353 #:references-graphs '#$graph
354 #:register-closures? #$register-closures?
355 #:compression? #$compression?
356 #:volume-id #$root-label
357 #:volume-uuid #$root-uuid)))))
358 (computed-file name builder
359 #:options `(#:references-graphs ,inputs
360 #:substitutable? ,substitutable?))))
361
362
363;;
364;; Image creation.
365;;
366
367(define (root-partition? partition)
368 "Return true if PARTITION is the root partition, false otherwise."
369 (member 'boot (partition-flags partition)))
370
371(define (find-root-partition image)
372 "Return the root partition of the given IMAGE."
373 (srfi-1:find root-partition? (image-partitions image)))
374
375(define (image->root-file-system image)
376 "Return the IMAGE root partition file-system type."
377 (let ((format (image-format image)))
378 (if (eq? format 'iso9660)
379 "iso9660"
380 (partition-file-system (find-root-partition image)))))
381
382(define (root-size image)
383 "Return the root partition size of IMAGE."
384 (let* ((image-size (image-size image))
385 (root-partition (find-root-partition image))
386 (root-size (partition-size root-partition)))
387 (cond
388 ((and (eq? root-size 'guess) image-size)
389 image-size)
390 (else root-size))))
391
392(define* (image-with-os base-image os)
393 "Return an image based on BASE-IMAGE but with the operating-system field set
394to OS. Also set the UUID and the size of the root partition."
395 (define root-file-system
396 (srfi-1:find
397 (lambda (fs)
398 (string=? (file-system-mount-point fs) "/"))
399 (operating-system-file-systems os)))
400
401 (let*-values (((partitions) (image-partitions base-image))
402 ((root-partition other-partitions)
403 (srfi-1:partition root-partition? partitions)))
404 (image
405 (inherit base-image)
406 (operating-system os)
407 (partitions
408 (cons (partition
409 (inherit (car root-partition))
410 (uuid (file-system-device root-file-system))
411 (size (root-size base-image)))
412 other-partitions)))))
413
414(define (operating-system-for-image image)
415 "Return an operating-system based on the one specified in IMAGE, but
416suitable for image creation. Assign an UUID to the root file-system, so that
417it can be used for bootloading."
418 (define volatile-root? (image-volatile-root? image))
419
420 (define (root-uuid os)
421 ;; UUID of the root file system, computed in a deterministic fashion.
422 ;; This is what we use to locate the root file system so it has to be
423 ;; different from the user's own file system UUIDs.
424 (let ((type (if (eq? (image-format image) 'iso9660)
425 'iso9660
426 'dce)))
427 (operating-system-uuid os type)))
428
429 (let* ((root-file-system-type (image->root-file-system image))
430 (base-os (image-operating-system image))
431 (file-systems-to-keep
432 (srfi-1:remove
433 (lambda (fs)
434 (string=? (file-system-mount-point fs) "/"))
435 (operating-system-file-systems base-os)))
436 (format (image-format image))
437 (os
438 (operating-system
439 (inherit base-os)
440 (initrd (lambda (file-systems . rest)
441 (apply (operating-system-initrd base-os)
442 file-systems
443 #:volatile-root? volatile-root?
444 rest)))
445 (bootloader (if (eq? format 'iso9660)
446 (bootloader-configuration
447 (inherit
448 (operating-system-bootloader base-os))
449 (bootloader grub-mkrescue-bootloader))
450 (operating-system-bootloader base-os)))
451 (file-systems (cons (file-system
452 (mount-point "/")
453 (device "/dev/placeholder")
454 (type root-file-system-type))
455 file-systems-to-keep))))
456 (uuid (root-uuid os)))
457 (operating-system
458 (inherit os)
459 (file-systems (cons (file-system
460 (mount-point "/")
461 (device uuid)
462 (type root-file-system-type))
463 file-systems-to-keep)))))
464
465(define* (make-system-image image)
466 "Return the derivation of IMAGE. It can be a raw disk-image or an ISO9660
467image, depending on IMAGE format."
468 (define substitutable? (image-substitutable? image))
469
470 (let* ((os (operating-system-for-image image))
471 (image* (image-with-os image os))
472 (register-closures? (has-guix-service-type? os))
473 (bootcfg (operating-system-bootcfg os))
474 (bootloader (bootloader-configuration-bootloader
475 (operating-system-bootloader os))))
476 (case (image-format image)
477 ((disk-image)
478 (system-disk-image image*
479 #:bootcfg bootcfg
480 #:bootloader bootloader
481 #:register-closures? register-closures?
482 #:inputs `(("system" ,os)
483 ("bootcfg" ,bootcfg))))
484 ((iso9660)
485 (system-iso9660-image image*
486 #:bootcfg bootcfg
487 #:bootloader bootloader
488 #:register-closures? register-closures?
489 #:inputs `(("system" ,os)
490 ("bootcfg" ,bootcfg))
491 #:grub-mkrescue-environment
492 '(("MKRESCUE_SED_MODE" . "mbr_hfs")))))))
493
494(define (find-image file-system-type)
495 "Find and return an image that could match the given FILE-SYSTEM-TYPE. This
496is useful to adapt to interfaces written before the addition of the <image>
497record."
498 ;; XXX: Add support for system and target here, or in the caller.
499 (match file-system-type
500 ("iso9660" iso9660-image)
501 (_ efi-disk-image)))
502
503(define (system-image image)
504 "Wrap 'make-system-image' call, so that it is used only if the given IMAGE
505is supported. Otherwise, fallback to image creation in a VM. This is
506temporary and should be removed once 'make-system-image' is able to deal with
507all types of images."
508 (define substitutable? (image-substitutable? image))
509 (define volatile-root? (image-volatile-root? image))
510
511 (let* ((image-os (image-operating-system image))
512 (image-root-filesystem-type (image->root-file-system image))
513 (bootloader (bootloader-configuration-bootloader
514 (operating-system-bootloader image-os)))
515 (bootloader-name (bootloader-name bootloader))
516 (size (image-size image))
517 (format (image-format image)))
518 (mbegin %store-monad
519 (if (and (or (eq? bootloader-name 'grub)
520 (eq? bootloader-name 'extlinux))
521 (eq? format 'disk-image))
522 ;; Fallback to image creation in a VM when it is not yet supported
523 ;; by this module.
524 (system-disk-image-in-vm image-os
525 #:disk-image-size size
526 #:file-system-type image-root-filesystem-type
527 #:volatile? volatile-root?
528 #:substitutable? substitutable?)
529 (lower-object
530 (make-system-image image))))))
531
532;;; image.scm ends here
diff --git a/gnu/system/vm.scm b/gnu/system/vm.scm
index 2fdf954883b..37840ce3555 100644
--- a/gnu/system/vm.scm
+++ b/gnu/system/vm.scm
@@ -77,7 +77,7 @@
77 77
78 system-qemu-image/shared-store 78 system-qemu-image/shared-store
79 system-qemu-image/shared-store-script 79 system-qemu-image/shared-store-script
80 system-disk-image 80 system-disk-image-in-vm
81 system-docker-image 81 system-docker-image
82 82
83 virtual-machine 83 virtual-machine
@@ -604,14 +604,13 @@ system."
604;;; VM and disk images. 604;;; VM and disk images.
605;;; 605;;;
606 606
607 607(define* (system-disk-image-in-vm os
608(define* (system-disk-image os 608 #:key
609 #:key 609 (name "disk-image")
610 (name "disk-image") 610 (file-system-type "ext4")
611 (file-system-type "ext4") 611 (disk-image-size (* 900 (expt 2 20)))
612 (disk-image-size (* 900 (expt 2 20))) 612 (volatile? #t)
613 (volatile? #t) 613 (substitutable? #t))
614 (substitutable? #t))
615 "Return the derivation of a disk image of DISK-IMAGE-SIZE bytes of the 614 "Return the derivation of a disk image of DISK-IMAGE-SIZE bytes of the
616system described by OS. Said image can be copied on a USB stick as is. When 615system described by OS. Said image can be copied on a USB stick as is. When
617VOLATILE? is true, the root file system is made volatile; this is useful 616VOLATILE? is true, the root file system is made volatile; this is useful
diff --git a/gnu/tests/install.scm b/gnu/tests/install.scm
index 23f60c68bf5..2e5913953e7 100644
--- a/gnu/tests/install.scm
+++ b/gnu/tests/install.scm
@@ -22,9 +22,11 @@
22(define-module (gnu tests install) 22(define-module (gnu tests install)
23 #:use-module (gnu) 23 #:use-module (gnu)
24 #:use-module (gnu bootloader extlinux) 24 #:use-module (gnu bootloader extlinux)
25 #:use-module (gnu image)
25 #:use-module (gnu tests) 26 #:use-module (gnu tests)
26 #:use-module (gnu tests base) 27 #:use-module (gnu tests base)
27 #:use-module (gnu system) 28 #:use-module (gnu system)
29 #:use-module (gnu system image)
28 #:use-module (gnu system install) 30 #:use-module (gnu system install)
29 #:use-module (gnu system vm) 31 #:use-module (gnu system vm)
30 #:use-module ((gnu build vm) #:select (qemu-command)) 32 #:use-module ((gnu build vm) #:select (qemu-command))
@@ -229,14 +231,18 @@ packages defined in installation-os."
229 ;; we cheat a little bit by adding TARGET to its GC 231 ;; we cheat a little bit by adding TARGET to its GC
230 ;; roots. This way, we know 'guix system init' will 232 ;; roots. This way, we know 'guix system init' will
231 ;; succeed. 233 ;; succeed.
232 (image (system-disk-image 234 (image
233 (operating-system-with-gc-roots 235 (system-image
234 os (list target)) 236 (image
235 #:disk-image-size install-size 237 (inherit
236 #:file-system-type 238 (find-image
237 installation-disk-image-file-system-type 239 installation-disk-image-file-system-type))
238 ;; Don't provide substitutes; too big. 240 (size install-size)
239 #:substitutable? #f))) 241 (operating-system
242 (operating-system-with-gc-roots
243 os (list target)))
244 ;; Don't provide substitutes; too big.
245 (substitutable? #f)))))
240 (define install 246 (define install
241 (with-imported-modules '((guix build utils) 247 (with-imported-modules '((guix build utils)
242 (gnu build marionette)) 248 (gnu build marionette))