projects/guix·blob ruby-team · refs

760 lines · 27.2 KiB

1;;; GNU Guix --- Functional package management for GNU
2;;; Copyright © 2013-2020, 2022-2023, 2025-2026 Ludovic Courtès <[email protected]>
3;;; Copyright © 2016 Alex Griffin <[email protected]>
4;;; Copyright © 2020 Jan (janneke) Nieuwenhuizen <[email protected]>
5;;; Copyright © 2020, 2023 Efraim Flashner <[email protected]>
6;;; Copyright © 2020 Maxim Cournoyer <[email protected]>
7;;; Copyright © 2024 Giacomo Leidi <[email protected]>
8;;;
9;;; This file is part of GNU Guix.
10;;;
11;;; GNU Guix is free software; you can redistribute it and/or modify it
12;;; under the terms of the GNU General Public License as published by
13;;; the Free Software Foundation; either version 3 of the License, or (at
14;;; your option) any later version.
15;;;
16;;; GNU Guix is distributed in the hope that it will be useful, but
17;;; WITHOUT ANY WARRANTY; without even the implied warranty of
18;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;;; GNU General Public License for more details.
20;;;
21;;; You should have received a copy of the GNU General Public License
22;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
23
24(define-module (gnu system shadow)
25 #:use-module ((guix diagnostics) #:select (formatted-message))
26 #:use-module (guix records)
27 #:use-module (guix gexp)
28 #:use-module (guix store)
29 #:use-module (guix modules)
30 #:use-module (guix sets)
31 #:use-module (guix ui)
32 #:use-module ((gnu build accounts)
33 #:select (%subordinate-id-count
34 %subordinate-id-max
35 %subordinate-id-min))
36 #:use-module (gnu system accounts)
37 #:use-module (gnu services)
38 #:use-module (gnu services shepherd)
39 #:use-module ((gnu system file-systems)
40 #:select (%tty-gid))
41 #:use-module ((gnu packages admin)
42 #:select (shadow))
43 #:use-module (gnu packages bash)
44 #:use-module (ice-9 match)
45 #:use-module (srfi srfi-1)
46 #:use-module (srfi srfi-26)
47 #:use-module (srfi srfi-34)
48 #:use-module (srfi srfi-35)
49
50 ;; Re-export these bindings for backward compatibility.
51 #:re-export (user-account
52 user-account?
53 user-account-name
54 user-account-password
55 user-account-uid
56 user-account-group
57 user-account-supplementary-groups
58 user-account-comment
59 user-account-home-directory
60 user-account-create-home-directory?
61 user-account-shell
62 user-account-system?
63
64 user-group
65 user-group?
66 user-group-name
67 user-group-password
68 user-group-id
69 user-group-system?)
70
71 #:export (%default-bashrc
72 %default-bash-profile
73 %default-zprofile
74 %default-xdefaults
75 %default-gdbinit
76 %default-nanorc
77 %default-dotguile
78 %default-skeleton-home-config
79 default-skeletons
80 skeleton-directory
81 %base-groups
82 %base-user-accounts
83
84 account-service-type
85 account-service
86
87 subids-configuration
88 subids-configuration?
89 subids-configuration-add-root?
90 subids-configuration-subgids
91 subids-configuration-subuids
92
93 subids-extension
94 subids-extension?
95 subids-extension-subgids
96 subids-extension-subuids
97
98 subids-service-type))
99
100;;; Commentary:
101;;;
102;;; Utilities for configuring the Shadow tool suite ('login', 'passwd', etc.)
103;;;
104;;; Code:
105
106;; Change the default shell used by new <user-account> records.
107(default-shell (file-append bash "/bin/bash"))
108
109(define %base-groups
110 ;; Default set of groups.
111 (let-syntax ((system-group (syntax-rules ()
112 ((_ args ...)
113 (user-group (system? #t) args ...)))))
114 (list (system-group (name "root") (id 0))
115 (system-group (name "wheel")) ; root-like users
116 (system-group (name "users")) ; normal users
117 (system-group (name "nogroup")) ; for daemons etc.
118
119 ;; The following groups are conventionally used by things like udev to
120 ;; control access to hardware devices.
121 (system-group (name "tty") (id %tty-gid))
122 (system-group (name "dialout"))
123 (system-group (name "kmem"))
124 (system-group (name "input")) ; input devices, from udev
125 (system-group (name "video"))
126 (system-group (name "audio"))
127 (system-group (name "netdev")) ; used in avahi-dbus.conf
128 (system-group (name "lp"))
129 (system-group (name "disk"))
130 (system-group (name "floppy"))
131 (system-group (name "cdrom"))
132 (system-group (name "tape"))
133 (system-group (name "kvm")) ; for /dev/kvm
134 (system-group (name "sgx")))))
135
136(define %base-user-accounts
137 ;; List of standard user accounts. Note that "root" is a special case, so
138 ;; it's not listed here.
139 (list (user-account
140 (name "nobody")
141 (uid 65534)
142 (group "nogroup")
143 (shell (file-append shadow "/sbin/nologin"))
144 (home-directory "/nonexistent")
145 (create-home-directory? #f)
146 (system? #t))))
147
148(define %default-bashrc
149 (plain-file "bashrc" "\
150# Bash-specific initialization, including for non-login and remote
151# shells (info \"(bash) Bash Startup Files\").
152
153# Provide a default prompt.
154PS1='\\u@\\h \\w${GUIX_ENVIRONMENT:+ [env]}\\$ '
155
156# Export 'SHELL' to child processes. Programs such as 'screen'
157# honor it and otherwise use /bin/sh.
158export SHELL
159
160if [[ $- != *i* ]]
161then
162 # We are being invoked from a non-interactive shell. If this
163 # is an SSH session (as in \"ssh host command\"), source
164 # /etc/profile so we get PATH and other essential variables.
165 [[ -n \"$SSH_CLIENT\" ]] && source /etc/profile
166
167 # Don't do anything else, returning a successful return code.
168 return 0
169fi
170
171for i in /etc/bashrc.d/*.sh; do
172 [[ -r $i ]] && source \"$i\"
173done
174unset i
175
176# Increase the history size (default is 500 entries).
177HISTSIZE=10000
178"))
179
180(define %default-bash-profile
181 (plain-file "bash_profile" "\
182# Set up Guix Home profile
183if [ -f ~/.profile ]; then . ~/.profile; fi
184
185# Honor per-interactive-shell startup file
186if [ -f ~/.bashrc ]; then . ~/.bashrc; fi
187
188# Merge search-paths from multiple profiles, the order matters.
189eval \"$(guix package --search-paths \\
190-p $HOME/.config/guix/current \\
191-p $HOME/.guix-home/profile \\
192-p $HOME/.guix-profile \\
193-p /run/current-system/profile)\"
194
195# Prepend setuid programs.
196export PATH=/run/setuid-programs:$PATH
197"))
198
199(define %default-zprofile
200 (plain-file "zprofile" "\
201# Set up the system, user profile, and related variables.
202emulate sh -c '. /etc/profile'
203# Set up the home environment profile.
204emulate sh -c '. ~/.profile'
205"))
206
207(define %default-xdefaults
208 (plain-file "Xdefaults" "\
209XTerm*utf8: always
210XTerm*metaSendsEscape: true\n"))
211
212(define %default-gdbinit
213 (plain-file "gdbinit"
214 "\
215# Record command history.
216set history filename ~/.cache/gdb/history
217set history save on
218set history size 50000
219
220# Tell GDB where to look for separate debugging files.
221guile
222(use-modules (gdb))
223(execute (string-append \"set debug-file-directory \"
224 (string-join
225 (append
226 (if (getenv \"GDB_DEBUG_FILE_DIRECTORY\")
227 (list (getenv \"GDB_DEBUG_FILE_DIRECTORY\"))
228 '())
229 ;; Note: GDB performs tilde expansion.
230 (list \"~/.guix-home/profile/lib/debug\"
231 \"~/.guix-profile/lib/debug\"
232 \"/run/current-system/profile/lib/debug\"))
233 \":\")))
234end
235
236# Authorize extensions found in the store, such as the
237# pretty-printers of libstdc++.
238set auto-load safe-path /gnu/store/*/lib\n"))
239
240(define %default-nanorc
241 (plain-file "nanorc"
242 "# Include all the syntax highlighting modules.
243include /run/current-system/profile/share/nano/*.nanorc\n"))
244
245(define %default-dotguile
246 (plain-file "dot-guile"
247 "(cond ((false-if-exception (resolve-interface '(ice-9 readline)))
248 =>
249 (lambda (module)
250 ;; Enable completion and input history at the REPL.
251 ((module-ref module 'activate-readline))))
252 (else
253 (display \"Consider installing the 'guile-readline' package for
254convenient interactive line editing and input history.\\n\\n\")))
255
256 (unless (getenv \"INSIDE_EMACS\")
257 (cond ((false-if-exception (resolve-interface '(ice-9 colorized)))
258 =>
259 (lambda (module)
260 ;; Enable completion and input history at the REPL.
261 ((module-ref module 'activate-colorized))))
262 (else
263 (display \"Consider installing the 'guile-colorized' package
264for a colorful Guile experience.\\n\\n\"))))\n"))
265
266(define %default-skeleton-home-config
267 (plain-file "default-home-config" "\
268;; This is a sample Guix Home configuration which can help setup your
269;; home directory in the same declarative manner as Guix System.
270;; For more information, see the Home Configuration section of the manual.
271(define-module (guix-home-config)
272 #:use-module (gnu home)
273 #:use-module (gnu home services)
274 #:use-module (gnu home services shells)
275 #:use-module (gnu services)
276 #:use-module (gnu system shadow))
277
278(define home-config
279 (home-environment
280 (services
281 (append
282 (list
283 ;; Uncomment the shell you wish to use for your user:
284 ;(service home-bash-service-type)
285 ;(service home-fish-service-type)
286 ;(service home-zsh-service-type)
287
288 (service home-files-service-type
289 `((\".guile\" ,%default-dotguile)
290 (\".Xdefaults\" ,%default-xdefaults)))
291
292 (service home-xdg-configuration-files-service-type
293 `((\"gdb/gdbinit\" ,%default-gdbinit)
294 (\"nano/nanorc\" ,%default-nanorc))))
295
296 %base-home-services))))
297
298home-config"))
299
300(define gdb-history-placeholder
301 ;; Since GDB will not create ~/.cache/gdb by itself when attempting to save
302 ;; its history file, add a placeholder that ensures this directory exists.
303 (plain-file "gdb-history"
304 "# GDB command history.\n"))
305
306(define (default-skeletons)
307 "Return the default skeleton files for /etc/skel. These files are copied by
308'useradd' in the home directory of newly created user accounts."
309
310 (let ((profile %default-bash-profile)
311 (zprofile %default-zprofile)
312 (xdefaults %default-xdefaults)
313 (gdbinit %default-gdbinit))
314 `((".bash_profile" ,profile)
315 ;; Zsh sources ~/.zprofile before ~/.zshrc, and it sources ~/.zlogin
316 ;; after ~/.zshrc. To avoid interfering with any customizations a user
317 ;; may have made in their ~/.zshrc, put this in .zprofile, not .zlogin.
318 (".zprofile" ,zprofile)
319 (".config/nano/nanorc" ,%default-nanorc)
320 (".Xdefaults" ,xdefaults)
321 (".guile" ,%default-dotguile)
322 (".config/gdb/gdbinit" ,gdbinit)
323 (".cache/gdb/history" ,gdb-history-placeholder)
324 ("guix-home-config.scm" ,%default-skeleton-home-config))))
325
326(define (skeleton-directory skeletons)
327 "Return a directory containing SKELETONS, a list of name/derivation tuples."
328 (computed-file "skel"
329 (with-imported-modules '((guix build utils))
330 #~(begin
331 (use-modules (ice-9 match)
332 (guix build utils))
333
334 (mkdir #$output)
335 (chdir #$output)
336
337 ;; Note: copy the skeletons instead of symlinking
338 ;; them like 'file-union' does, because 'useradd'
339 ;; would just copy the symlinks as is.
340 (for-each (match-lambda
341 ((target source)
342 (mkdir-p (dirname target))
343 (copy-recursively source target)))
344 '#$skeletons)))))
345
346(define (find-duplicates list)
347 "Find duplicate entries in @var{list}.
348Two entries are considered duplicates, if they are @code{equal?} to each other.
349This implementation is made asymptotically faster than @code{delete-duplicates}
350through the internal use of hash tables."
351 (let loop ((list list)
352 ;; We actually modify table in-place, but still allocate it here
353 ;; so that we only need one level of indentation.
354 (table (make-hash-table)))
355 (match list
356 (()
357 (hash-fold (lambda (key value seed)
358 (if (> value 1)
359 (cons key seed)
360 seed))
361 '()
362 table))
363 ((first . rest)
364 (hash-set! table first
365 (1+ (hash-ref table first 0)))
366 (loop rest table)))))
367
368(define (assert-unique-account-names users)
369 (match (find-duplicates (map user-account-name users))
370 (() *unspecified*)
371 (duplicates
372 (warning
373 (G_ "the following accounts appear more than once:~{ ~a~}~%")
374 duplicates))))
375
376(define (assert-unique-group-names groups)
377 (match (find-duplicates (map user-group-name groups))
378 (() *unspecified*)
379 (duplicates
380 (warning
381 (G_ "the following groups appear more than once:~{ ~a~}~%")
382 duplicates))))
383
384(define (assert-valid-users/groups users groups)
385 "Raise an error if USERS refer to groups not listed in GROUPS."
386 (let ((groups (list->set (map user-group-name groups))))
387 (define (validate-supplementary-group user group)
388 (unless (set-contains? groups group)
389 (raise (condition
390 (&message
391 (message
392 (format #f (G_ "supplementary group '~a' \
393of user '~a' is undeclared")
394 group
395 (user-account-name user))))))))
396
397 (for-each (lambda (user)
398 (unless (set-contains? groups (user-account-group user))
399 (raise (condition
400 (&message
401 (message
402 (format #f (G_ "primary group '~a' \
403of user '~a' is undeclared")
404 (user-account-group user)
405 (user-account-name user)))))))
406
407 (for-each (cut validate-supplementary-group user <>)
408 (user-account-supplementary-groups user)))
409 users)))
410
411
412;;;
413;;; Accounts Service.
414;;;
415
416(define (user-group->gexp group)
417 "Turn GROUP, a <user-group> object, into a list-valued gexp suitable for
418'active-groups'."
419 #~(list #$(user-group-name group)
420 #$(user-group-password group)
421 #$(user-group-id group)
422 #$(user-group-system? group)))
423
424(define (user-account->gexp account)
425 "Turn ACCOUNT, a <user-account> object, into a list-valued gexp suitable for
426'activate-users'."
427 #~`(#$(user-account-name account)
428 #$(user-account-uid account)
429 #$(user-account-group account)
430 #$(user-account-supplementary-groups account)
431 #$(user-account-comment account)
432 #$(user-account-home-directory account)
433 #$(user-account-create-home-directory? account)
434 ,#$(user-account-shell account) ; this one is a gexp
435 #$(user-account-password account)
436 #$(user-account-system? account)))
437
438(define (account-activation accounts+groups)
439 "Return a gexp that activates ACCOUNTS+GROUPS, a list of <user-account> and
440<user-group> objects. Raise an error if a user account refers to a undefined
441group."
442 (define accounts
443 (delete-duplicates (filter user-account? accounts+groups) eq?))
444
445 (define user-specs
446 (map user-account->gexp accounts))
447
448 (define groups
449 (delete-duplicates (filter user-group? accounts+groups) eq?))
450
451 (define group-specs
452 (map user-group->gexp groups))
453
454 (assert-unique-account-names accounts)
455 (assert-unique-group-names groups)
456 (assert-valid-users/groups accounts groups)
457
458 ;; Add users and user groups.
459 (with-imported-modules (source-module-closure '((gnu system accounts)))
460 #~(begin
461 (use-modules (gnu system accounts))
462
463 (activate-users+groups (map sexp->user-account (list #$@user-specs))
464 (map sexp->user-group (list #$@group-specs))))))
465
466(define (account-shepherd-service accounts+groups)
467 "Return a Shepherd service that creates the home directories for the user
468accounts among ACCOUNTS+GROUPS."
469 (define accounts
470 (filter user-account? accounts+groups))
471
472 (define regular-account-names
473 (filter-map (lambda (account)
474 (and (not (user-account-system? account))
475 (user-account-name account)))
476 accounts))
477
478 ;; Create home directories only once 'file-systems' is up. This makes sure
479 ;; they are created in the right place if /home lives on a separate
480 ;; partition.
481 ;;
482 ;; XXX: We arrange for this service to stop right after it's done its job so
483 ;; that 'guix system reconfigure' knows that it can reload it fearlessly
484 ;; (and thus create new home directories).
485 (list (shepherd-service
486 (requirement '(file-systems))
487 (provision '(user-homes))
488 (one-shot? #t)
489 (modules '((gnu build activation)
490 (gnu system accounts)))
491 (start (with-imported-modules (source-module-closure
492 '((gnu build activation)
493 (gnu system accounts)))
494 #~(lambda ()
495 (activate-user-home
496 (map sexp->user-account
497 (list #$@(map user-account->gexp accounts))))
498
499 ;; Create the user's profile directory upfront:
500 ;; guix-daemon lacks permissions to create it when it is
501 ;; running as an unprivileged user.
502 (for-each (lambda (account)
503 (let ((profile (in-vicinity
504 "/var/guix/profiles/per-user"
505 account))
506 (owner (getpwnam account)))
507 (mkdir-p/perms profile owner #o755)))
508 '#$regular-account-names)
509 #t))) ;success
510 (documentation "Create user home directories."))))
511
512(define (shells-file shells)
513 "Return a file-like object that builds a shell list for use as /etc/shells
514based on SHELLS. /etc/shells is used by xterm, polkit, and other programs."
515 (computed-file "shells"
516 #~(begin
517 (use-modules (srfi srfi-1))
518
519 (define shells
520 (delete-duplicates (list #$@shells)))
521
522 (call-with-output-file #$output
523 (lambda (port)
524 (display "\
525/bin/sh
526/run/current-system/profile/bin/sh
527/run/current-system/profile/bin/bash\n" port)
528 (for-each (lambda (shell)
529 (display shell port)
530 (newline port))
531 shells))))))
532(define (etc-files arguments)
533 "Filter out among ARGUMENTS things corresponding to skeletons, and return
534the /etc/skel directory for those."
535 (let ((skels (filter pair? arguments))
536 (users (filter user-account? arguments)))
537 `(("skel" ,(skeleton-directory skels))
538 ("shells" ,(shells-file (map user-account-shell users))))))
539
540(define account-service-type
541 (service-type (name 'account)
542
543 ;; Concatenate <user-account>, <user-group>, and skeleton
544 ;; lists.
545 (compose concatenate)
546 (extend append)
547
548 (extensions
549 (list (service-extension activation-service-type
550 account-activation)
551 (service-extension shepherd-root-service-type
552 account-shepherd-service)
553 ;; Have 'user-processes' depend on 'user-homes' so that
554 ;; daemons start after their home directory has been
555 ;; created.
556 (service-extension user-processes-service-type
557 (const '(user-homes)))
558 (service-extension etc-service-type
559 etc-files)))
560 (default-value '())
561 (description
562 "Ensure the specified user accounts and groups exist, as well
563as each account home directory.")))
564
565(define (account-service accounts+groups skeletons)
566 "Return a <service> that takes care of user accounts and user groups, with
567ACCOUNTS+GROUPS as its initial list of accounts and groups."
568 (service account-service-type
569 (append skeletons accounts+groups)))
570
571
572;;;
573;;; Subids Service.
574;;;
575
576(define* (%root-subid #:optional (start %subordinate-id-min) (count %subordinate-id-count))
577 (subid-range
578 (name "root")
579 (start start)
580 (count count)))
581
582(define-record-type* <subids-configuration>
583 subids-configuration make-subids-configuration
584 subids-configuration?
585 this-subids-configuration
586
587 (add-root? subids-configuration-add-root? ; boolean
588 (default #t))
589 (subgids subids-configuration-subgids ; list of <subid-range>
590 (default '()))
591 (subuids subids-configuration-subuids ; list of <subid-range>
592 (default '())))
593
594(define (subid-range->gexp range)
595 "Turn RANGE, a <subid-range> object, into a list-valued gexp suitable for
596'activate-subuids+subgids'."
597 (define count (subid-range-count range))
598 #~`(#$(subid-range-name range)
599 #$(subid-range-start range)
600 #$(if (and (number? count)
601 (> count 0))
602 count
603 %subordinate-id-count)))
604
605(define (assert-valid-subids ranges)
606 (cond ((>= (fold + 0 (map subid-range-count ranges))
607 (- %subordinate-id-max %subordinate-id-min -1))
608 (raise
609 (formatted-message
610 (G_
611 "The configured ranges are more than the ~a max allowed.")
612 (- %subordinate-id-max %subordinate-id-min -1))))
613 ((any (lambda (r)
614 (define start (subid-range-start r))
615 (and start
616 (< start %subordinate-id-min)))
617 ranges)
618 (raise
619 (formatted-message
620 (G_
621 "One subid-range starts before the minimum allowed sub id ~a.")
622 %subordinate-id-min)))
623 ((any (lambda (r)
624 (define end (subid-range-end r))
625 (and end
626 (> end %subordinate-id-max)))
627 ranges)
628 (raise
629 (formatted-message
630 (G_
631 "One subid-range ends after the maximum allowed sub id ~a.")
632 %subordinate-id-max)))
633 ((any (compose null? subid-range-name)
634 ranges)
635 (raise
636 (formatted-message
637 (G_
638 "One subid-range has a null name."))))
639 ((any (compose string-null? subid-range-name)
640 ranges)
641 (raise
642 (formatted-message
643 (G_
644 "One subid-range has a name equal to the empty string."))))
645 (else #t)))
646
647(define (delete-duplicate-ranges ranges)
648 (delete-duplicates ranges
649 (lambda args
650 (apply string=? (map subid-range-name ranges)))))
651
652(define (subids-activation config)
653 "Return a gexp that activates SUBUIDS+SUBGIDS, a list of <subid-range>
654objects."
655 (define (add-root-when-missing ranges)
656 (define sorted-ranges
657 (sort-list ranges subid-range-less))
658 (define root-missing?
659 (not
660 (find (lambda (r)
661 (string=? "root"
662 (subid-range-name r)))
663 sorted-ranges)))
664 (define first-start
665 (and (> (length sorted-ranges) 0)
666 (subid-range-start (first sorted-ranges))))
667 (define first-has-start?
668 (number? first-start))
669 (define root-start
670 (if first-has-start?
671 (and
672 (> first-start %subordinate-id-min)
673 %subordinate-id-min)
674 %subordinate-id-min))
675 (define root-count
676 (if first-has-start?
677 (- first-start %subordinate-id-min)
678 %subordinate-id-count))
679 (if (and root-missing?
680 (subids-configuration-add-root? config))
681 (append (list (%root-subid root-start root-count))
682 sorted-ranges)
683 sorted-ranges))
684
685 (define subuids
686 (delete-duplicate-ranges (subids-configuration-subuids config)))
687
688 (define subuids-specs
689 (map subid-range->gexp (add-root-when-missing subuids)))
690
691 (define subgids
692 (delete-duplicate-ranges (subids-configuration-subgids config)))
693
694 (define subgids-specs
695 (map subid-range->gexp (add-root-when-missing subgids)))
696
697 (assert-valid-subids subgids)
698 (assert-valid-subids subuids)
699
700 ;; Add subuids and subgids.
701 (with-imported-modules (source-module-closure '((gnu system accounts)))
702 #~(begin
703 (use-modules (gnu system accounts))
704
705 (activate-subuids+subgids (map sexp->subid-range (list #$@subuids-specs))
706 (map sexp->subid-range (list #$@subgids-specs))))))
707
708(define-record-type* <subids-extension>
709 subids-extension make-subids-extension
710 subids-extension?
711 this-subids-extension
712
713 (subgids subids-extension-subgids ; list of <subid-range>
714 (default '()))
715 (subuids subids-extension-subuids ; list of <subid-range>
716 (default '())))
717
718(define append-subid-ranges
719 (lambda args
720 (delete-duplicate-ranges
721 (apply append args))))
722
723(define (subids-extension-merge a b)
724 (subids-extension
725 (subgids (append-subid-ranges
726 (subids-extension-subgids a)
727 (subids-extension-subgids b)))
728 (subuids (append-subid-ranges
729 (subids-extension-subuids a)
730 (subids-extension-subuids b)))))
731
732(define subids-service-type
733 (service-type (name 'subids)
734 ;; Concatenate <subid-range> lists.
735 (compose (lambda (args)
736 (fold subids-extension-merge
737 (subids-extension)
738 args)))
739 (extend
740 (lambda (config extension)
741 (subids-configuration
742 (inherit config)
743 (subgids
744 (append-subid-ranges
745 (subids-configuration-subgids config)
746 (subids-extension-subgids extension)))
747 (subuids
748 (append-subid-ranges
749 (subids-configuration-subuids config)
750 (subids-extension-subuids extension))))))
751 (extensions
752 (list (service-extension activation-service-type
753 subids-activation)))
754 (default-value
755 (subids-configuration))
756 (description
757 "Ensure the specified sub UIDs and sub GIDs exist in
758/etc/subuid and /etc/subgid.")))
759
760;;; shadow.scm ends here