1 { config, lib, utils, pkgs, ... }:
44 # Check whether a password hash will allow login.
46 hash == "" # login without password
48 [ null # password login disabled
49 "!" # password login disabled
50 "!!" # a variant of "!"
54 passwordDescription = ''
55 The options {option}`hashedPassword`,
56 {option}`password` and {option}`hashedPasswordFile`
57 controls what password is set for the user.
58 {option}`hashedPassword` overrides both
59 {option}`password` and {option}`hashedPasswordFile`.
60 {option}`password` overrides {option}`hashedPasswordFile`.
61 If none of these three options are set, no password is assigned to
62 the user, and the user will not be able to do password logins.
63 If the option {option}`users.mutableUsers` is true, the
64 password defined in one of the three options will only be set when
65 the user is created for the first time. After that, you are free to
66 change the password with the ordinary user management commands. If
67 {option}`users.mutableUsers` is false, you cannot change
68 user passwords, they will always be set according to the password
72 hashedPasswordDescription = ''
73 To generate a hashed password run `mkpasswd`.
75 If set to an empty string (`""`), this user will
76 be able to log in without being asked for a password (but not via remote
77 services such as SSH, or indirectly via {command}`su` or
78 {command}`sudo`). This should only be used for e.g. bootable
79 live systems. Note: this is different from setting an empty password,
80 which can be achieved using {option}`users.users.<name?>.password`.
82 If set to `null` (default) this user will not
83 be able to log in using a password (i.e. via {command}`login`
87 userOpts = { name, config, ... }: {
92 type = types.passwdEntry types.str;
93 apply = x: assert (stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x;
95 The name of the user account. If undefined, the name of the
96 attribute set will be used.
100 description = mkOption {
101 type = types.passwdEntry types.str;
103 example = "Alice Q. User";
105 A short description of the user account, typically the
106 user's full name. This is actually the “GECOS” or “comment”
107 field in {file}`/etc/passwd`.
112 type = with types; nullOr int;
115 The account UID. If the UID is null, a free UID is picked on
120 isSystemUser = mkOption {
124 Indicates if the user is a system user or not. This option
125 only has an effect if {option}`uid` is
126 {option}`null`, in which case it determines whether
127 the user's UID is allocated in the range for system users
128 (below 1000) or in the range for normal users (starting at
130 Exactly one of `isNormalUser` and
131 `isSystemUser` must be true.
135 isNormalUser = mkOption {
139 Indicates whether this is an account for a “real” user.
140 This automatically sets {option}`group` to `users`,
141 {option}`createHome` to `true`,
142 {option}`home` to {file}`/home/«username»`,
143 {option}`useDefaultShell` to `true`,
144 and {option}`isSystemUser` to `false`.
145 Exactly one of `isNormalUser` and `isSystemUser` must be true.
151 apply = x: assert (stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x;
153 description = "The user's primary group.";
156 extraGroups = mkOption {
157 type = types.listOf types.str;
159 description = "The user's auxiliary groups.";
163 type = types.passwdEntry types.path;
164 default = "/var/empty";
165 description = "The user's home directory.";
168 homeMode = mkOption {
169 type = types.strMatching "[0-7]{1,5}";
171 description = "The user's home directory mode in numeric format. See chmod(1). The mode is only applied if {option}`users.users.<name>.createHome` is true.";
174 cryptHomeLuks = mkOption {
175 type = with types; nullOr str;
178 Path to encrypted luks device that contains
179 the user's home directory.
183 pamMount = mkOption {
184 type = with types; attrsOf str;
187 Attributes for user's entry in
188 {file}`pam_mount.conf.xml`.
189 Useful attributes might include `path`,
190 `options`, `fstype`, and `server`.
191 See <https://pam-mount.sourceforge.net/pam_mount.conf.5.html>
192 for more information.
197 type = types.nullOr (types.either types.shellPackage (types.passwdEntry types.path));
198 default = pkgs.shadow;
199 defaultText = literalExpression "pkgs.shadow";
200 example = literalExpression "pkgs.bashInteractive";
202 The path to the user's shell. Can use shell derivations,
203 like `pkgs.bashInteractive`. Don’t
204 forget to enable your shell in
205 `programs` if necessary,
206 like `programs.zsh.enable = true;`.
210 ignoreShellProgramCheck = mkOption {
214 By default, nixos will check that programs.SHELL.enable is set to
215 true if the user has a custom shell specified. If that behavior isn't
216 required and there are custom overrides in place to make sure that the
217 shell is functional, set this to true.
221 subUidRanges = mkOption {
222 type = with types; listOf (submodule subordinateUidRange);
225 { startUid = 1000; count = 1; }
226 { startUid = 100001; count = 65534; }
229 Subordinate user ids that user is allowed to use.
230 They are set into {file}`/etc/subuid` and are used
231 by `newuidmap` for user namespaces.
235 subGidRanges = mkOption {
236 type = with types; listOf (submodule subordinateGidRange);
239 { startGid = 100; count = 1; }
240 { startGid = 1001; count = 999; }
243 Subordinate group ids that user is allowed to use.
244 They are set into {file}`/etc/subgid` and are used
245 by `newgidmap` for user namespaces.
249 autoSubUidGidRange = mkOption {
254 Automatically allocate subordinate user and group ids for this user.
255 Allocated range is currently always of size 65536.
259 createHome = mkOption {
263 Whether to create the home directory and ensure ownership as well as
264 permissions to match the user.
268 useDefaultShell = mkOption {
272 If true, the user's shell will be set to
273 {option}`users.defaultUserShell`.
277 hashedPassword = mkOption {
278 type = with types; nullOr (passwdEntry str);
281 Specifies the hashed password for the user.
282 ${passwordDescription}
283 ${hashedPasswordDescription}
287 password = mkOption {
288 type = with types; nullOr str;
291 Specifies the (clear text) password for the user.
292 Warning: do not set confidential information here
293 because it is world-readable in the Nix store. This option
294 should only be used for public accounts.
295 ${passwordDescription}
299 hashedPasswordFile = mkOption {
300 type = with types; nullOr str;
301 default = cfg.users.${name}.passwordFile;
302 defaultText = literalExpression "null";
304 The full path to a file that contains the hash of the user's
305 password. The password file is read on each system activation. The
306 file should contain exactly one line, which should be the password in
307 an encrypted form that is suitable for the `chpasswd -e` command.
308 ${passwordDescription}
312 passwordFile = mkOption {
313 type = with types; nullOr str;
316 description = "Deprecated alias of hashedPasswordFile";
319 initialHashedPassword = mkOption {
320 type = with types; nullOr (passwdEntry str);
323 Specifies the initial hashed password for the user, i.e. the
324 hashed password assigned if the user does not already
325 exist. If {option}`users.mutableUsers` is true, the
326 password can be changed subsequently using the
327 {command}`passwd` command. Otherwise, it's
328 equivalent to setting the {option}`hashedPassword` option.
330 Note that the {option}`hashedPassword` option will override
331 this option if both are set.
333 ${hashedPasswordDescription}
337 initialPassword = mkOption {
338 type = with types; nullOr str;
341 Specifies the initial password for the user, i.e. the
342 password assigned if the user does not already exist. If
343 {option}`users.mutableUsers` is true, the password
344 can be changed subsequently using the
345 {command}`passwd` command. Otherwise, it's
346 equivalent to setting the {option}`password`
347 option. The same caveat applies: the password specified here
348 is world-readable in the Nix store, so it should only be
349 used for guest accounts or passwords that will be changed
352 Note that the {option}`password` option will override this
353 option if both are set.
357 packages = mkOption {
358 type = types.listOf types.package;
360 example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]";
362 The set of packages that should be made available to the user.
363 This is in contrast to {option}`environment.systemPackages`,
364 which adds packages to all users.
369 type = types.nullOr (types.strMatching "[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}");
372 Set the date on which the user's account will no longer be
373 accessible. The date is expressed in the format YYYY-MM-DD, or null
374 to disable the expiry.
375 A user whose account is locked must contact the system
376 administrator before being able to use the system again.
384 Whether to enable lingering for this user. If true, systemd user
385 units will start at boot, rather than starting at login and stopping
386 at logout. This is the declarative equivalent of running
387 `loginctl enable-linger` for this user.
389 If false, user units will not be started until the user logs in, and
390 may be stopped on logout depending on the settings in `logind.conf`.
396 [ { name = mkDefault name;
397 shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell);
399 (mkIf config.isNormalUser {
400 group = mkDefault "users";
401 createHome = mkDefault true;
402 home = mkDefault "/home/${config.name}";
403 homeMode = mkDefault "700";
404 useDefaultShell = mkDefault true;
405 isSystemUser = mkDefault false;
407 # If !mutableUsers, setting ‘initialPassword’ is equivalent to
408 # setting ‘password’ (and similarly for hashed passwords).
409 (mkIf (!cfg.mutableUsers && config.initialPassword != null) {
410 password = mkDefault config.initialPassword;
412 (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) {
413 hashedPassword = mkDefault config.initialHashedPassword;
415 (mkIf (config.isNormalUser && config.subUidRanges == [] && config.subGidRanges == []) {
416 autoSubUidGidRange = mkDefault true;
422 groupOpts = { name, config, ... }: {
427 type = types.passwdEntry types.str;
429 The name of the group. If undefined, the name of the attribute set
435 type = with types; nullOr int;
438 The group GID. If the GID is null, a free GID is picked on
444 type = with types; listOf (passwdEntry str);
447 The user names of the group members, added to the
455 name = mkDefault name;
457 members = mapAttrsToList (n: u: u.name) (
458 filterAttrs (n: u: elem config.name u.extraGroups) cfg.users
464 subordinateUidRange = {
466 startUid = mkOption {
469 Start of the range of subordinate user ids that user is
476 description = "Count of subordinate user ids";
481 subordinateGidRange = {
483 startGid = mkOption {
486 Start of the range of subordinate group ids that user is
493 description = "Count of subordinate group ids";
498 idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }:
500 id = toString (getAttr idAttr (getAttr name set));
501 exists = hasAttr id acc;
502 newAcc = acc // (listToAttrs [ { name = id; value = true; } ]);
503 in if dup then args else if exists
504 then trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; }
505 else { dup = false; acc = newAcc; }
506 ) { dup = false; acc = {}; } (attrNames set)).dup;
508 uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid";
509 gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid";
510 sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid";
511 sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid";
512 groupNames = lib.mapAttrsToList (n: g: g.name) cfg.groups;
513 usersWithoutExistingGroup = lib.filterAttrs (n: u: u.group != "" && !lib.elem u.group groupNames) cfg.users;
515 spec = pkgs.writeText "users-groups.json" (builtins.toJSON {
516 inherit (cfg) mutableUsers;
517 users = mapAttrsToList (_: u:
519 name uid group description home homeMode createHome isSystemUser
520 password hashedPasswordFile hashedPassword
521 autoSubUidGidRange subUidRanges subGidRanges
522 initialPassword initialHashedPassword expires;
523 shell = utils.toShellPath u.shell;
525 groups = attrValues cfg.groups;
530 shells = mapAttrsToList (_: u: u.shell) cfg.users;
532 filter types.shellPackage.check shells;
534 lingeringUsers = map (u: u.name) (attrValues (flip filterAttrs cfg.users (n: u: u.linger)));
537 (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ])
538 (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ])
539 (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"])
545 users.mutableUsers = mkOption {
549 If set to `true`, you are free to add new users and groups to the system
550 with the ordinary `useradd` and
551 `groupadd` commands. On system activation, the
552 existing contents of the `/etc/passwd` and
553 `/etc/group` files will be merged with the
554 contents generated from the `users.users` and
555 `users.groups` options.
556 The initial password for a user will be set
557 according to `users.users`, but existing passwords
561 If set to `false`, the contents of the user and
562 group files will simply be replaced on system activation. This also
563 holds for the user passwords; all changed
564 passwords will be reset according to the
565 `users.users` configuration on activation.
570 users.enforceIdUniqueness = mkOption {
574 Whether to require that no two users/groups share the same uid/gid.
578 users.users = mkOption {
580 type = with types; attrsOf (submodule userOpts);
584 description = "Alice Q. User";
585 home = "/home/alice";
588 extraGroups = ["wheel"];
593 Additional user accounts to be created automatically by the system.
594 This can also be used to set options for root.
598 users.groups = mkOption {
601 { students.gid = 1001;
604 type = with types; attrsOf (submodule groupOpts);
606 Additional groups to be created automatically by the system.
611 users.allowNoPasswordLogin = mkOption {
615 Disable checking that at least the `root` user or a user in the `wheel` group can log in using
616 a password or an SSH key.
618 WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing.
623 boot.initrd.systemd.users = mkOption {
625 Users to include in initrd.
628 type = types.attrsOf (types.submodule ({ name, ... }: {
629 options.uid = mkOption {
632 ID of the user in initrd.
634 defaultText = literalExpression "config.users.users.\${name}.uid";
635 default = cfg.users.${name}.uid;
637 options.group = mkOption {
638 type = types.singleLineStr;
640 Group the user belongs to in initrd.
642 defaultText = literalExpression "config.users.users.\${name}.group";
643 default = cfg.users.${name}.group;
645 options.shell = mkOption {
646 type = types.passwdEntry types.path;
648 The path to the user's shell in initrd.
650 default = "${pkgs.shadow}/bin/nologin";
651 defaultText = literalExpression "\${pkgs.shadow}/bin/nologin";
656 boot.initrd.systemd.groups = mkOption {
658 Groups to include in initrd.
661 type = types.attrsOf (types.submodule ({ name, ... }: {
662 options.gid = mkOption {
665 ID of the group in initrd.
667 defaultText = literalExpression "config.users.groups.\${name}.gid";
668 default = cfg.groups.${name}.gid;
675 ###### implementation
678 cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})";
684 description = "System administrator";
686 shell = mkDefault cfg.defaultUserShell;
690 uid = ids.uids.nobody;
692 description = "Unprivileged account (don't use!)";
698 root.gid = ids.gids.root;
699 wheel.gid = ids.gids.wheel;
700 disk.gid = ids.gids.disk;
701 kmem.gid = ids.gids.kmem;
702 tty.gid = ids.gids.tty;
703 floppy.gid = ids.gids.floppy;
704 uucp.gid = ids.gids.uucp;
705 lp.gid = ids.gids.lp;
706 cdrom.gid = ids.gids.cdrom;
707 tape.gid = ids.gids.tape;
708 audio.gid = ids.gids.audio;
709 video.gid = ids.gids.video;
710 dialout.gid = ids.gids.dialout;
711 nogroup.gid = ids.gids.nogroup;
712 users.gid = ids.gids.users;
713 nixbld.gid = ids.gids.nixbld;
714 utmp.gid = ids.gids.utmp;
715 adm.gid = ids.gids.adm;
716 input.gid = ids.gids.input;
717 kvm.gid = ids.gids.kvm;
718 render.gid = ids.gids.render;
719 sgx.gid = ids.gids.sgx;
720 shadow.gid = ids.gids.shadow;
723 system.activationScripts.users = if !config.systemd.sysusers.enable then {
724 supportsDryActivation = true;
726 install -m 0700 -d /root
727 install -m 0755 -d /home
729 ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \
730 -w ${./update-users-groups.pl} ${spec}
732 } else ""; # keep around for backwards compatibility
734 systemd.services.linger-users = lib.mkIf ((length lingeringUsers) > 0) {
735 wantedBy = ["multi-user.target"];
736 after = ["systemd-logind.service"];
737 requires = ["systemd-logind.service"];
740 lingerDir = "/var/lib/systemd/linger";
741 lingeringUsersFile = builtins.toFile "lingering-users"
742 (concatStrings (map (s: "${s}\n")
743 (sort (a: b: a < b) lingeringUsers))); # this sorting is important for `comm` to work correctly
745 mkdir -vp ${lingerDir}
747 for user in $(ls); do
748 if ! id "$user" >/dev/null; then
749 echo "Removing linger for missing user $user"
750 rm --force -- "$user"
753 ls | sort | comm -3 -1 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl disable-linger
754 ls | sort | comm -3 -2 ${lingeringUsersFile} - | xargs -r ${pkgs.systemd}/bin/loginctl enable-linger
757 serviceConfig.Type = "oneshot";
760 # Warn about user accounts with deprecated password hashing schemes
761 # This does not work when the users and groups are created by
762 # systemd-sysusers because the users are created too late then.
763 system.activationScripts.hashes = if !config.systemd.sysusers.enable then {
767 while IFS=: read -r user hash _; do
768 if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then
773 if (( "''${#users[@]}" )); then
775 WARNING: The following user accounts rely on password hashing algorithms
776 that have been removed. They need to be renewed as soon as possible, as
777 they do prevent their users from logging in."
778 printf ' - %s\n' "''${users[@]}"
781 } else ""; # keep around for backwards compatibility
783 # for backwards compatibility
784 system.activationScripts.groups = stringAfter [ "users" ] "";
786 # Install all the user shells
787 environment.systemPackages = systemShells;
789 environment.etc = mapAttrs' (_: { packages, name, ... }: {
790 name = "profiles/per-user/${name}";
791 value.source = pkgs.buildEnv {
792 name = "user-environment";
794 inherit (config.environment) pathsToLink extraOutputsToInstall;
795 inherit (config.system.path) ignoreCollisions postBuild;
797 }) (filterAttrs (_: u: u.packages != []) cfg.users);
799 environment.profiles = [
801 "\${XDG_STATE_HOME}/nix/profile"
802 "$HOME/.local/state/nix/profile"
803 "/etc/profiles/per-user/$USER"
807 boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable {
809 "/etc/passwd".text = ''
810 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group, shell }: let
811 g = config.boot.initrd.systemd.groups.${group};
812 in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:${shell}") config.boot.initrd.systemd.users)}
814 "/etc/group".text = ''
815 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)}
817 "/etc/shells".text = lib.concatStringsSep "\n" (lib.unique (lib.mapAttrsToList (_: u: u.shell) config.boot.initrd.systemd.users)) + "\n";
820 storePaths = [ "${pkgs.shadow}/bin/nologin" ];
823 root = { shell = lib.mkDefault "/bin/bash"; };
830 systemd-journal = {};
849 { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique);
850 message = "UIDs and GIDs must be unique!";
852 { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique);
853 message = "systemd initrd UIDs and GIDs must be unique!";
855 { assertion = usersWithoutExistingGroup == {};
858 errUsers = lib.attrNames usersWithoutExistingGroup;
859 missingGroups = lib.unique (lib.mapAttrsToList (n: u: u.group) usersWithoutExistingGroup);
860 mkConfigHint = group: "users.groups.${group} = {};";
862 The following users have a primary group that is undefined: ${lib.concatStringsSep " " errUsers}
863 Hint: Add this to your NixOS configuration:
864 ${lib.concatStringsSep "\n " (map mkConfigHint missingGroups)}
867 { # If mutableUsers is false, to prevent users creating a
868 # configuration that locks them out of the system, ensure that
869 # there is at least one "privileged" account that has a
870 # password or an SSH authorized key. Privileged accounts are
871 # root and users in the wheel group.
872 # The check does not apply when users.disableLoginPossibilityAssertion
873 # The check does not apply when users.mutableUsers
874 assertion = !cfg.mutableUsers -> !cfg.allowNoPasswordLogin ->
875 any id (mapAttrsToList (name: cfg:
877 || cfg.group == "wheel"
878 || elem "wheel" cfg.extraGroups)
880 (allowsLogin cfg.hashedPassword
881 || cfg.password != null
882 || cfg.hashedPasswordFile != null
883 || cfg.openssh.authorizedKeys.keys != []
884 || cfg.openssh.authorizedKeys.keyFiles != [])
886 config.security.googleOsLogin.enable
889 Neither the root account nor any wheel user has a password or SSH authorized key.
890 You must set one to prevent being locked out of your system.
891 If you really want to be locked out of your system, set users.allowNoPasswordLogin = true;
892 However you are most probably better off by setting users.mutableUsers = true; and
893 manually running passwd root to set the root password.
896 ] ++ flatten (flip mapAttrsToList cfg.users (name: user:
899 assertion = (user.hashedPassword != null)
900 -> (match ".*:.*" user.hashedPassword == null);
902 The password hash of user "${user.name}" contains a ":" character.
903 This is invalid and would break the login system because the fields
904 of /etc/shadow (file where hashes are stored) are colon-separated.
905 Please check the value of option `users.users."${user.name}".hashedPassword`.'';
909 isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000);
910 in xor isEffectivelySystemUser user.isNormalUser;
912 Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set.
916 assertion = user.group != "";
918 users.users.${user.name}.group is unset. This used to default to
919 nogroup, but this is unsafe. For example you can create a group
921 users.users.${user.name}.group = "${user.name}";
922 users.groups.${user.name} = {};
926 assertion = !user.ignoreShellProgramCheck -> (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true);
928 users.users.${user.name}.shell is set to ${shell}, but
929 programs.${shell}.enable is not true. This will cause the ${shell}
930 shell to lack the basic nix directories in its PATH and might make
931 logging in as that user impossible. You can fix it with:
932 programs.${shell}.enable = true;
934 If you know what you're doing and you are fine with the behavior,
935 set users.users.${user.name}.ignoreShellProgramCheck = true;
946 flip concatMap (attrValues cfg.users) (user: let
947 unambiguousPasswordConfiguration = 1 >= length (filter (x: x != null) ([
949 user.hashedPasswordFile
951 ] ++ optionals cfg.mutableUsers [
952 # For immutable users, initialHashedPassword is set to hashedPassword,
953 # so using these options would always trigger the assertion.
954 user.initialHashedPassword
957 in optional (!unambiguousPasswordConfiguration) ''
958 The user '${user.name}' has multiple of the options
959 `hashedPassword`, `password`, `hashedPasswordFile`, `initialPassword`
960 & `initialHashedPassword` set to a non-null value.
961 The options silently discard others by the order of precedence
962 given above which can lead to surprising results. To resolve this warning,
963 set at most one of the options above to a non-`null` value.
965 ++ filter (x: x != null) (
966 flip mapAttrsToList cfg.users (_: user:
967 # This regex matches a subset of the Modular Crypto Format (MCF)[1]
968 # informal standard. Since this depends largely on the OS or the
969 # specific implementation of crypt(3) we only support the (sane)
970 # schemes implemented by glibc and BSDs. In particular the original
971 # DES hash is excluded since, having no structure, it would validate
972 # common mistakes like typing the plaintext password.
974 # [1]: https://en.wikipedia.org/wiki/Crypt_(C)
977 base64 = "[a-zA-Z0-9./]+";
978 id = cryptSchemeIdPatternGroup;
980 value = "[a-zA-Z0-9/+.-]+";
981 options = "${name}(=${value})?(,${name}=${value})*";
982 scheme = "${id}(${sep}${options})?";
983 content = "${base64}${sep}${base64}(${sep}${base64})?";
984 mcf = "^${sep}${scheme}${sep}${content}$";
986 if (allowsLogin user.hashedPassword
987 && user.hashedPassword != "" # login without password
988 && match mcf user.hashedPassword == null)
990 The password hash of user "${user.name}" may be invalid. You must set a
991 valid hash or the user will be locked out of their account. Please
992 check the value of option `users.users."${user.name}".hashedPassword`.''
994 ++ flip mapAttrsToList cfg.users (name: user:
995 if user.passwordFile != null then
996 ''The option `users.users."${name}".passwordFile' has been renamed '' +
997 ''to `users.users."${name}".hashedPasswordFile'.''