reconfigure.scm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014-2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2016 Alex Kost <alezost@gmail.com>
  4. ;;; Copyright © 2016, 2017, 2018 Chris Marusich <cmmarusich@gmail.com>
  5. ;;; Copyright © 2017 Mathieu Othacehe <m.othacehe@gmail.com>
  6. ;;; Copyright © 2018 Ricardo Wurmus <rekado@elephly.net>
  7. ;;; Copyright © 2019 Christopher Baines <mail@cbaines.net>
  8. ;;; Copyright © 2019 Jakob L. Kreuze <zerodaysfordays@sdf.org>
  9. ;;; Copyright © 2022 Arun Isaac <arunisaac@systemreboot.net>
  10. ;;;
  11. ;;; This file is part of GNU Guix.
  12. ;;;
  13. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  14. ;;; under the terms of the GNU General Public License as published by
  15. ;;; the Free Software Foundation; either version 3 of the License, or (at
  16. ;;; your option) any later version.
  17. ;;;
  18. ;;; GNU Guix is distributed in the hope that it will be useful, but
  19. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  20. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. ;;; GNU General Public License for more details.
  22. ;;;
  23. ;;; You should have received a copy of the GNU General Public License
  24. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  25. (define-module (guix scripts system reconfigure)
  26. #:autoload (gnu packages gnupg) (guile-gcrypt)
  27. #:use-module (gnu bootloader)
  28. #:use-module (gnu services)
  29. #:use-module (gnu services herd)
  30. #:use-module (gnu services shepherd)
  31. #:use-module (gnu system)
  32. #:use-module (guix gexp)
  33. #:use-module (guix modules)
  34. #:use-module (guix monads)
  35. #:use-module (guix store)
  36. #:use-module ((guix self) #:select (make-config.scm))
  37. #:use-module (guix channels)
  38. #:autoload (guix git) (update-cached-checkout)
  39. #:use-module (guix i18n)
  40. #:use-module (guix diagnostics)
  41. #:use-module (ice-9 match)
  42. #:use-module (srfi srfi-1)
  43. #:use-module (srfi srfi-34)
  44. #:use-module (srfi srfi-35)
  45. #:use-module (srfi srfi-71)
  46. #:use-module ((guix config) #:select (%guix-package-name))
  47. #:export (switch-system-program
  48. switch-to-system
  49. upgrade-services-program
  50. upgrade-shepherd-services
  51. install-bootloader-program
  52. install-bootloader
  53. check-forward-update
  54. ensure-forward-reconfigure
  55. warn-about-backward-reconfigure))
  56. ;;; Commentary:
  57. ;;;
  58. ;;; This module implements the "effectful" parts of system
  59. ;;; reconfiguration. Although building a system derivation is a pure
  60. ;;; operation, a number of impure operations must be carried out for the
  61. ;;; system configuration to be realized -- chiefly, creation of generation
  62. ;;; symlinks and invocation of activation scripts.
  63. ;;;
  64. ;;; Code:
  65. ;;;
  66. ;;; Profile creation.
  67. ;;;
  68. (define not-config?
  69. ;; Select (guix …) and (gnu …) modules, except (guix config).
  70. (match-lambda
  71. (('guix 'config) #f)
  72. (('guix rest ...) #t)
  73. (('gnu rest ...) #t)
  74. (_ #f)))
  75. (define* (switch-system-program os #:optional profile)
  76. "Return an executable store item that, upon being evaluated, will create a
  77. new generation of PROFILE pointing to the directory of OS, switch to it
  78. atomically, and run OS's activation script."
  79. (program-file
  80. "switch-to-system.scm"
  81. (with-extensions (list guile-gcrypt)
  82. (with-imported-modules `(,@(source-module-closure
  83. '((guix profiles)
  84. (guix utils))
  85. #:select? not-config?)
  86. ((guix config) => ,(make-config.scm)))
  87. #~(begin
  88. (use-modules (guix build utils)
  89. (guix config)
  90. (guix profiles)
  91. (guix utils))
  92. (define profile
  93. (or #$profile (string-append %state-directory "/profiles/system")))
  94. (let* ((number (1+ (generation-number profile)))
  95. (generation (generation-file-name profile number)))
  96. (switch-symlinks generation #$os)
  97. (switch-symlinks profile generation)
  98. (setenv "GUIX_NEW_SYSTEM" #$os)
  99. (primitive-load #$(operating-system-activation-script os))))))))
  100. (define* (switch-to-system eval os #:optional profile)
  101. "Using EVAL, a monadic procedure taking a single G-Expression as an argument,
  102. create a new generation of PROFILE pointing to the directory of OS, switch to
  103. it atomically, and run OS's activation script."
  104. (eval #~(parameterize ((current-warning-port (%make-void-port "w")))
  105. (primitive-load #$(switch-system-program os profile)))))
  106. ;;;
  107. ;;; Services.
  108. ;;;
  109. (define (running-services eval)
  110. "Using EVAL, a monadic procedure taking a single G-Expression as an argument,
  111. return the <live-service> objects that are currently running on MACHINE."
  112. (define exp
  113. (with-imported-modules '((gnu services herd))
  114. #~(begin
  115. (use-modules (gnu services herd)
  116. (ice-9 match))
  117. (let ((services (current-services)))
  118. (and services
  119. (map (lambda (service)
  120. (list (live-service-provision service)
  121. (live-service-requirement service)
  122. (live-service-transient? service)
  123. (match (live-service-running service)
  124. (#f #f)
  125. (#t #t)
  126. ((? number? pid) pid)
  127. (_ #t)))) ;not serializable
  128. services))))))
  129. (mlet %store-monad ((services (eval exp)))
  130. (return (map (match-lambda
  131. ((provision requirement transient? running)
  132. (live-service provision requirement
  133. transient? running)))
  134. services))))
  135. ;; XXX: Currently, this does NOT attempt to restart running services. See
  136. ;; <https://issues.guix.info/issue/33508> for details.
  137. (define (upgrade-services-program service-files to-start to-unload to-restart)
  138. "Return an executable store item that, upon being evaluated, will upgrade
  139. the Shepherd (PID 1) by unloading obsolete services and loading new
  140. services. SERVICE-FILES is a list of Shepherd service files to load, and
  141. TO-START, TO-UNLOAD, and TO-RESTART are lists of the Shepherd services'
  142. canonical names (symbols)."
  143. (program-file
  144. "upgrade-shepherd-services.scm"
  145. (with-imported-modules '((gnu services herd))
  146. #~(begin
  147. (use-modules (gnu services herd)
  148. (srfi srfi-1))
  149. ;; Load the service files for any new services.
  150. ;; Silence messages coming from shepherd such as "Evaluating
  151. ;; expression ..." since they are unhelpful.
  152. (parameterize ((shepherd-message-port (%make-void-port "w")))
  153. (load-services/safe '#$service-files))
  154. ;; Unload obsolete services and start new services.
  155. (for-each unload-service '#$to-unload)
  156. (for-each start-service '#$to-start)))))
  157. (define* (upgrade-shepherd-services eval os)
  158. "Using EVAL, a monadic procedure taking a single G-Expression as an argument,
  159. upgrade the Shepherd (PID 1) by unloading obsolete services and loading new
  160. services as defined by OS."
  161. (define target-services
  162. (shepherd-configuration-services
  163. (service-value
  164. (fold-services (operating-system-services os)
  165. #:target-type shepherd-root-service-type))))
  166. (mlet* %store-monad ((live-services (running-services eval)))
  167. (let ((to-unload to-restart
  168. (shepherd-service-upgrade live-services target-services)))
  169. (let* ((to-unload (map live-service-canonical-name to-unload))
  170. (to-restart (map shepherd-service-canonical-name to-restart))
  171. (running (map live-service-canonical-name
  172. (filter live-service-running live-services)))
  173. (to-start (lset-difference eqv?
  174. (map shepherd-service-canonical-name
  175. target-services)
  176. running))
  177. (service-files (map shepherd-service-file target-services)))
  178. (eval #~(parameterize ((current-warning-port (%make-void-port "w")))
  179. (primitive-load #$(upgrade-services-program service-files
  180. to-start
  181. to-unload
  182. to-restart))))))))
  183. ;;;
  184. ;;; Bootloader configuration.
  185. ;;;
  186. (define (install-bootloader-program installer disk-installer
  187. bootloader-package bootcfg
  188. bootcfg-file devices target)
  189. "Return an executable store item that, upon being evaluated, will install
  190. BOOTCFG to BOOTCFG-FILE, a target file name, on DEVICES, a list of file system
  191. devices, at TARGET, a mount point, and subsequently run INSTALLER from
  192. BOOTLOADER-PACKAGE."
  193. (program-file
  194. "install-bootloader.scm"
  195. (with-extensions (list guile-gcrypt)
  196. (with-imported-modules `(,@(source-module-closure
  197. '((gnu build bootloader)
  198. (gnu build install)
  199. (guix store)
  200. (guix utils))
  201. #:select? not-config?)
  202. ((guix config) => ,(make-config.scm)))
  203. #~(begin
  204. (use-modules (gnu build bootloader)
  205. (gnu build install)
  206. (guix build utils)
  207. (guix store)
  208. (guix utils)
  209. (ice-9 binary-ports)
  210. (ice-9 match)
  211. (srfi srfi-34)
  212. (srfi srfi-35))
  213. (let* ((gc-root (string-append #$target %gc-roots-directory "/bootcfg"))
  214. (new-gc-root (string-append gc-root ".new")))
  215. ;; #$bootcfg has dependencies.
  216. ;; The bootloader magically loads the configuration from
  217. ;; (string-append #$target #$bootcfg-file) (for example
  218. ;; "/boot/grub/grub.cfg").
  219. ;; If we didn't do something special, the garbage collector
  220. ;; would remove the dependencies of #$bootcfg.
  221. ;; Register #$bootcfg as a GC root.
  222. ;; Preserve the previous activation's garbage collector root
  223. ;; until the bootloader installer has run, so that a failure in
  224. ;; the bootloader's installer script doesn't leave the user with
  225. ;; a broken installation.
  226. (switch-symlinks new-gc-root #$bootcfg)
  227. (install-boot-config #$bootcfg #$bootcfg-file #$target)
  228. (when (or #$installer #$disk-installer)
  229. (catch #t
  230. (lambda ()
  231. ;; The bootloader might not support installation on a
  232. ;; mounted directory using the BOOTLOADER-INSTALLER
  233. ;; procedure. In that case, fallback to installing the
  234. ;; bootloader directly on DEVICES using the
  235. ;; BOOTLOADER-DISK-IMAGE-INSTALLER procedure.
  236. (if #$installer
  237. (for-each (lambda (device)
  238. (#$installer #$bootloader-package device
  239. #$target))
  240. '#$devices)
  241. (for-each (lambda (device)
  242. (#$disk-installer #$bootloader-package
  243. 0 device))
  244. '#$devices)))
  245. (lambda args
  246. (delete-file new-gc-root)
  247. (match args
  248. (('%exception exception) ;Guile 3 SRFI-34 or similar
  249. (raise-exception exception))
  250. ((key . args)
  251. (apply throw key args))))))
  252. ;; We are sure that the installation of the bootloader
  253. ;; succeeded, so we can replace the old GC root by the new
  254. ;; GC root now.
  255. (rename-file new-gc-root gc-root)))))))
  256. (define* (install-bootloader eval configuration bootcfg
  257. #:key
  258. (run-installer? #t)
  259. (target "/"))
  260. "Using EVAL, a monadic procedure taking a single G-Expression as an argument,
  261. configure the bootloader on TARGET such that OS will be booted by default and
  262. additional configurations specified by MENU-ENTRIES can be selected."
  263. (let* ((bootloader (bootloader-configuration-bootloader configuration))
  264. (installer (and run-installer?
  265. (bootloader-installer bootloader)))
  266. (disk-installer (and run-installer?
  267. (bootloader-disk-image-installer bootloader)))
  268. (package (bootloader-package bootloader))
  269. (devices (bootloader-configuration-targets configuration))
  270. (bootcfg-file (bootloader-configuration-file bootloader)))
  271. (eval #~(parameterize ((current-warning-port (%make-void-port "w")))
  272. (primitive-load #$(install-bootloader-program installer
  273. disk-installer
  274. package
  275. bootcfg
  276. bootcfg-file
  277. devices
  278. target))))))
  279. ;;;
  280. ;;; Downgrade detection.
  281. ;;;
  282. (define (ensure-forward-reconfigure channel start commit relation)
  283. "Raise an error if RELATION is not 'ancestor, meaning that START is not an
  284. ancestor of COMMIT, unless CHANNEL specifies a commit."
  285. (match relation
  286. ('ancestor #t)
  287. ('self #t)
  288. (_
  289. (raise (make-compound-condition
  290. (formatted-message (G_ "\
  291. aborting reconfiguration because commit ~a of channel '~a' is not a descendant of ~a")
  292. commit (channel-name channel)
  293. start)
  294. (condition
  295. (&fix-hint
  296. (hint (G_ "Use @option{--allow-downgrades} to force
  297. this downgrade.")))))))))
  298. (define (warn-about-backward-reconfigure channel start commit relation)
  299. "Warn about non-forward updates of CHANNEL from START to COMMIT, without
  300. aborting."
  301. (match relation
  302. ((or 'ancestor 'self)
  303. #t)
  304. ('descendant
  305. (warning (G_ "rolling back channel '~a' from ~a to ~a~%")
  306. (channel-name channel) start commit))
  307. ('unrelated
  308. (warning (G_ "moving channel '~a' from ~a to unrelated commit ~a~%")
  309. (channel-name channel) start commit))))
  310. (define (channel-relations old new)
  311. "Return a list of channel/relation pairs, where each relation is a symbol as
  312. returned by 'commit-relation' denoting how commits of channels in OLD relate
  313. to commits of channels in NEW."
  314. (filter-map (lambda (old)
  315. (let ((new (find (lambda (channel)
  316. (eq? (channel-name channel)
  317. (channel-name old)))
  318. new)))
  319. (and new
  320. (let ((checkout commit relation
  321. (update-cached-checkout
  322. (channel-url new)
  323. #:ref `(commit . ,(channel-commit new))
  324. #:starting-commit (channel-commit old)
  325. #:check-out? #f)))
  326. (list new
  327. (channel-commit old) (channel-commit new)
  328. relation)))))
  329. old))
  330. (define* (check-forward-update #:optional
  331. (validate-reconfigure
  332. ensure-forward-reconfigure)
  333. #:key
  334. (current-channels
  335. (system-provenance "/run/current-system")))
  336. "Call VALIDATE-RECONFIGURE passing it, for each channel, the channel, the
  337. currently-deployed commit (from CURRENT-CHANNELS, which is as returned by
  338. 'guix system describe' by default) and the target commit (as returned by 'guix
  339. describe')."
  340. (define new
  341. ((@ (guix describe) current-channels)))
  342. (when (null? current-channels)
  343. (warning (G_ "cannot determine provenance for current system~%")))
  344. (when (and (null? new) (not (getenv "GUIX_UNINSTALLED")))
  345. (warning (G_ "cannot determine provenance of ~a~%") %guix-package-name))
  346. (for-each (match-lambda
  347. ((channel old new relation)
  348. (validate-reconfigure channel old new relation)))
  349. (channel-relations current-channels new)))