reconfigure.scm 16 KB

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