11 cfg = config.systemd.sysusers;
12 userCfg = config.users;
14 systemUsers = lib.filterAttrs (_username: opts: !opts.isNormalUser) userCfg.users;
16 sysusersConfig = pkgs.writeTextDir "00-nixos.conf" ''
17 # Type Name ID GECOS Home directory Shell
24 uid = if opts.uid == null then "/var/lib/nixos/uid/${username}" else toString opts.uid;
26 ''u ${username} ${uid}:${opts.group} "${opts.description}" ${opts.home} ${utils.toShellPath opts.shell}''
35 if opts.gid == null then "/var/lib/nixos/gid/${groupname}" else toString opts.gid
43 groupname: opts: (lib.concatMapStrings (username: "m ${username} ${groupname}\n")) opts.members
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.
66 # This module doesn't set it's own user options but reuses the ones from
70 enable = lib.mkEnableOption "systemd-sysusers" // {
72 If enabled, users are created with systemd-sysusers instead of with
73 the custom `update-users-groups.pl` script.
75 Note: This is experimental.
82 config = lib.mkIf cfg.enable {
87 assertion = config.system.activationScripts.users == "";
88 message = "system.activationScripts.users has to be empty to use systemd-sysusers";
91 ++ (lib.mapAttrsToList (username: opts: {
92 assertion = !opts.isNormalUser;
93 message = "${username} is a normal user. systemd-sysusers doesn't create normal users, only system users.";
95 ++ lib.mapAttrsToList (username: opts: {
97 (opts.password == opts.initialPassword || opts.password == null)
98 && (opts.hashedPassword == opts.initialHashedPassword || opts.hashedPassword == null);
99 message = "user '${username}' uses password or hashedPassword. systemd-sysupdate only supports initial passwords. It'll never update your passwords.";
104 # Create home directories, do not create /var/empty even if that's a user's
106 tmpfiles.settings.home-directories = lib.mapAttrs' (
108 lib.nameValuePair opts.home {
110 mode = opts.homeMode;
115 ) (lib.filterAttrs (_username: opts: opts.home != "/var/empty") systemUsers);
117 # Create uid/gid marker files for those without an explicit id
118 tmpfiles.settings.nixos-uid = lib.mapAttrs' (
120 lib.nameValuePair "/var/lib/nixos/uid/${username}" {
125 ) (lib.filterAttrs (_username: opts: opts.uid == null) systemUsers);
127 tmpfiles.settings.nixos-gid = lib.mapAttrs' (
129 lib.nameValuePair "/var/lib/nixos/gid/${groupname}" {
134 ) (lib.filterAttrs (_groupname: opts: opts.gid == null) userCfg.groups);
136 additionalUpstreamSystemUnits = [
137 "systemd-sysusers.service"
140 services.systemd-sysusers = {
141 # Enable switch-to-configuration to restart the service.
142 unitConfig.ConditionNeedsUpdate = [ "" ];
143 requiredBy = [ "sysinit-reactivation.target" ];
144 before = [ "sysinit-reactivation.target" ];
145 restartTriggers = [ "${config.environment.etc."sysusers.d".source}" ];
148 # When we have an immutable /etc we cannot write the files directly
149 # to /etc so we write it to a different directory and symlink them
152 # We need to explicitly list the config file, otherwise
153 # systemd-sysusers cannot find it when we also pass another flag.
154 ExecStart = lib.mkIf immutableEtc [
156 "${config.systemd.package}/bin/systemd-sysusers --root ${builtins.dirOf immutablePasswordFilesLocation} /etc/sysusers.d/00-nixos.conf"
159 # Make the source files writable before executing sysusers.
160 ExecStartPre = lib.mkIf (!userCfg.mutableUsers) (
161 lib.map (file: "-${pkgs.util-linux}/bin/umount ${passwordFilesLocation}/${file}") passwordFiles
163 # Make the source files read-only after sysusers has finished.
164 ExecStartPost = lib.mkIf (!userCfg.mutableUsers) (
167 "${pkgs.util-linux}/bin/mount --bind -o ro ${passwordFilesLocation}/${file} ${passwordFilesLocation}/${file}"
171 LoadCredential = lib.mapAttrsToList (
172 username: opts: "passwd.hashed-password.${username}:${opts.hashedPasswordFile}"
173 ) (lib.filterAttrs (_username: opts: opts.hashedPasswordFile != null) systemUsers);
175 (lib.mapAttrsToList (
176 username: opts: "passwd.hashed-password.${username}:${opts.initialHashedPassword}"
177 ) (lib.filterAttrs (_username: opts: opts.initialHashedPassword != null) systemUsers))
178 ++ (lib.mapAttrsToList (
179 username: opts: "passwd.plaintext-password.${username}:${opts.initialPassword}"
180 ) (lib.filterAttrs (_username: opts: opts.initialPassword != null) systemUsers));
186 environment.etc = lib.mkMerge [
188 "sysusers.d".source = sysusersConfig;
191 # Statically create the symlinks to immutablePasswordFilesLocation when
192 # using an immutable /etc because we will not be able to do it at
194 (lib.mkIf immutableEtc (
198 lib.nameValuePair file {
199 source = "${immutablePasswordFilesLocation}/${file}";
200 mode = "direct-symlink";
208 meta.maintainers = with lib.maintainers; [ nikstur ];