From b8713d540c3928a800ea676f38643012c89a97dc Mon Sep 17 00:00:00 2001 From: Daiderd Jordan Date: Sat, 13 Jan 2018 13:33:54 +0100 Subject: [PATCH] users: add options for user creation --- default.nix | 1 + modules/system/activation-scripts.nix | 1 + modules/users/groups.nix | 13 ++- modules/users/users.nix | 137 ++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 modules/users/users.nix diff --git a/default.nix b/default.nix index 0fad7f563..930631fc4 100644 --- a/default.nix +++ b/default.nix @@ -62,6 +62,7 @@ let ./modules/programs/tmux.nix ./modules/programs/vim.nix ./modules/programs/zsh + ./modules/users/users.nix ./modules/users/groups.nix ]; }; diff --git a/modules/system/activation-scripts.nix b/modules/system/activation-scripts.nix index 3079f4818..43f085c62 100644 --- a/modules/system/activation-scripts.nix +++ b/modules/system/activation-scripts.nix @@ -53,6 +53,7 @@ in ${cfg.activationScripts.extraActivation.text} ${cfg.activationScripts.groups.text} + ${cfg.activationScripts.users.text} ${cfg.activationScripts.nix.text} ${cfg.activationScripts.applications.text} ${cfg.activationScripts.etc.text} diff --git a/modules/users/groups.nix b/modules/users/groups.nix index ff3a127a4..58c90dae8 100644 --- a/modules/users/groups.nix +++ b/modules/users/groups.nix @@ -64,16 +64,23 @@ in echo "setting up groups..." >&2 ${concatMapStringsSep "\n" (v: '' - if ! dscl . -read '/Groups/${v.name}' PrimaryGroupID 2> /dev/null | grep -q 'PrimaryGroupID: ${toString v.gid}'; then + g=$(dscl . -read '/Groups/${v.name}' PrimaryGroupID 2> /dev/null) || true + g=''${g#PrimaryGroupID: } + if [ -z "$g" ]; then echo "creating group ${v.name}..." >&2 dscl . -create '/Groups/${v.name}' PrimaryGroupID ${toString v.gid} dscl . -create '/Groups/${v.name}' RealName '${v.description}' + else + if [ "$g" -ne ${toString v.gid} ]; then + echo "warning: existing group '${v.name}' has unexpected gid $g, skipping..." >&2 + fi fi '') createdGroups} ${concatMapStringsSep "\n" (name: '' - if dscl . -read '/Groups/${name}' PrimaryGroupID 2> /dev/null | grep -q 'PrimaryGroupID: '; then - g=$(dscl . -read '/Groups/${name}' PrimaryGroupID | awk '{print $2}') + g=$(dscl . -read '/Groups/${name}' PrimaryGroupID 2> /dev/null) || true + g=''${g#PrimaryGroupID: } + if [ -n "$g" ]; then if [ "$g" -gt 501 ]; then echo "deleting group ${name}..." >&2 dscl . -delete '/Groups/${name}' 2> /dev/null diff --git a/modules/users/users.nix b/modules/users/users.nix new file mode 100644 index 000000000..f023f1019 --- /dev/null +++ b/modules/users/users.nix @@ -0,0 +1,137 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.users; + + isCreatedUser = name: elem name cfg.knownUsers; + isDeletedUser = name: ! elem name (mapAttrsToList (n: v: v.name) cfg.users); + + createdUsers = mapAttrsToList (n: v: v) (filterAttrs (n: v: isCreatedUser v.name) cfg.users); + deletedUsers = filter (n: isDeletedUser n) cfg.knownUsers; + + user = + { name, ... }: + { + options = { + enable = mkOption { + type = types.bool; + default = true; + description = "Whether this user should be created."; + }; + + name = mkOption { + type = types.str; + description = '' + The name of the user account. If undefined, the name of the + attribute set will be used. + ''; + }; + + description = mkOption { + type = types.str; + default = ""; + example = "Alice Q. User"; + description = '' + A short description of the user account, typically the + user's full name. + ''; + }; + + uid = mkOption { + type = types.int; + description = "The user's UID."; + }; + + gid = mkOption { + type = types.int; + default = 20; + description = "The user's primary group."; + }; + + isHidden = mkOption { + type = types.bool; + default = false; + description = "Whether to make the user account hidden."; + }; + + # extraGroups = mkOption { + # type = types.listOf types.str; + # default = []; + # description = "The user's auxiliary groups."; + # }; + + home = mkOption { + type = types.path; + default = "/var/empty"; + description = "The user's home directory."; + }; + + shell = mkOption { + type = types.either types.shellPackage types.path; + default = "/sbin/nologin"; + example = literalExample "pkgs.bashInteractive"; + description = "The user's shell."; + }; + }; + config = { + name = mkDefault name; + }; + }; +in + +{ + options = { + users.knownUsers = mkOption { + type = types.listOf types.str; + default = []; + description = "List of users that should be created and configured."; + }; + + users.users = mkOption { + type = types.loaOf (types.submodule user); + default = {}; + description = "Configuration for users."; + }; + }; + + config = { + + system.activationScripts.users.text = mkIf (cfg.knownUsers != []) '' + echo "setting up users..." >&2 + + ${concatMapStringsSep "\n" (v: '' + u=$(dscl . -read '/Users/${v.name}' UniqueID 2> /dev/null) || true + u=''${u#UniqueID: } + if [ -z "$u" ]; then + echo "creating user ${v.name}..." >&2 + dscl . -create '/Users/${v.name}' UniqueID ${toString v.uid} + dscl . -create '/Users/${v.name}' PrimaryGroupID ${toString v.gid} + dscl . -create '/Users/${v.name}' IsHidden ${if v.isHidden then "1" else "0"} + dscl . -create '/Users/${v.name}' RealName '${v.description}' + dscl . -create '/Users/${v.name}' NFSHomeDirectory '${v.home}' + dscl . -create '/Users/${v.name}' UserShell '${v.shell}' + else + if [ "$u" -ne ${toString v.uid} ]; then + echo "warning: existing user '${v.name}' has unexpected uid $u, skipping..." >&2 + fi + fi + '') createdUsers} + + ${concatMapStringsSep "\n" (name: '' + u=$(dscl . -read '/Users/${name}' UniqueID 2> /dev/null) || true + u=''${u#UniqueID: } + if [ -n "$u" ]; then + if [ "$u" -gt 501 ]; then + echo "deleting user ${name}..." >&2 + dscl . -delete '/Users/${name}' 2> /dev/null + else + echo "warning: existing user '${name}' has unexpected uid $u, skipping..." >&2 + fi + fi + '') deletedUsers} + ''; + + }; +}