diff mbox

[08/10] Implement switch-generation and roll-back

Message ID 20161028100727.1182-9-cmmarusich@gmail.com
State New
Headers show

Commit Message

Chris Marusich Oct. 28, 2016, 10:07 a.m. UTC
From: Chris Marusich <cmmarusich@gmail.com>

* guix/scripts/system.scm (roll-back-system, switch-to-system-generation,
  reinstall-grub): New procedures.

* guix/scripts/system.scm (show-help, process-command, guix-system): Add
  references to the new actions.
---
 guix/scripts/system.scm | 97 ++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 91 insertions(+), 6 deletions(-)
diff mbox

Patch

diff --git a/guix/scripts/system.scm b/guix/scripts/system.scm
index 2c80c2e..086b431 100644
--- a/guix/scripts/system.scm
+++ b/guix/scripts/system.scm
@@ -407,6 +407,67 @@  NUMBERS, which is a list of generation numbers."
 
 
 ;;;
+;;; Roll-back.
+;;;
+(define (roll-back-system store)
+  "Roll back the system profile to its previous generation."
+  (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."
+  (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."
+  (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))
+         (store-mount-point (boot-parameters-store-fs-mount-point params))
+         (grub-root-search-device (boot-parameters-store-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 (grub-entries %system-profile (list number)))
+         (old-generations (delv number (generation-numbers %system-profile)))
+         (old-entries (grub-entries %system-profile old-generations))
+         (grub.cfg (run-with-store store
+                     (grub-configuration-file grub-config
+                                              store-mount-point
+                                              grub-root-search-device
+                                              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 +702,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 +875,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 +911,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))))))