system: Add 'guix system' actions: switch-generation and roll-back.

* 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) <switch-generation, roll-back>: Add the
new actions.
<reconfigure>: In the footnote, mention that the new actions also only work on
GuixSD.

Signed-off-by: Ludovic Courtès <ludo@gnu.org>
This commit is contained in:
Chris Marusich 2016-11-01 22:48:15 -07:00 committed by Ludovic Courtès
parent 1e17a2d5f2
commit 8074b33077
No known key found for this signature in database
GPG key ID: 090B11993D9AEBB5
2 changed files with 138 additions and 8 deletions

View file

@ -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.

View file

@ -405,6 +405,65 @@ (define (system->grub-entry system number time)
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 @@ (define (export-shepherd-graph os port)
;;;
(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 @@ (define (process-command command args opts)
"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 @@ (define (parse-sub-command arg result)
(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))))))