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