1 { config, lib, utils, pkgs, ... }:
46 # Check whether a password hash will allow login.
48 hash == "" # login without password
50 [ null # password login disabled
51 "!" # password login disabled
52 "!!" # a variant of "!"
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}
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.
74 overrideDescription = ''
75 ${multiplePasswordsWarning}
77 ${overrideOrderText false}
79 ${overrideOrderText true}
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
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}
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).
123 userOpts = { name, config, ... }: {
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;
131 The name of the user account. If undefined, the name of the
132 attribute set will be used.
136 description = mkOption {
137 type = types.passwdEntry types.str;
139 example = "Alice Q. User";
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`.
148 type = with types; nullOr int;
151 The account UID. If the UID is null, a free UID is picked on
156 isSystemUser = mkOption {
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
166 Exactly one of `isNormalUser` and
167 `isSystemUser` must be true.
171 isNormalUser = mkOption {
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.
187 apply = x: assert (stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
189 description = "The user's primary group.";
192 extraGroups = mkOption {
193 type = types.listOf types.str;
195 description = "The user's auxiliary groups.";
199 type = types.passwdEntry types.path;
200 default = "/var/empty";
201 description = "The user's home directory.";
204 homeMode = mkOption {
205 type = types.strMatching "[0-7]{1,5}";
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.";
210 cryptHomeLuks = mkOption {
211 type = with types; nullOr str;
214 Path to encrypted luks device that contains
215 the user's home directory.
219 pamMount = mkOption {
220 type = with types; attrsOf str;
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.
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";
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;`.
246 ignoreShellProgramCheck = mkOption {
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.
257 subUidRanges = mkOption {
258 type = with types; listOf (submodule subordinateUidRange);
261 { startUid = 1000; count = 1; }
262 { startUid = 100001; count = 65534; }
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.
271 subGidRanges = mkOption {
272 type = with types; listOf (submodule subordinateGidRange);
275 { startGid = 100; count = 1; }
276 { startGid = 1001; count = 999; }
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.
285 autoSubUidGidRange = mkOption {
290 Automatically allocate subordinate user and group ids for this user.
291 Allocated range is currently always of size 65536.
295 createHome = mkOption {
299 Whether to create the home directory and ensure ownership as well as
300 permissions to match the user.
304 useDefaultShell = mkOption {
308 If true, the user's shell will be set to
309 {option}`users.defaultUserShell`.
313 hashedPassword = mkOption {
314 type = with types; nullOr (passwdEntry str);
317 Specifies the hashed password for the user.
319 ${passwordDescription}
320 ${hashedPasswordDescription}
324 password = mkOption {
325 type = with types; nullOr str;
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}
337 hashedPasswordFile = mkOption {
338 type = with types; nullOr str;
339 default = cfg.users.${name}.passwordFile;
340 defaultText = literalExpression "null";
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}
351 passwordFile = mkOption {
352 type = with types; nullOr str;
355 description = "Deprecated alias of hashedPasswordFile";
358 initialHashedPassword = mkOption {
359 type = with types; nullOr (passwdEntry str);
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}
374 initialPassword = mkOption {
375 type = with types; nullOr str;
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
389 ${passwordDescription}
393 packages = mkOption {
394 type = types.listOf types.package;
396 example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
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.
405 type = types.nullOr (types.strMatching "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}");
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.
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`.
432 [ { name = mkDefault name;
433 shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell);
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;
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;
448 (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) {
449 hashedPassword = mkDefault config.initialHashedPassword;
451 (mkIf (config.isNormalUser && config.subUidRanges == [] && config.subGidRanges == []) {
452 autoSubUidGidRange = mkDefault true;
458 groupOpts = { name, config, ... }: {
463 type = types.passwdEntry types.str;
465 The name of the group. If undefined, the name of the attribute set
471 type = with types; nullOr int;
474 The group GID. If the GID is null, a free GID is picked on
480 type = with types; listOf (passwdEntry str);
483 The user names of the group members, added to the
491 name = mkDefault name;
493 members = mapAttrsToList (n: u: u.name) (
494 filterAttrs (n: u: elem config.name u.extraGroups) cfg.users
500 subordinateUidRange = {
502 startUid = mkOption {
505 Start of the range of subordinate user ids that user is
512 description = "Count of subordinate user ids";
517 subordinateGidRange = {
519 startGid = mkOption {
522 Start of the range of subordinate group ids that user is
529 description = "Count of subordinate group ids";
534 idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }:
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:
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;
561 groups = attrValues cfg.groups;
566 shells = mapAttrsToList (_: u: u.shell) cfg.users;
568 filter types.shellPackage.check shells;
570 lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
573 (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ])
574 (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ])
575 (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"])
581 users.mutableUsers = mkOption {
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
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.
606 users.enforceIdUniqueness = mkOption {
610 Whether to require that no two users/groups share the same uid/gid.
614 users.users = mkOption {
616 type = with types; attrsOf (submodule userOpts);
620 description = "Alice Q. User";
621 home = "/home/alice";
624 extraGroups = ["wheel"];
629 Additional user accounts to be created automatically by the system.
630 This can also be used to set options for root.
634 users.groups = mkOption {
637 { students.gid = 1001;
640 type = with types; attrsOf (submodule groupOpts);
642 Additional groups to be created automatically by the system.
647 users.allowNoPasswordLogin = mkOption {
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.
659 boot.initrd.systemd.users = mkOption {
661 Users to include in initrd.
664 type = types.attrsOf (types.submodule ({ name, ... }: {
665 options.uid = mkOption {
668 ID of the user in initrd.
670 defaultText = literalExpression "config.users.users.\${name}.uid";
671 default = cfg.users.${name}.uid;
673 options.group = mkOption {
674 type = types.singleLineStr;
676 Group the user belongs to in initrd.
678 defaultText = literalExpression "config.users.users.\${name}.group";
679 default = cfg.users.${name}.group;
681 options.shell = mkOption {
682 type = types.passwdEntry types.path;
684 The path to the user's shell in initrd.
686 default = "${pkgs.shadow}/bin/nologin";
687 defaultText = literalExpression "\${pkgs.shadow}/bin/nologin";
692 boot.initrd.systemd.groups = mkOption {
694 Groups to include in initrd.
697 type = types.attrsOf (types.submodule ({ name, ... }: {
698 options.gid = mkOption {
701 ID of the group in initrd.
703 defaultText = literalExpression "config.users.groups.\${name}.gid";
704 default = cfg.groups.${name}.gid;
711 ###### implementation
714 cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})";
720 description = "System administrator";
722 shell = mkDefault cfg.defaultUserShell;
726 uid = ids.uids.nobody;
728 description = "Unprivileged account (don't use!)";
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;
759 system.activationScripts.users = if !config.systemd.sysusers.enable then {
760 supportsDryActivation = true;
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}
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"];
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
781 mkdir -vp ${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"
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
793 serviceConfig.Type = "oneshot";
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 {
803 while IFS=: read -r user hash _; do
804 if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then
809 if (( "''${#users[@]}" )); then
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[@]}"
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";
830 inherit (config.environment) pathsToLink extraOutputsToInstall;
831 inherit (config.system.path) ignoreCollisions postBuild;
833 }) (filterAttrs (_: u: u.packages != []) cfg.users);
835 environment.profiles = [
837 "\${XDG_STATE_HOME}/nix/profile"
838 "$HOME/.local/state/nix/profile"
839 "/etc/profiles/per-user/$USER"
843 boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
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)}
850 "/etc/group".text = ''
851 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
853 "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n";
856 storePaths = [ "${pkgs.shadow}/bin/nologin" ];
859 root = { shell = lib.mkDefault "/bin/bash"; };
866 systemd-journal = {};
885 { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
886 message = "UIDs and GIDs must be unique!";
888 { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
889 message = "systemd initrd UIDs and GIDs must be unique!";
891 { assertion = usersWithoutExistingGroup == {};
894 errUsers = lib.attrNames usersWithoutExistingGroup;
895 missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup);
896 mkConfigHint = group: "users.groups.${group} = {};";
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)}
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:
913 || cfg.group == "wheel"
914 || elem "wheel" cfg.extraGroups)
916 (allowsLogin cfg.hashedPassword
917 || cfg.password != null
918 || cfg.hashedPasswordFile != null
919 || cfg.openssh.authorizedKeys.keys != []
920 || cfg.openssh.authorizedKeys.keyFiles != [])
922 config.security.googleOsLogin.enable
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.
932 ] ++ flatten (flip mapAttrsToList cfg.users (name: user:
935 assertion = (user.hashedPassword != null)
936 -> (match ".*:.*" user.hashedPassword == null);
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`.'';
945 isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000);
946 in xor isEffectivelySystemUser user.isNormalUser;
948 Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
952 assertion = user.group != "";
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
957 users.users.${user.name}.group = "${user.name}";
958 users.groups.${user.name} = {};
962 assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
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;
982 flip concatMap (attrValues cfg.users) (user: let
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"
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
1006 "* users.users.\"${user.name}\".${value}: ${generators.toPretty {} user.${value}}")
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.
1018 # [1]: https://en.wikipedia.org/wiki/Crypt_(C)
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}$";
1030 if (allowsLogin user.hashedPassword
1031 && user.hashedPassword != "" # login without password
1032 && match mcf user.hashedPassword == null)
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`.''
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'.''