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

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