nixpkgs/modules/config/users-groups.nix

255 lines
7.2 KiB
Nix
Raw Normal View History

{pkgs, config, ...}:
with pkgs.lib;
let
ids = config.ids;
# User accounts to be created/updated by NixOS.
users =
let
defaultUsers =
[ { name = "root";
uid = ids.uids.root;
description = "System administrator";
home = "/root";
shell = config.users.defaultUserShell;
group = "root";
}
{ name = "nobody";
uid = ids.uids.nobody;
description = "Unprivileged account (don't use!)";
}
];
addAttrs =
{ name
, description
, uid ? ""
, group ? "nogroup"
, extraGroups ? []
, home ? "/var/empty"
, shell ? (if useDefaultShell then config.users.defaultUserShell else "/noshell")
, createHome ? false
, useDefaultShell ? false
, password ? null
}:
{ inherit name description uid group extraGroups home shell createHome password; };
in map addAttrs (defaultUsers ++ config.users.extraUsers);
# Groups to be created/updated by NixOS.
groups =
let
defaultGroups =
[ { name = "root";
gid = ids.gids.root;
}
{ name = "wheel";
gid = ids.gids.wheel;
}
{ name = "disk";
gid = ids.gids.disk;
}
{ name = "kmem";
gid = ids.gids.kmem;
}
{ name = "tty";
gid = ids.gids.tty;
}
{ name = "floppy";
gid = ids.gids.floppy;
}
{ name = "uucp";
gid = ids.gids.uucp;
}
{ name = "lp";
gid = ids.gids.lp;
}
{ name = "cdrom";
gid = ids.gids.cdrom;
}
{ name = "tape";
gid = ids.gids.tape;
}
{ name = "video";
gid = ids.gids.video;
}
{ name = "dialout";
gid = ids.gids.dialout;
}
{ name = "nogroup";
gid = ids.gids.nogroup;
}
{ name = "users";
gid = ids.gids.users;
}
{ name = "nixbld";
gid = ids.gids.nixbld;
}
{ name = "utmp";
gid = ids.gids.utmp;
}
];
addAttrs =
{ name, gid ? "" }:
{ inherit name gid; };
in map addAttrs (defaultGroups ++ config.users.extraGroups);
# Note: the 'X' in front of the password is to distinguish between
# having an empty password, and not having a password.
serializedUser = u: "${u.name}\n${u.description}\n${toString u.uid}\n${u.group}\n${toString (concatStringsSep "," u.extraGroups)}\n${u.home}\n${u.shell}\n${toString u.createHome}\n${if u.password != null then "X" + u.password else ""}\n";
serializedGroup = g: "${g.name}\n${toString g.gid}";
# keep this extra file so that cat can be used to pass special chars such as "`" which is used in the avahi daemon
usersFile = pkgs.writeText "users" (concatStrings (map serializedUser users));
in
{
###### interface
options = {
users.extraUsers = mkOption {
default = [];
example =
[ { name = "alice";
uid = 1234;
description = "Alice";
home = "/home/alice";
createHome = true;
group = "users";
extraGroups = ["wheel"];
shell = "/bin/sh";
password = "foobar";
}
];
description = ''
Additional user accounts to be created automatically by the system.
'';
};
users.extraGroups = mkOption {
default = [];
example =
[ { name = "students";
gid = 1001;
}
];
description = ''
Additional groups to be created automatically by the system.
'';
};
};
###### implementation
config = {
system.activationScripts.rootPasswd = stringAfter [ "etc" ]
''
# If there is no password file yet, create a root account with an
# empty password.
if ! test -e /etc/passwd; then
rootHome=/root
touch /etc/passwd; chmod 0644 /etc/passwd
touch /etc/group; chmod 0644 /etc/group
touch /etc/shadow; chmod 0600 /etc/shadow
# Can't use useradd, since it complains that it doesn't know us
# (bootstrap problem!).
echo "root:x:0:0:System administrator:$rootHome:${config.users.defaultUserShell}" >> /etc/passwd
echo "root::::::::" >> /etc/shadow
fi
'';
system.activationScripts.users = stringAfter [ "groups" ]
''
echo "updating users..."
cat ${usersFile} | while true; do
read name || break
read description
read uid
read group
read extraGroups
read home
read shell
read createHome
read password
if ! curEnt=$(getent passwd "$name"); then
useradd --system \
--comment "$description" \
''${uid:+--uid $uid} \
--gid "$group" \
--groups "$extraGroups" \
--home "$home" \
--shell "$shell" \
''${createHome:+--create-home} \
"$name"
if test "''${password:0:1}" = 'X'; then
2010-06-03 01:02:09 +02:00
(echo "''${password:1}"; echo "''${password:1}") | ${pkgs.shadow}/bin/passwd "$name"
fi
else
#echo "updating user $name..."
oldIFS="$IFS"; IFS=:; set -- $curEnt; IFS="$oldIFS"
prevUid=$3
prevHome=$6
# Don't change the UID if it's the same, otherwise usermod
# will complain.
if test "$prevUid" = "$uid"; then unset uid; fi
# Don't change the home directory if it's the same to prevent
# unnecessary warnings about logged in users.
if test "$prevHome" = "$home"; then unset home; fi
usermod \
--comment "$description" \
''${uid:+--uid $uid} \
--gid "$group" \
--groups "$extraGroups" \
''${home:+--home "$home"} \
--shell "$shell" \
"$name"
fi
done
'';
system.activationScripts.groups = stringAfter [ "rootPasswd" "binsh" "etc" "var" ]
''
echo "updating groups..."
while true; do
read name || break
read gid
if ! curEnt=$(getent group "$name"); then
groupadd --system \
''${gid:+--gid $gid} \
"$name"
else
#echo "updating group $name..."
oldIFS="$IFS"; IFS=:; set -- $curEnt; IFS="$oldIFS"
prevGid=$3
if test -n "$gid" -a "$prevGid" != "$gid"; then
groupmod --gid $gid "$name"
fi
fi
done <<EndOfGroupList
${concatStringsSep "\n" (map serializedGroup groups)}
EndOfGroupList
'';
};
}