From 8074b3307746a94b0a656a41ceed0c577d7e8133 Mon Sep 17 00:00:00 2001 From: Chris Marusich Date: Tue, 1 Nov 2016 22:48:15 -0700 Subject: [PATCH] system: Add 'guix system' actions: switch-generation and roll-back. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * guix/scripts/system.scm (roll-back-system, switch-to-system-generation): new actions. (reinstall-grub): New procedure, used by switch-to-system-generation. (show-help, process-command, guix-system): Honor the new actions. * doc/guix.texi (Invoking guix system) : Add the new actions. : In the footnote, mention that the new actions also only work on GuixSD. Signed-off-by: Ludovic Courtès --- doc/guix.texi | 51 +++++++++++++++++++++- guix/scripts/system.scm | 95 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 138 insertions(+), 8 deletions(-) diff --git a/doc/guix.texi b/doc/guix.texi index 1075a7e178..b8cb01f48a 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -11155,8 +11155,9 @@ supported: @table @code @item reconfigure Build the operating system described in @var{file}, activate it, and -switch to it@footnote{This action is usable only on systems already -running GuixSD.}. +switch to it@footnote{This action (and the related actions +@code{switch-generation} and @code{roll-back}) are usable only on +systems already running GuixSD.}. This effects all the configuration specified in @var{file}: user accounts, system services, global package list, setuid programs, etc. @@ -11178,6 +11179,52 @@ guix pull}). Failing to do that you would see an older version of Guix once @command{reconfigure} has completed. @end quotation +@item switch-generation +Switch to an existing system generation. This action atomically +switches the system profile to the specified system generation. It also +rearranges the system's existing GRUB menu entries. It makes the menu +entry for the specified system generation the default, and it moves the +entries for the other generations to a submenu. The next time the +system boots, it will use the specified system generation. + +The target generation can be specified explicitly by its generation +number. For example, the following invocation would switch to system +generation 7: + +@example +guix system switch-generation 7 +@end example + +The target generation can also be specified relative to the current +generation with the form @code{+N} or @code{-N}, where @code{+3} means +``3 generations ahead of the current generation,'' and @code{-1} means +``1 generation prior to the current generation.'' When specifying a +negative value such as @code{-1}, you must precede it with @code{--} to +prevent it from being parsed as an option. For example: + +@example +guix system switch-generation -- -1 +@end example + +Currently, the effect of invoking this action is @emph{only} to switch +the system profile to an existing generation and rearrange the GRUB menu +entries. To actually start using the target system generation, you must +reboot after running this action. In the future, it will be updated to +do the same things as @command{reconfigure}, like activating and +deactivating services. + +This action will fail if the specified generation does not exist. + +@item roll-back +Switch to the preceding system generation. The next time the system +boots, it will use the preceding system generation. This is the inverse +of @command{reconfigure}, and it is exactly the same as invoking +@command{switch-generation} with an argument of @code{-1}. + +Currently, as with @command{switch-generation}, you must reboot after +running this action to actually start using the preceding system +generation. + @item build Build the derivation of the operating system, which includes all the configuration files and programs needed to boot and run the system. diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm index 5bc40ed735..df9b37d544 100644 --- a/guix/scripts/system.scm +++ b/guix/scripts/system.scm @@ -405,6 +405,65 @@ NUMBERS, which is a list of generation numbers." systems))) (filter-map system->grub-entry systems numbers times))) + +;;; +;;; Roll-back. +;;; +(define (roll-back-system store) + "Roll back the system profile to its previous generation. STORE is an open +connection to the store." + (switch-to-system-generation store "-1")) + +;;; +;;; Switch generations. +;;; +(define (switch-to-system-generation store spec) + "Switch the system profile to the generation specified by SPEC, and +re-install grub with a grub configuration file that uses the specified system +generation as its default entry. STORE is an open connection to the store." + (let ((number (relative-generation-spec->number %system-profile spec))) + (if number + (begin + (reinstall-grub store number) + (switch-to-generation* %system-profile number)) + (leave (_ "cannot switch to system generation '~a'~%") spec)))) + +(define (reinstall-grub store number) + "Re-install grub for existing system profile generation NUMBER. STORE is an +open connection to the store." + (let* ((generation (generation-file-name %system-profile number)) + (file (string-append generation "/parameters")) + (params (unless-file-not-found + (call-with-input-file file read-boot-parameters))) + (root-device (boot-parameters-root-device params)) + ;; We don't currently keep track of past menu entries' details. The + ;; default values will allow the system to boot, even if they differ + ;; from the actual past values for this generation's entry. + (grub-config (grub-configuration (device root-device))) + ;; Make the specified system generation the default entry. + (entries (profile-grub-entries %system-profile (list number))) + (old-generations (delv number (generation-numbers %system-profile))) + (old-entries (profile-grub-entries %system-profile old-generations)) + (grub.cfg (run-with-store store + (grub-configuration-file grub-config + entries + #:old-entries old-entries)))) + (show-what-to-build store (list grub.cfg)) + (build-derivations store (list grub.cfg)) + ;; This is basically the same as install-grub*, but for now we avoid + ;; re-installing the GRUB boot loader itself onto a device, mainly because + ;; we don't in general have access to the same version of the GRUB package + ;; which was used when installing this other system generation. + (let* ((grub.cfg-path (derivation->output-path grub.cfg)) + (gc-root (string-append %gc-roots-directory "/grub.cfg")) + (temp-gc-root (string-append gc-root ".new"))) + (switch-symlinks temp-gc-root grub.cfg-path) + (unless (false-if-exception (install-grub-config grub.cfg-path "/")) + (delete-file temp-gc-root) + (leave (_ "failed to re-install GRUB configuration file: '~a'~%") + grub.cfg-path)) + (rename-file temp-gc-root gc-root)))) + ;;; ;;; Graphs. @@ -641,13 +700,18 @@ building anything." ;;; (define (show-help) - (display (_ "Usage: guix system [OPTION] ACTION [FILE] -Build the operating system declared in FILE according to ACTION.\n")) + (display (_ "Usage: guix system [OPTION ...] ACTION [ARG ...] [FILE] +Build the operating system declared in FILE according to ACTION. +Some ACTIONS support additional ARGS.\n")) (newline) (display (_ "The valid values for ACTION are:\n")) (newline) (display (_ "\ reconfigure switch to a new operating system configuration\n")) + (display (_ "\ + roll-back switch to the previous operating system configuration\n")) + (display (_ "\ + switch-generation switch to an existing operating system configuration\n")) (display (_ "\ list-generations list the system generations\n")) (display (_ "\ @@ -809,15 +873,33 @@ resulting from command-line parsing." "Process COMMAND, one of the 'guix system' sub-commands. ARGS is its argument list and OPTS is the option alist." (case command + ;; The following commands do not need to use the store, and they do not need + ;; an operating system configuration file. ((list-generations) - ;; List generations. No need to connect to the daemon, etc. (let ((pattern (match args (() "") ((pattern) pattern) (x (leave (_ "wrong number of arguments~%")))))) (list-generations pattern))) - (else - (process-action command args opts)))) + ;; The following commands need to use the store, but they do not need an + ;; operating system configuration file. + ((switch-generation) + (let ((pattern (match args + ((pattern) pattern) + (x (leave (_ "wrong number of arguments~%")))))) + (with-store store + (set-build-options-from-command-line store opts) + (switch-to-system-generation store pattern)))) + ((roll-back) + (let ((pattern (match args + (() "") + (x (leave (_ "wrong number of arguments~%")))))) + (with-store store + (set-build-options-from-command-line store opts) + (roll-back-system store)))) + ;; The following commands need to use the store, and they also + ;; need an operating system configuration file. + (else (process-action command args opts)))) (define (guix-system . args) (define (parse-sub-command arg result) @@ -827,7 +909,8 @@ argument list and OPTS is the option alist." (let ((action (string->symbol arg))) (case action ((build container vm vm-image disk-image reconfigure init - extension-graph shepherd-graph list-generations) + extension-graph shepherd-graph list-generations roll-back + switch-generation) (alist-cons 'action action result)) (else (leave (_ "~a: unknown action~%") action))))))