at 23.11-pre 29 kB view raw
1{ config, lib, utils, pkgs, ... }: 2 3with lib; 4 5let 6 ids = config.ids; 7 cfg = config.users; 8 9 # Check whether a password hash will allow login. 10 allowsLogin = hash: 11 hash == "" # login without password 12 || !(lib.elem hash 13 [ null # password login disabled 14 "!" # password login disabled 15 "!!" # a variant of "!" 16 "*" # password unset 17 ]); 18 19 passwordDescription = '' 20 The options {option}`hashedPassword`, 21 {option}`password` and {option}`passwordFile` 22 controls what password is set for the user. 23 {option}`hashedPassword` overrides both 24 {option}`password` and {option}`passwordFile`. 25 {option}`password` overrides {option}`passwordFile`. 26 If none of these three options are set, no password is assigned to 27 the user, and the user will not be able to do password logins. 28 If the option {option}`users.mutableUsers` is true, the 29 password defined in one of the three options will only be set when 30 the user is created for the first time. After that, you are free to 31 change the password with the ordinary user management commands. If 32 {option}`users.mutableUsers` is false, you cannot change 33 user passwords, they will always be set according to the password 34 options. 35 ''; 36 37 hashedPasswordDescription = '' 38 To generate a hashed password run `mkpasswd`. 39 40 If set to an empty string (`""`), this user will 41 be able to log in without being asked for a password (but not via remote 42 services such as SSH, or indirectly via {command}`su` or 43 {command}`sudo`). This should only be used for e.g. bootable 44 live systems. Note: this is different from setting an empty password, 45 which can be achieved using {option}`users.users.<name?>.password`. 46 47 If set to `null` (default) this user will not 48 be able to log in using a password (i.e. via {command}`login` 49 command). 50 ''; 51 52 userOpts = { name, config, ... }: { 53 54 options = { 55 56 name = mkOption { 57 type = types.passwdEntry types.str; 58 apply = x: assert (builtins.stringLength x < 32 || abort "Username '${x}' is longer than 31 characters which is not allowed!"); x; 59 description = lib.mdDoc '' 60 The name of the user account. If undefined, the name of the 61 attribute set will be used. 62 ''; 63 }; 64 65 description = mkOption { 66 type = types.passwdEntry types.str; 67 default = ""; 68 example = "Alice Q. User"; 69 description = lib.mdDoc '' 70 A short description of the user account, typically the 71 user's full name. This is actually the GECOS or comment 72 field in {file}`/etc/passwd`. 73 ''; 74 }; 75 76 uid = mkOption { 77 type = with types; nullOr int; 78 default = null; 79 description = lib.mdDoc '' 80 The account UID. If the UID is null, a free UID is picked on 81 activation. 82 ''; 83 }; 84 85 isSystemUser = mkOption { 86 type = types.bool; 87 default = false; 88 description = lib.mdDoc '' 89 Indicates if the user is a system user or not. This option 90 only has an effect if {option}`uid` is 91 {option}`null`, in which case it determines whether 92 the user's UID is allocated in the range for system users 93 (below 1000) or in the range for normal users (starting at 94 1000). 95 Exactly one of `isNormalUser` and 96 `isSystemUser` must be true. 97 ''; 98 }; 99 100 isNormalUser = mkOption { 101 type = types.bool; 102 default = false; 103 description = lib.mdDoc '' 104 Indicates whether this is an account for a real user. 105 This automatically sets {option}`group` to `users`, 106 {option}`createHome` to `true`, 107 {option}`home` to {file}`/home/«username»`, 108 {option}`useDefaultShell` to `true`, 109 and {option}`isSystemUser` to `false`. 110 Exactly one of `isNormalUser` and `isSystemUser` must be true. 111 ''; 112 }; 113 114 group = mkOption { 115 type = types.str; 116 apply = x: assert (builtins.stringLength x < 32 || abort "Group name '${x}' is longer than 31 characters which is not allowed!"); x; 117 default = ""; 118 description = lib.mdDoc "The user's primary group."; 119 }; 120 121 extraGroups = mkOption { 122 type = types.listOf types.str; 123 default = []; 124 description = lib.mdDoc "The user's auxiliary groups."; 125 }; 126 127 home = mkOption { 128 type = types.passwdEntry types.path; 129 default = "/var/empty"; 130 description = lib.mdDoc "The user's home directory."; 131 }; 132 133 homeMode = mkOption { 134 type = types.strMatching "[0-7]{1,5}"; 135 default = "700"; 136 description = lib.mdDoc "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."; 137 }; 138 139 cryptHomeLuks = mkOption { 140 type = with types; nullOr str; 141 default = null; 142 description = lib.mdDoc '' 143 Path to encrypted luks device that contains 144 the user's home directory. 145 ''; 146 }; 147 148 pamMount = mkOption { 149 type = with types; attrsOf str; 150 default = {}; 151 description = lib.mdDoc '' 152 Attributes for user's entry in 153 {file}`pam_mount.conf.xml`. 154 Useful attributes might include `path`, 155 `options`, `fstype`, and `server`. 156 See <http://pam-mount.sourceforge.net/pam_mount.conf.5.html> 157 for more information. 158 ''; 159 }; 160 161 shell = mkOption { 162 type = types.nullOr (types.either types.shellPackage (types.passwdEntry types.path)); 163 default = pkgs.shadow; 164 defaultText = literalExpression "pkgs.shadow"; 165 example = literalExpression "pkgs.bashInteractive"; 166 description = lib.mdDoc '' 167 The path to the user's shell. Can use shell derivations, 168 like `pkgs.bashInteractive`. Dont 169 forget to enable your shell in 170 `programs` if necessary, 171 like `programs.zsh.enable = true;`. 172 ''; 173 }; 174 175 subUidRanges = mkOption { 176 type = with types; listOf (submodule subordinateUidRange); 177 default = []; 178 example = [ 179 { startUid = 1000; count = 1; } 180 { startUid = 100001; count = 65534; } 181 ]; 182 description = lib.mdDoc '' 183 Subordinate user ids that user is allowed to use. 184 They are set into {file}`/etc/subuid` and are used 185 by `newuidmap` for user namespaces. 186 ''; 187 }; 188 189 subGidRanges = mkOption { 190 type = with types; listOf (submodule subordinateGidRange); 191 default = []; 192 example = [ 193 { startGid = 100; count = 1; } 194 { startGid = 1001; count = 999; } 195 ]; 196 description = lib.mdDoc '' 197 Subordinate group ids that user is allowed to use. 198 They are set into {file}`/etc/subgid` and are used 199 by `newgidmap` for user namespaces. 200 ''; 201 }; 202 203 autoSubUidGidRange = mkOption { 204 type = types.bool; 205 default = false; 206 example = true; 207 description = lib.mdDoc '' 208 Automatically allocate subordinate user and group ids for this user. 209 Allocated range is currently always of size 65536. 210 ''; 211 }; 212 213 createHome = mkOption { 214 type = types.bool; 215 default = false; 216 description = lib.mdDoc '' 217 Whether to create the home directory and ensure ownership as well as 218 permissions to match the user. 219 ''; 220 }; 221 222 useDefaultShell = mkOption { 223 type = types.bool; 224 default = false; 225 description = lib.mdDoc '' 226 If true, the user's shell will be set to 227 {option}`users.defaultUserShell`. 228 ''; 229 }; 230 231 hashedPassword = mkOption { 232 type = with types; nullOr (passwdEntry str); 233 default = null; 234 description = lib.mdDoc '' 235 Specifies the hashed password for the user. 236 ${passwordDescription} 237 ${hashedPasswordDescription} 238 ''; 239 }; 240 241 password = mkOption { 242 type = with types; nullOr str; 243 default = null; 244 description = lib.mdDoc '' 245 Specifies the (clear text) password for the user. 246 Warning: do not set confidential information here 247 because it is world-readable in the Nix store. This option 248 should only be used for public accounts. 249 ${passwordDescription} 250 ''; 251 }; 252 253 passwordFile = mkOption { 254 type = with types; nullOr str; 255 default = null; 256 description = lib.mdDoc '' 257 The full path to a file that contains the user's password. The password 258 file is read on each system activation. The file should contain 259 exactly one line, which should be the password in an encrypted form 260 that is suitable for the `chpasswd -e` command. 261 ${passwordDescription} 262 ''; 263 }; 264 265 initialHashedPassword = mkOption { 266 type = with types; nullOr (passwdEntry str); 267 default = null; 268 description = lib.mdDoc '' 269 Specifies the initial hashed password for the user, i.e. the 270 hashed password assigned if the user does not already 271 exist. If {option}`users.mutableUsers` is true, the 272 password can be changed subsequently using the 273 {command}`passwd` command. Otherwise, it's 274 equivalent to setting the {option}`hashedPassword` option. 275 276 Note that the {option}`hashedPassword` option will override 277 this option if both are set. 278 279 ${hashedPasswordDescription} 280 ''; 281 }; 282 283 initialPassword = mkOption { 284 type = with types; nullOr str; 285 default = null; 286 description = lib.mdDoc '' 287 Specifies the initial password for the user, i.e. the 288 password assigned if the user does not already exist. If 289 {option}`users.mutableUsers` is true, the password 290 can be changed subsequently using the 291 {command}`passwd` command. Otherwise, it's 292 equivalent to setting the {option}`password` 293 option. The same caveat applies: the password specified here 294 is world-readable in the Nix store, so it should only be 295 used for guest accounts or passwords that will be changed 296 promptly. 297 298 Note that the {option}`password` option will override this 299 option if both are set. 300 ''; 301 }; 302 303 packages = mkOption { 304 type = types.listOf types.package; 305 default = []; 306 example = literalExpression "[ pkgs.firefox pkgs.thunderbird ]"; 307 description = lib.mdDoc '' 308 The set of packages that should be made available to the user. 309 This is in contrast to {option}`environment.systemPackages`, 310 which adds packages to all users. 311 ''; 312 }; 313 314 }; 315 316 config = mkMerge 317 [ { name = mkDefault name; 318 shell = mkIf config.useDefaultShell (mkDefault cfg.defaultUserShell); 319 } 320 (mkIf config.isNormalUser { 321 group = mkDefault "users"; 322 createHome = mkDefault true; 323 home = mkDefault "/home/${config.name}"; 324 homeMode = mkDefault "700"; 325 useDefaultShell = mkDefault true; 326 isSystemUser = mkDefault false; 327 }) 328 # If !mutableUsers, setting ‘initialPassword’ is equivalent to 329 # setting ‘password’ (and similarly for hashed passwords). 330 (mkIf (!cfg.mutableUsers && config.initialPassword != null) { 331 password = mkDefault config.initialPassword; 332 }) 333 (mkIf (!cfg.mutableUsers && config.initialHashedPassword != null) { 334 hashedPassword = mkDefault config.initialHashedPassword; 335 }) 336 (mkIf (config.isNormalUser && config.subUidRanges == [] && config.subGidRanges == []) { 337 autoSubUidGidRange = mkDefault true; 338 }) 339 ]; 340 341 }; 342 343 groupOpts = { name, config, ... }: { 344 345 options = { 346 347 name = mkOption { 348 type = types.passwdEntry types.str; 349 description = lib.mdDoc '' 350 The name of the group. If undefined, the name of the attribute set 351 will be used. 352 ''; 353 }; 354 355 gid = mkOption { 356 type = with types; nullOr int; 357 default = null; 358 description = lib.mdDoc '' 359 The group GID. If the GID is null, a free GID is picked on 360 activation. 361 ''; 362 }; 363 364 members = mkOption { 365 type = with types; listOf (passwdEntry str); 366 default = []; 367 description = lib.mdDoc '' 368 The user names of the group members, added to the 369 `/etc/group` file. 370 ''; 371 }; 372 373 }; 374 375 config = { 376 name = mkDefault name; 377 378 members = mapAttrsToList (n: u: u.name) ( 379 filterAttrs (n: u: elem config.name u.extraGroups) cfg.users 380 ); 381 }; 382 383 }; 384 385 subordinateUidRange = { 386 options = { 387 startUid = mkOption { 388 type = types.int; 389 description = lib.mdDoc '' 390 Start of the range of subordinate user ids that user is 391 allowed to use. 392 ''; 393 }; 394 count = mkOption { 395 type = types.int; 396 default = 1; 397 description = lib.mdDoc "Count of subordinate user ids"; 398 }; 399 }; 400 }; 401 402 subordinateGidRange = { 403 options = { 404 startGid = mkOption { 405 type = types.int; 406 description = lib.mdDoc '' 407 Start of the range of subordinate group ids that user is 408 allowed to use. 409 ''; 410 }; 411 count = mkOption { 412 type = types.int; 413 default = 1; 414 description = lib.mdDoc "Count of subordinate group ids"; 415 }; 416 }; 417 }; 418 419 idsAreUnique = set: idAttr: !(foldr (name: args@{ dup, acc }: 420 let 421 id = builtins.toString (builtins.getAttr idAttr (builtins.getAttr name set)); 422 exists = builtins.hasAttr id acc; 423 newAcc = acc // (builtins.listToAttrs [ { name = id; value = true; } ]); 424 in if dup then args else if exists 425 then builtins.trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; } 426 else { dup = false; acc = newAcc; } 427 ) { dup = false; acc = {}; } (builtins.attrNames set)).dup; 428 429 uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.users) "uid"; 430 gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.groups) "gid"; 431 sdInitrdUidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) config.boot.initrd.systemd.users) "uid"; 432 sdInitrdGidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) config.boot.initrd.systemd.groups) "gid"; 433 434 spec = pkgs.writeText "users-groups.json" (builtins.toJSON { 435 inherit (cfg) mutableUsers; 436 users = mapAttrsToList (_: u: 437 { inherit (u) 438 name uid group description home homeMode createHome isSystemUser 439 password passwordFile hashedPassword 440 autoSubUidGidRange subUidRanges subGidRanges 441 initialPassword initialHashedPassword; 442 shell = utils.toShellPath u.shell; 443 }) cfg.users; 444 groups = attrValues cfg.groups; 445 }); 446 447 systemShells = 448 let 449 shells = mapAttrsToList (_: u: u.shell) cfg.users; 450 in 451 filter types.shellPackage.check shells; 452 453in { 454 imports = [ 455 (mkAliasOptionModuleMD [ "users" "extraUsers" ] [ "users" "users" ]) 456 (mkAliasOptionModuleMD [ "users" "extraGroups" ] [ "users" "groups" ]) 457 (mkRenamedOptionModule ["security" "initialRootPassword"] ["users" "users" "root" "initialHashedPassword"]) 458 ]; 459 460 ###### interface 461 options = { 462 463 users.mutableUsers = mkOption { 464 type = types.bool; 465 default = true; 466 description = lib.mdDoc '' 467 If set to `true`, you are free to add new users and groups to the system 468 with the ordinary `useradd` and 469 `groupadd` commands. On system activation, the 470 existing contents of the `/etc/passwd` and 471 `/etc/group` files will be merged with the 472 contents generated from the `users.users` and 473 `users.groups` options. 474 The initial password for a user will be set 475 according to `users.users`, but existing passwords 476 will not be changed. 477 478 ::: {.warning} 479 If set to `false`, the contents of the user and 480 group files will simply be replaced on system activation. This also 481 holds for the user passwords; all changed 482 passwords will be reset according to the 483 `users.users` configuration on activation. 484 ::: 485 ''; 486 }; 487 488 users.enforceIdUniqueness = mkOption { 489 type = types.bool; 490 default = true; 491 description = lib.mdDoc '' 492 Whether to require that no two users/groups share the same uid/gid. 493 ''; 494 }; 495 496 users.users = mkOption { 497 default = {}; 498 type = with types; attrsOf (submodule userOpts); 499 example = { 500 alice = { 501 uid = 1234; 502 description = "Alice Q. User"; 503 home = "/home/alice"; 504 createHome = true; 505 group = "users"; 506 extraGroups = ["wheel"]; 507 shell = "/bin/sh"; 508 }; 509 }; 510 description = lib.mdDoc '' 511 Additional user accounts to be created automatically by the system. 512 This can also be used to set options for root. 513 ''; 514 }; 515 516 users.groups = mkOption { 517 default = {}; 518 example = 519 { students.gid = 1001; 520 hackers = { }; 521 }; 522 type = with types; attrsOf (submodule groupOpts); 523 description = lib.mdDoc '' 524 Additional groups to be created automatically by the system. 525 ''; 526 }; 527 528 529 users.allowNoPasswordLogin = mkOption { 530 type = types.bool; 531 default = false; 532 description = lib.mdDoc '' 533 Disable checking that at least the `root` user or a user in the `wheel` group can log in using 534 a password or an SSH key. 535 536 WARNING: enabling this can lock you out of your system. Enable this only if you know what are you doing. 537 ''; 538 }; 539 540 # systemd initrd 541 boot.initrd.systemd.users = mkOption { 542 visible = false; 543 description = '' 544 Users to include in initrd. 545 ''; 546 default = {}; 547 type = types.attrsOf (types.submodule ({ name, ... }: { 548 options.uid = mkOption { 549 visible = false; 550 type = types.int; 551 description = '' 552 ID of the user in initrd. 553 ''; 554 defaultText = literalExpression "config.users.users.\${name}.uid"; 555 default = cfg.users.${name}.uid; 556 }; 557 options.group = mkOption { 558 visible = false; 559 type = types.singleLineStr; 560 description = '' 561 Group the user belongs to in initrd. 562 ''; 563 defaultText = literalExpression "config.users.users.\${name}.group"; 564 default = cfg.users.${name}.group; 565 }; 566 })); 567 }; 568 569 boot.initrd.systemd.groups = mkOption { 570 visible = false; 571 description = '' 572 Groups to include in initrd. 573 ''; 574 default = {}; 575 type = types.attrsOf (types.submodule ({ name, ... }: { 576 options.gid = mkOption { 577 visible = false; 578 type = types.int; 579 description = '' 580 ID of the group in initrd. 581 ''; 582 defaultText = literalExpression "config.users.groups.\${name}.gid"; 583 default = cfg.groups.${name}.gid; 584 }; 585 })); 586 }; 587 }; 588 589 590 ###### implementation 591 592 config = let 593 cryptSchemeIdPatternGroup = "(${lib.concatStringsSep "|" pkgs.libxcrypt.enabledCryptSchemeIds})"; 594 in { 595 596 users.users = { 597 root = { 598 uid = ids.uids.root; 599 description = "System administrator"; 600 home = "/root"; 601 shell = mkDefault cfg.defaultUserShell; 602 group = "root"; 603 initialHashedPassword = mkDefault "!"; 604 }; 605 nobody = { 606 uid = ids.uids.nobody; 607 isSystemUser = true; 608 description = "Unprivileged account (don't use!)"; 609 group = "nogroup"; 610 }; 611 }; 612 613 users.groups = { 614 root.gid = ids.gids.root; 615 wheel.gid = ids.gids.wheel; 616 disk.gid = ids.gids.disk; 617 kmem.gid = ids.gids.kmem; 618 tty.gid = ids.gids.tty; 619 floppy.gid = ids.gids.floppy; 620 uucp.gid = ids.gids.uucp; 621 lp.gid = ids.gids.lp; 622 cdrom.gid = ids.gids.cdrom; 623 tape.gid = ids.gids.tape; 624 audio.gid = ids.gids.audio; 625 video.gid = ids.gids.video; 626 dialout.gid = ids.gids.dialout; 627 nogroup.gid = ids.gids.nogroup; 628 users.gid = ids.gids.users; 629 nixbld.gid = ids.gids.nixbld; 630 utmp.gid = ids.gids.utmp; 631 adm.gid = ids.gids.adm; 632 input.gid = ids.gids.input; 633 kvm.gid = ids.gids.kvm; 634 render.gid = ids.gids.render; 635 sgx.gid = ids.gids.sgx; 636 shadow.gid = ids.gids.shadow; 637 }; 638 639 system.activationScripts.users = { 640 supportsDryActivation = true; 641 text = '' 642 install -m 0700 -d /root 643 install -m 0755 -d /home 644 645 ${pkgs.perl.withPackages (p: [ p.FileSlurp p.JSON ])}/bin/perl \ 646 -w ${./update-users-groups.pl} ${spec} 647 ''; 648 }; 649 650 # Warn about user accounts with deprecated password hashing schemes 651 system.activationScripts.hashes = { 652 deps = [ "users" ]; 653 text = '' 654 users=() 655 while IFS=: read -r user hash tail; do 656 if [[ "$hash" = "$"* && ! "$hash" =~ ^\''$${cryptSchemeIdPatternGroup}\$ ]]; then 657 users+=("$user") 658 fi 659 done </etc/shadow 660 661 if (( "''${#users[@]}" )); then 662 echo " 663 WARNING: The following user accounts rely on password hashing algorithms 664 that have been removed. They need to be renewed as soon as possible, as 665 they do prevent their users from logging in." 666 printf ' - %s\n' "''${users[@]}" 667 fi 668 ''; 669 }; 670 671 # for backwards compatibility 672 system.activationScripts.groups = stringAfter [ "users" ] ""; 673 674 # Install all the user shells 675 environment.systemPackages = systemShells; 676 677 environment.etc = mapAttrs' (_: { packages, name, ... }: { 678 name = "profiles/per-user/${name}"; 679 value.source = pkgs.buildEnv { 680 name = "user-environment"; 681 paths = packages; 682 inherit (config.environment) pathsToLink extraOutputsToInstall; 683 inherit (config.system.path) ignoreCollisions postBuild; 684 }; 685 }) (filterAttrs (_: u: u.packages != []) cfg.users); 686 687 environment.profiles = [ 688 "$HOME/.nix-profile" 689 "/etc/profiles/per-user/$USER" 690 ]; 691 692 # systemd initrd 693 boot.initrd.systemd = lib.mkIf config.boot.initrd.systemd.enable { 694 contents = { 695 "/etc/passwd".text = '' 696 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { uid, group }: let 697 g = config.boot.initrd.systemd.groups.${group}; 698 in "${n}:x:${toString uid}:${toString g.gid}::/var/empty:") config.boot.initrd.systemd.users)} 699 ''; 700 "/etc/group".text = '' 701 ${lib.concatStringsSep "\n" (lib.mapAttrsToList (n: { gid }: "${n}:x:${toString gid}:") config.boot.initrd.systemd.groups)} 702 ''; 703 }; 704 705 users = { 706 root = {}; 707 nobody = {}; 708 }; 709 710 groups = { 711 root = {}; 712 nogroup = {}; 713 systemd-journal = {}; 714 tty = {}; 715 dialout = {}; 716 kmem = {}; 717 input = {}; 718 video = {}; 719 render = {}; 720 sgx = {}; 721 audio = {}; 722 video = {}; 723 lp = {}; 724 disk = {}; 725 cdrom = {}; 726 tape = {}; 727 kvm = {}; 728 }; 729 }; 730 731 assertions = [ 732 { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique); 733 message = "UIDs and GIDs must be unique!"; 734 } 735 { assertion = !cfg.enforceIdUniqueness || (sdInitrdUidsAreUnique && sdInitrdGidsAreUnique); 736 message = "systemd initrd UIDs and GIDs must be unique!"; 737 } 738 { # If mutableUsers is false, to prevent users creating a 739 # configuration that locks them out of the system, ensure that 740 # there is at least one "privileged" account that has a 741 # password or an SSH authorized key. Privileged accounts are 742 # root and users in the wheel group. 743 # The check does not apply when users.disableLoginPossibilityAssertion 744 # The check does not apply when users.mutableUsers 745 assertion = !cfg.mutableUsers -> !cfg.allowNoPasswordLogin -> 746 any id (mapAttrsToList (name: cfg: 747 (name == "root" 748 || cfg.group == "wheel" 749 || elem "wheel" cfg.extraGroups) 750 && 751 (allowsLogin cfg.hashedPassword 752 || cfg.password != null 753 || cfg.passwordFile != null 754 || cfg.openssh.authorizedKeys.keys != [] 755 || cfg.openssh.authorizedKeys.keyFiles != []) 756 ) cfg.users ++ [ 757 config.security.googleOsLogin.enable 758 ]); 759 message = '' 760 Neither the root account nor any wheel user has a password or SSH authorized key. 761 You must set one to prevent being locked out of your system. 762 If you really want to be locked out of your system, set users.allowNoPasswordLogin = true; 763 However you are most probably better off by setting users.mutableUsers = true; and 764 manually running passwd root to set the root password. 765 ''; 766 } 767 ] ++ flatten (flip mapAttrsToList cfg.users (name: user: 768 [ 769 { 770 assertion = (user.hashedPassword != null) 771 -> (builtins.match ".*:.*" user.hashedPassword == null); 772 message = '' 773 The password hash of user "${user.name}" contains a ":" character. 774 This is invalid and would break the login system because the fields 775 of /etc/shadow (file where hashes are stored) are colon-separated. 776 Please check the value of option `users.users."${user.name}".hashedPassword`.''; 777 } 778 { 779 assertion = let 780 xor = a: b: a && !b || b && !a; 781 isEffectivelySystemUser = user.isSystemUser || (user.uid != null && user.uid < 1000); 782 in xor isEffectivelySystemUser user.isNormalUser; 783 message = '' 784 Exactly one of users.users.${user.name}.isSystemUser and users.users.${user.name}.isNormalUser must be set. 785 ''; 786 } 787 { 788 assertion = user.group != ""; 789 message = '' 790 users.users.${user.name}.group is unset. This used to default to 791 nogroup, but this is unsafe. For example you can create a group 792 for this user with: 793 users.users.${user.name}.group = "${user.name}"; 794 users.groups.${user.name} = {}; 795 ''; 796 } 797 ] ++ (map (shell: { 798 assertion = (user.shell == pkgs.${shell}) -> (config.programs.${shell}.enable == true); 799 message = '' 800 users.users.${user.name}.shell is set to ${shell}, but 801 programs.${shell}.enable is not true. This will cause the ${shell} 802 shell to lack the basic nix directories in its PATH and might make 803 logging in as that user impossible. You can fix it with: 804 programs.${shell}.enable = true; 805 ''; 806 }) [ 807 "fish" 808 "xonsh" 809 "zsh" 810 ]) 811 )); 812 813 warnings = 814 builtins.filter (x: x != null) ( 815 flip mapAttrsToList cfg.users (_: user: 816 # This regex matches a subset of the Modular Crypto Format (MCF)[1] 817 # informal standard. Since this depends largely on the OS or the 818 # specific implementation of crypt(3) we only support the (sane) 819 # schemes implemented by glibc and BSDs. In particular the original 820 # DES hash is excluded since, having no structure, it would validate 821 # common mistakes like typing the plaintext password. 822 # 823 # [1]: https://en.wikipedia.org/wiki/Crypt_(C) 824 let 825 sep = "\\$"; 826 base64 = "[a-zA-Z0-9./]+"; 827 id = cryptSchemeIdPatternGroup; 828 name = "[a-z0-9-]+"; 829 value = "[a-zA-Z0-9/+.-]+"; 830 options = "${name}(=${value})?(,${name}=${value})*"; 831 scheme = "${id}(${sep}${options})?"; 832 content = "${base64}${sep}${base64}(${sep}${base64})?"; 833 mcf = "^${sep}${scheme}${sep}${content}$"; 834 in 835 if (allowsLogin user.hashedPassword 836 && user.hashedPassword != "" # login without password 837 && builtins.match mcf user.hashedPassword == null) 838 then '' 839 The password hash of user "${user.name}" may be invalid. You must set a 840 valid hash or the user will be locked out of their account. Please 841 check the value of option `users.users."${user.name}".hashedPassword`.'' 842 else null 843 )); 844 845 }; 846 847}