at master 6.9 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8 9let 10 11 cfg = config.systemd.sysusers; 12 userCfg = config.users; 13 14 systemUsers = lib.filterAttrs (_username: opts: opts.enable && !opts.isNormalUser) userCfg.users; 15 16 sysusersConfig = pkgs.writeTextDir "00-nixos.conf" '' 17 # Type Name ID GECOS Home directory Shell 18 19 # Users 20 ${lib.concatLines ( 21 lib.mapAttrsToList ( 22 username: opts: 23 let 24 uid = if opts.uid == null then "/var/lib/nixos/uid/${username}" else toString opts.uid; 25 in 26 ''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}'' 27 ) systemUsers 28 )} 29 30 # Groups 31 ${lib.concatLines ( 32 lib.mapAttrsToList ( 33 groupname: opts: 34 ''g ${groupname} ${ 35 if opts.gid == null then "/var/lib/nixos/gid/${groupname}" else toString opts.gid 36 }'' 37 ) userCfg.groups 38 )} 39 40 # Group membership 41 ${lib.concatStrings ( 42 lib.mapAttrsToList ( 43 groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members 44 ) userCfg.groups 45 )} 46 ''; 47 48 immutableEtc = config.system.etc.overlay.enable && !config.system.etc.overlay.mutable; 49 # The location of the password files when using an immutable /etc. 50 immutablePasswordFilesLocation = "/var/lib/nixos/etc"; 51 passwordFilesLocation = if immutableEtc then immutablePasswordFilesLocation else "/etc"; 52 # The filenames created by systemd-sysusers. 53 passwordFiles = [ 54 "passwd" 55 "group" 56 "shadow" 57 "gshadow" 58 ]; 59 60in 61 62{ 63 64 options = { 65 66 # This module doesn't set it's own user options but reuses the ones from 67 # users-groups.nix 68 69 systemd.sysusers = { 70 enable = lib.mkEnableOption "systemd-sysusers" // { 71 description = '' 72 If enabled, users are created with systemd-sysusers instead of with 73 the custom `update-users-groups.pl` script. 74 75 Note: This is experimental. 76 ''; 77 }; 78 }; 79 80 }; 81 82 config = lib.mkIf cfg.enable { 83 84 assertions = [ 85 { 86 assertion = config.system.activationScripts.users == ""; 87 message = "system.activationScripts.users has to be empty to use systemd-sysusers"; 88 } 89 ] 90 ++ (lib.mapAttrsToList (username: opts: { 91 assertion = opts.enable -> !opts.isNormalUser; 92 message = "${username} is a normal user. systemd-sysusers doesn't create normal users, only system users."; 93 }) userCfg.users) 94 ++ lib.mapAttrsToList (username: opts: { 95 assertion = 96 (opts.password == opts.initialPassword || opts.password == null) 97 && (opts.hashedPassword == opts.initialHashedPassword || opts.hashedPassword == null); 98 message = "user '${username}' uses password or hashedPassword. systemd-sysupdate only supports initial passwords. It'll never update your passwords."; 99 }) systemUsers; 100 101 systemd = { 102 103 # Create home directories, do not create /var/empty even if that's a user's 104 # home. 105 tmpfiles.settings.home-directories = lib.mapAttrs' ( 106 username: opts: 107 lib.nameValuePair opts.home { 108 d = { 109 mode = opts.homeMode; 110 user = username; 111 group = opts.group; 112 }; 113 } 114 ) (lib.filterAttrs (_username: opts: opts.home != "/var/empty") systemUsers); 115 116 # Create uid/gid marker files for those without an explicit id 117 tmpfiles.settings.nixos-uid = lib.mapAttrs' ( 118 username: opts: 119 lib.nameValuePair "/var/lib/nixos/uid/${username}" { 120 f = { 121 user = username; 122 }; 123 } 124 ) (lib.filterAttrs (_username: opts: opts.uid == null) systemUsers); 125 126 tmpfiles.settings.nixos-gid = lib.mapAttrs' ( 127 groupname: opts: 128 lib.nameValuePair "/var/lib/nixos/gid/${groupname}" { 129 f = { 130 group = groupname; 131 }; 132 } 133 ) (lib.filterAttrs (_groupname: opts: opts.gid == null) userCfg.groups); 134 135 additionalUpstreamSystemUnits = [ 136 "systemd-sysusers.service" 137 ]; 138 139 services.systemd-sysusers = { 140 # Enable switch-to-configuration to restart the service. 141 unitConfig.ConditionNeedsUpdate = [ "" ]; 142 requiredBy = [ "sysinit-reactivation.target" ]; 143 before = [ "sysinit-reactivation.target" ]; 144 restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ]; 145 146 serviceConfig = { 147 # When we have an immutable /etc we cannot write the files directly 148 # to /etc so we write it to a different directory and symlink them 149 # into /etc. 150 # 151 # We need to explicitly list the config file, otherwise 152 # systemd-sysusers cannot find it when we also pass another flag. 153 ExecStart = lib.mkIf immutableEtc [ 154 "" 155 "${config.systemd.package}/bin/systemd-sysusers --root ${builtins.dirOf immutablePasswordFilesLocation} /etc/sysusers.d/00-nixos.conf" 156 ]; 157 158 # Make the source files writable before executing sysusers. 159 ExecStartPre = lib.mkIf (!userCfg.mutableUsers) ( 160 lib.map (file: "-${pkgs.util-linux}/bin/umount ${passwordFilesLocation}/${file}") passwordFiles 161 ); 162 # Make the source files read-only after sysusers has finished. 163 ExecStartPost = lib.mkIf (!userCfg.mutableUsers) ( 164 lib.map ( 165 file: 166 "${pkgs.util-linux}/bin/mount --bind -o ro ${passwordFilesLocation}/${file} ${passwordFilesLocation}/${file}" 167 ) passwordFiles 168 ); 169 170 LoadCredential = lib.mapAttrsToList ( 171 username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}" 172 ) (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) systemUsers); 173 SetCredential = 174 (lib.mapAttrsToList ( 175 username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}" 176 ) (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) systemUsers)) 177 ++ (lib.mapAttrsToList ( 178 username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}" 179 ) (lib.filterAttrs (_username: opts: opts.initialPassword != null) systemUsers)); 180 }; 181 }; 182 183 }; 184 185 environment.etc = lib.mkMerge [ 186 ({ 187 "sysusers.d".source = sysusersConfig; 188 }) 189 190 # Statically create the symlinks to immutablePasswordFilesLocation when 191 # using an immutable /etc because we will not be able to do it at 192 # runtime! 193 (lib.mkIf immutableEtc ( 194 lib.listToAttrs ( 195 lib.map ( 196 file: 197 lib.nameValuePair file { 198 source = "${immutablePasswordFilesLocation}/${file}"; 199 mode = "direct-symlink"; 200 } 201 ) passwordFiles 202 ) 203 )) 204 ]; 205 }; 206 207 meta.maintainers = with lib.maintainers; [ nikstur ]; 208 209}