home.scm
1 | ;;; GNU Guix --- Functional package management for GNU |
2 | ;;; Copyright © 2019 Julien Lepiller <julien@lepiller.eu> |
3 | ;;; |
4 | ;;; This file is part of GNU Guix. |
5 | ;;; |
6 | ;;; GNU Guix is free software; you can redistribute it and/or modify it |
7 | ;;; under the terms of the GNU General Public License as published by |
8 | ;;; the Free Software Foundation; either version 3 of the License, or (at |
9 | ;;; your option) any later version. |
10 | ;;; |
11 | ;;; GNU Guix is distributed in the hope that it will be useful, but |
12 | ;;; WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | ;;; GNU General Public License for more details. |
15 | ;;; |
16 | ;;; You should have received a copy of the GNU General Public License |
17 | ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. |
18 | |
19 | (define-module (guix scripts home) |
20 | #:use-module (guix derivations) |
21 | #:use-module (guix grafts) |
22 | #:use-module (guix monads) |
23 | #:use-module (guix packages) |
24 | #:use-module (guix profiles) |
25 | #:use-module (guix scripts) |
26 | #:use-module (guix scripts build) |
27 | #:use-module (guix status) |
28 | #:use-module (guix store) |
29 | #:use-module (guix ui) |
30 | #:use-module (guix utils) |
31 | #:use-module (home) |
32 | #:use-module (ice-9 match) |
33 | #:use-module (srfi srfi-1) |
34 | #:use-module (srfi srfi-26) |
35 | #:use-module (srfi srfi-37) |
36 | #:export (guix-home)) |
37 | |
38 | ;;; |
39 | ;;; Command-line options. |
40 | ;;; |
41 | |
42 | (define %options |
43 | (list (option '(#\h "help") #f #f |
44 | (lambda args |
45 | (show-help) |
46 | (exit 0))) |
47 | (option '(#\V "version") #f #f |
48 | (lambda args |
49 | (show-version-and-exit "guix edit"))))) |
50 | |
51 | (define (show-help) |
52 | (display (G_ "Usage: guix home [OPTION ...] ACTION [ARG ...] [FILE] |
53 | Manage a user home environment according to FILE and ACTION. Some actions |
54 | support additional ARGs.\n")) |
55 | (display (G_ "The valid values for ACTION are:\n")) |
56 | (newline) |
57 | (display (G_ "\ |
58 | reconfigure switch to or create a new home configuration\n")) |
59 | (display (G_ "\ |
60 | build build the home configuration without installing anything\n")) |
61 | (display (G_ "\ |
62 | list-generations list the home generations\n")) |
63 | (show-build-options-help) |
64 | (display (G_ " |
65 | -h, --help display this help and exit")) |
66 | (display (G_ " |
67 | -V, --version display version information and exit")) |
68 | (newline) |
69 | (show-bug-report-information)) |
70 | |
71 | (define %options |
72 | ;; Specifications of the command-line options. |
73 | (cons* (option '(#\h "help") #f #f |
74 | (lambda args |
75 | (show-help) |
76 | (exit 0))) |
77 | (option '(#\V "version") #f #f |
78 | (lambda args |
79 | (show-version-and-exit "guix system"))) |
80 | (option '(#\e "expression") #t #f |
81 | (lambda (opt name arg result) |
82 | (alist-cons 'expression arg result))) |
83 | (option '(#\d "derivation") #f #f |
84 | (lambda (opt name arg result) |
85 | (alist-cons 'derivations-only? #t result))) |
86 | (option '("on-error") #t #f |
87 | (lambda (opt name arg result) |
88 | (alist-cons 'on-error (string->symbol arg) |
89 | result))) |
90 | (option '(#\t "file-system-type") #t #f |
91 | (lambda (opt name arg result) |
92 | (alist-cons 'file-system-type arg |
93 | result))) |
94 | (option '("image-size") #t #f |
95 | (lambda (opt name arg result) |
96 | (alist-cons 'image-size (size->number arg) |
97 | result))) |
98 | (option '(#\N "network") #f #f |
99 | (lambda (opt name arg result) |
100 | (alist-cons 'container-shared-network? #t result))) |
101 | (option '("no-bootloader" "no-grub") #f #f |
102 | (lambda (opt name arg result) |
103 | (alist-cons 'install-bootloader? #f result))) |
104 | (option '("full-boot") #f #f |
105 | (lambda (opt name arg result) |
106 | (alist-cons 'full-boot? #t result))) |
107 | (option '("skip-checks") #f #f |
108 | (lambda (opt name arg result) |
109 | (alist-cons 'skip-safety-checks? #t result))) |
110 | |
111 | (option '("share") #t #f |
112 | (lambda (opt name arg result) |
113 | (alist-cons 'file-system-mapping |
114 | (specification->file-system-mapping arg #t) |
115 | result))) |
116 | (option '("expose") #t #f |
117 | (lambda (opt name arg result) |
118 | (alist-cons 'file-system-mapping |
119 | (specification->file-system-mapping arg #f) |
120 | result))) |
121 | |
122 | (option '(#\n "dry-run") #f #f |
123 | (lambda (opt name arg result) |
124 | (alist-cons 'dry-run? #t (alist-cons 'graft? #f result)))) |
125 | (option '(#\v "verbosity") #t #f |
126 | (lambda (opt name arg result) |
127 | (let ((level (string->number* arg))) |
128 | (alist-cons 'verbosity level |
129 | (alist-delete 'verbosity result))))) |
130 | (option '(#\s "system") #t #f |
131 | (lambda (opt name arg result) |
132 | (alist-cons 'system arg |
133 | (alist-delete 'system result eq?)))) |
134 | (option '(#\r "root") #t #f |
135 | (lambda (opt name arg result) |
136 | (alist-cons 'gc-root arg result))) |
137 | %standard-build-options)) |
138 | |
139 | (define %default-options |
140 | ;; Alist of default option values. |
141 | `((system . ,(%current-system)) |
142 | (substitutes? . #t) |
143 | (build-hook? . #t) |
144 | (print-build-trace? . #t) |
145 | (print-extended-build-trace? . #t) |
146 | (multiplexed-build-output? . #t) |
147 | (graft? . #t) |
148 | (debug . 0) |
149 | (verbosity . #f) ;default |
150 | (file-system-type . "ext4") |
151 | (image-size . guess) |
152 | (install-bootloader? . #t))) |
153 | |
154 | ;;; |
155 | ;;; Profiles |
156 | ;;; |
157 | |
158 | (define %user-module |
159 | ;; Module in which the machine description file is loaded. |
160 | (make-user-module '())) |
161 | |
162 | (define %home (getenv "HOME")) |
163 | |
164 | (define %current-home |
165 | (string-append %profile-directory "/home")) |
166 | |
167 | (define (ensure-home-profile data-directory) |
168 | "Ensures $HOME is a symlink to the profile. If it is not yet the case, move |
169 | it to the @var{data-directory} directory, unless it already exists, in which case |
170 | report an error." |
171 | (ensure-profile-directory) |
172 | |
173 | (when %home %current-home |
174 | (let ((home (false-if-exception (lstat %home)))) |
175 | (if home |
176 | (unless (equal? (readlink %home) %current-home) |
177 | (if (false-if-exception (lstat data-directory)) |
178 | (leave (G_ "Your $HOME directory (~a) is not a symlink to the home profile, |
179 | and it cannot be moved as ~a already exists on the filesystem.~%") |
180 | %home data-directory) |
181 | (rename-file %home data-directory))) |
182 | (symlink %current-home %home))))) |
183 | |
184 | (define (list-generations pattern) |
185 | "Display in a human-readable format all the home generations matching |
186 | PATTERN, a string. When PATTERN is #f, display all the home generations." |
187 | (cond ((not (file-exists? %current-home)) ; XXX: race condition |
188 | (raise (condition (&profile-not-found-error |
189 | (profile %current-home))))) |
190 | ((not pattern) |
191 | (for-each (lambda (number) (display-generation %current-home number)) (profile-generations %current-home))) |
192 | ((matching-generations pattern %current-home) |
193 | => |
194 | (lambda (numbers) |
195 | (if (null-list? numbers) |
196 | (exit 1) |
197 | (leave-on-EPIPE |
198 | (for-each (lambda (number) (display-generation %current-home number)) numbers))))))) |
199 | |
200 | |
201 | ;;; |
202 | ;;; Entry point. |
203 | ;;; |
204 | |
205 | (define* (perform-action action home |
206 | #:key |
207 | dry-run? derivations-only? |
208 | use-substitutes?) |
209 | "Perform ACTION for HOME. When DERIVATIONS-ONLY? is true, print the |
210 | derivation file name(s) without building anything." |
211 | (define println |
212 | (cut format #t "~a~%" <>)) |
213 | |
214 | (when (eq? action 'reconfigure) |
215 | (ensure-home-profile (home-data-directory home))) |
216 | |
217 | (with-store store |
218 | (let* ((drv (run-with-store store (home->derivation home))) |
219 | (profile (derivation->output-path drv))) |
220 | (show-what-to-build store (list drv) |
221 | #:use-substitutes? use-substitutes? |
222 | #:dry-run? dry-run?) |
223 | |
224 | (unless (or dry-run? derivations-only?) |
225 | (begin |
226 | (build-derivations store (list drv)) |
227 | (case action |
228 | ((reconfigure) |
229 | (newline) |
230 | (format #t (G_ "Activating home...~%")) |
231 | (let* ((number (generation-number %current-home)) |
232 | (generation (generation-file-name %current-home (+ 1 number)))) |
233 | (switch-symlinks generation profile) |
234 | (switch-symlinks %current-home generation)) |
235 | (format #t (G_ "Your home directory has been reconfigured.~%"))) |
236 | (else |
237 | (display profile) |
238 | (newline)))))))) |
239 | |
240 | (define (process-action action args opts) |
241 | "Process ACTION, a sub-command, with the arguments are listed in ARGS. |
242 | ACTION must be one of the sub-commands that takes an operating system |
243 | declaration as an argument (a file name.) OPTS is the raw alist of options |
244 | resulting from command-line parsing." |
245 | (define (ensure-home-configuration file-or-exp obj) |
246 | (unless (home? obj) |
247 | (leave (G_ "'~a' does not return a home configuration~%") |
248 | file-or-exp)) |
249 | obj) |
250 | |
251 | (let* ((file (match args |
252 | (() #f) |
253 | ((x . _) x))) |
254 | (expr (assoc-ref opts 'expression)) |
255 | (system (assoc-ref opts 'system)) |
256 | (home (ensure-home-configuration |
257 | (or file expr) |
258 | (cond |
259 | ((and expr file) |
260 | (leave |
261 | (G_ "both file and expression cannot be specified~%"))) |
262 | (expr |
263 | (read/eval expr)) |
264 | (file |
265 | (load* file %user-module |
266 | #:on-error (assoc-ref opts 'on-error))) |
267 | (else |
268 | (leave (G_ "no configuration specified~%")))))) |
269 | |
270 | (dry? (assoc-ref opts 'dry-run?))) |
271 | |
272 | (with-store store |
273 | (set-build-options-from-command-line store opts) |
274 | |
275 | (set-guile-for-build (default-guile)) |
276 | |
277 | (case action |
278 | (else |
279 | (unless (eq? action 'build) |
280 | (warn-about-old-distro #:suggested-command |
281 | "guix home reconfigure")) |
282 | |
283 | (perform-action action home |
284 | #:dry-run? dry? |
285 | #:derivations-only? (assoc-ref opts |
286 | 'derivations-only?) |
287 | #:use-substitutes? (assoc-ref opts 'substitutes?))))) |
288 | (warn-about-disk-space))) |
289 | |
290 | (define (resolve-subcommand name) |
291 | (let ((module (resolve-interface |
292 | `(guix scripts home ,(string->symbol name)))) |
293 | (proc (string->symbol (string-append "guix-home-" name)))) |
294 | (module-ref module proc))) |
295 | |
296 | (define (process-command command args opts) |
297 | "Process COMMAND, one of the 'guix system' sub-commands. ARGS is its |
298 | argument list and OPTS is the option alist." |
299 | (case command |
300 | ;; The following commands do not need to use the store, and they do not need |
301 | ;; an operating system configuration file. |
302 | ((list-generations) |
303 | (let ((pattern (match args |
304 | (() #f) |
305 | ((pattern) pattern) |
306 | (x (leave (G_ "wrong number of arguments~%")))))) |
307 | (list-generations pattern))) |
308 | ;; The following commands need to use the store, but they do not need an |
309 | ;; operating system configuration file. |
310 | ;; The following commands need to use the store, and they also |
311 | ;; need an operating system configuration file. |
312 | (else (process-action command args opts)))) |
313 | |
314 | (define (guix-home . args) |
315 | (define (parse-sub-command arg result) |
316 | ;; Parse sub-command ARG and augment RESULT accordingly. |
317 | (if (assoc-ref result 'action) |
318 | (alist-cons 'argument arg result) |
319 | (let ((action (string->symbol arg))) |
320 | (case action |
321 | ((build reconfigure list-generations) |
322 | (alist-cons 'action action result)) |
323 | (else (leave (G_ "~a: unknown action~%") action)))))) |
324 | |
325 | (define (match-pair car) |
326 | ;; Return a procedure that matches a pair with CAR. |
327 | (match-lambda |
328 | ((head . tail) |
329 | (and (eq? car head) tail)) |
330 | (_ #f))) |
331 | |
332 | (define (option-arguments opts) |
333 | ;; Extract the plain arguments from OPTS. |
334 | (let* ((args (reverse (filter-map (match-pair 'argument) opts))) |
335 | (count (length args)) |
336 | (action (assoc-ref opts 'action)) |
337 | (expr (assoc-ref opts 'expression))) |
338 | (define (fail) |
339 | (leave (G_ "wrong number of arguments for action '~a'~%") |
340 | action)) |
341 | |
342 | (unless action |
343 | (format (current-error-port) |
344 | (G_ "guix home: missing command name~%")) |
345 | (format (current-error-port) |
346 | (G_ "Try 'guix home --help' for more information.~%")) |
347 | (exit 1)) |
348 | |
349 | (case action |
350 | ((build reconfigure) |
351 | (unless (= count 1) |
352 | (fail)))) |
353 | args)) |
354 | |
355 | (with-error-handling |
356 | (let* ((opts (parse-command-line args %options |
357 | (list %default-options) |
358 | #:argument-handler |
359 | parse-sub-command)) |
360 | (args (option-arguments opts)) |
361 | (command (assoc-ref opts 'action))) |
362 | (parameterize ((%graft? (assoc-ref opts 'graft?))) |
363 | (with-status-verbosity (or (assoc-ref opts 'verbosity) |
364 | (if (eq? command 'build) 2 1)) |
365 | (process-command command args opts)))))) |
366 | |
367 | ;;; home.scm ends here |
368 |