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 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
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`
89 userOpts = { name, config, ... }: {
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;
97 The name of the user account. If undefined, the name of the
98 attribute set will be used.
102 description = mkOption {
103 type = types.passwdEntry types.str;
105 example = "Alice Q. User";
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`.
114 type = with types; nullOr int;
117 The account UID. If the UID is null, a free UID is picked on
122 isSystemUser = mkOption {
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
132 Exactly one of `isNormalUser` and
133 `isSystemUser` must be true.
137 isNormalUser = mkOption {
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.
153 apply = x: assert (stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
155 description = "The user's primary group.";
158 extraGroups = mkOption {
159 type = types.listOf types.str;
161 description = "The user's auxiliary groups.";
165 type = types.passwdEntry types.path;
166 default = "/var/empty";
167 description = "The user's home directory.";
170 homeMode = mkOption {
171 type = types.strMatching "[0-7]{1,5}";
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.";
176 cryptHomeLuks = mkOption {
177 type = with types; nullOr str;
180 Path to encrypted luks device that contains
181 the user's home directory.
185 pamMount = mkOption {
186 type = with types; attrsOf str;
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.
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";
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;`.
212 ignoreShellProgramCheck = mkOption {
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.
223 subUidRanges = mkOption {
224 type = with types; listOf (submodule subordinateUidRange);
227 { startUid = 1000; count = 1; }
228 { startUid = 100001; count = 65534; }
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.
237 subGidRanges = mkOption {
238 type = with types; listOf (submodule subordinateGidRange);
241 { startGid = 100; count = 1; }
242 { startGid = 1001; count = 999; }
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.
251 autoSubUidGidRange = mkOption {
256 Automatically allocate subordinate user and group ids for this user.
257 Allocated range is currently always of size 65536.
261 createHome = mkOption {
265 Whether to create the home directory and ensure ownership as well as
266 permissions to match the user.
270 useDefaultShell = mkOption {
274 If true, the user's shell will be set to
275 {option}`users.defaultUserShell`.
279 hashedPassword = mkOption {
280 type = with types; nullOr (passwdEntry str);
283 Specifies the hashed password for the user.
284 ${passwordDescription}
285 ${hashedPasswordDescription}
289 password = mkOption {
290 type = with types; nullOr str;
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}
301 hashedPasswordFile = mkOption {
302 type = with types; nullOr str;
303 default = cfg.users.${name}.passwordFile;
304 defaultText = literalExpression "null";
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}
314 passwordFile = mkOption {
315 type = with types; nullOr str;
318 description = "Deprecated alias of hashedPasswordFile";
321 initialHashedPassword = mkOption {
322 type = with types; nullOr (passwdEntry str);
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}
339 initialPassword = mkOption {
340 type = with types; nullOr str;
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
354 Note that the {option}`password` option will override this
355 option if both are set.
359 packages = mkOption {
360 type = types.listOf types.package;
362 example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
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.
371 type = types.nullOr (types.strMatching "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}");
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.
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`.
398 [ { name = mkDefault name;
399 shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell);
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;
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;
414 (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) {
415 hashedPassword = mkDefault config.initialHashedPassword;
417 (mkIf (config.isNormalUser && config.subUidRanges == [] && config.subGidRanges == []) {
418 autoSubUidGidRange = mkDefault true;
424 groupOpts = { name, config, ... }: {
429 type = types.passwdEntry types.str;
431 The name of the group. If undefined, the name of the attribute set
437 type = with types; nullOr int;
440 The group GID. If the GID is null, a free GID is picked on
446 type = with types; listOf (passwdEntry str);
449 The user names of the group members, added to the
457 name = mkDefault name;
459 members = mapAttrsToList (n: u: u.name) (
460 filterAttrs (n: u: elem config.name u.extraGroups) cfg.users
466 subordinateUidRange = {
468 startUid = mkOption {
471 Start of the range of subordinate user ids that user is
478 description = "Count of subordinate user ids";
483 subordinateGidRange = {
485 startGid = mkOption {
488 Start of the range of subordinate group ids that user is
495 description = "Count of subordinate group ids";
500 idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }:
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:
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;
527 groups = attrValues cfg.groups;
532 shells = mapAttrsToList (_: u: u.shell) cfg.users;
534 filter types.shellPackage.check shells;
536 lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
539 (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ])
540 (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ])
541 (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"])
547 users.mutableUsers = mkOption {
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
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.
572 users.enforceIdUniqueness = mkOption {
576 Whether to require that no two users/groups share the same uid/gid.
580 users.users = mkOption {
582 type = with types; attrsOf (submodule userOpts);
586 description = "Alice Q. User";
587 home = "/home/alice";
590 extraGroups = ["wheel"];
595 Additional user accounts to be created automatically by the system.
596 This can also be used to set options for root.
600 users.groups = mkOption {
603 { students.gid = 1001;
606 type = with types; attrsOf (submodule groupOpts);
608 Additional groups to be created automatically by the system.
613 users.allowNoPasswordLogin = mkOption {
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.
625 boot.initrd.systemd.users = mkOption {
627 Users to include in initrd.
630 type = types.attrsOf (types.submodule ({ name, ... }: {
631 options.uid = mkOption {
634 ID of the user in initrd.
636 defaultText = literalExpression "config.users.users.\${name}.uid";
637 default = cfg.users.${name}.uid;
639 options.group = mkOption {
640 type = types.singleLineStr;
642 Group the user belongs to in initrd.
644 defaultText = literalExpression "config.users.users.\${name}.group";
645 default = cfg.users.${name}.group;
647 options.shell = mkOption {
648 type = types.passwdEntry types.path;
650 The path to the user's shell in initrd.
652 default = "${pkgs.shadow}/bin/nologin";
653 defaultText = literalExpression "\${pkgs.shadow}/bin/nologin";
658 boot.initrd.systemd.groups = mkOption {
660 Groups to include in initrd.
663 type = types.attrsOf (types.submodule ({ name, ... }: {
664 options.gid = mkOption {
667 ID of the group in initrd.
669 defaultText = literalExpression "config.users.groups.\${name}.gid";
670 default = cfg.groups.${name}.gid;
677 ###### implementation
680 cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})";
686 description = "System administrator";
688 shell = mkDefault cfg.defaultUserShell;
692 uid = ids.uids.nobody;
694 description = "Unprivileged account (don't use!)";
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;
725 system.activationScripts.users = if !config.systemd.sysusers.enable then {
726 supportsDryActivation = true;
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}
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"];
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
747 mkdir -vp ${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"
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
759 serviceConfig.Type = "oneshot";
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 {
769 while IFS=: read -r user hash _; do
770 if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then
775 if (( "''${#users[@]}" )); then
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[@]}"
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";
796 inherit (config.environment) pathsToLink extraOutputsToInstall;
797 inherit (config.system.path) ignoreCollisions postBuild;
799 }) (filterAttrs (_: u: u.packages != []) cfg.users);
801 environment.profiles = [
803 "\${XDG_STATE_HOME}/nix/profile"
804 "$HOME/.local/state/nix/profile"
805 "/etc/profiles/per-user/$USER"
809 boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
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)}
816 "/etc/group".text = ''
817 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
819 "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n";
822 storePaths = [ "${pkgs.shadow}/bin/nologin" ];
825 root = { shell = lib.mkDefault "/bin/bash"; };
832 systemd-journal = {};
851 { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
852 message = "UIDs and GIDs must be unique!";
854 { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
855 message = "systemd initrd UIDs and GIDs must be unique!";
857 { assertion = usersWithoutExistingGroup == {};
860 errUsers = lib.attrNames usersWithoutExistingGroup;
861 missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup);
862 mkConfigHint = group: "users.groups.${group} = {};";
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)}
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:
879 || cfg.group == "wheel"
880 || elem "wheel" cfg.extraGroups)
882 (allowsLogin cfg.hashedPassword
883 || cfg.password != null
884 || cfg.hashedPasswordFile != null
885 || cfg.openssh.authorizedKeys.keys != []
886 || cfg.openssh.authorizedKeys.keyFiles != [])
888 config.security.googleOsLogin.enable
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.
898 ] ++ flatten (flip mapAttrsToList cfg.users (name: user:
901 assertion = (user.hashedPassword != null)
902 -> (match ".*:.*" user.hashedPassword == null);
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`.'';
911 isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000);
912 in xor isEffectivelySystemUser user.isNormalUser;
914 Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
918 assertion = user.group != "";
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
923 users.users.${user.name}.group = "${user.name}";
924 users.groups.${user.name} = {};
928 assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
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;
948 flip concatMap (attrValues cfg.users) (user: let
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"
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
973 "* users.users.\"${user.name}\".${value}: ${generators.toPretty {} user.${value}}")
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.
985 # [1]: https://en.wikipedia.org/wiki/Crypt_(C)
988 base64 = "[a-zA-Z0-9./]+";
989 id = cryptSchemeIdPatternGroup;
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}$";
997 if (allowsLogin user.hashedPassword
998 && user.hashedPassword != "" # login without password
999 && match mcf user.hashedPassword == null)
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`.''
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'.''