reconfigure: Support loading the system for kexec reboot.

This allows rebooting straight into the new system with ‘reboot -k’.

* guix/scripts/system/reconfigure.scm (kexec-loading-program)
(load-system-for-kexec): New procedures.
* gnu/tests/reconfigure.scm (run-kexec-test): New procedure.
(%test-upgrade-kexec): New variable.
* guix/scripts/system.scm (perform-action): Add #:load-for-kexec?.
Call ‘load-system-for-kexec’.
(show-help, %options): Add ‘--no-kexec’.
(%default-options): Add ‘load-for-kexec?’.
(process-action): Honor it and pass it to ‘perform-action’.
* gnu/machine/ssh.scm (deploy-managed-host): Add call to
‘load-system-for-kexec’.
* doc/guix.texi (Invoking guix system): Document it.

Change-Id: I86d11f1c348e4359bc9e73c86e5aebff60fe875c
This commit is contained in:
Ludovic Courtès 2024-12-19 16:24:49 +01:00
parent ebe706a1e8
commit 1305f78d05
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
5 changed files with 143 additions and 3 deletions

View file

@ -43599,11 +43599,20 @@ list-generations}). If that generation already exists, it will be
overwritten. This behavior mirrors that of @command{guix package} overwritten. This behavior mirrors that of @command{guix package}
(@pxref{Invoking guix package}). (@pxref{Invoking guix package}).
It also adds a bootloader menu entry for the new OS configuration, It adds a bootloader menu entry for the new OS configuration,
---unless @option{--no-bootloader} is passed. For GRUB, it moves ---unless @option{--no-bootloader} is passed. For GRUB, it moves
entries for older configurations to a submenu, allowing you to choose entries for older configurations to a submenu, allowing you to choose
an older system generation at boot time should you need it. an older system generation at boot time should you need it.
@cindex kexec, for fast reboots
@cindex rebooting @i{via} Linux kexec
On Linux, @command{guix system reconfigure} also loads the new system
for fast reboot @i{via} kexec: running @command{reboot --kexec} will
boot the new system by directly executing its kernel, thus bypassing the
BIOS initialization phase and bootloader (@pxref{Invoking reboot,,,
shepherd, The GNU Shepherd Manual}). You can avoid this behavior by
passing the @option{--no-kexec} option.
@cindex provenance tracking, of the operating system @cindex provenance tracking, of the operating system
Upon completion, the new system is deployed under Upon completion, the new system is deployed under
@file{/run/current-system}. This directory contains @dfn{provenance @file{/run/current-system}. This directory contains @dfn{provenance

View file

@ -1,6 +1,6 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org> ;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org>
;;; Copyright © 2020-2023 Ludovic Courtès <ludo@gnu.org> ;;; Copyright © 2020-2024 Ludovic Courtès <ludo@gnu.org>
;;; Copyright © 2024 Ricardo <rekado@elephly.net> ;;; Copyright © 2024 Ricardo <rekado@elephly.net>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
@ -552,6 +552,13 @@ (define-syntax-rule (eval/error-handling condition handler ...)
(inferior-exception-arguments (inferior-exception-arguments
c))) c)))
os) os)
(load-system-for-kexec (eval/error-handling c
(warning (G_ "\
failed to load system of '~a' for kexec reboot:~%~{~s~^ ~}~%")
host
(inferior-exception-arguments
c)))
os)
(install-bootloader (eval/error-handling c (install-bootloader (eval/error-handling c
(raise (formatted-message (raise (formatted-message
(G_ "\ (G_ "\

View file

@ -1,5 +1,6 @@
;;; GNU Guix --- Functional package management for GNU ;;; GNU Guix --- Functional package management for GNU
;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org> ;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org>
;;; Copyright © 2024 Ludovic Courtès <ludo@gnu.org>
;;; ;;;
;;; This file is part of GNU Guix. ;;; This file is part of GNU Guix.
;;; ;;;
@ -18,9 +19,12 @@
(define-module (gnu tests reconfigure) (define-module (gnu tests reconfigure)
#:use-module (gnu bootloader) #:use-module (gnu bootloader)
#:use-module (gnu services)
#:use-module (gnu services base)
#:use-module (gnu services shepherd) #:use-module (gnu services shepherd)
#:use-module (gnu system) #:use-module (gnu system)
#:use-module (gnu system accounts) #:use-module (gnu system accounts)
#:use-module (gnu system file-systems)
#:use-module (gnu system shadow) #:use-module (gnu system shadow)
#:use-module (gnu system vm) #:use-module (gnu system vm)
#:use-module (gnu tests) #:use-module (gnu tests)
@ -31,6 +35,7 @@ (define-module (gnu tests reconfigure)
#:use-module (guix store) #:use-module (guix store)
#:export (%test-switch-to-system #:export (%test-switch-to-system
%test-upgrade-services %test-upgrade-services
%test-upgrade-kexec
%test-install-bootloader)) %test-install-bootloader))
;;; Commentary: ;;; Commentary:
@ -178,6 +183,73 @@ (define (running-services marionette)
(disable (upgrade-services-program '() '() '(dummy) '()))) (disable (upgrade-services-program '() '() '(dummy) '())))
(test enable disable)))) (test enable disable))))
(define (run-kexec-test)
"Run a test aiming to reboot via Linux kexec into a new system."
(define os
(marionette-operating-system
(operating-system
(inherit %simple-os)
(services (modify-services %base-services
(syslog-service-type
config => (syslog-configuration
(inherit config)
(config-file
(plain-file
"syslog.conf"
"*.* /dev/console\n")))))))
#:imported-modules '((gnu services herd)
(guix combinators))))
(define new-os
(marionette-operating-system
(virtualized-operating-system ;run as with "guix system vm"
(operating-system
(inherit %simple-os)
(host-name "the-new-os")
(kernel-arguments '("console=ttyS0"))) ;be verbose
#:volatile? #t) ;mount root read-only
#:imported-modules '((gnu services herd)
(guix combinators))))
(define vm (virtual-machine os))
(define test
(with-imported-modules '((gnu build marionette))
#~(begin
(use-modules (gnu build marionette)
(srfi srfi-64))
(define marionette
(make-marionette (list #$vm)))
(test-runner-current (system-test-runner #$output))
(test-begin "kexec")
(test-equal "host name"
#$(operating-system-host-name os)
(marionette-eval '(gethostname) marionette))
(test-assert "kexec-loading-program"
(marionette-eval
'(primitive-load #$(kexec-loading-program new-os))
marionette))
(test-assert "reboot/kexec"
(marionette-eval
'(begin
(use-modules (gnu services herd))
(with-shepherd-action 'root ('kexec) result
(pk 'reboot-kexec result)))
marionette))
(test-equal "host name of new OS"
#$(operating-system-host-name new-os)
(marionette-eval '(gethostname) marionette))
(test-end))))
(gexp->derivation "kexec-test" test))
(define* (run-install-bootloader-test) (define* (run-install-bootloader-test)
"Run a test of an OS running INSTALL-BOOTLOADER-PROGRAM, which installs a "Run a test of an OS running INSTALL-BOOTLOADER-PROGRAM, which installs a
bootloader's configuration file." bootloader's configuration file."
@ -268,6 +340,12 @@ (define %test-upgrade-services
loading new services.") loading new services.")
(value (run-upgrade-services-test)))) (value (run-upgrade-services-test))))
(define %test-upgrade-kexec
(system-test
(name "upgrade-kexec")
(description "Load a system and reboot into it via Linux kexec.")
(value (run-kexec-test))))
(define %test-install-bootloader (define %test-install-bootloader
(system-test (system-test
(name "install-bootloader") (name "install-bootloader")

View file

@ -798,6 +798,7 @@ (define* (perform-action action image
save-provenance? save-provenance?
skip-safety-checks? skip-safety-checks?
install-bootloader? install-bootloader?
load-for-kexec?
dry-run? derivations-only? dry-run? derivations-only?
use-substitutes? target use-substitutes? target
full-boot? full-boot?
@ -900,7 +901,13 @@ (define bootcfg
To complete the upgrade, run 'herd restart SERVICE' to stop, To complete the upgrade, run 'herd restart SERVICE' to stop,
upgrade, and restart each service that was not automatically restarted.\n"))) upgrade, and restart each service that was not automatically restarted.\n")))
(return (format #t (G_ "\ (return (format #t (G_ "\
Run 'herd status' to view the list of services on your system.\n")))))) Run 'herd status' to view the list of services on your system.\n"))))
(mwhen load-for-kexec?
(mlet %store-monad ((kexec? (load-system-for-kexec local-eval
os)))
(mwhen kexec?
(return (info (G_ "system loaded for fast reboot \
with 'reboot --kexec'~%"))))))))
((init) ((init)
(newline) (newline)
(format #t (G_ "initializing operating system under '~a'...~%") (format #t (G_ "initializing operating system under '~a'...~%")
@ -1025,6 +1032,8 @@ (define (show-help)
--image-size=SIZE for 'image', produce an image of SIZE")) --image-size=SIZE for 'image', produce an image of SIZE"))
(display (G_ " (display (G_ "
--no-bootloader for 'init', do not install a bootloader")) --no-bootloader for 'init', do not install a bootloader"))
(display (G_ "
--no-kexec for 'reconfigure', do not load system for kexec reboot"))
(display (G_ " (display (G_ "
--volatile for 'image', make the root file system volatile")) --volatile for 'image', make the root file system volatile"))
(display (G_ " (display (G_ "
@ -1127,6 +1136,9 @@ (define %options
(option '("no-bootloader" "no-grub") #f #f (option '("no-bootloader" "no-grub") #f #f
(lambda (opt name arg result) (lambda (opt name arg result)
(alist-cons 'install-bootloader? #f result))) (alist-cons 'install-bootloader? #f result)))
(option '("no-kexec") #f #f
(lambda (opt name arg result)
(alist-cons 'load-for-kexec? #f result)))
(option '("volatile") #f #f (option '("volatile") #f #f
(lambda (opt name arg result) (lambda (opt name arg result)
(alist-cons 'volatile-image-root? #t result))) (alist-cons 'volatile-image-root? #t result)))
@ -1198,6 +1210,7 @@ (define %default-options
(image-type . mbr-hybrid-raw) (image-type . mbr-hybrid-raw)
(image-size . guess) (image-size . guess)
(install-bootloader? . #t) (install-bootloader? . #t)
(load-for-kexec? . #t)
(label . #f) (label . #f)
(volatile-image-root? . #f) (volatile-image-root? . #f)
(volatile-vm-root? . #t) (volatile-vm-root? . #t)
@ -1275,6 +1288,7 @@ (define save-provenance?
(leave (G_ "no configuration specified~%"))))))) (leave (G_ "no configuration specified~%")))))))
(dry? (assoc-ref opts 'dry-run?)) (dry? (assoc-ref opts 'dry-run?))
(bootloader? (assoc-ref opts 'install-bootloader?)) (bootloader? (assoc-ref opts 'install-bootloader?))
(kexec? (assoc-ref opts 'load-for-kexec?))
(label (assoc-ref opts 'label)) (label (assoc-ref opts 'label))
(image-type (lookup-image-type-by-name (image-type (lookup-image-type-by-name
(assoc-ref opts 'image-type))) (assoc-ref opts 'image-type)))
@ -1360,6 +1374,7 @@ (define (graph-backend)
(_ #f)) (_ #f))
opts) opts)
#:install-bootloader? bootloader? #:install-bootloader? bootloader?
#:load-for-kexec? kexec?
#:target target-file #:target target-file
#:gc-root (assoc-ref opts 'gc-root))))) #:gc-root (assoc-ref opts 'gc-root)))))
#:target target #:target target

View file

@ -31,6 +31,7 @@ (define-module (guix scripts system reconfigure)
#:use-module (gnu services herd) #:use-module (gnu services herd)
#:use-module (gnu services shepherd) #:use-module (gnu services shepherd)
#:use-module (gnu system) #:use-module (gnu system)
#:autoload (gnu system file-systems) (file-system-device)
#:use-module (guix gexp) #:use-module (guix gexp)
#:use-module (guix modules) #:use-module (guix modules)
#:use-module (guix monads) #:use-module (guix monads)
@ -52,6 +53,9 @@ (define-module (guix scripts system reconfigure)
upgrade-services-program upgrade-services-program
upgrade-shepherd-services upgrade-shepherd-services
kexec-loading-program
load-system-for-kexec
install-bootloader-program install-bootloader-program
install-bootloader install-bootloader
@ -176,6 +180,27 @@ (define (upgrade-services-program service-files to-start to-unload to-restart)
(for-each unload-service '#$to-unload) (for-each unload-service '#$to-unload)
(for-each start-service '#$to-start))))) (for-each start-service '#$to-start)))))
(define (kexec-loading-program os)
"Return a program that calls 'kexec_file_load' to allow rebooting into OS
via 'kexec'."
(let ((root-device (file-system-device
(operating-system-root-file-system os))))
(program-file
"kexec-load-system.scm"
(with-imported-modules '((guix build syscalls))
#~(begin
(use-modules (guix build syscalls))
(let ((kernel (open-fdes #$(operating-system-kernel-file os)
O_RDONLY))
(initrd (open-fdes #$(operating-system-initrd-file os)
O_RDONLY)))
(kexec-load-file kernel initrd
(string-join
(list #$@(operating-system-kernel-arguments
os root-device)))
KEXEC_FILE_DEBUG)))))))
(define* (upgrade-shepherd-services eval os) (define* (upgrade-shepherd-services eval os)
"Using EVAL, a monadic procedure taking a single G-Expression as an argument, "Using EVAL, a monadic procedure taking a single G-Expression as an argument,
upgrade the Shepherd (PID 1) by unloading obsolete services and loading new upgrade the Shepherd (PID 1) by unloading obsolete services and loading new
@ -205,6 +230,12 @@ (define target-services
to-unload to-unload
to-restart))))))) to-restart)))))))
(define (load-system-for-kexec eval os)
"Load OS so that it can be rebooted into via kexec, if supported. Return
true on success."
(eval #~(and (string-contains %host-type "-linux")
(primitive-load #$(kexec-loading-program os)))))
;;; ;;;
;;; Bootloader configuration. ;;; Bootloader configuration.