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";
+ }
+ ];
};