| 1 | |
| 2 | |
| 3 | |
| 4 | |
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 |
|
| 21 | (define-module (gnu installer steps)
|
| 22 | #:use-module (guix records)
|
| 23 | #:use-module (guix build utils)
|
| 24 | #:use-module (guix i18n)
|
| 25 | #:use-module (guix read-print)
|
| 26 | #:use-module (guix utils)
|
| 27 | #:use-module (gnu installer utils)
|
| 28 | #:use-module (ice-9 match)
|
| 29 | #:use-module (srfi srfi-1)
|
| 30 | #:use-module (srfi srfi-34)
|
| 31 | #:use-module (srfi srfi-35)
|
| 32 | #:use-module (rnrs io ports)
|
| 33 | #:export (&user-abort-error
|
| 34 | user-abort-error?
|
| 35 |
|
| 36 | <installer-step>
|
| 37 | installer-step
|
| 38 | make-installer-step
|
| 39 | installer-step?
|
| 40 | installer-step-id
|
| 41 | installer-step-description
|
| 42 | installer-step-compute
|
| 43 | installer-step-configuration-formatter
|
| 44 |
|
| 45 | run-installer-steps
|
| 46 | find-step-by-id
|
| 47 | result->step-ids
|
| 48 | result-step
|
| 49 | result-step-done?
|
| 50 |
|
| 51 | %installer-configuration-file
|
| 52 | %installer-target-dir
|
| 53 | format-configuration
|
| 54 | configuration->file
|
| 55 |
|
| 56 | %current-result))
|
| 57 |
|
| 58 | (define-condition-type &user-abort-error &error
|
| 59 | user-abort-error?)
|
| 60 |
|
| 61 | |
| 62 | |
| 63 | (define %current-result (make-hash-table))
|
| 64 |
|
| 65 | |
| 66 | |
| 67 | |
| 68 | |
| 69 | |
| 70 | |
| 71 | (define-record-type* <installer-step>
|
| 72 | installer-step make-installer-step
|
| 73 | installer-step?
|
| 74 | (id installer-step-id) |
| 75 | (description installer-step-description |
| 76 | (default #f)
|
| 77 |
|
| 78 | |
| 79 | |
| 80 | |
| 81 | (thunked))
|
| 82 | (compute installer-step-compute) |
| 83 | (configuration-formatter installer-step-configuration-formatter |
| 84 | (default #f)))
|
| 85 |
|
| 86 | (define* (run-installer-steps #:key
|
| 87 | steps
|
| 88 | (rewind-strategy 'previous)
|
| 89 | (menu-proc (const #f))
|
| 90 | dry-run?)
|
| 91 | "Run the COMPUTE procedure of all <installer-step> records in STEPS
|
| 92 | sequentially, inside a the 'installer-step prompt. When aborted to with a
|
| 93 | parameter of 'abort, fallback to a previous install-step, accordingly to the
|
| 94 | specified REWIND-STRATEGY. When aborted to with a parameter of 'break, stop
|
| 95 | the computation and return the accumulated result so far.
|
| 96 |
|
| 97 | REWIND-STRATEGY possible values are 'previous, 'menu and 'start. If 'previous
|
| 98 | is selected, the execution will resume at the previous installer-step. If
|
| 99 | 'menu is selected, the MENU-PROC procedure will be called. Its return value
|
| 100 | has to be an installer-step ID to jump to. The ID has to be the one of a
|
| 101 | previously executed step. It is impossible to jump forward. Finally if 'start
|
| 102 | is selected, the execution will resume at the first installer-step.
|
| 103 |
|
| 104 | The result of every COMPUTE procedures is stored in an association list, under
|
| 105 | the form:
|
| 106 |
|
| 107 | '((STEP-ID . COMPUTE-RESULT) ...)
|
| 108 |
|
| 109 | where STEP-ID is the ID field of the installer-step and COMPUTE-RESULT the
|
| 110 | result of the associated COMPUTE procedure. This result association list is
|
| 111 | passed as argument of every COMPUTE procedure. It is finally returned when the
|
| 112 | computation is over."
|
| 113 | (define (pop-result list)
|
| 114 | (cdr list))
|
| 115 |
|
| 116 | (define (first-step? steps step)
|
| 117 | (match steps
|
| 118 | ((first-step . rest-steps)
|
| 119 | (equal? first-step step))))
|
| 120 |
|
| 121 | (define* (skip-to-step step result
|
| 122 | #:key todo-steps done-steps)
|
| 123 | (match todo-steps
|
| 124 | ((todo . rest-todo)
|
| 125 | (let ((found? (eq? (installer-step-id todo)
|
| 126 | (installer-step-id step))))
|
| 127 | (cond
|
| 128 | (found?
|
| 129 | (run result
|
| 130 | #:todo-steps todo-steps
|
| 131 | #:done-steps done-steps))
|
| 132 | ((and (not found?)
|
| 133 | (null? done-steps))
|
| 134 | (error (format #f "Step ~a not found" (installer-step-id step))))
|
| 135 | (else
|
| 136 | (match done-steps
|
| 137 | ((prev-done ... last-done)
|
| 138 | (skip-to-step step (pop-result result)
|
| 139 | #:todo-steps (cons last-done todo-steps)
|
| 140 | #:done-steps prev-done)))))))))
|
| 141 |
|
| 142 | (define* (run result #:key todo-steps done-steps)
|
| 143 | (match todo-steps
|
| 144 | (() (reverse result))
|
| 145 | ((step . rest-steps)
|
| 146 | (call-with-prompt 'installer-step
|
| 147 | (lambda ()
|
| 148 | (installer-log-line "running step '~a'" (installer-step-id step))
|
| 149 | (let* ((id (installer-step-id step))
|
| 150 | (compute (installer-step-compute step))
|
| 151 | (res (compute result done-steps)))
|
| 152 | (hash-set! %current-result id res)
|
| 153 | (run (alist-cons id res result)
|
| 154 | #:todo-steps rest-steps
|
| 155 | #:done-steps (append done-steps (list step)))))
|
| 156 | (lambda (k action)
|
| 157 | (match action
|
| 158 | ('abort
|
| 159 | (case rewind-strategy
|
| 160 | ((previous)
|
| 161 | (match done-steps
|
| 162 | (()
|
| 163 | |
| 164 | |
| 165 | |
| 166 | (abort-to-prompt 'installer-step action))
|
| 167 | ((prev-done ... last-done)
|
| 168 | (run (pop-result result)
|
| 169 | #:todo-steps (cons last-done todo-steps)
|
| 170 | #:done-steps prev-done))))
|
| 171 | ((menu)
|
| 172 | (let ((goto-step (menu-proc
|
| 173 | (append done-steps (list step)))))
|
| 174 | (if (eq? goto-step step)
|
| 175 | (run result
|
| 176 | #:todo-steps todo-steps
|
| 177 | #:done-steps done-steps)
|
| 178 | (skip-to-step goto-step result
|
| 179 | #:todo-steps todo-steps
|
| 180 | #:done-steps done-steps))))
|
| 181 | ((start)
|
| 182 | (if (null? done-steps)
|
| 183 | |
| 184 | |
| 185 | |
| 186 | (abort-to-prompt 'installer-step action)
|
| 187 | (run '()
|
| 188 | #:todo-steps steps
|
| 189 | #:done-steps '())))))
|
| 190 | ('break
|
| 191 | (reverse result))))))))
|
| 192 |
|
| 193 | |
| 194 | |
| 195 | (sigaction SIGPIPE SIG_IGN)
|
| 196 |
|
| 197 | (if dry-run?
|
| 198 | (run '()
|
| 199 | #:todo-steps steps
|
| 200 | #:done-steps '())
|
| 201 | (with-server-socket
|
| 202 | (run '()
|
| 203 | #:todo-steps steps
|
| 204 | #:done-steps '()))))
|
| 205 |
|
| 206 | (define (find-step-by-id steps id)
|
| 207 | "Find and return the step in STEPS whose id is equal to ID."
|
| 208 | (find (lambda (step)
|
| 209 | (eq? (installer-step-id step) id))
|
| 210 | steps))
|
| 211 |
|
| 212 | (define (result-step results step-id)
|
| 213 | "Return the result of the installer-step specified by STEP-ID in
|
| 214 | RESULTS."
|
| 215 | (assoc-ref results step-id))
|
| 216 |
|
| 217 | (define (result-step-done? results step-id)
|
| 218 | "Return #t if the installer-step specified by STEP-ID has a COMPUTE value
|
| 219 | stored in RESULTS. Return #f otherwise."
|
| 220 | (and (assoc step-id results) #t))
|
| 221 |
|
| 222 | (define %installer-configuration-file (make-parameter "/mnt/etc/config.scm"))
|
| 223 | (define %installer-target-dir (make-parameter "/mnt"))
|
| 224 |
|
| 225 | (define (format-configuration steps results)
|
| 226 | "Return the list resulting from the application of the procedure defined in
|
| 227 | CONFIGURATION-FORMATTER field of <installer-step> on the associated result
|
| 228 | found in RESULTS."
|
| 229 | (let ((configuration
|
| 230 | (append-map
|
| 231 | (lambda (step)
|
| 232 | (let* ((step-id (installer-step-id step))
|
| 233 | (conf-formatter
|
| 234 | (installer-step-configuration-formatter step))
|
| 235 | (result-step (result-step results step-id)))
|
| 236 | (if (and result-step conf-formatter)
|
| 237 | (conf-formatter result-step)
|
| 238 | '())))
|
| 239 | steps))
|
| 240 | (modules `(,(vertical-space 1)
|
| 241 | ,(comment (G_ "\
|
| 242 | ;; Indicate which modules to import to access the variables
|
| 243 | ;; used in this configuration.\n"))
|
| 244 | ,@(if (target-hurd?)
|
| 245 | '((use-modules (gnu) (gnu system hurd))
|
| 246 | (use-package-modules hurd ssh))
|
| 247 | '((use-modules (gnu))))
|
| 248 | (use-service-modules cups desktop networking ssh xorg))))
|
| 249 | `(,@modules
|
| 250 | ,(vertical-space 1)
|
| 251 | (operating-system ,@configuration))))
|
| 252 |
|
| 253 | (define* (configuration->file configuration
|
| 254 | #:key (file-name (%installer-configuration-file)))
|
| 255 | "Write the given CONFIGURATION to FILE-NAME."
|
| 256 | (mkdir-p (dirname file-name))
|
| 257 | (call-with-output-file file-name
|
| 258 | (lambda (port)
|
| 259 | |
| 260 | |
| 261 | |
| 262 | (display (G_ "\
|
| 263 | ;; This is an operating system configuration generated
|
| 264 | ;; by the graphical installer.
|
| 265 | ;;
|
| 266 | ;; Once installation is complete, you can learn and modify
|
| 267 | ;; this file to tweak the system configuration, and pass it
|
| 268 | ;; to the 'guix system reconfigure' command to effect your
|
| 269 | ;; changes.\n")
|
| 270 | port)
|
| 271 | (newline port)
|
| 272 | (pretty-print-with-comments/splice port configuration
|
| 273 | #:max-width 75
|
| 274 | #:format-comment
|
| 275 | (lambda (c indent)
|
| 276 | |
| 277 | (comment (G_ (comment->string c))
|
| 278 | (comment-margin? c))))
|
| 279 |
|
| 280 | (flush-output-port port))))
|
| 281 |
|
| 282 | |
| 283 | |
| 284 | |