diff --git a/nixos/modules/config/users-groups.nix b/nixos/modules/config/users-groups.nix index 2d9b941a2cae..c5d44223ecf8 100644 --- a/nixos/modules/config/users-groups.nix +++ b/nixos/modules/config/users-groups.nix @@ -7,6 +7,9 @@ let ids = config.ids; cfg = config.users; + nonUidUsers = filterAttrs (n: u: u.uid == null) cfg.extraUsers; + nonGidGroups = filterAttrs (n: g: g.gid == null) cfg.extraGroups; + passwordDescription = '' The options hashedPassword, password and passwordFile @@ -31,7 +34,10 @@ let name = mkOption { type = types.str; - description = "The name of the user account. If undefined, the name of the attribute set will be used."; + description = '' + The name of the user account. If undefined, the name of the + attribute set will be used. + ''; }; description = mkOption { @@ -46,8 +52,14 @@ let }; uid = mkOption { - type = with types; uniq int; - description = "The account UID."; + type = with types; nullOr int; + default = null; + description = '' + The account UID. If the mutableUsers option + is false, the UID cannot be null. Otherwise, the UID might be + null, in which case a free UID is picked on activation (by the + useradd command). + ''; }; group = mkOption { @@ -151,12 +163,21 @@ let name = mkOption { type = types.str; - description = "The name of the group. If undefined, the name of the attribute set will be used."; + description = '' + The name of the group. If undefined, the name of the attribute set + will be used. + ''; }; gid = mkOption { - type = with types; uniq int; - description = "The GID of the group."; + type = with types; nullOr int; + default = null; + description = '' + The group GID. If the mutableUsers option + is false, the GID cannot be null. Otherwise, the GID might be + null, in which case a free GID is picked on activation (by the + groupadd command). + ''; }; members = mkOption { @@ -218,13 +239,15 @@ let groupFile = pkgs.writeText "group" ( concatStringsSep "\n" (map (g: mkGroupEntry g.name) ( - sortOn "gid" (attrValues cfg.extraGroups) + let f = g: g.gid != null; in + sortOn "gid" (filter f (attrValues cfg.extraGroups)) )) ); passwdFile = pkgs.writeText "passwd" ( concatStringsSep "\n" (map (u: mkPasswdEntry u.name) ( - sortOn "uid" (filter (u: u.createUser) (attrValues cfg.extraUsers)) + let f = u: u.createUser && (u.uid != null); in + sortOn "uid" (filter f (attrValues cfg.extraUsers)) )) ); @@ -261,11 +284,11 @@ let then builtins.trace "Duplicate ${idAttr} ${id}" { dup = true; acc = null; } else { dup = false; acc = newAcc; } ) { dup = false; acc = {}; } (builtins.attrNames set)).dup; - uidsAreUnique = idsAreUnique cfg.extraUsers "uid"; - gidsAreUnique = idsAreUnique cfg.extraGroups "gid"; -in -{ + uidsAreUnique = idsAreUnique (filterAttrs (n: u: u.uid != null) cfg.extraUsers) "uid"; + gidsAreUnique = idsAreUnique (filterAttrs (n: g: g.gid != null) cfg.extraGroups) "gid"; + +in { ###### interface @@ -424,16 +447,31 @@ in } fi ''; - mkhome = n: u: - let - uid = toString u.uid; - gid = toString ((getGroup u.group).gid); - h = u.home; - in '' - test -a "${h}" || mkdir -p "${h}" || true - test "$(stat -c %u "${h}")" = ${uid} || chown ${uid} "${h}" || true - test "$(stat -c %g "${h}")" = ${gid} || chgrp ${gid} "${h}" || true - ''; + mkhome = n: u: '' + uid="$(id -u ${u.name})" + gid="$(id -g ${u.name})" + h="${u.home}" + test -a "$h" || mkdir -p "$h" || true + test "$(stat -c %u "$h")" = $uid || chown $uid "$h" || true + test "$(stat -c %g "$h")" = $gid || chgrp $gid "$h" || true + ''; + groupadd = n: g: '' + if [ -z "$(getent group "${g.name}")" ]; then + echo "Adding group ${g.name}" + ${pkgs.shadow}/sbin/groupadd "${g.name}" + fi + ''; + useradd = n: u: '' + if ! id "${u.name}" &>/dev/null; then + echo "Adding user ${u.name}" + ${pkgs.shadow}/sbin/useradd \ + -g "${u.group}" \ + -s "${u.shell}" \ + -d "${u.home}" \ + "${u.name}" + echo "${u.name}:x" | ${pkgs.shadow}/sbin/chpasswd -e + fi + ''; in stringAfter [ "etc" ] '' touch /etc/group touch /etc/passwd @@ -441,6 +479,8 @@ in VISUAL=${merger passwdFile} ${pkgs.shadow}/sbin/vipw &>/dev/null ${pkgs.shadow}/sbin/grpconv ${pkgs.shadow}/sbin/pwconv + ${concatStrings (mapAttrsToList groupadd nonGidGroups)} + ${concatStrings (mapAttrsToList useradd nonUidUsers)} ${concatStrings (mapAttrsToList mkhome mkhomeUsers)} ${concatStrings (mapAttrsToList setpw setpwUsers)} ''; @@ -448,7 +488,17 @@ in # for backwards compatibility system.activationScripts.groups = stringAfter [ "users" ] ""; - assertions = [ { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique); message = "uids and gids must be unique!"; } ]; + assertions = [ + { assertion = !cfg.enforceIdUniqueness || (uidsAreUnique && gidsAreUnique); + message = "uids and gids must be unique!"; + } + { assertion = cfg.mutableUsers || (nonUidUsers == {}); + message = "When mutableUsers is false, no uid can be null"; + } + { assertion = cfg.mutableUsers || (nonGidGroups == {}); + message = "When mutableUsers is false, no gid can be null"; + } + ]; };