python312Packages.dissect-extfs: 3.11 -> 3.12
[NixPkgs.git] / nixos / modules / config / users-groups.nix
blob845f9fdaf68ef4178fd666ad9e43e924d47dda97
1 { config, lib, utils, pkgs, ... }:
3 let
4   inherit (lib)
5     any
6     attrNames
7     attrValues
8     concatMap
9     concatMapStringsSep
10     concatStrings
11     elem
12     filter
13     filterAttrs
14     flatten
15     flip
16     foldr
17     generators
18     getAttr
19     hasAttr
20     id
21     length
22     listToAttrs
23     literalExpression
24     mapAttrs'
25     mapAttrsToList
26     match
27     mkAliasOptionModuleMD
28     mkDefault
29     mkIf
30     mkMerge
31     mkOption
32     mkRenamedOptionModule
33     optional
34     optionals
35     sort
36     stringAfter
37     stringLength
38     trace
39     types
40     xor
41     ;
43   ids = config.ids;
44   cfg = config.users;
46   # Check whether a password hash will allow login.
47   allowsLogin = hash:
48     hash == "" # login without password
49     || !(lib.elem hash
50       [ null   # password login disabled
51         "!"    # password login disabled
52         "!!"   # a variant of "!"
53         "*"    # password unset
54       ]);
56   passwordDescription = ''
57     The options {option}`hashedPassword`,
58     {option}`password` and {option}`hashedPasswordFile`
59     controls what password is set for the user.
60     {option}`hashedPassword` overrides both
61     {option}`password` and {option}`hashedPasswordFile`.
62     {option}`password` overrides {option}`hashedPasswordFile`.
63     If none of these three options are set, no password is assigned to
64     the user, and the user will not be able to do password logins.
65     If the option {option}`users.mutableUsers` is true, the
66     password defined in one of the three options will only be set when
67     the user is created for the first time. After that, you are free to
68     change the password with the ordinary user management commands. If
69     {option}`users.mutableUsers` is false, you cannot change
70     user passwords, they will always be set according to the password
71     options.
72   '';
74   hashedPasswordDescription = ''
75     To generate a hashed password run `mkpasswd`.
77     If set to an empty string (`""`), this user will
78     be able to log in without being asked for a password (but not via remote
79     services such as SSH, or indirectly via {command}`su` or
80     {command}`sudo`). This should only be used for e.g. bootable
81     live systems. Note: this is different from setting an empty password,
82     which can be achieved using {option}`users.users.<name?>.password`.
84     If set to `null` (default) this user will not
85     be able to log in using a password (i.e. via {command}`login`
86     command).
87   '';
89   userOpts = { name, config, ... }: {
91     options = {
93       name = mkOption {
94         type = types.passwdEntry types.str;
95         apply = x: assert (stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
96         description = ''
97           The name of the user account. If undefined, the name of the
98           attribute set will be used.
99         '';
100       };
102       description = mkOption {
103         type = types.passwdEntry types.str;
104         default = "";
105         example = "Alice Q. User";
106         description = ''
107           A short description of the user account, typically the
108           user's full name.  This is actually the “GECOS” or “comment”
109           field in {file}`/etc/passwd`.
110         '';
111       };
113       uid = mkOption {
114         type = with types; nullOr int;
115         default = null;
116         description = ''
117           The account UID. If the UID is null, a free UID is picked on
118           activation.
119         '';
120       };
122       isSystemUser = mkOption {
123         type = types.bool;
124         default = false;
125         description = ''
126           Indicates if the user is a system user or not. This option
127           only has an effect if {option}`uid` is
128           {option}`null`, in which case it determines whether
129           the user's UID is allocated in the range for system users
130           (below 1000) or in the range for normal users (starting at
131           1000).
132           Exactly one of `isNormalUser` and
133           `isSystemUser` must be true.
134         '';
135       };
137       isNormalUser = mkOption {
138         type = types.bool;
139         default = false;
140         description = ''
141           Indicates whether this is an account for a “real” user.
142           This automatically sets {option}`group` to `users`,
143           {option}`createHome` to `true`,
144           {option}`home` to {file}`/home/«username»`,
145           {option}`useDefaultShell` to `true`,
146           and {option}`isSystemUser` to `false`.
147           Exactly one of `isNormalUser` and `isSystemUser` must be true.
148         '';
149       };
151       group = mkOption {
152         type = types.str;
153         apply = x: assert (stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
154         default = "";
155         description = "The user's primary group.";
156       };
158       extraGroups = mkOption {
159         type = types.listOf types.str;
160         default = [];
161         description = "The user's auxiliary groups.";
162       };
164       home = mkOption {
165         type = types.passwdEntry types.path;
166         default = "/var/empty";
167         description = "The user's home directory.";
168       };
170       homeMode = mkOption {
171         type = types.strMatching "[0-7]{1,5}";
172         default = "700";
173         description = "The user's home directory mode in numeric format. See chmod(1). The mode is only applied if {option}`users.users.<name>.createHome` is true.";
174       };
176       cryptHomeLuks = mkOption {
177         type = with types; nullOr str;
178         default = null;
179         description = ''
180           Path to encrypted luks device that contains
181           the user's home directory.
182         '';
183       };
185       pamMount = mkOption {
186         type = with types; attrsOf str;
187         default = {};
188         description = ''
189           Attributes for user's entry in
190           {file}`pam_mount.conf.xml`.
191           Useful attributes might include `path`,
192           `options`, `fstype`, and `server`.
193           See <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>
194           for more information.
195         '';
196       };
198       shell = mkOption {
199         type = types.nullOr (types.either types.shellPackage (types.passwdEntry types.path));
200         default = pkgs.shadow;
201         defaultText = literalExpression "pkgs.shadow";
202         example = literalExpression "pkgs.bashInteractive";
203         description = ''
204           The path to the user's shell. Can use shell derivations,
205           like `pkgs.bashInteractive`. Don’t
206           forget to enable your shell in
207           `programs` if necessary,
208           like `programs.zsh.enable = true;`.
209         '';
210       };
212       ignoreShellProgramCheck = mkOption {
213         type = types.bool;
214         default = false;
215         description = ''
216           By default, nixos will check that programs.SHELL.enable is set to
217           true if the user has a custom shell specified. If that behavior isn't
218           required and there are custom overrides in place to make sure that the
219           shell is functional, set this to true.
220         '';
221       };
223       subUidRanges = mkOption {
224         type = with types; listOf (submodule subordinateUidRange);
225         default = [];
226         example = [
227           { startUid = 1000; count = 1; }
228           { startUid = 100001; count = 65534; }
229         ];
230         description = ''
231           Subordinate user ids that user is allowed to use.
232           They are set into {file}`/etc/subuid` and are used
233           by `newuidmap` for user namespaces.
234         '';
235       };
237       subGidRanges = mkOption {
238         type = with types; listOf (submodule subordinateGidRange);
239         default = [];
240         example = [
241           { startGid = 100; count = 1; }
242           { startGid = 1001; count = 999; }
243         ];
244         description = ''
245           Subordinate group ids that user is allowed to use.
246           They are set into {file}`/etc/subgid` and are used
247           by `newgidmap` for user namespaces.
248         '';
249       };
251       autoSubUidGidRange = mkOption {
252         type = types.bool;
253         default = false;
254         example = true;
255         description = ''
256           Automatically allocate subordinate user and group ids for this user.
257           Allocated range is currently always of size 65536.
258         '';
259       };
261       createHome = mkOption {
262         type = types.bool;
263         default = false;
264         description = ''
265           Whether to create the home directory and ensure ownership as well as
266           permissions to match the user.
267         '';
268       };
270       useDefaultShell = mkOption {
271         type = types.bool;
272         default = false;
273         description = ''
274           If true, the user's shell will be set to
275           {option}`users.defaultUserShell`.
276         '';
277       };
279       hashedPassword = mkOption {
280         type = with types; nullOr (passwdEntry str);
281         default = null;
282         description = ''
283           Specifies the hashed password for the user.
284           ${passwordDescription}
285           ${hashedPasswordDescription}
286         '';
287       };
289       password = mkOption {
290         type = with types; nullOr str;
291         default = null;
292         description = ''
293           Specifies the (clear text) password for the user.
294           Warning: do not set confidential information here
295           because it is world-readable in the Nix store. This option
296           should only be used for public accounts.
297           ${passwordDescription}
298         '';
299       };
301       hashedPasswordFile = mkOption {
302         type = with types; nullOr str;
303         default = cfg.users.${name}.passwordFile;
304         defaultText = literalExpression "null";
305         description = ''
306           The full path to a file that contains the hash of the user's
307           password. The password file is read on each system activation. The
308           file should contain exactly one line, which should be the password in
309           an encrypted form that is suitable for the `chpasswd -e` command.
310           ${passwordDescription}
311         '';
312       };
314       passwordFile = mkOption {
315         type = with types; nullOr str;
316         default = null;
317         visible = false;
318         description = "Deprecated alias of hashedPasswordFile";
319       };
321       initialHashedPassword = mkOption {
322         type = with types; nullOr (passwdEntry str);
323         default = null;
324         description = ''
325           Specifies the initial hashed password for the user, i.e. the
326           hashed password assigned if the user does not already
327           exist. If {option}`users.mutableUsers` is true, the
328           password can be changed subsequently using the
329           {command}`passwd` command. Otherwise, it's
330           equivalent to setting the {option}`hashedPassword` option.
332           Note that the {option}`hashedPassword` option will override
333           this option if both are set.
335           ${hashedPasswordDescription}
336         '';
337       };
339       initialPassword = mkOption {
340         type = with types; nullOr str;
341         default = null;
342         description = ''
343           Specifies the initial password for the user, i.e. the
344           password assigned if the user does not already exist. If
345           {option}`users.mutableUsers` is true, the password
346           can be changed subsequently using the
347           {command}`passwd` command. Otherwise, it's
348           equivalent to setting the {option}`password`
349           option. The same caveat applies: the password specified here
350           is world-readable in the Nix store, so it should only be
351           used for guest accounts or passwords that will be changed
352           promptly.
354           Note that the {option}`password` option will override this
355           option if both are set.
356         '';
357       };
359       packages = mkOption {
360         type = types.listOf types.package;
361         default = [];
362         example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
363         description = ''
364           The set of packages that should be made available to the user.
365           This is in contrast to {option}`environment.systemPackages`,
366           which adds packages to all users.
367         '';
368       };
370       expires = mkOption {
371         type = types.nullOr (types.strMatching "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}");
372         default = null;
373         description = ''
374           Set the date on which the user's account will no longer be
375           accessible. The date is expressed in the format YYYY-MM-DD, or null
376           to disable the expiry.
377           A user whose account is locked must contact the system
378           administrator before being able to use the system again.
379         '';
380       };
382       linger = mkOption {
383         type = types.bool;
384         default = false;
385         description = ''
386           Whether to enable lingering for this user. If true, systemd user
387           units will start at boot, rather than starting at login and stopping
388           at logout. This is the declarative equivalent of running
389           `loginctl enable-linger` for this user.
391           If false, user units will not be started until the user logs in, and
392           may be stopped on logout depending on the settings in `logind.conf`.
393         '';
394       };
395     };
397     config = mkMerge
398       [ { name = mkDefault name;
399           shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell);
400         }
401         (mkIf config.isNormalUser {
402           group = mkDefault "users";
403           createHome = mkDefault true;
404           home = mkDefault "/home/${config.name}";
405           homeMode = mkDefault "700";
406           useDefaultShell = mkDefault true;
407           isSystemUser = mkDefault false;
408         })
409         # If !mutableUsers, setting ‘initialPassword’ is equivalent to
410         # setting ‘password’ (and similarly for hashed passwords).
411         (mkIf (!cfg.mutableUsers && config.initialPassword != null) {
412           password = mkDefault config.initialPassword;
413         })
414         (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) {
415           hashedPassword = mkDefault config.initialHashedPassword;
416         })
417         (mkIf (config.isNormalUser && config.subUidRanges == [] && config.subGidRanges == []) {
418           autoSubUidGidRange = mkDefault true;
419         })
420       ];
422   };
424   groupOpts = { name, config, ... }: {
426     options = {
428       name = mkOption {
429         type = types.passwdEntry types.str;
430         description = ''
431           The name of the group. If undefined, the name of the attribute set
432           will be used.
433         '';
434       };
436       gid = mkOption {
437         type = with types; nullOr int;
438         default = null;
439         description = ''
440           The group GID. If the GID is null, a free GID is picked on
441           activation.
442         '';
443       };
445       members = mkOption {
446         type = with types; listOf (passwdEntry str);
447         default = [];
448         description = ''
449           The user names of the group members, added to the
450           `/etc/group` file.
451         '';
452       };
454     };
456     config = {
457       name = mkDefault name;
459       members = mapAttrsToList (n: u: u.name) (
460         filterAttrs (n: u: elem config.name u.extraGroups) cfg.users
461       );
462     };
464   };
466   subordinateUidRange = {
467     options = {
468       startUid = mkOption {
469         type = types.int;
470         description = ''
471           Start of the range of subordinate user ids that user is
472           allowed to use.
473         '';
474       };
475       count = mkOption {
476         type = types.int;
477         default = 1;
478         description = "Count of subordinate user ids";
479       };
480     };
481   };
483   subordinateGidRange = {
484     options = {
485       startGid = mkOption {
486         type = types.int;
487         description = ''
488           Start of the range of subordinate group ids that user is
489           allowed to use.
490         '';
491       };
492       count = mkOption {
493         type = types.int;
494         default = 1;
495         description = "Count of subordinate group ids";
496       };
497     };
498   };
500   idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }:
501     let
502       id = toString (getAttr idAttr (getAttr name set));
503       exists = hasAttr id acc;
504       newAcc = acc // (listToAttrs [ { name = id; value = true; } ]);
505     in if dup then args else if exists
506       then trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; }
507       else { dup = false; acc = newAcc; }
508     ) { dup = false; acc = {}; } (attrNames set)).dup;
510   uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid";
511   gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
512   sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid";
513   sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid";
514   groupNames = lib.mapAttrsToList (n: g: g.name) cfg.groups;
515   usersWithoutExistingGroup = lib.filterAttrs (n: u: u.group != "" && !lib.elem u.group groupNames) cfg.users;
517   spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
518     inherit (cfg) mutableUsers;
519     users = mapAttrsToList (_: u:
520       { inherit (u)
521           name uid group description home homeMode createHome isSystemUser
522           password hashedPasswordFile hashedPassword
523           autoSubUidGidRange subUidRanges subGidRanges
524           initialPassword initialHashedPassword expires;
525         shell = utils.toShellPath u.shell;
526       }) cfg.users;
527     groups = attrValues cfg.groups;
528   });
530   systemShells =
531     let
532       shells = mapAttrsToList (_: u: u.shell) cfg.users;
533     in
534       filter types.shellPackage.check shells;
536   lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
537 in {
538   imports = [
539     (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ])
540     (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ])
541     (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"])
542   ];
544   ###### interface
545   options = {
547     users.mutableUsers = mkOption {
548       type = types.bool;
549       default = true;
550       description = ''
551         If set to `true`, you are free to add new users and groups to the system
552         with the ordinary `useradd` and
553         `groupadd` commands. On system activation, the
554         existing contents of the `/etc/passwd` and
555         `/etc/group` files will be merged with the
556         contents generated from the `users.users` and
557         `users.groups` options.
558         The initial password for a user will be set
559         according to `users.users`, but existing passwords
560         will not be changed.
562         ::: {.warning}
563         If set to `false`, the contents of the user and
564         group files will simply be replaced on system activation. This also
565         holds for the user passwords; all changed
566         passwords will be reset according to the
567         `users.users` configuration on activation.
568         :::
569       '';
570     };
572     users.enforceIdUniqueness = mkOption {
573       type = types.bool;
574       default = true;
575       description = ''
576         Whether to require that no two users/groups share the same uid/gid.
577       '';
578     };
580     users.users = mkOption {
581       default = {};
582       type = with types; attrsOf (submodule userOpts);
583       example = {
584         alice = {
585           uid = 1234;
586           description = "Alice Q. User";
587           home = "/home/alice";
588           createHome = true;
589           group = "users";
590           extraGroups = ["wheel"];
591           shell = "/bin/sh";
592         };
593       };
594       description = ''
595         Additional user accounts to be created automatically by the system.
596         This can also be used to set options for root.
597       '';
598     };
600     users.groups = mkOption {
601       default = {};
602       example =
603         { students.gid = 1001;
604           hackers = { };
605         };
606       type = with types; attrsOf (submodule groupOpts);
607       description = ''
608         Additional groups to be created automatically by the system.
609       '';
610     };
613     users.allowNoPasswordLogin = mkOption {
614       type = types.bool;
615       default = false;
616       description = ''
617         Disable checking that at least the `root` user or a user in the `wheel` group can log in using
618         a password or an SSH key.
620         WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing.
621       '';
622     };
624     # systemd initrd
625     boot.initrd.systemd.users = mkOption {
626       description = ''
627         Users to include in initrd.
628       '';
629       default = {};
630       type = types.attrsOf (types.submodule ({ name, ... }: {
631         options.uid = mkOption {
632           type = types.int;
633           description = ''
634             ID of the user in initrd.
635           '';
636           defaultText = literalExpression "config.users.users.\${name}.uid";
637           default = cfg.users.${name}.uid;
638         };
639         options.group = mkOption {
640           type = types.singleLineStr;
641           description = ''
642             Group the user belongs to in initrd.
643           '';
644           defaultText = literalExpression "config.users.users.\${name}.group";
645           default = cfg.users.${name}.group;
646         };
647         options.shell = mkOption {
648           type = types.passwdEntry types.path;
649           description = ''
650             The path to the user's shell in initrd.
651           '';
652           default = "${pkgs.shadow}/bin/nologin";
653           defaultText = literalExpression "\${pkgs.shadow}/bin/nologin";
654         };
655       }));
656     };
658     boot.initrd.systemd.groups = mkOption {
659       description = ''
660         Groups to include in initrd.
661       '';
662       default = {};
663       type = types.attrsOf (types.submodule ({ name, ... }: {
664         options.gid = mkOption {
665           type = types.int;
666           description = ''
667             ID of the group in initrd.
668           '';
669           defaultText = literalExpression "config.users.groups.\${name}.gid";
670           default = cfg.groups.${name}.gid;
671         };
672       }));
673     };
674   };
677   ###### implementation
679   config = let
680     cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})";
681   in {
683     users.users = {
684       root = {
685         uid = ids.uids.root;
686         description = "System administrator";
687         home = "/root";
688         shell = mkDefault cfg.defaultUserShell;
689         group = "root";
690       };
691       nobody = {
692         uid = ids.uids.nobody;
693         isSystemUser = true;
694         description = "Unprivileged account (don't use!)";
695         group = "nogroup";
696       };
697     };
699     users.groups = {
700       root.gid = ids.gids.root;
701       wheel.gid = ids.gids.wheel;
702       disk.gid = ids.gids.disk;
703       kmem.gid = ids.gids.kmem;
704       tty.gid = ids.gids.tty;
705       floppy.gid = ids.gids.floppy;
706       uucp.gid = ids.gids.uucp;
707       lp.gid = ids.gids.lp;
708       cdrom.gid = ids.gids.cdrom;
709       tape.gid = ids.gids.tape;
710       audio.gid = ids.gids.audio;
711       video.gid = ids.gids.video;
712       dialout.gid = ids.gids.dialout;
713       nogroup.gid = ids.gids.nogroup;
714       users.gid = ids.gids.users;
715       nixbld.gid = ids.gids.nixbld;
716       utmp.gid = ids.gids.utmp;
717       adm.gid = ids.gids.adm;
718       input.gid = ids.gids.input;
719       kvm.gid = ids.gids.kvm;
720       render.gid = ids.gids.render;
721       sgx.gid = ids.gids.sgx;
722       shadow.gid = ids.gids.shadow;
723     };
725     system.activationScripts.users = if !config.systemd.sysusers.enable then {
726       supportsDryActivation = true;
727       text = ''
728         install -m 0700 -d /root
729         install -m 0755 -d /home
731         ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
732         -w ${./update-users-groups.pl} ${spec}
733       '';
734     } else ""; # keep around for backwards compatibility
736     systemd.services.linger-users = lib.mkIf ((length lingeringUsers) > 0) {
737       wantedBy = ["multi-user.target"];
738       after = ["systemd-logind.service"];
739       requires = ["systemd-logind.service"];
741       script = let
742         lingerDir = "/var/lib/systemd/linger";
743         lingeringUsersFile = builtins.toFile "lingering-users"
744           (concatStrings (map (s: "${s}\n")
745             (sort (a: b: a < b) lingeringUsers)));  # this sorting is important for `comm` to work correctly
746       in ''
747         mkdir -vp ${lingerDir}
748         cd ${lingerDir}
749         for user in $(ls); do
750           if ! id "$user" >/dev/null; then
751             echo "Removing linger for missing user $user"
752             rm --force -- "$user"
753           fi
754         done
755         ls | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger
756         ls | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl  enable-linger
757       '';
759       serviceConfig.Type = "oneshot";
760     };
762     # Warn about user accounts with deprecated password hashing schemes
763     # This does not work when the users and groups are created by
764     # systemd-sysusers because the users are created too late then.
765     system.activationScripts.hashes = if !config.systemd.sysusers.enable then {
766       deps = [ "users" ];
767       text = ''
768         users=()
769         while IFS=: read -r user hash _; do
770           if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then
771             users+=("$user")
772           fi
773         done </etc/shadow
775         if (( "''${#users[@]}" )); then
776           echo "
777         WARNING: The following user accounts rely on password hashing algorithms
778         that have been removed. They need to be renewed as soon as possible, as
779         they do prevent their users from logging in."
780           printf ' - %s\n' "''${users[@]}"
781         fi
782       '';
783     } else ""; # keep around for backwards compatibility
785     # for backwards compatibility
786     system.activationScripts.groups = stringAfter [ "users" ] "";
788     # Install all the user shells
789     environment.systemPackages = systemShells;
791     environment.etc = mapAttrs' (_: { packages, name, ... }: {
792       name = "profiles/per-user/${name}";
793       value.source = pkgs.buildEnv {
794         name = "user-environment";
795         paths = packages;
796         inherit (config.environment) pathsToLink extraOutputsToInstall;
797         inherit (config.system.path) ignoreCollisions postBuild;
798       };
799     }) (filterAttrs (_: u: u.packages != []) cfg.users);
801     environment.profiles = [
802       "$HOME/.nix-profile"
803       "\${XDG_STATE_HOME}/nix/profile"
804       "$HOME/.local/state/nix/profile"
805       "/etc/profiles/per-user/$USER"
806     ];
808     # systemd initrd
809     boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
810       contents = {
811         "/etc/passwd".text = ''
812           ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group, shell }: let
813             g = config.boot.initrd.systemd.groups.${group};
814           in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}") config.boot.initrd.systemd.users)}
815         '';
816         "/etc/group".text = ''
817           ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
818         '';
819         "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n";
820       };
822       storePaths = [ "${pkgs.shadow}/bin/nologin" ];
824       users = {
825         root = { shell = lib.mkDefault "/bin/bash"; };
826         nobody = {};
827       };
829       groups = {
830         root = {};
831         nogroup = {};
832         systemd-journal = {};
833         tty = {};
834         dialout = {};
835         kmem = {};
836         input = {};
837         video = {};
838         render = {};
839         sgx = {};
840         audio = {};
841         video = {};
842         lp = {};
843         disk = {};
844         cdrom = {};
845         tape = {};
846         kvm = {};
847       };
848     };
850     assertions = [
851       { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
852         message = "UIDs and GIDs must be unique!";
853       }
854       { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
855         message = "systemd initrd UIDs and GIDs must be unique!";
856       }
857       { assertion = usersWithoutExistingGroup == {};
858         message =
859           let
860             errUsers = lib.attrNames usersWithoutExistingGroup;
861             missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup);
862             mkConfigHint = group: "users.groups.${group} = {};";
863           in ''
864             The following users have a primary group that is undefined: ${lib.concatStringsSep " " errUsers}
865             Hint: Add this to your NixOS configuration:
866               ${lib.concatStringsSep "\n  " (map mkConfigHint missingGroups)}
867           '';
868       }
869       { # If mutableUsers is false, to prevent users creating a
870         # configuration that locks them out of the system, ensure that
871         # there is at least one "privileged" account that has a
872         # password or an SSH authorized key. Privileged accounts are
873         # root and users in the wheel group.
874         # The check does not apply when users.disableLoginPossibilityAssertion
875         # The check does not apply when users.mutableUsers
876         assertion = !cfg.mutableUsers -> !cfg.allowNoPasswordLogin ->
877           any id (mapAttrsToList (name: cfg:
878             (name == "root"
879              || cfg.group == "wheel"
880              || elem "wheel" cfg.extraGroups)
881             &&
882             (allowsLogin cfg.hashedPassword
883              || cfg.password != null
884              || cfg.hashedPasswordFile != null
885              || cfg.openssh.authorizedKeys.keys != []
886              || cfg.openssh.authorizedKeys.keyFiles != [])
887           ) cfg.users ++ [
888             config.security.googleOsLogin.enable
889           ]);
890         message = ''
891           Neither the root account nor any wheel user has a password or SSH authorized key.
892           You must set one to prevent being locked out of your system.
893           If you really want to be locked out of your system, set users.allowNoPasswordLogin = true;
894           However you are most probably better off by setting users.mutableUsers = true; and
895           manually running passwd root to set the root password.
896           '';
897       }
898     ] ++ flatten (flip mapAttrsToList cfg.users (name: user:
899       [
900         {
901         assertion = (user.hashedPassword != null)
902         -> (match ".*:.*" user.hashedPassword == null);
903         message = ''
904             The password hash of user "${user.name}" contains a ":" character.
905             This is invalid and would break the login system because the fields
906             of /etc/shadow (file where hashes are stored) are colon-separated.
907             Please check the value of option `users.users."${user.name}".hashedPassword`.'';
908           }
909           {
910             assertion = let
911               isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000);
912             in xor isEffectivelySystemUser user.isNormalUser;
913             message = ''
914               Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
915             '';
916           }
917           {
918             assertion = user.group != "";
919             message = ''
920               users.users.${user.name}.group is unset. This used to default to
921               nogroup, but this is unsafe. For example you can create a group
922               for this user with:
923               users.users.${user.name}.group = "${user.name}";
924               users.groups.${user.name} = {};
925             '';
926           }
927         ] ++ (map (shell: {
928             assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
929             message = ''
930               users.users.${user.name}.shell is set to ${shell}, but
931               programs.${shell}.enable is not true. This will cause the ${shell}
932               shell to lack the basic nix directories in its PATH and might make
933               logging in as that user impossible. You can fix it with:
934               programs.${shell}.enable = true;
936               If you know what you're doing and you are fine with the behavior,
937               set users.users.${user.name}.ignoreShellProgramCheck = true;
938               instead.
939             '';
940           }) [
941           "fish"
942           "xonsh"
943           "zsh"
944         ])
945     ));
947     warnings =
948       flip concatMap (attrValues cfg.users) (user: let
949         passwordOptions = [
950           "hashedPassword"
951           "hashedPasswordFile"
952           "password"
953         ] ++ optionals cfg.mutableUsers [
954           # For immutable users, initialHashedPassword is set to hashedPassword,
955           # so using these options would always trigger the assertion.
956           "initialHashedPassword"
957           "initialPassword"
958         ];
959         unambiguousPasswordConfiguration = 1 >= length
960           (filter (x: x != null) (map (flip getAttr user) passwordOptions));
961       in optional (!unambiguousPasswordConfiguration) ''
962         The user '${user.name}' has multiple of the options
963         `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword`
964         & `initialHashedPassword` set to a non-null value.
965         The options silently discard others by the order of precedence
966         given above which can lead to surprising results. To resolve this warning,
967         set at most one of the options above to a non-`null` value.
969         The values of these options are:
970         ${concatMapStringsSep
971           "\n"
972           (value:
973             "* users.users.\"${user.name}\".${value}: ${generators.toPretty {} user.${value}}")
974           passwordOptions}
975       '')
976       ++ filter (x: x != null) (
977         flip mapAttrsToList cfg.users (_: user:
978         # This regex matches a subset of the Modular Crypto Format (MCF)[1]
979         # informal standard. Since this depends largely on the OS or the
980         # specific implementation of crypt(3) we only support the (sane)
981         # schemes implemented by glibc and BSDs. In particular the original
982         # DES hash is excluded since, having no structure, it would validate
983         # common mistakes like typing the plaintext password.
984         #
985         # [1]: https://en.wikipedia.org/wiki/Crypt_(C)
986         let
987           sep = "\\$";
988           base64 = "[a-zA-Z0-9./]+";
989           id = cryptSchemeIdPatternGroup;
990           name = "[a-z0-9-]+";
991           value = "[a-zA-Z0-9/+.-]+";
992           options = "${name}(=${value})?(,${name}=${value})*";
993           scheme  = "${id}(${sep}${options})?";
994           content = "${base64}${sep}${base64}(${sep}${base64})?";
995           mcf = "^${sep}${scheme}${sep}${content}$";
996         in
997         if (allowsLogin user.hashedPassword
998             && user.hashedPassword != ""  # login without password
999             && match mcf user.hashedPassword == null)
1000         then ''
1001           The password hash of user "${user.name}" may be invalid. You must set a
1002           valid hash or the user will be locked out of their account. Please
1003           check the value of option `users.users."${user.name}".hashedPassword`.''
1004         else null)
1005         ++ flip mapAttrsToList cfg.users (name: user:
1006           if user.passwordFile != null then
1007             ''The option `users.users."${name}".passwordFile' has been renamed '' +
1008             ''to `users.users."${name}".hashedPasswordFile'.''
1009           else null)
1010       );
1011   };