Patchwork [5/5] system: Add 'guix system' actions: switch-generation and roll-back.

login
register
mail settings
Submitter Chris Marusich
Date Nov. 2, 2016, 5:48 a.m.
Message ID <20161102054815.11253-6-cmmarusich@gmail.com>
Download mbox | patch
Permalink /patch/17094/
State New
Headers show

Comments

Chris Marusich - Nov. 2, 2016, 5:48 a.m.
From: Chris Marusich <cmmarusich@gmail.com>

* 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.
---
 doc/guix.texi           | 49 +++++++++++++++++++++++--
 guix/scripts/system.scm | 95 +++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 136 insertions(+), 8 deletions(-)
Ludovic Courtès - Nov. 6, 2016, 5:13 p.m.
cmmarusich@gmail.com skribis:

> From: Chris Marusich <cmmarusich@gmail.com>
>
> * 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.

Applied.

> I realized I forgot to include some info in the manual, so here's one
> more patch to add the missing info.

I merged it with the one above.

I’m really happy we have these two new commands now, it fixes a glaring
lack.

Thanks for your hard work and patience!

With this in place, and once you’ve recovered from this patch series
;-), it should be relatively easy to add ‘guix system
delete-generations’ (similar to ‘guix package --delete-generations’.)
WDYT?

The next more complicated thing would be to see what needs to be done so
we can restart system services upon roll-back.

Ludo’.
Chris Marusich - Nov. 7, 2016, 3:17 a.m.
ludo@gnu.org (Ludovic Courtès) writes:

> I’m really happy we have these two new commands now, it fixes a glaring
> lack.
>
> Thanks for your hard work and patience!

I'm glad we have it now, too!  There's a lot more to do, but it's a good
start.

> With this in place, and once you’ve recovered from this patch series
> ;-), it should be relatively easy to add ‘guix system
> delete-generations’ (similar to ‘guix package --delete-generations’.)
> WDYT?

Sounds like a good idea!

> The next more complicated thing would be to see what needs to be done so
> we can restart system services upon roll-back.

Right.  Eventually we should have all the stuff discussed here:

https://lists.gnu.org/archive/html/guix-devel/2016-06/msg00173.html

Thank you for all your help and feedback.  It's helped me to understand
Guile and Guix better.  I hope to keep helping out where I can!

Patch

diff --git a/doc/guix.texi b/doc/guix.texi
index 1075a7e..fbcf3b0 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,50 @@  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 rearranges the
+existing GRUB menu entries.  It makes the menu entry for the target
+system generation the default, and it moves the entries for the other
+system 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 only to 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 5bc40ed..df9b37d 100644
--- a/guix/scripts/system.scm
+++ b/guix/scripts/system.scm
@@ -407,6 +407,65 @@  NUMBERS, which is a list of generation numbers."
 
 
 ;;;
+;;; 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,14 +700,19 @@  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 (_ "\
    build            build the operating system without installing anything\n"))
@@ -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))))))