reconfigure.scm 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2014, 2015, 2016, 2017, 2018, 2019, 2020 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. (let ((services (current-services)))
  116. (and services
  117. ;; 'live-service-running' is ignored, as we can't necessarily
  118. ;; serialize arbitrary objects. This should be fine for now,
  119. ;; since 'machine-current-services' is not exposed publicly,
  120. ;; and the resultant <live-service> objects are only used for
  121. ;; resolving service dependencies.
  122. (map (lambda (service)
  123. (list (live-service-provision service)
  124. (live-service-requirement service)))
  125. services))))))
  126. (mlet %store-monad ((services (eval exp)))
  127. (return (map (match-lambda
  128. ((provision requirement)
  129. (live-service provision requirement #f)))
  130. services))))
  131. ;; XXX: Currently, this does NOT attempt to restart running services. See
  132. ;; <https://issues.guix.info/issue/33508> for details.
  133. (define (upgrade-services-program service-files to-start to-unload to-restart)
  134. "Return an executable store item that, upon being evaluated, will upgrade
  135. the Shepherd (PID 1) by unloading obsolete services and loading new
  136. services. SERVICE-FILES is a list of Shepherd service files to load, and
  137. TO-START, TO-UNLOAD, and TO-RESTART are lists of the Shepherd services'
  138. canonical names (symbols)."
  139. (program-file
  140. "upgrade-shepherd-services.scm"
  141. (with-imported-modules '((gnu services herd))
  142. #~(begin
  143. (use-modules (gnu services herd)
  144. (srfi srfi-1))
  145. ;; Load the service files for any new services.
  146. ;; Silence messages coming from shepherd such as "Evaluating
  147. ;; expression ..." since they are unhelpful.
  148. (parameterize ((shepherd-message-port (%make-void-port "w")))
  149. (load-services/safe '#$service-files))
  150. ;; Unload obsolete services and start new services.
  151. (for-each unload-service '#$to-unload)
  152. (for-each start-service '#$to-start)))))
  153. (define* (upgrade-shepherd-services eval os)
  154. "Using EVAL, a monadic procedure taking a single G-Expression as an argument,
  155. upgrade the Shepherd (PID 1) by unloading obsolete services and loading new
  156. services as defined by OS."
  157. (define target-services
  158. (service-value
  159. (fold-services (operating-system-services os)
  160. #:target-type shepherd-root-service-type)))
  161. (mlet* %store-monad ((live-services (running-services eval)))
  162. (let*-values (((to-unload to-restart)
  163. (shepherd-service-upgrade live-services target-services)))
  164. (let* ((to-unload (map live-service-canonical-name to-unload))
  165. (to-restart (map shepherd-service-canonical-name to-restart))
  166. (to-start (lset-difference eqv?
  167. (map shepherd-service-canonical-name
  168. target-services)
  169. (map live-service-canonical-name
  170. live-services)))
  171. (service-files (map shepherd-service-file target-services)))
  172. (eval #~(parameterize ((current-warning-port (%make-void-port "w")))
  173. (primitive-load #$(upgrade-services-program service-files
  174. to-start
  175. to-unload
  176. to-restart))))))))
  177. ;;;
  178. ;;; Bootloader configuration.
  179. ;;;
  180. (define (install-bootloader-program installer bootloader-package bootcfg
  181. bootcfg-file device target)
  182. "Return an executable store item that, upon being evaluated, will install
  183. BOOTCFG to BOOTCFG-FILE, a target file name, on DEVICE, a file system device,
  184. at TARGET, a mount point, and subsequently run INSTALLER from
  185. BOOTLOADER-PACKAGE."
  186. (program-file
  187. "install-bootloader.scm"
  188. (with-extensions (list guile-gcrypt)
  189. (with-imported-modules `(,@(source-module-closure
  190. '((gnu build bootloader)
  191. (gnu build install)
  192. (guix store)
  193. (guix utils))
  194. #:select? not-config?)
  195. ((guix config) => ,(make-config.scm)))
  196. #~(begin
  197. (use-modules (gnu build bootloader)
  198. (gnu build install)
  199. (guix build utils)
  200. (guix store)
  201. (guix utils)
  202. (ice-9 binary-ports)
  203. (ice-9 match)
  204. (srfi srfi-34)
  205. (srfi srfi-35))
  206. (let* ((gc-root (string-append #$target %gc-roots-directory "/bootcfg"))
  207. (new-gc-root (string-append gc-root ".new")))
  208. ;; #$bootcfg has dependencies.
  209. ;; The bootloader magically loads the configuration from
  210. ;; (string-append #$target #$bootcfg-file) (for example
  211. ;; "/boot/grub/grub.cfg").
  212. ;; If we didn't do something special, the garbage collector
  213. ;; would remove the dependencies of #$bootcfg.
  214. ;; Register #$bootcfg as a GC root.
  215. ;; Preserve the previous activation's garbage collector root
  216. ;; until the bootloader installer has run, so that a failure in
  217. ;; the bootloader's installer script doesn't leave the user with
  218. ;; a broken installation.
  219. (switch-symlinks new-gc-root #$bootcfg)
  220. (install-boot-config #$bootcfg #$bootcfg-file #$target)
  221. (when #$installer
  222. (catch #t
  223. (lambda ()
  224. (#$installer #$bootloader-package #$device #$target))
  225. (lambda args
  226. (delete-file new-gc-root)
  227. (match args
  228. (('%exception exception) ;Guile 3 SRFI-34 or similar
  229. (raise-exception exception))
  230. ((key . args)
  231. (apply throw key args))))))
  232. ;; We are sure that the installation of the bootloader
  233. ;; succeeded, so we can replace the old GC root by the new
  234. ;; GC root now.
  235. (rename-file new-gc-root gc-root)))))))
  236. (define* (install-bootloader eval configuration bootcfg
  237. #:key
  238. (run-installer? #t)
  239. (target "/"))
  240. "Using EVAL, a monadic procedure taking a single G-Expression as an argument,
  241. configure the bootloader on TARGET such that OS will be booted by default and
  242. additional configurations specified by MENU-ENTRIES can be selected."
  243. (let* ((bootloader (bootloader-configuration-bootloader configuration))
  244. (installer (and run-installer?
  245. (bootloader-installer bootloader)))
  246. (package (bootloader-package bootloader))
  247. (device (bootloader-configuration-target configuration))
  248. (bootcfg-file (bootloader-configuration-file bootloader)))
  249. (eval #~(parameterize ((current-warning-port (%make-void-port "w")))
  250. (primitive-load #$(install-bootloader-program installer
  251. package
  252. bootcfg
  253. bootcfg-file
  254. device
  255. target))))))
  256. ;;;
  257. ;;; Downgrade detection.
  258. ;;;
  259. (define (ensure-forward-reconfigure channel start commit relation)
  260. "Raise an error if RELATION is not 'ancestor, meaning that START is not an
  261. ancestor of COMMIT, unless CHANNEL specifies a commit."
  262. (match relation
  263. ('ancestor #t)
  264. ('self #t)
  265. (_
  266. (raise (make-compound-condition
  267. (condition
  268. (&message (message
  269. (format #f (G_ "\
  270. aborting reconfiguration because commit ~a of channel '~a' is not a descendant of ~a")
  271. commit (channel-name channel)
  272. start)))
  273. (&fix-hint
  274. (hint (G_ "Use @option{--allow-downgrades} to force
  275. this downgrade.")))))))))
  276. (define (warn-about-backward-reconfigure channel start commit relation)
  277. "Warn about non-forward updates of CHANNEL from START to COMMIT, without
  278. aborting."
  279. (match relation
  280. ((or 'ancestor 'self)
  281. #t)
  282. ('descendant
  283. (warning (G_ "rolling back channel '~a' from ~a to ~a~%")
  284. (channel-name channel) start commit))
  285. ('unrelated
  286. (warning (G_ "moving channel '~a' from ~a to unrelated commit ~a~%")
  287. (channel-name channel) start commit))))
  288. (define (channel-relations old new)
  289. "Return a list of channel/relation pairs, where each relation is a symbol as
  290. returned by 'commit-relation' denoting how commits of channels in OLD relate
  291. to commits of channels in NEW."
  292. (filter-map (lambda (old)
  293. (let ((new (find (lambda (channel)
  294. (eq? (channel-name channel)
  295. (channel-name old)))
  296. new)))
  297. (and new
  298. (let-values (((checkout commit relation)
  299. (update-cached-checkout
  300. (channel-url new)
  301. #:ref
  302. `(commit . ,(channel-commit new))
  303. #:starting-commit
  304. (channel-commit old)
  305. #:check-out? #f)))
  306. (list new
  307. (channel-commit old) (channel-commit new)
  308. relation)))))
  309. old))
  310. (define* (check-forward-update #:optional
  311. (validate-reconfigure
  312. ensure-forward-reconfigure)
  313. #:key
  314. (current-channels
  315. (system-provenance "/run/current-system")))
  316. "Call VALIDATE-RECONFIGURE passing it, for each channel, the channel, the
  317. currently-deployed commit (from CURRENT-CHANNELS, which is as returned by
  318. 'guix system describe' by default) and the target commit (as returned by 'guix
  319. describe')."
  320. (define new
  321. (or (and=> (current-profile) profile-channels)
  322. '()))
  323. (when (null? current-channels)
  324. (warning (G_ "cannot determine provenance for current system~%")))
  325. (when (and (null? new) (not (getenv "GUIX_UNINSTALLED")))
  326. (warning (G_ "cannot determine provenance of ~a~%") %guix-package-name))
  327. (for-each (match-lambda
  328. ((channel old new relation)
  329. (validate-reconfigure channel old new relation)))
  330. (channel-relations current-channels new)))