gnu: Add r-sva.
[jackhill/guix/guix.git] / gnu / build / activation.scm
CommitLineData
4dfe6c58 1;;; GNU Guix --- Functional package management for GNU
4e8b7502 2;;; Copyright © 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
78ab0746 3;;; Copyright © 2015 Mark H Weaver <mhw@netris.org>
4dfe6c58
LC
4;;;
5;;; This file is part of GNU Guix.
6;;;
7;;; GNU Guix is free software; you can redistribute it and/or modify it
8;;; under the terms of the GNU General Public License as published by
9;;; the Free Software Foundation; either version 3 of the License, or (at
10;;; your option) any later version.
11;;;
12;;; GNU Guix is distributed in the hope that it will be useful, but
13;;; WITHOUT ANY WARRANTY; without even the implied warranty of
14;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;;; GNU General Public License for more details.
16;;;
17;;; You should have received a copy of the GNU General Public License
18;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
19
548f7a8f 20(define-module (gnu build activation)
8a9e21d1 21 #:use-module (gnu build linux-boot)
09e028f4 22 #:use-module (guix build utils)
4dfe6c58 23 #:use-module (ice-9 ftw)
ab6a279a
LC
24 #:use-module (ice-9 match)
25 #:use-module (srfi srfi-1)
ad896f23 26 #:use-module (srfi srfi-26)
ab6a279a
LC
27 #:export (activate-users+groups
28 activate-etc
b4140694 29 activate-setuid-programs
ee248b6a 30 activate-/bin/sh
d460204f 31 activate-modprobe
f34c56be 32 activate-firmware
b158f1d7 33 activate-ptrace-attach
b4140694 34 activate-current-system))
4dfe6c58
LC
35
36;;; Commentary:
37;;;
38;;; This module provides "activation" helpers. Activation is the process that
39;;; consists in setting up system-wide files and directories so that an
40;;; 'operating-system' configuration becomes active.
41;;;
42;;; Code:
43
9bea87a5
LC
44(define (enumerate thunk)
45 "Return the list of values returned by THUNK until it returned #f."
46 (let loop ((entry (thunk))
47 (result '()))
48 (if (not entry)
49 (reverse result)
50 (loop (thunk) (cons entry result)))))
51
52(define (current-users)
53 "Return the passwd entries for all the currently defined user accounts."
54 (setpw)
55 (enumerate getpwent))
56
57(define (current-groups)
58 "Return the group entries for all the currently defined user groups."
59 (setgr)
60 (enumerate getgrent))
61
c8fa3426 62(define* (add-group name #:key gid password system?
ab6a279a
LC
63 (log-port (current-error-port)))
64 "Add NAME as a user group, with the given numeric GID if specified."
65 ;; Use 'groupadd' from the Shadow package.
66 (format log-port "adding group '~a'...~%" name)
67 (let ((args `(,@(if gid `("-g" ,(number->string gid)) '())
68 ,@(if password `("-p" ,password) '())
c8fa3426 69 ,@(if system? `("--system") '())
ab6a279a
LC
70 ,name)))
71 (zero? (apply system* "groupadd" args))))
72
45c5b47b
LC
73(define %skeleton-directory
74 ;; Directory containing skeleton files for new accounts.
75 ;; Note: keep the trailing '/' so that 'scandir' enters it.
76 "/etc/skel/")
77
78(define (dot-or-dot-dot? file)
79 (member file '("." "..")))
80
356a62b8
LC
81(define (make-file-writable file)
82 "Make FILE writable for its owner.."
83 (let ((stat (lstat file))) ;XXX: symlinks
84 (chmod file (logior #o600 (stat:perms stat)))))
85
45c5b47b
LC
86(define* (copy-account-skeletons home
87 #:optional (directory %skeleton-directory))
88 "Copy the account skeletons from DIRECTORY to HOME."
89 (let ((files (scandir directory (negate dot-or-dot-dot?)
90 string<?)))
91 (mkdir-p home)
92 (for-each (lambda (file)
356a62b8 93 (let ((target (string-append home "/" file)))
4e8b7502 94 (copy-recursively (string-append directory "/" file)
2fa909b2
LC
95 target
96 #:log (%make-void-port "w"))
356a62b8
LC
97 (make-file-writable target)))
98 files)))
99
100(define* (make-skeletons-writable home
101 #:optional (directory %skeleton-directory))
102 "Make sure that the files that have been copied from DIRECTORY to HOME are
103owner-writable in HOME."
104 (let ((files (scandir directory (negate dot-or-dot-dot?)
105 string<?)))
106 (for-each (lambda (file)
107 (let ((target (string-append home "/" file)))
108 (when (file-exists? target)
109 (make-file-writable target))))
45c5b47b
LC
110 files)))
111
ab6a279a 112(define* (add-user name group
eb56ee02
LC
113 #:key uid comment home create-home?
114 shell password system?
ab6a279a
LC
115 (supplementary-groups '())
116 (log-port (current-error-port)))
117 "Create an account for user NAME part of GROUP, with the specified
118properties. Return #t on success."
119 (format log-port "adding user '~a'...~%" name)
120
121 (if (and uid (zero? uid))
122
123 ;; 'useradd' fails with "Cannot determine your user name" if the root
124 ;; account doesn't exist. Thus, for bootstrapping purposes, create that
125 ;; one manually.
126 (begin
127 (call-with-output-file "/etc/shadow"
128 (cut format <> "~a::::::::~%" name))
129 (call-with-output-file "/etc/passwd"
130 (cut format <> "~a:x:~a:~a:~a:~a:~a~%"
131 name "0" "0" comment home shell))
132 (chmod "/etc/shadow" #o600)
45c5b47b 133 (copy-account-skeletons (or home "/root"))
ab6a279a
LC
134 #t)
135
136 ;; Use 'useradd' from the Shadow package.
137 (let ((args `(,@(if uid `("-u" ,(number->string uid)) '())
138 "-g" ,(if (number? group) (number->string group) group)
139 ,@(if (pair? supplementary-groups)
140 `("-G" ,(string-join supplementary-groups ","))
141 '())
142 ,@(if comment `("-c" ,comment) '())
eb56ee02 143 ,@(if (and home create-home?)
f3b692ac
LC
144 (if (file-exists? home)
145 `("-d" ,home) ; avoid warning from 'useradd'
146 `("-d" ,home "--create-home"))
147 '())
ab6a279a
LC
148 ,@(if shell `("-s" ,shell) '())
149 ,@(if password `("-p" ,password) '())
459dd9ea 150 ,@(if system? '("--system") '())
ab6a279a 151 ,name)))
356a62b8
LC
152 (and (zero? (apply system* "useradd" args))
153 (begin
154 ;; Since /etc/skel is a link to a directory in the store where
155 ;; all files have the writable bit cleared, and since 'useradd'
156 ;; preserves permissions when it copies them, explicitly make
157 ;; them writable.
158 (make-skeletons-writable home)
159 #t)))))
ab6a279a 160
e2b464b7 161(define* (modify-user name group
eb56ee02
LC
162 #:key uid comment home create-home?
163 shell password system?
e2b464b7
LC
164 (supplementary-groups '())
165 (log-port (current-error-port)))
166 "Modify user account NAME to have all the given settings."
167 ;; Use 'usermod' from the Shadow package.
168 (let ((args `(,@(if uid `("-u" ,(number->string uid)) '())
169 "-g" ,(if (number? group) (number->string group) group)
170 ,@(if (pair? supplementary-groups)
171 `("-G" ,(string-join supplementary-groups ","))
172 '())
173 ,@(if comment `("-c" ,comment) '())
174 ;; Don't use '--move-home', so ignore HOME.
175 ,@(if shell `("-s" ,shell) '())
176 ,name)))
177 (zero? (apply system* "usermod" args))))
178
9bea87a5
LC
179(define* (delete-user name #:key (log-port (current-error-port)))
180 "Remove user account NAME. Return #t on success. This may fail if NAME is
181logged in."
182 (format log-port "deleting user '~a'...~%" name)
183 (zero? (system* "userdel" name)))
184
185(define* (delete-group name #:key (log-port (current-error-port)))
186 "Remove group NAME. Return #t on success."
187 (format log-port "deleting group '~a'...~%" name)
188 (zero? (system* "groupdel" name)))
189
e2b464b7 190(define* (ensure-user name group
eb56ee02
LC
191 #:key uid comment home create-home?
192 shell password system?
e2b464b7
LC
193 (supplementary-groups '())
194 (log-port (current-error-port))
195 #:rest rest)
196 "Make sure user NAME exists and has the relevant settings."
197 (if (false-if-exception (getpwnam name))
198 (apply modify-user name group rest)
199 (apply add-user name group rest)))
200
ab6a279a
LC
201(define (activate-users+groups users groups)
202 "Make sure the accounts listed in USERS and the user groups listed in GROUPS
203are all available.
204
205Each item in USERS is a list of all the characteristics of a user account;
206each item in GROUPS is a tuple with the group name, group password or #f, and
207numeric gid or #f."
208 (define (touch file)
f01efec0 209 (close-port (open-file file "a0b")))
ab6a279a
LC
210
211 (define activate-user
212 (match-lambda
eb56ee02
LC
213 ((name uid group supplementary-groups comment home create-home?
214 shell password system?)
e2b464b7
LC
215 (let ((profile-dir (string-append "/var/guix/profiles/per-user/"
216 name)))
217 (ensure-user name group
218 #:uid uid
219 #:system? system?
220 #:supplementary-groups supplementary-groups
221 #:comment comment
222 #:home home
eb56ee02 223 #:create-home? create-home?
e2b464b7
LC
224 #:shell shell
225 #:password password)
226
227 (unless system?
228 ;; Create the profile directory for the new account.
229 (let ((pw (getpwnam name)))
230 (mkdir-p profile-dir)
231 (chown profile-dir (passwd:uid pw) (passwd:gid pw))))))))
ab6a279a
LC
232
233 ;; 'groupadd' aborts if the file doesn't already exist.
234 (touch "/etc/group")
235
6526d43e 236 ;; Allow home directories to be created under /var/lib.
a7199b7d 237 (mkdir-p "/var/lib")
6526d43e 238
ab6a279a
LC
239 ;; Create the root account so we can use 'useradd' and 'groupadd'.
240 (activate-user (find (match-lambda
241 ((name (? zero?) _ ...) #t)
242 (_ #f))
243 users))
244
245 ;; Then create the groups.
246 (for-each (match-lambda
c8fa3426 247 ((name password gid system?)
e2fcc23a 248 (unless (false-if-exception (getgrnam name))
c8fa3426
LC
249 (add-group name
250 #:gid gid #:password password
251 #:system? system?))))
ab6a279a
LC
252 groups)
253
9bea87a5
LC
254 ;; Create the other user accounts.
255 (for-each activate-user users)
256
257 ;; Finally, delete extra user accounts and groups.
258 (for-each delete-user
259 (lset-difference string=?
260 (map passwd:name (current-users))
261 (match users
262 (((names . _) ...)
263 names))))
264 (for-each delete-group
265 (lset-difference string=?
266 (map group:name (current-groups))
267 (match groups
268 (((names . _) ...)
269 names)))))
ab6a279a 270
4dfe6c58
LC
271(define (activate-etc etc)
272 "Install ETC, a directory in the store, as the source of static files for
273/etc."
274
275 ;; /etc is a mixture of static and dynamic settings. Here is where we
276 ;; initialize it from the static part.
277
ee7bae3b
LC
278 (define (rm-f file)
279 (false-if-exception (delete-file file)))
280
4dfe6c58 281 (format #t "populating /etc from ~a...~%" etc)
ee7bae3b 282
78ab0746
MW
283 ;; Create the /etc/ssl -> /run/current-system/profile/etc/ssl symlink. This
284 ;; symlink, to a target outside of the store, probably doesn't belong in the
285 ;; static 'etc' store directory. However, if it were to be put there,
286 ;; beware that if /run/current-system/profile/etc/ssl doesn't exist at the
287 ;; time of activation (e.g. when installing a fresh system), the call to
288 ;; 'file-is-directory?' below will fail because it uses 'stat', not 'lstat'.
289 (rm-f "/etc/ssl")
290 (symlink "/run/current-system/profile/etc/ssl" "/etc/ssl")
291
ee7bae3b
LC
292 (rm-f "/etc/static")
293 (symlink etc "/etc/static")
294 (for-each (lambda (file)
295 (let ((target (string-append "/etc/" file))
296 (source (string-append "/etc/static/" file)))
297 (rm-f target)
298
299 ;; Things such as /etc/sudoers must be regular files, not
300 ;; symlinks; furthermore, they could be modified behind our
301 ;; back---e.g., with 'visudo'. Thus, make a copy instead of
302 ;; symlinking them.
303 (if (file-is-directory? source)
304 (symlink source target)
305 (copy-file source target))
306
307 ;; XXX: Dirty hack to meet sudo's expectations.
308 (when (string=? (basename target) "sudoers")
309 (chmod target #o440))))
45c5b47b 310 (scandir etc (negate dot-or-dot-dot?)
ee7bae3b
LC
311
312 ;; The default is 'string-locale<?', but we don't have
313 ;; it when run from the initrd's statically-linked
314 ;; Guile.
6496de9b 315 string<?)))
4dfe6c58 316
09e028f4
LC
317(define %setuid-directory
318 ;; Place where setuid programs are stored.
319 "/run/setuid-programs")
320
095f4deb
LC
321(define (link-or-copy source target)
322 "Attempt to make TARGET a hard link to SOURCE; if it fails, fall back to
323copy SOURCE to TARGET."
324 (catch 'system-error
325 (lambda ()
326 (link source target))
327 (lambda args
328 ;; Perhaps SOURCE and TARGET live in a different file system, so copy
329 ;; SOURCE.
330 (copy-file source target))))
331
09e028f4
LC
332(define (activate-setuid-programs programs)
333 "Turn PROGRAMS, a list of file names, into setuid programs stored under
334%SETUID-DIRECTORY."
335 (define (make-setuid-program prog)
336 (let ((target (string-append %setuid-directory
337 "/" (basename prog))))
095f4deb 338 (link-or-copy prog target)
09e028f4
LC
339 (chown target 0 0)
340 (chmod target #o6555)))
341
342 (format #t "setting up setuid programs in '~a'...~%"
343 %setuid-directory)
344 (if (file-exists? %setuid-directory)
ad896f23
LC
345 (for-each (compose delete-file
346 (cut string-append %setuid-directory "/" <>))
09e028f4
LC
347 (scandir %setuid-directory
348 (lambda (file)
349 (not (member file '("." ".."))))
350 string<?))
351 (mkdir-p %setuid-directory))
352
353 (for-each make-setuid-program programs))
354
ee248b6a
LC
355(define (activate-/bin/sh shell)
356 "Change /bin/sh to point to SHELL."
357 (symlink shell "/bin/sh.new")
358 (rename-file "/bin/sh.new" "/bin/sh"))
359
d460204f
LC
360(define (activate-modprobe modprobe)
361 "Tell the kernel to use MODPROBE to load modules."
362 (call-with-output-file "/proc/sys/kernel/modprobe"
363 (lambda (port)
364 (display modprobe port))))
365
f34c56be
LC
366(define (activate-firmware directory)
367 "Tell the kernel to look for device firmware under DIRECTORY. This
368mechanism bypasses udev: it allows Linux to handle firmware loading directly
369by itself, without having to resort to a \"user helper\"."
370 (call-with-output-file "/sys/module/firmware_class/parameters/path"
371 (lambda (port)
372 (display directory port))))
b158f1d7
LC
373
374(define (activate-ptrace-attach)
375 "Allow users to PTRACE_ATTACH their own processes.
376
377This works around a regression introduced in the default \"security\" policy
378found in Linux 3.4 onward that prevents users from attaching to their own
379processes--see Yama.txt in the Linux source tree for the rationale. This
380sounds like an unacceptable restriction for little or no security
381improvement."
15f0de05
MW
382 (let ((file "/proc/sys/kernel/yama/ptrace_scope"))
383 (when (file-exists? file)
384 (call-with-output-file file
385 (lambda (port)
386 (display 0 port))))))
f34c56be
LC
387
388\f
b4140694
LC
389(define %current-system
390 ;; The system that is current (a symlink.) This is not necessarily the same
484a2b3a
LC
391 ;; as the system we booted (aka. /run/booted-system) because we can re-build
392 ;; a new system configuration and activate it, without rebooting.
b4140694
LC
393 "/run/current-system")
394
395(define (boot-time-system)
396 "Return the '--system' argument passed on the kernel command line."
397 (find-long-option "--system" (linux-command-line)))
398
6d49355d
LC
399(define* (activate-current-system
400 #:optional (system (or (getenv "GUIX_NEW_SYSTEM")
401 (boot-time-system))))
484a2b3a 402 "Atomically make SYSTEM the current system."
6d49355d
LC
403 ;; The 'GUIX_NEW_SYSTEM' environment variable is used as a way for 'guix
404 ;; system reconfigure' to pass the file name of the new system.
405
b4140694 406 (format #t "making '~a' the current system...~%" system)
b4140694
LC
407
408 ;; Atomically make SYSTEM current.
409 (let ((new (string-append %current-system ".new")))
410 (symlink system new)
411 (rename-file new %current-system)))
412
4dfe6c58 413;;; activation.scm ends here