guix-home-manager/guix/scripts/home.scm

home.scm

1
;;; Guix Home Manager.
2
;;; Copyright © 2014, 2015, 2018 David Thompson <davet@gnu.org>
3
;;; Copyright © 2015, 2016, 2017, 2018, 2019, 2020 Ludovic Courtès <ludo@gnu.org>
4
;;; Copyright © 2018 Mike Gerwitz <mtg@gnu.org>
5
;;; Copyright © 2019 Julien Lepiller <julien@lepiller.eu>
6
;;; Copyright © 2020 Jelle Licht <jlicht@fsfe.org>
7
;;;
8
;;; This program is free software: you can redistribute it and/or modify
9
;;; it under the terms of the GNU General Public License as published by
10
;;; the Free Software Foundation, either version 3 of the License, or
11
;;; (at your option) any later version.
12
;;;
13
;;; This program is distributed in the hope that it will be useful,
14
;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15
;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
;;; GNU General Public License for more details.
17
;;;
18
;;; You should have received a copy of the GNU General Public License
19
;;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
;;; This file is part of GNU Guix.
21
22
(define-module (guix scripts home)
23
  #:use-module (guix derivations)
24
  #:use-module (guix grafts)
25
  #:use-module (guix monads)
26
  #:use-module (guix packages)
27
  #:use-module (guix profiles)
28
  #:use-module (guix scripts)
29
  #:use-module (guix scripts build)
30
  #:use-module (guix status)
31
  #:use-module (guix store)
32
  #:use-module (guix ui)
33
  #:use-module (guix utils)
34
  #:use-module (home)
35
  #:use-module (ice-9 match)
36
  #:use-module (srfi srfi-1)
37
  #:use-module (srfi srfi-26)
38
  #:use-module (srfi srfi-35)
39
  #:use-module (srfi srfi-37)
40
  #:use-module (srfi srfi-98)
41
  #:export (guix-home))
42
43
;;;
44
;;; Command-line options.
45
;;;
46
47
(define %options
48
  (list (option '(#\h "help") #f #f
49
                (lambda args
50
                  (show-help)
51
                  (exit 0)))
52
        (option '(#\V "version") #f #f
53
                (lambda args
54
                  (show-version-and-exit "guix edit")))))
55
56
(define (show-help)
57
  (display (G_ "Usage: guix home [OPTION ...] ACTION [ARG ...] [FILE]
58
Manage a user home environment according to FILE and ACTION.  Some actions
59
support additional ARGs.\n"))
60
  (display (G_ "The valid values for ACTION are:\n"))
61
  (newline)
62
  (display (G_ "\
63
   reconfigure      switch to or create a new home configuration\n"))
64
  (display (G_ "\
65
   build            build the home configuration without installing anything\n"))
66
  (display (G_ "\
67
   try              try the home configuration without installing anything\n"))
68
  (display (G_ "\
69
   list-generations list the home generations\n"))
70
  (show-build-options-help)
71
  (display (G_ "
72
  -h, --help             display this help and exit"))
73
  (display (G_ "
74
  -V, --version          display version information and exit"))
75
  (newline)
76
  (show-bug-report-information))
77
78
(define %options
79
  ;; Specifications of the command-line options.
80
  (cons* (option '(#\h "help") #f #f
81
                 (lambda args
82
                   (show-help)
83
                   (exit 0)))
84
         (option '(#\V "version") #f #f
85
                 (lambda args
86
                   (show-version-and-exit "guix system")))
87
         (option '(#\e "expression") #t #f
88
                 (lambda (opt name arg result)
89
                   (alist-cons 'expression arg result)))
90
         (option '(#\d "derivation") #f #f
91
                 (lambda (opt name arg result)
92
                   (alist-cons 'derivations-only? #t result)))
93
         (option '("on-error") #t #f
94
                 (lambda (opt name arg result)
95
                   (alist-cons 'on-error (string->symbol arg)
96
                               result)))
97
         (option '(#\t "file-system-type") #t #f
98
                 (lambda (opt name arg result)
99
                   (alist-cons 'file-system-type arg
100
                               result)))
101
         (option '("image-size") #t #f
102
                 (lambda (opt name arg result)
103
                   (alist-cons 'image-size (size->number arg)
104
                               result)))
105
         (option '(#\N "network") #f #f
106
                 (lambda (opt name arg result)
107
                   (alist-cons 'container-shared-network? #t result)))
108
         (option '("no-bootloader" "no-grub") #f #f
109
                 (lambda (opt name arg result)
110
                   (alist-cons 'install-bootloader? #f result)))
111
         (option '("full-boot") #f #f
112
                 (lambda (opt name arg result)
113
                   (alist-cons 'full-boot? #t result)))
114
         (option '("skip-checks") #f #f
115
                 (lambda (opt name arg result)
116
                   (alist-cons 'skip-safety-checks? #t result)))
117
118
         (option '(#\n "dry-run") #f #f
119
                 (lambda (opt name arg result)
120
                   (alist-cons 'dry-run? #t (alist-cons 'graft? #f result))))
121
         (option '(#\v "verbosity") #t #f
122
                 (lambda (opt name arg result)
123
                   (let ((level (string->number* arg)))
124
                     (alist-cons 'verbosity level
125
                                 (alist-delete 'verbosity result)))))
126
         (option '(#\s "system") #t #f
127
                 (lambda (opt name arg result)
128
                   (alist-cons 'system arg
129
                               (alist-delete 'system result eq?))))
130
         (option '(#\r "root") #t #f
131
                 (lambda (opt name arg result)
132
                   (alist-cons 'gc-root arg result)))
133
         %standard-build-options))
134
135
(define %default-options
136
  ;; Alist of default option values.
137
  `((system . ,(%current-system))
138
    (substitutes? . #t)
139
    (build-hook? . #t)
140
    (print-build-trace? . #t)
141
    (print-extended-build-trace? . #t)
142
    (multiplexed-build-output? . #t)
143
    (graft? . #t)
144
    (debug . 0)
145
    (verbosity . #f)                              ;default
146
    (file-system-type . "ext4")
147
    (image-size . guess)
148
    (install-bootloader? . #t)))
149
150
;;;
151
;;; Profiles
152
;;;
153
154
(define %user-module
155
  ;; Module in which the machine description file is loaded.
156
  (make-user-module '()))
157
158
(define %home (getenv "HOME"))
159
160
(define %current-home
161
  (string-append %profile-directory "/home"))
162
163
(define (ensure-home-profile data-directory)
164
  "Ensures $HOME is a symlink to the profile.  If it is not yet the case, move
165
it to the @var{data-directory} directory, unless it already exists, in which case
166
report an error."
167
  (ensure-profile-directory)
168
169
  (when (and %home %current-home)
170
    (let ((home (false-if-exception (lstat %home))))
171
      (cond
172
        ((equal? (stat:type home) 'symlink)
173
         (unless (equal? (readlink %home) %current-home)
174
           (delete-file %home)
175
           (symlink %current-home %home)))
176
        ((false-if-exception (lstat data-directory))
177
         (leave (G_ "Your $HOME directory (~a) is not a symlink to the home
178
profile, and it cannot be moved as ~a already exists on the filesystem.~%")
179
                %home data-directory))
180
        ((not home)
181
         (symlink %current-home %home))
182
        (else
183
         (rename-file %home data-directory)
184
         (symlink %current-home %home))))))
185
186
(define (list-generations pattern)
187
  "Display in a human-readable format all the home generations matching
188
PATTERN, a string.  When PATTERN is #f, display all the home generations."
189
  (cond ((not (file-exists? %current-home))             ; XXX: race condition
190
	 (raise (condition (&profile-not-found-error
191
			     (profile %current-home)))))
192
	((not pattern)
193
	 (for-each (lambda (number) (display-generation %current-home number)) (profile-generations %current-home)))
194
	((matching-generations pattern %current-home)
195
	 =>
196
	 (lambda (numbers)
197
	   (if (null-list? numbers)
198
	       (exit 1)
199
	       (leave-on-EPIPE
200
		 (for-each (lambda (number) (display-generation %current-home number)) numbers)))))))
201
202
;;;
203
;;; Try
204
;;;
205
;;; Very directly inspired by GNU Guix' (guix scripts environment)
206
207
(define %precious-variables
208
  '("USER" "LOGNAME" "DISPLAY" "TERM" "TZ" "PAGER"))
209
210
(define %default-shell
211
  (or (getenv "SHELL") "/bin/sh"))
212
213
(define (purify-environment white-list)
214
  "Unset all environment variables except those that match the regexps in
215
WHITE-LIST and those listed in %PRECIOUS-VARIABLES.  A small number of
216
variables such as 'USER' are left untouched."
217
  (for-each unsetenv
218
            (remove (lambda (variable)
219
                      (or (member variable %precious-variables)
220
                          (find (cut regexp-exec <> variable)
221
                                white-list)))
222
                    (match (get-environment-variables)
223
                      (((names . _) ...)
224
                       names)))))
225
226
(define* (create-environment #:optional (white-list '()))
227
 "Unset the variables in the current environment except those that match the
228
regexps in WHITE-LIST."
229
 (purify-environment white-list))
230
231
232
(define* (launch-environment command new-home #:key (white-list '()))
233
  "Run COMMAND in a new environment containing INPUTS, using the native search
234
paths defined by the list PATHS.  When PURE?, pre-existing environment
235
variables are cleared before setting the new ones, except those matching the
236
regexps in WHITE-LIST."
237
  ;; Properly handle SIGINT, so pressing C-c in an interactive terminal
238
  ;; application works.
239
  (sigaction SIGINT SIG_DFL)
240
  (create-environment white-list)
241
  (setenv "HOME" new-home)
242
  (chdir new-home)
243
  (match command
244
    ((program . args)
245
     (apply execlp program program args))))
246
247
(define* (launch-environment/fork command new-home #:key (white-list '()))
248
  "Run COMMAND in a new process with an environment containing PROFILE, with
249
the search paths specified by MANIFEST.  When PURE?, pre-existing environment
250
variables are cleared before setting the new ones, except those matching the
251
regexps in WHITE-LIST."
252
  (match (primitive-fork)
253
    (0 (launch-environment command new-home #:white-list white-list))
254
    (pid (match (waitpid pid)
255
           ((_ . status) status)))))
256
257
(define (status->exit-code status)
258
  "Compute the exit code made from STATUS, a value as returned by 'waitpid',
259
and suitable for 'exit'."
260
  ;; See <bits/waitstatus.h>.
261
  (or (status:exit-val status)
262
      (logior #x80 (status:term-sig status))))
263
(define exit/status (compose exit status->exit-code))
264
265
;;;
266
;;; Entry point.
267
;;;
268
269
(define* (perform-action action home
270
                         #:key
271
                         dry-run? derivations-only?
272
                         use-substitutes?)
273
  "Perform ACTION for HOME.  When DERIVATIONS-ONLY? is true, print the
274
derivation file name(s) without building anything."
275
  (define println
276
    (cut format #t "~a~%" <>))
277
278
  (with-store store
279
    (let* ((drv      (run-with-store store (home->derivation home)))
280
           (profile  (derivation->output-path drv)))
281
      (show-what-to-build store (list drv)
282
                          #:use-substitutes? use-substitutes?
283
                          #:dry-run? dry-run?)
284
285
      (unless (or dry-run? derivations-only?)
286
        (begin
287
          (build-derivations store (list drv))
288
          (case action
289
            ((reconfigure)
290
             (newline)
291
             (format #t (G_ "Activating home...~%"))
292
             (ensure-home-profile (home-data-directory home))
293
             (let* ((number (generation-number %current-home))
294
                    (generation (generation-file-name %current-home (+ 1 number))))
295
               (switch-symlinks generation profile)
296
               (switch-symlinks %current-home generation))
297
             (format #t (G_ "Your home directory has been reconfigured.~%")))
298
            ((try)
299
             (newline)
300
             (format #t (G_ "Trying out home...~%"))
301
             (exit/status
302
              (launch-environment/fork (list %default-shell "-l") profile)))
303
            (else
304
             (display profile)
305
             (newline))))))))
306
307
(define (process-action action args opts)
308
  "Process ACTION, a sub-command, with the arguments are listed in ARGS.
309
ACTION must be one of the sub-commands that takes an operating system
310
declaration as an argument (a file name.)  OPTS is the raw alist of options
311
resulting from command-line parsing."
312
  (define (ensure-home-configuration file-or-exp obj)
313
    (unless (home? obj)
314
      (leave (G_ "'~a' does not return a home configuration~%")
315
             file-or-exp))
316
    obj)
317
318
  (let* ((file        (match args
319
                        (() #f)
320
                        ((x . _) x)))
321
         (expr        (assoc-ref opts 'expression))
322
         (system      (assoc-ref opts 'system))
323
         (home        (ensure-home-configuration
324
                       (or file expr)
325
                       (cond
326
                        ((and expr file)
327
                         (leave
328
                          (G_ "both file and expression cannot be specified~%")))
329
                        (expr
330
                         (read/eval expr))
331
                        (file
332
                         (load* file %user-module
333
                                #:on-error (assoc-ref opts 'on-error)))
334
                        (else
335
                         (leave (G_ "no configuration specified~%"))))))
336
337
         (dry?        (assoc-ref opts 'dry-run?)))
338
339
    (with-store store
340
      (set-build-options-from-command-line store opts)
341
342
      (set-guile-for-build (default-guile))
343
344
      (case action
345
        (else
346
         (unless (eq? action 'build)
347
           (warn-about-old-distro #:suggested-command
348
                                  "guix home reconfigure"))
349
350
         (perform-action action home
351
                         #:dry-run? dry?
352
                         #:derivations-only? (assoc-ref opts
353
                                                        'derivations-only?)
354
                         #:use-substitutes? (assoc-ref opts 'substitutes?)))))
355
    (warn-about-disk-space)))
356
357
(define (resolve-subcommand name)
358
  (let ((module (resolve-interface
359
                 `(guix scripts home ,(string->symbol name))))
360
        (proc (string->symbol (string-append "guix-home-" name))))
361
    (module-ref module proc)))
362
363
(define (process-command command args opts)
364
  "Process COMMAND, one of the 'guix system' sub-commands.  ARGS is its
365
argument list and OPTS is the option alist."
366
  (case command
367
    ;; The following commands do not need to use the store, and they do not need
368
    ;; an operating system configuration file.
369
    ((list-generations)
370
     (let ((pattern (match args
371
			   (() #f)
372
			   ((pattern) pattern)
373
			   (x (leave (G_ "wrong number of arguments~%"))))))
374
       (list-generations pattern)))
375
    ;; The following commands need to use the store, but they do not need an
376
    ;; operating system configuration file.
377
    ;; The following commands need to use the store, and they also
378
    ;; need an operating system configuration file.
379
    (else (process-action command args opts))))
380
381
(define (guix-home . args)
382
  (define (parse-sub-command arg result)
383
    ;; Parse sub-command ARG and augment RESULT accordingly.
384
    (if (assoc-ref result 'action)
385
        (alist-cons 'argument arg result)
386
        (let ((action (string->symbol arg)))
387
          (case action
388
            ((build reconfigure list-generations try)
389
             (alist-cons 'action action result))
390
            (else (leave (G_ "~a: unknown action~%") action))))))
391
392
  (define (match-pair car)
393
    ;; Return a procedure that matches a pair with CAR.
394
    (match-lambda
395
      ((head . tail)
396
       (and (eq? car head) tail))
397
      (_ #f)))
398
399
  (define (option-arguments opts)
400
    ;; Extract the plain arguments from OPTS.
401
    (let* ((args   (reverse (filter-map (match-pair 'argument) opts)))
402
           (count  (length args))
403
           (action (assoc-ref opts 'action))
404
           (expr   (assoc-ref opts 'expression)))
405
      (define (fail)
406
        (leave (G_ "wrong number of arguments for action '~a'~%")
407
               action))
408
409
      (unless action
410
        (format (current-error-port)
411
                (G_ "guix home: missing command name~%"))
412
        (format (current-error-port)
413
                (G_ "Try 'guix home --help' for more information.~%"))
414
        (exit 1))
415
416
      (case action
417
        ((build reconfigure try)
418
         (unless (= count 1)
419
           (fail))))
420
      args))
421
422
  (with-error-handling
423
    (let* ((opts     (parse-command-line args %options
424
                                         (list %default-options)
425
                                         #:argument-handler
426
                                         parse-sub-command))
427
           (args     (option-arguments opts))
428
           (command  (assoc-ref opts 'action)))
429
      (parameterize ((%graft? (assoc-ref opts 'graft?)))
430
        (with-status-verbosity (or (assoc-ref opts 'verbosity)
431
                                   (if (eq? command 'build) 2 1))
432
          (process-command command args opts))))))
433
434
;;; home.scm ends here
435