installer.scm 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2018, 2020 Mathieu Othacehe <m.othacehe@gmail.com>
  3. ;;; Copyright © 2019, 2020, 2022 Ludovic Courtès <ludo@gnu.org>
  4. ;;; Copyright © 2019, 2020 Tobias Geerinckx-Rice <me@tobias.gr>
  5. ;;; Copyright © 2020 Florian Pelz <pelzflorian@pelzflorian.de>
  6. ;;;
  7. ;;; This file is part of GNU Guix.
  8. ;;;
  9. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  10. ;;; under the terms of the GNU General Public License as published by
  11. ;;; the Free Software Foundation; either version 3 of the License, or (at
  12. ;;; your option) any later version.
  13. ;;;
  14. ;;; GNU Guix is distributed in the hope that it will be useful, but
  15. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;;; GNU General Public License for more details.
  18. ;;;
  19. ;;; You should have received a copy of the GNU General Public License
  20. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  21. (define-module (gnu installer)
  22. #:use-module (guix discovery)
  23. #:use-module (guix packages)
  24. #:use-module (guix gexp)
  25. #:use-module (guix modules)
  26. #:use-module (guix utils)
  27. #:use-module (guix ui)
  28. #:use-module ((guix self) #:select (make-config.scm))
  29. #:use-module (guix describe)
  30. #:use-module (guix channels)
  31. #:use-module (guix packages)
  32. #:use-module (guix git-download)
  33. #:use-module (gnu installer utils)
  34. #:use-module (gnu packages admin)
  35. #:use-module (gnu packages base)
  36. #:use-module (gnu packages bash)
  37. #:use-module (gnu packages compression)
  38. #:use-module (gnu packages connman)
  39. #:use-module (gnu packages cryptsetup)
  40. #:use-module (gnu packages disk)
  41. #:use-module (gnu packages file-systems)
  42. #:use-module (gnu packages guile)
  43. #:use-module (gnu packages guile-xyz)
  44. #:autoload (gnu packages gnupg) (guile-gcrypt)
  45. #:use-module (gnu packages iso-codes)
  46. #:use-module (gnu packages linux)
  47. #:use-module (gnu packages nano)
  48. #:use-module (gnu packages ncurses)
  49. #:use-module (gnu packages package-management)
  50. #:use-module (gnu packages pciutils)
  51. #:use-module (gnu packages tls)
  52. #:use-module (gnu packages xorg)
  53. #:use-module (gnu system locale)
  54. #:use-module (ice-9 match)
  55. #:use-module (srfi srfi-1)
  56. #:use-module (web uri)
  57. #:export (installer-program))
  58. (define module-to-import?
  59. ;; Return true for modules that should be imported. For (gnu system …) and
  60. ;; (gnu packages …) modules, we simply add the whole 'guix' package via
  61. ;; 'with-extensions' (to avoid having to rebuild it all), which is why these
  62. ;; modules are excluded here.
  63. (match-lambda
  64. (('guix 'config) #f)
  65. (('gnu 'installer _ ...) #t)
  66. (('gnu 'build _ ...) #t)
  67. (('guix 'build _ ...) #t)
  68. (('guix 'read-print) #t)
  69. (_ #f)))
  70. (define not-config?
  71. ;; Select (guix …) and (gnu …) modules, except (guix config).
  72. (match-lambda
  73. (('guix 'config) #f)
  74. (('guix _ ...) #t)
  75. (('gnu _ ...) #t)
  76. (_ #f)))
  77. (define* (build-compiled-file name locale-builder)
  78. "Return a file-like object that evaluates the gexp LOCALE-BUILDER and store
  79. its result in the scheme file NAME. The derivation will also build a compiled
  80. version of this file."
  81. (define set-utf8-locale
  82. #~(begin
  83. (setenv "LOCPATH"
  84. #$(file-append glibc-utf8-locales "/lib/locale/"
  85. (version-major+minor
  86. (package-version glibc-utf8-locales))))
  87. (setlocale LC_ALL "en_US.utf8")))
  88. (define builder
  89. (with-extensions (list guile-json-3)
  90. (with-imported-modules `(,@(source-module-closure
  91. '((gnu installer locale))
  92. #:select? not-config?)
  93. ((guix config) => ,(make-config.scm)))
  94. #~(begin
  95. (use-modules (gnu installer locale))
  96. ;; The locale files contain non-ASCII characters.
  97. #$set-utf8-locale
  98. (mkdir #$output)
  99. (let ((locale-file
  100. (string-append #$output "/" #$name ".scm"))
  101. (locale-compiled-file
  102. (string-append #$output "/" #$name ".go")))
  103. (call-with-output-file locale-file
  104. (lambda (port)
  105. (write #$locale-builder port)))
  106. (compile-file locale-file
  107. #:output-file locale-compiled-file))))))
  108. (computed-file name builder))
  109. (define apply-locale
  110. ;; Install the specified locale.
  111. (with-imported-modules (source-module-closure '((gnu services herd)))
  112. #~(lambda (locale)
  113. (false-if-exception
  114. (setlocale LC_ALL locale))
  115. ;; Restart the documentation viewer so it displays the manual in
  116. ;; language that corresponds to LOCALE. Make sure that nothing is
  117. ;; printed on the console.
  118. (parameterize ((shepherd-message-port
  119. (%make-void-port "w")))
  120. (stop-service 'term-tty2)
  121. (start-service 'term-tty2 (list locale))))))
  122. (define* (compute-locale-step #:key
  123. locales-name
  124. iso639-languages-name
  125. iso3166-territories-name)
  126. "Return a gexp that run the locale-page of INSTALLER, and install the
  127. selected locale. The list of locales, languages and territories passed to
  128. locale-page are computed in derivations named respectively LOCALES-NAME,
  129. ISO639-LANGUAGES-NAME and ISO3166-TERRITORIES-NAME. Those lists are compiled,
  130. so that when the installer is run, all the lengthy operations have already
  131. been performed at build time."
  132. (define (compiled-file-loader file name)
  133. #~(load-compiled
  134. (string-append #$file "/" #$name ".go")))
  135. (let* ((supported-locales #~(supported-locales->locales
  136. #+(glibc-supported-locales)))
  137. (iso-codes #~(string-append #$iso-codes "/share/iso-codes/json/"))
  138. (iso639-3 #~(string-append #$iso-codes "iso_639-3.json"))
  139. (iso639-5 #~(string-append #$iso-codes "iso_639-5.json"))
  140. (iso3166 #~(string-append #$iso-codes "iso_3166-1.json"))
  141. (locales-file (build-compiled-file
  142. locales-name
  143. #~`(quote ,#$supported-locales)))
  144. (iso639-file (build-compiled-file
  145. iso639-languages-name
  146. #~`(quote ,(iso639->iso639-languages
  147. #$supported-locales
  148. #$iso639-3 #$iso639-5))))
  149. (iso3166-file (build-compiled-file
  150. iso3166-territories-name
  151. #~`(quote ,(iso3166->iso3166-territories #$iso3166))))
  152. (locales-loader (compiled-file-loader locales-file
  153. locales-name))
  154. (iso639-loader (compiled-file-loader iso639-file
  155. iso639-languages-name))
  156. (iso3166-loader (compiled-file-loader iso3166-file
  157. iso3166-territories-name)))
  158. #~(lambda (current-installer)
  159. (let ((result
  160. ((installer-locale-page current-installer)
  161. #:supported-locales #$locales-loader
  162. #:iso639-languages #$iso639-loader
  163. #:iso3166-territories #$iso3166-loader)))
  164. (#$apply-locale result)
  165. result))))
  166. (define apply-keymap
  167. ;; Apply the specified keymap. Use the default keyboard model.
  168. #~(match-lambda
  169. ((layout variant options)
  170. (kmscon-update-keymap (default-keyboard-model)
  171. layout variant options))))
  172. (define* (compute-keymap-step context)
  173. "Return a gexp that runs the keymap-page of INSTALLER and install the
  174. selected keymap."
  175. #~(lambda (current-installer)
  176. (let ((result
  177. (call-with-values
  178. (lambda ()
  179. (xkb-rules->models+layouts
  180. (string-append #$xkeyboard-config
  181. "/share/X11/xkb/rules/base.xml")))
  182. (lambda (models layouts)
  183. ((installer-keymap-page current-installer)
  184. layouts '#$context)))))
  185. (and result (#$apply-keymap result))
  186. result)))
  187. (define (installer-steps)
  188. (let ((locale-step (compute-locale-step
  189. #:locales-name "locales"
  190. #:iso639-languages-name "iso639-languages"
  191. #:iso3166-territories-name "iso3166-territories"))
  192. (timezone-data #~(string-append #$tzdata
  193. "/share/zoneinfo/zone.tab")))
  194. #~(lambda (current-installer)
  195. ((installer-parameters-menu current-installer)
  196. (lambda ()
  197. ((installer-parameters-page current-installer)
  198. (lambda _
  199. (#$(compute-keymap-step 'param)
  200. current-installer)))))
  201. (list
  202. ;; Ask the user to choose a locale among those supported by
  203. ;; the glibc. Install the selected locale right away, so that
  204. ;; the user may benefit from any available translation for the
  205. ;; installer messages.
  206. (installer-step
  207. (id 'locale)
  208. (description (G_ "Locale"))
  209. (compute (lambda _
  210. (#$locale-step current-installer)))
  211. (configuration-formatter locale->configuration))
  212. ;; Welcome the user and ask them to choose between manual
  213. ;; installation and graphical install.
  214. (installer-step
  215. (id 'welcome)
  216. (compute (lambda _
  217. ((installer-welcome-page current-installer)
  218. #$(local-file "installer/aux-files/logo.txt")
  219. #:pci-database
  220. #$(file-append pciutils "/share/hwdata/pci.ids.gz")))))
  221. ;; Ask the user to select a timezone under glibc format.
  222. (installer-step
  223. (id 'timezone)
  224. (description (G_ "Timezone"))
  225. (compute (lambda _
  226. ((installer-timezone-page current-installer)
  227. #$timezone-data)))
  228. (configuration-formatter posix-tz->configuration))
  229. ;; The installer runs in a kmscon virtual terminal where loadkeys
  230. ;; won't work. kmscon uses libxkbcommon as a backend for keyboard
  231. ;; input. It is possible to update kmscon current keymap by sending
  232. ;; it a keyboard model, layout, variant and options, in a somehow
  233. ;; similar way as what is done with setxkbmap utility.
  234. ;;
  235. ;; So ask for a keyboard model, layout and variant to update the
  236. ;; current kmscon keymap. For non-Latin layouts, we add an
  237. ;; appropriate second layout and toggle via Alt+Shift.
  238. (installer-step
  239. (id 'keymap)
  240. (description (G_ "Keyboard mapping selection"))
  241. (compute (lambda _
  242. (#$(compute-keymap-step 'default)
  243. current-installer)))
  244. (configuration-formatter keyboard-layout->configuration))
  245. ;; Ask the user to input a hostname for the system.
  246. (installer-step
  247. (id 'hostname)
  248. (description (G_ "Hostname"))
  249. (compute (lambda _
  250. ((installer-hostname-page current-installer))))
  251. (configuration-formatter hostname->configuration))
  252. ;; Provide an interface above connmanctl, so that the user can select
  253. ;; a network susceptible to acces Internet.
  254. (installer-step
  255. (id 'network)
  256. (description (G_ "Network selection"))
  257. (compute (lambda _
  258. ((installer-network-page current-installer)))))
  259. ;; Ask whether to enable substitute server discovery.
  260. (installer-step
  261. (id 'substitutes)
  262. (description (G_ "Substitute server discovery"))
  263. (compute (lambda _
  264. ((installer-substitutes-page current-installer)))))
  265. ;; Prompt for users (name, group and home directory).
  266. (installer-step
  267. (id 'user)
  268. (description (G_ "User creation"))
  269. (compute (lambda _
  270. ((installer-user-page current-installer))))
  271. (configuration-formatter users->configuration))
  272. ;; Ask the user to choose one or many desktop environment(s).
  273. (installer-step
  274. (id 'services)
  275. (description (G_ "Services"))
  276. (compute (lambda _
  277. ((installer-services-page current-installer))))
  278. (configuration-formatter system-services->configuration))
  279. ;; Run a partitioning tool allowing the user to modify
  280. ;; partition tables, partitions and their mount points.
  281. ;; Do this last so the user has something to boot if any
  282. ;; of the previous steps didn't go as expected.
  283. (installer-step
  284. (id 'partition)
  285. (description (G_ "Partitioning"))
  286. (compute (lambda _
  287. ((installer-partition-page current-installer))))
  288. (configuration-formatter user-partitions->configuration))
  289. (installer-step
  290. (id 'final)
  291. (description (G_ "Configuration file"))
  292. (compute
  293. (lambda (result prev-steps)
  294. ((installer-final-page current-installer)
  295. result prev-steps))))))))
  296. (define (provenance-sexp)
  297. "Return an sexp representing the currently-used channels, for logging
  298. purposes."
  299. (match (match (current-channels)
  300. (() (and=> (repository->guix-channel (dirname (current-filename)))
  301. list))
  302. (channels channels))
  303. (#f
  304. (warning (G_ "cannot determine installer provenance~%"))
  305. 'unknown)
  306. ((channels ...)
  307. (map (lambda (channel)
  308. (let* ((uri (string->uri (channel-url channel)))
  309. (url (if (or (not uri) (eq? 'file (uri-scheme uri)))
  310. "local checkout"
  311. (channel-url channel))))
  312. `(channel ,(channel-name channel) ,url ,(channel-commit channel))))
  313. channels))))
  314. (define (installer-program)
  315. "Return a file-like object that runs the given INSTALLER."
  316. (define init-gettext
  317. ;; Initialize gettext support, so that installer messages can be
  318. ;; translated.
  319. #~(begin
  320. (bindtextdomain "guix" (string-append #$guix "/share/locale"))
  321. (textdomain "guix")
  322. (setlocale LC_ALL "")))
  323. (define set-installer-path
  324. ;; Add the specified binary to PATH for later use by the installer.
  325. #~(let* ((inputs
  326. '#$(list bash ;start subshells
  327. connman ;call connmanctl
  328. cryptsetup
  329. dosfstools ;mkfs.fat
  330. e2fsprogs ;mkfs.ext4
  331. lvm2-static ;dmsetup
  332. btrfs-progs
  333. jfsutils ;jfs_mkfs
  334. ntfs-3g ;mkfs.ntfs
  335. xfsprogs ;mkfs.xfs
  336. kbd ;chvt
  337. util-linux ;mkwap
  338. nano
  339. shadow
  340. tar ;dump
  341. gzip ;dump
  342. coreutils)))
  343. (with-output-to-port (%make-void-port "w")
  344. (lambda ()
  345. (set-path-environment-variable "PATH" '("bin" "sbin") inputs)))))
  346. (define steps (installer-steps))
  347. (define modules
  348. (scheme-modules*
  349. (string-append (current-source-directory) "/..")
  350. "gnu/installer"))
  351. (define installer-builder
  352. ;; Note: Include GUIX as an extension to get all the (gnu system …), (gnu
  353. ;; packages …), etc. modules.
  354. (with-extensions (list guile-gcrypt guile-newt
  355. guile-parted guile-bytestructures
  356. guile-json-3 guile-git guile-webutils
  357. guile-gnutls
  358. guile-zlib ;for (gnu build linux-modules)
  359. (current-guix))
  360. (with-imported-modules `(,@(source-module-closure
  361. `(,@modules
  362. (gnu services herd)
  363. (guix build utils))
  364. #:select? module-to-import?)
  365. ((guix config) => ,(make-config.scm)))
  366. #~(begin
  367. (use-modules (gnu installer record)
  368. (gnu installer keymap)
  369. (gnu installer steps)
  370. (gnu installer dump)
  371. (gnu installer final)
  372. (gnu installer hostname)
  373. (gnu installer locale)
  374. (gnu installer parted)
  375. (gnu installer services)
  376. (gnu installer timezone)
  377. (gnu installer user)
  378. (gnu installer utils)
  379. (gnu installer newt)
  380. ((gnu installer newt keymap)
  381. #:select (keyboard-layout->configuration))
  382. (gnu services herd)
  383. (guix i18n)
  384. (guix build utils)
  385. ((system repl debug)
  386. #:select (terminal-width))
  387. (ice-9 match)
  388. (ice-9 textual-ports))
  389. ;; Enable core dump generation.
  390. (setrlimit 'core #f #f)
  391. (call-with-output-file "/proc/sys/kernel/core_pattern"
  392. (lambda (port)
  393. (format port %core-dump)))
  394. ;; Initialize gettext support so that installers can use
  395. ;; (guix i18n) module.
  396. #$init-gettext
  397. ;; Add some binaries used by the installers to PATH.
  398. #$set-installer-path
  399. ;; Arrange for language and territory name translations to be
  400. ;; available. We need them at run time, not just compile time,
  401. ;; because some territories have several corresponding languages
  402. ;; (e.g., "French" is always displayed as "français", but
  403. ;; "Belgium" could be translated to Dutch, French, or German.)
  404. (bindtextdomain "iso_639-3" ;languages
  405. #+(file-append iso-codes "/share/locale"))
  406. (bindtextdomain "iso_3166-1" ;territories
  407. #+(file-append iso-codes "/share/locale"))
  408. ;; Likewise for XKB keyboard layout names.
  409. (bindtextdomain "xkeyboard-config"
  410. #+(file-append xkeyboard-config "/share/locale"))
  411. ;; Initialize 'terminal-width' in (system repl debug)
  412. ;; to a large-enough value to make backtrace more
  413. ;; verbose.
  414. (terminal-width 200)
  415. (define current-installer newt-installer)
  416. (define steps (#$steps current-installer))
  417. (installer-log-line "installer provenance: ~s"
  418. '#$(provenance-sexp))
  419. (dynamic-wind
  420. (installer-init current-installer)
  421. (lambda ()
  422. (parameterize
  423. ((run-command-in-installer
  424. (installer-run-command current-installer)))
  425. (catch #t
  426. (lambda ()
  427. (define results
  428. (run-installer-steps
  429. #:rewind-strategy 'menu
  430. #:menu-proc (installer-menu-page current-installer)
  431. #:steps steps))
  432. (match (result-step results 'final)
  433. ('success
  434. ;; We did it! Let's reboot!
  435. (sync)
  436. (stop-service 'root))
  437. (_
  438. ;; The installation failed, exit so that it is
  439. ;; restarted by login.
  440. #f)))
  441. (const #f)
  442. (lambda (key . args)
  443. (installer-log-line "crashing due to uncaught exception: ~s ~s"
  444. key args)
  445. (define dump-dir
  446. (prepare-dump key args #:result %current-result))
  447. (define user-abort?
  448. (match args
  449. (((? user-abort-error? obj)) #t)
  450. (_ #f)))
  451. (define action
  452. (if user-abort?
  453. 'dump
  454. ((installer-exit-error current-installer)
  455. (get-string-all
  456. (open-input-file
  457. (string-append dump-dir
  458. "/installer-backtrace"))))))
  459. (match action
  460. ('dump
  461. (let* ((dump-files
  462. ((installer-dump-page current-installer)
  463. dump-dir))
  464. (dump-archive
  465. (make-dump dump-dir dump-files)))
  466. ((installer-report-page current-installer)
  467. dump-archive)))
  468. (_ #f))
  469. (exit 1)))))
  470. (installer-exit current-installer))))))
  471. (program-file
  472. "installer"
  473. #~(begin
  474. ;; Set the default locale to install unicode support. For
  475. ;; some reason, unicode support is not correctly installed
  476. ;; when calling this in 'installer-builder'.
  477. (setenv "LANG" "en_US.UTF-8")
  478. (execl #$(program-file "installer-real" installer-builder
  479. #:guile guile-3.0-latest)
  480. "installer-real"))))