steps.scm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2019 Mathieu Othacehe <m.othacehe@gmail.com>
  3. ;;; Copyright © 2020-2022 Ludovic Courtès <ludo@gnu.org>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. (define-module (gnu installer steps)
  20. #:use-module (guix records)
  21. #:use-module (guix build utils)
  22. #:use-module (guix i18n)
  23. #:use-module (guix read-print)
  24. #:use-module (gnu installer utils)
  25. #:use-module (ice-9 match)
  26. #:use-module (srfi srfi-1)
  27. #:use-module (srfi srfi-34)
  28. #:use-module (srfi srfi-35)
  29. #:use-module (rnrs io ports)
  30. #:export (&user-abort-error
  31. user-abort-error?
  32. <installer-step>
  33. installer-step
  34. make-installer-step
  35. installer-step?
  36. installer-step-id
  37. installer-step-description
  38. installer-step-compute
  39. installer-step-configuration-formatter
  40. run-installer-steps
  41. find-step-by-id
  42. result->step-ids
  43. result-step
  44. result-step-done?
  45. %installer-configuration-file
  46. %installer-target-dir
  47. format-configuration
  48. configuration->file
  49. %current-result))
  50. (define-condition-type &user-abort-error &error
  51. user-abort-error?)
  52. ;; Hash table storing the step results. Use it only for logging and debug
  53. ;; purposes.
  54. (define %current-result (make-hash-table))
  55. ;; An installer-step record is basically an id associated to a compute
  56. ;; procedure. The COMPUTE procedure takes exactly one argument, an association
  57. ;; list containing the results of previously executed installer-steps (see
  58. ;; RUN-INSTALLER-STEPS description). The value returned by the COMPUTE
  59. ;; procedure will be stored in the results list passed to the next
  60. ;; installer-step and so on.
  61. (define-record-type* <installer-step>
  62. installer-step make-installer-step
  63. installer-step?
  64. (id installer-step-id) ;symbol
  65. (description installer-step-description ;string
  66. (default #f)
  67. ;; Make it thunked so that 'G_' is called at the
  68. ;; right time, as opposed to being called once
  69. ;; when the installer starts.
  70. (thunked))
  71. (compute installer-step-compute) ;procedure
  72. (configuration-formatter installer-step-configuration-formatter ;procedure
  73. (default #f)))
  74. (define* (run-installer-steps #:key
  75. steps
  76. (rewind-strategy 'previous)
  77. (menu-proc (const #f)))
  78. "Run the COMPUTE procedure of all <installer-step> records in STEPS
  79. sequentially, inside a the 'installer-step prompt. When aborted to with a
  80. parameter of 'abort, fallback to a previous install-step, accordingly to the
  81. specified REWIND-STRATEGY. When aborted to with a parameter of 'break, stop
  82. the computation and return the accumalated result so far.
  83. REWIND-STRATEGY possible values are 'previous, 'menu and 'start. If 'previous
  84. is selected, the execution will resume at the previous installer-step. If
  85. 'menu is selected, the MENU-PROC procedure will be called. Its return value
  86. has to be an installer-step ID to jump to. The ID has to be the one of a
  87. previously executed step. It is impossible to jump forward. Finally if 'start
  88. is selected, the execution will resume at the first installer-step.
  89. The result of every COMPUTE procedures is stored in an association list, under
  90. the form:
  91. '((STEP-ID . COMPUTE-RESULT) ...)
  92. where STEP-ID is the ID field of the installer-step and COMPUTE-RESULT the
  93. result of the associated COMPUTE procedure. This result association list is
  94. passed as argument of every COMPUTE procedure. It is finally returned when the
  95. computation is over."
  96. (define (pop-result list)
  97. (cdr list))
  98. (define (first-step? steps step)
  99. (match steps
  100. ((first-step . rest-steps)
  101. (equal? first-step step))))
  102. (define* (skip-to-step step result
  103. #:key todo-steps done-steps)
  104. (match todo-steps
  105. ((todo . rest-todo)
  106. (let ((found? (eq? (installer-step-id todo)
  107. (installer-step-id step))))
  108. (cond
  109. (found?
  110. (run result
  111. #:todo-steps todo-steps
  112. #:done-steps done-steps))
  113. ((and (not found?)
  114. (null? done-steps))
  115. (error (format #f "Step ~a not found" (installer-step-id step))))
  116. (else
  117. (match done-steps
  118. ((prev-done ... last-done)
  119. (skip-to-step step (pop-result result)
  120. #:todo-steps (cons last-done todo-steps)
  121. #:done-steps prev-done)))))))))
  122. (define* (run result #:key todo-steps done-steps)
  123. (match todo-steps
  124. (() (reverse result))
  125. ((step . rest-steps)
  126. (call-with-prompt 'installer-step
  127. (lambda ()
  128. (installer-log-line "running step '~a'" (installer-step-id step))
  129. (let* ((id (installer-step-id step))
  130. (compute (installer-step-compute step))
  131. (res (compute result done-steps)))
  132. (hash-set! %current-result id res)
  133. (run (alist-cons id res result)
  134. #:todo-steps rest-steps
  135. #:done-steps (append done-steps (list step)))))
  136. (lambda (k action)
  137. (match action
  138. ('abort
  139. (case rewind-strategy
  140. ((previous)
  141. (match done-steps
  142. (()
  143. ;; We cannot go previous the first step. Abort again to
  144. ;; 'installer-step prompt. It might be useful in the case
  145. ;; of nested run-installer-steps.
  146. (abort-to-prompt 'installer-step action))
  147. ((prev-done ... last-done)
  148. (run (pop-result result)
  149. #:todo-steps (cons last-done todo-steps)
  150. #:done-steps prev-done))))
  151. ((menu)
  152. (let ((goto-step (menu-proc
  153. (append done-steps (list step)))))
  154. (if (eq? goto-step step)
  155. (run result
  156. #:todo-steps todo-steps
  157. #:done-steps done-steps)
  158. (skip-to-step goto-step result
  159. #:todo-steps todo-steps
  160. #:done-steps done-steps))))
  161. ((start)
  162. (if (null? done-steps)
  163. ;; Same as above, it makes no sense to jump to start
  164. ;; when we are at the first installer-step. Abort to
  165. ;; 'installer-step prompt again.
  166. (abort-to-prompt 'installer-step action)
  167. (run '()
  168. #:todo-steps steps
  169. #:done-steps '())))))
  170. ('break
  171. (reverse result))))))))
  172. ;; Ignore SIGPIPE so that we don't die if a client closes the connection
  173. ;; prematurely.
  174. (sigaction SIGPIPE SIG_IGN)
  175. (with-server-socket
  176. (run '()
  177. #:todo-steps steps
  178. #:done-steps '())))
  179. (define (find-step-by-id steps id)
  180. "Find and return the step in STEPS whose id is equal to ID."
  181. (find (lambda (step)
  182. (eq? (installer-step-id step) id))
  183. steps))
  184. (define (result-step results step-id)
  185. "Return the result of the installer-step specified by STEP-ID in
  186. RESULTS."
  187. (assoc-ref results step-id))
  188. (define (result-step-done? results step-id)
  189. "Return #t if the installer-step specified by STEP-ID has a COMPUTE value
  190. stored in RESULTS. Return #f otherwise."
  191. (and (assoc step-id results) #t))
  192. (define %installer-configuration-file (make-parameter "/mnt/etc/config.scm"))
  193. (define %installer-target-dir (make-parameter "/mnt"))
  194. (define (format-configuration steps results)
  195. "Return the list resulting from the application of the procedure defined in
  196. CONFIGURATION-FORMATTER field of <installer-step> on the associated result
  197. found in RESULTS."
  198. (let ((configuration
  199. (append-map
  200. (lambda (step)
  201. (let* ((step-id (installer-step-id step))
  202. (conf-formatter
  203. (installer-step-configuration-formatter step))
  204. (result-step (result-step results step-id)))
  205. (if (and result-step conf-formatter)
  206. (conf-formatter result-step)
  207. '())))
  208. steps))
  209. (modules `(,(vertical-space 1)
  210. ,(comment (G_ "\
  211. ;; Indicate which modules to import to access the variables
  212. ;; used in this configuration.\n"))
  213. (use-modules (gnu))
  214. (use-service-modules cups desktop networking ssh xorg))))
  215. `(,@modules
  216. ,(vertical-space 1)
  217. (operating-system ,@configuration))))
  218. (define* (configuration->file configuration
  219. #:key (filename (%installer-configuration-file)))
  220. "Write the given CONFIGURATION to FILENAME."
  221. (mkdir-p (dirname filename))
  222. (call-with-output-file filename
  223. (lambda (port)
  224. ;; TRANSLATORS: This is a comment within a Scheme file. Each line must
  225. ;; start with ";; " (two semicolons and a space). Please keep line
  226. ;; length below 60 characters.
  227. (display (G_ "\
  228. ;; This is an operating system configuration generated
  229. ;; by the graphical installer.
  230. ;;
  231. ;; Once installation is complete, you can learn and modify
  232. ;; this file to tweak the system configuration, and pass it
  233. ;; to the 'guix system reconfigure' command to effect your
  234. ;; changes.\n")
  235. port)
  236. (newline port)
  237. (pretty-print-with-comments/splice port configuration
  238. #:max-width 75
  239. #:format-comment
  240. (lambda (c indent)
  241. ;; Localize C.
  242. (comment (G_ (comment->string c))
  243. (comment-margin? c))))
  244. (flush-output-port port))))
  245. ;;; Local Variables:
  246. ;;; eval: (put 'with-server-socket 'scheme-indent-function 0)
  247. ;;; End: