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