summaryrefslogtreecommitdiff
path: root/gnu/system/image.scm
diff options
context:
space:
mode:
Diffstat (limited to 'gnu/system/image.scm')
-rw-r--r--gnu/system/image.scm532
1 files changed, 532 insertions, 0 deletions
diff --git a/gnu/system/image.scm b/gnu/system/image.scm
new file mode 100644
index 0000000000..571b7af5f3
--- /dev/null
+++ b/gnu/system/image.scm
@@ -0,0 +1,532 @@
+;;; GNU Guix --- Functional package management for GNU
+;;; Copyright © 2020 Mathieu Othacehe <m.othacehe@gmail.com>
+;;;
+;;; 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 system image)
+ #:use-module (guix gexp)
+ #:use-module (guix modules)
+ #:use-module (guix monads)
+ #:use-module (guix records)
+ #:use-module (guix store)
+ #:use-module (guix ui)
+ #:use-module (guix utils)
+ #:use-module ((guix self) #:select (make-config.scm))
+ #:use-module (gnu bootloader)
+ #:use-module (gnu bootloader grub)
+ #:use-module (gnu image)
+ #:use-module (gnu services)
+ #:use-module (gnu services base)
+ #:use-module (gnu system)
+ #:use-module (gnu system file-systems)
+ #:use-module (gnu system uuid)
+ #:use-module (gnu system vm)
+ #:use-module (guix packages)
+ #:use-module (gnu packages base)
+ #:use-module (gnu packages bootloaders)
+ #:use-module (gnu packages cdrom)
+ #:use-module (gnu packages disk)
+ #:use-module (gnu packages gawk)
+ #:use-module (gnu packages genimage)
+ #:use-module (gnu packages guile)
+ #:autoload (gnu packages gnupg) (guile-gcrypt)
+ #:use-module (gnu packages linux)
+ #:use-module (gnu packages mtools)
+ #:use-module ((srfi srfi-1) #:prefix srfi-1:)
+ #:use-module (srfi srfi-11)
+ #:use-module (srfi srfi-26)
+ #:use-module (srfi srfi-35)
+ #:use-module (rnrs bytevectors)
+ #:use-module (ice-9 match)
+ #:export (esp-partition
+ root-partition
+
+ efi-disk-image
+ iso9660-image
+
+ find-image
+ system-image))
+
+
+;;;
+;;; Images definitions.
+;;;
+
+(define esp-partition
+ (partition
+ (size (* 40 (expt 2 20)))
+ (label "GNU-ESP") ;cosmetic only
+ ;; Use "vfat" here since this property is used when mounting. The actual
+ ;; FAT-ness is based on file system size (16 in this case).
+ (file-system "vfat")
+ (flags '(esp))
+ (initializer (gexp initialize-efi-partition))))
+
+(define root-partition
+ (partition
+ (size 'guess)
+ (label "Guix_image")
+ (file-system "ext4")
+ (flags '(boot))
+ (initializer (gexp initialize-root-partition))))
+
+(define efi-disk-image
+ (image
+ (format 'disk-image)
+ (partitions (list esp-partition root-partition))))
+
+(define iso9660-image
+ (image
+ (format 'iso9660)
+ (partitions
+ (list (partition
+ (size 'guess)
+ (label "GUIX_IMAGE")
+ (flags '(boot)))))
+ ;; XXX: Temporarily disable compression to speed-up the tests.
+ (compression? #f)))
+
+
+;;
+;; Helpers.
+;;
+
+(define not-config?
+ ;; Select (guix …) and (gnu …) modules, except (guix config).
+ (match-lambda
+ (('guix 'config) #f)
+ (('guix rest ...) #t)
+ (('gnu rest ...) #t)
+ (rest #f)))
+
+(define (partition->gexp partition)
+ "Turn PARTITION, a <partition> object, into a list-valued gexp suitable for
+'make-partition-image'."
+ #~'(#$@(list (partition-size partition))
+ #$(partition-file-system partition)
+ #$(partition-label partition)
+ #$(and=> (partition-uuid partition)
+ uuid-bytevector)))
+
+(define gcrypt-sqlite3&co
+ ;; Guile-Gcrypt, Guile-SQLite3, and their propagated inputs.
+ (srfi-1:append-map
+ (lambda (package)
+ (cons package
+ (match (package-transitive-propagated-inputs package)
+ (((labels packages) ...)
+ packages))))
+ (list guile-gcrypt guile-sqlite3)))
+
+(define-syntax-rule (with-imported-modules* gexp* ...)
+ (with-extensions gcrypt-sqlite3&co
+ (with-imported-modules `(,@(source-module-closure
+ '((gnu build vm)
+ (gnu build image)
+ (guix store database))
+ #:select? not-config?)
+ ((guix config) => ,(make-config.scm)))
+ #~(begin
+ (use-modules (gnu build vm)
+ (gnu build image)
+ (guix store database)
+ (guix build utils))
+ gexp* ...))))
+
+
+;;
+;; Disk image.
+;;
+
+(define* (system-disk-image image
+ #:key
+ (name "disk-image")
+ bootcfg
+ bootloader
+ register-closures?
+ (inputs '()))
+ "Return as a file-like object, the disk-image described by IMAGE. Said
+image can be copied on a USB stick as is. BOOTLOADER is the bootloader that
+will be installed and configured according to BOOTCFG parameter.
+
+Raw images of the IMAGE partitions are first created. Then, genimage is used
+to assemble the partition images into a disk-image without resorting to a
+virtual machine.
+
+INPUTS is a list of inputs (as for packages). When REGISTER-CLOSURES? is
+true, register INPUTS in the store database of the image so that Guix can be
+used in the image."
+
+ (define genimage-name "image")
+
+ (define (image->genimage-cfg image)
+ ;; Return as a file-like object, the genimage configuration file
+ ;; describing the given IMAGE.
+ (define (format->image-type format)
+ ;; Return the genimage format corresponding to FORMAT. For now, only
+ ;; the hdimage format (raw disk-image) is supported.
+ (case format
+ ((disk-image) "hdimage")
+ (else
+ (raise (condition
+ (&message
+ (message
+ (format #f (G_ "Unsupported image type ~a~%.") format))))))))
+
+ (define (partition->dos-type partition)
+ ;; Return the MBR partition type corresponding to the given PARTITION.
+ ;; See: https://en.wikipedia.org/wiki/Partition_type.
+ (let ((flags (partition-flags partition)))
+ (cond
+ ((member 'esp flags) "0xEF")
+ (else "0x83"))))
+
+ (define (partition-image partition)
+ ;; Return as a file-like object, an image of the given PARTITION. A
+ ;; directory, filled by calling the PARTITION initializer procedure, is
+ ;; first created within the store. Then, an image of this directory is
+ ;; created using tools such as 'mke2fs' or 'mkdosfs', depending on the
+ ;; partition file-system type.
+ (let* ((os (image-operating-system image))
+ (schema (local-file (search-path %load-path
+ "guix/store/schema.sql")))
+ (graph (match inputs
+ (((names . _) ...)
+ names)))
+ (root-builder
+ (with-imported-modules*
+ (let* ((initializer #$(partition-initializer partition)))
+ (sql-schema #$schema)
+
+ ;; Allow non-ASCII file names--e.g., 'nss-certs'--to be
+ ;; decoded.
+ (setenv "GUIX_LOCPATH"
+ #+(file-append glibc-utf8-locales "/lib/locale"))
+ (setlocale LC_ALL "en_US.utf8")
+
+ (initializer #$output
+ #:references-graphs '#$graph
+ #:deduplicate? #f
+ #:system-directory #$os
+ #:bootloader-package
+ #$(bootloader-package bootloader)
+ #:bootcfg #$bootcfg
+ #:bootcfg-location
+ #$(bootloader-configuration-file bootloader)))))
+ (image-root
+ (computed-file "partition-image-root" root-builder
+ #:options `(#:references-graphs ,inputs)))
+ (type (partition-file-system partition))
+ (image-builder
+ (with-imported-modules*
+ (let ((inputs '#$(list e2fsprogs dosfstools mtools)))
+ (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
+ (make-partition-image #$(partition->gexp partition)
+ #$output
+ #$image-root)))))
+ (computed-file "partition.img" image-builder)))
+
+ (define (partition->config partition)
+ ;; Return the genimage partition configuration for PARTITION.
+ (let ((label (partition-label partition))
+ (dos-type (partition->dos-type partition))
+ (image (partition-image partition)))
+ #~(format #f "~/partition ~a {
+ ~/~/partition-type = ~a
+ ~/~/image = \"~a\"
+ ~/}" #$label #$dos-type #$image)))
+
+ (let* ((format (image-format image))
+ (image-type (format->image-type format))
+ (partitions (image-partitions image))
+ (partitions-config (map partition->config partitions))
+ (builder
+ #~(begin
+ (let ((format (@ (ice-9 format) format)))
+ (call-with-output-file #$output
+ (lambda (port)
+ (format port
+ "\
+image ~a {
+~/~a {}
+~{~a~^~%~}
+}~%" #$genimage-name #$image-type (list #$@partitions-config))))))))
+ (computed-file "genimage.cfg" builder)))
+
+ (let* ((substitutable? (image-substitutable? image))
+ (builder
+ (with-imported-modules*
+ (let ((inputs '#$(list genimage coreutils findutils)))
+ (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
+ (genimage #$(image->genimage-cfg image) #$output))))
+ (image-dir (computed-file "image-dir" builder)))
+ (computed-file name
+ #~(symlink
+ (string-append #$image-dir "/" #$genimage-name)
+ #$output)
+ #:options `(#:substitutable? ,substitutable?))))
+
+
+;;
+;; ISO9660 image.
+;;
+
+(define (has-guix-service-type? os)
+ "Return true if OS contains a service of the type GUIX-SERVICE-TYPE."
+ (not (not (srfi-1:find (lambda (service)
+ (eq? (service-kind service) guix-service-type))
+ (operating-system-services os)))))
+
+(define* (system-iso9660-image image
+ #:key
+ (name "iso9660-image")
+ bootcfg
+ bootloader
+ register-closures?
+ (inputs '())
+ (grub-mkrescue-environment '()))
+ "Return as a file-like object a bootable, stand-alone iso9660 image.
+
+INPUTS is a list of inputs (as for packages). When REGISTER-CLOSURES? is
+true, register INPUTS in the store database of the image so that Guix can be
+used in the image. "
+ (define root-label
+ (match (image-partitions image)
+ ((partition)
+ (partition-label partition))))
+
+ (define root-uuid
+ (match (image-partitions image)
+ ((partition)
+ (uuid-bytevector (partition-uuid partition)))))
+
+ (let* ((os (image-operating-system image))
+ (bootloader (bootloader-package bootloader))
+ (compression? (image-compression? image))
+ (substitutable? (image-substitutable? image))
+ (schema (local-file (search-path %load-path
+ "guix/store/schema.sql")))
+ (graph (match inputs
+ (((names . _) ...)
+ names)))
+ (root-builder
+ (with-imported-modules*
+ (sql-schema #$schema)
+
+ ;; Allow non-ASCII file names--e.g., 'nss-certs'--to be decoded.
+ (setenv "GUIX_LOCPATH"
+ #+(file-append glibc-utf8-locales "/lib/locale"))
+ (setlocale LC_ALL "en_US.utf8")
+
+ (initialize-root-partition #$output
+ #:references-graphs '#$graph
+ #:deduplicate? #f
+ #:system-directory #$os)))
+ (image-root
+ (computed-file "image-root" root-builder
+ #:options `(#:references-graphs ,inputs)))
+ (builder
+ (with-imported-modules*
+ (let* ((inputs '#$(list parted e2fsprogs dosfstools xorriso
+ sed grep coreutils findutils gawk)))
+ (set-path-environment-variable "PATH" '("bin" "sbin") inputs)
+ (make-iso9660-image #$xorriso
+ '#$grub-mkrescue-environment
+ #$bootloader
+ #$bootcfg
+ #$os
+ #$image-root
+ #$output
+ #:references-graphs '#$graph
+ #:register-closures? #$register-closures?
+ #:compression? #$compression?
+ #:volume-id #$root-label
+ #:volume-uuid #$root-uuid)))))
+ (computed-file name builder
+ #:options `(#:references-graphs ,inputs
+ #:substitutable? ,substitutable?))))
+
+
+;;
+;; Image creation.
+;;
+
+(define (root-partition? partition)
+ "Return true if PARTITION is the root partition, false otherwise."
+ (member 'boot (partition-flags partition)))
+
+(define (find-root-partition image)
+ "Return the root partition of the given IMAGE."
+ (srfi-1:find root-partition? (image-partitions image)))
+
+(define (image->root-file-system image)
+ "Return the IMAGE root partition file-system type."
+ (let ((format (image-format image)))
+ (if (eq? format 'iso9660)
+ "iso9660"
+ (partition-file-system (find-root-partition image)))))
+
+(define (root-size image)
+ "Return the root partition size of IMAGE."
+ (let* ((image-size (image-size image))
+ (root-partition (find-root-partition image))
+ (root-size (partition-size root-partition)))
+ (cond
+ ((and (eq? root-size 'guess) image-size)
+ image-size)
+ (else root-size))))
+
+(define* (image-with-os base-image os)
+ "Return an image based on BASE-IMAGE but with the operating-system field set
+to OS. Also set the UUID and the size of the root partition."
+ (define root-file-system
+ (srfi-1:find
+ (lambda (fs)
+ (string=? (file-system-mount-point fs) "/"))
+ (operating-system-file-systems os)))
+
+ (let*-values (((partitions) (image-partitions base-image))
+ ((root-partition other-partitions)
+ (srfi-1:partition root-partition? partitions)))
+ (image
+ (inherit base-image)
+ (operating-system os)
+ (partitions
+ (cons (partition
+ (inherit (car root-partition))
+ (uuid (file-system-device root-file-system))
+ (size (root-size base-image)))
+ other-partitions)))))
+
+(define (operating-system-for-image image)
+ "Return an operating-system based on the one specified in IMAGE, but
+suitable for image creation. Assign an UUID to the root file-system, so that
+it can be used for bootloading."
+ (define volatile-root? (image-volatile-root? image))
+
+ (define (root-uuid os)
+ ;; UUID of the root file system, computed in a deterministic fashion.
+ ;; This is what we use to locate the root file system so it has to be
+ ;; different from the user's own file system UUIDs.
+ (let ((type (if (eq? (image-format image) 'iso9660)
+ 'iso9660
+ 'dce)))
+ (operating-system-uuid os type)))
+
+ (let* ((root-file-system-type (image->root-file-system image))
+ (base-os (image-operating-system image))
+ (file-systems-to-keep
+ (srfi-1:remove
+ (lambda (fs)
+ (string=? (file-system-mount-point fs) "/"))
+ (operating-system-file-systems base-os)))
+ (format (image-format image))
+ (os
+ (operating-system
+ (inherit base-os)
+ (initrd (lambda (file-systems . rest)
+ (apply (operating-system-initrd base-os)
+ file-systems
+ #:volatile-root? volatile-root?
+ rest)))
+ (bootloader (if (eq? format 'iso9660)
+ (bootloader-configuration
+ (inherit
+ (operating-system-bootloader base-os))
+ (bootloader grub-mkrescue-bootloader))
+ (operating-system-bootloader base-os)))
+ (file-systems (cons (file-system
+ (mount-point "/")
+ (device "/dev/placeholder")
+ (type root-file-system-type))
+ file-systems-to-keep))))
+ (uuid (root-uuid os)))
+ (operating-system
+ (inherit os)
+ (file-systems (cons (file-system
+ (mount-point "/")
+ (device uuid)
+ (type root-file-system-type))
+ file-systems-to-keep)))))
+
+(define* (make-system-image image)
+ "Return the derivation of IMAGE. It can be a raw disk-image or an ISO9660
+image, depending on IMAGE format."
+ (define substitutable? (image-substitutable? image))
+
+ (let* ((os (operating-system-for-image image))
+ (image* (image-with-os image os))
+ (register-closures? (has-guix-service-type? os))
+ (bootcfg (operating-system-bootcfg os))
+ (bootloader (bootloader-configuration-bootloader
+ (operating-system-bootloader os))))
+ (case (image-format image)
+ ((disk-image)
+ (system-disk-image image*
+ #:bootcfg bootcfg
+ #:bootloader bootloader
+ #:register-closures? register-closures?
+ #:inputs `(("system" ,os)
+ ("bootcfg" ,bootcfg))))
+ ((iso9660)
+ (system-iso9660-image image*
+ #:bootcfg bootcfg
+ #:bootloader bootloader
+ #:register-closures? register-closures?
+ #:inputs `(("system" ,os)
+ ("bootcfg" ,bootcfg))
+ #:grub-mkrescue-environment
+ '(("MKRESCUE_SED_MODE" . "mbr_hfs")))))))
+
+(define (find-image file-system-type)
+ "Find and return an image that could match the given FILE-SYSTEM-TYPE. This
+is useful to adapt to interfaces written before the addition of the <image>
+record."
+ ;; XXX: Add support for system and target here, or in the caller.
+ (match file-system-type
+ ("iso9660" iso9660-image)
+ (_ efi-disk-image)))
+
+(define (system-image image)
+ "Wrap 'make-system-image' call, so that it is used only if the given IMAGE
+is supported. Otherwise, fallback to image creation in a VM. This is
+temporary and should be removed once 'make-system-image' is able to deal with
+all types of images."
+ (define substitutable? (image-substitutable? image))
+ (define volatile-root? (image-volatile-root? image))
+
+ (let* ((image-os (image-operating-system image))
+ (image-root-filesystem-type (image->root-file-system image))
+ (bootloader (bootloader-configuration-bootloader
+ (operating-system-bootloader image-os)))
+ (bootloader-name (bootloader-name bootloader))
+ (size (image-size image))
+ (format (image-format image)))
+ (mbegin %store-monad
+ (if (and (or (eq? bootloader-name 'grub)
+ (eq? bootloader-name 'extlinux))
+ (eq? format 'disk-image))
+ ;; Fallback to image creation in a VM when it is not yet supported
+ ;; by this module.
+ (system-disk-image-in-vm image-os
+ #:disk-image-size size
+ #:file-system-type image-root-filesystem-type
+ #:volatile? volatile-root?
+ #:substitutable? substitutable?)
+ (lower-object
+ (make-system-image image))))))
+
+;;; image.scm ends here