2018-01-13 12:41:08 +00:00
|
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
|
|
with lib;
|
|
|
|
|
|
|
|
|
|
let
|
|
|
|
|
cfg = config.users;
|
|
|
|
|
|
|
|
|
|
group = import ./group.nix;
|
|
|
|
|
user = import ./user.nix;
|
|
|
|
|
|
2018-01-14 12:26:18 +00:00
|
|
|
|
toArguments = concatMapStringsSep " " (v: "'${v}'");
|
|
|
|
|
|
2018-01-13 12:41:08 +00:00
|
|
|
|
isCreated = list: name: elem name list;
|
|
|
|
|
isDeleted = attrs: name: ! elem name (mapAttrsToList (n: v: v.name) attrs);
|
|
|
|
|
|
|
|
|
|
createdGroups = mapAttrsToList (n: v: v) (filterAttrs (n: v: isCreated cfg.knownGroups v.name) cfg.groups);
|
|
|
|
|
createdUsers = mapAttrsToList (n: v: v) (filterAttrs (n: v: isCreated cfg.knownUsers v.name) cfg.users);
|
|
|
|
|
deletedGroups = filter (n: isDeleted cfg.groups n) cfg.knownGroups;
|
|
|
|
|
deletedUsers = filter (n: isDeleted cfg.users n) cfg.knownUsers;
|
|
|
|
|
in
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
options = {
|
|
|
|
|
users.knownGroups = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
2018-03-03 12:51:48 +00:00
|
|
|
|
description = ''
|
|
|
|
|
List of groups owned and managed by nix-darwin. Used to indicate
|
|
|
|
|
what users are safe to create/delete based on the configuration.
|
|
|
|
|
Don't add system groups to this.
|
|
|
|
|
'';
|
2018-01-13 12:41:08 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
users.knownUsers = mkOption {
|
|
|
|
|
type = types.listOf types.str;
|
|
|
|
|
default = [];
|
2018-03-03 12:51:48 +00:00
|
|
|
|
description = ''
|
|
|
|
|
List of users owned and managed by nix-darwin. Used to indicate
|
|
|
|
|
what users are safe to create/delete based on the configuration.
|
|
|
|
|
Don't add the admin user or other system users to this.
|
|
|
|
|
'';
|
2018-01-13 12:41:08 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
users.groups = mkOption {
|
|
|
|
|
type = types.loaOf (types.submodule group);
|
|
|
|
|
default = {};
|
|
|
|
|
description = "Configuration for groups.";
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
users.users = mkOption {
|
|
|
|
|
type = types.loaOf (types.submodule user);
|
|
|
|
|
default = {};
|
|
|
|
|
description = "Configuration for users.";
|
|
|
|
|
};
|
2018-06-22 10:16:51 +00:00
|
|
|
|
|
|
|
|
|
users.forceRecreate = mkOption {
|
|
|
|
|
internal = true;
|
|
|
|
|
type = types.bool;
|
|
|
|
|
default = false;
|
|
|
|
|
description = "Remove and recreate existing groups/users.";
|
|
|
|
|
};
|
2018-01-13 12:41:08 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
config = {
|
|
|
|
|
|
|
|
|
|
system.activationScripts.groups.text = mkIf (cfg.knownGroups != []) ''
|
|
|
|
|
echo "setting up groups..." >&2
|
|
|
|
|
|
|
|
|
|
${concatMapStringsSep "\n" (v: ''
|
2018-06-22 10:16:51 +00:00
|
|
|
|
${optionalString cfg.forceRecreate ''
|
|
|
|
|
g=$(dscl . -read '/Groups/${v.name}' PrimaryGroupID 2> /dev/null) || true
|
|
|
|
|
g=''${g#PrimaryGroupID: }
|
|
|
|
|
if [ "$g" -eq ${toString v.gid} ]; then
|
|
|
|
|
echo "deleting group ${v.name}..." >&2
|
|
|
|
|
dscl . -delete '/Groups/${v.name}' 2> /dev/null
|
|
|
|
|
else
|
|
|
|
|
echo "[1;31mwarning: existing group '${v.name}' has unexpected gid $g, skipping...[0m" >&2
|
|
|
|
|
fi
|
|
|
|
|
''}
|
|
|
|
|
|
2018-01-13 12:41:08 +00:00
|
|
|
|
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}'
|
2018-01-14 12:26:18 +00:00
|
|
|
|
g=${toString v.gid}
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [ "$g" -eq ${toString v.gid} ]; then
|
|
|
|
|
g=$(dscl . -read '/Groups/${v.name}' GroupMembership 2> /dev/null) || true
|
|
|
|
|
if [ "$g" != 'GroupMembership: ${concatStringsSep " " v.members}' ]; then
|
|
|
|
|
echo "updating group members ${v.name}..." >&2
|
|
|
|
|
dscl . -create '/Groups/${v.name}' GroupMembership ${toArguments v.members}
|
2018-01-13 12:41:08 +00:00
|
|
|
|
fi
|
2018-01-14 12:26:18 +00:00
|
|
|
|
else
|
|
|
|
|
echo "[1;31mwarning: existing group '${v.name}' has unexpected gid $g, skipping...[0m" >&2
|
2018-01-13 12:41:08 +00:00
|
|
|
|
fi
|
|
|
|
|
'') createdGroups}
|
|
|
|
|
|
|
|
|
|
${concatMapStringsSep "\n" (name: ''
|
|
|
|
|
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
|
|
|
|
|
else
|
|
|
|
|
echo "[1;31mwarning: existing group '${name}' has unexpected gid $g, skipping...[0m" >&2
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
'') deletedGroups}
|
|
|
|
|
'';
|
|
|
|
|
|
|
|
|
|
system.activationScripts.users.text = mkIf (cfg.knownUsers != []) ''
|
|
|
|
|
echo "setting up users..." >&2
|
|
|
|
|
|
|
|
|
|
${concatMapStringsSep "\n" (v: ''
|
2018-06-22 10:16:51 +00:00
|
|
|
|
${optionalString cfg.forceRecreate ''
|
|
|
|
|
u=$(dscl . -read '/Users/${v.name}' UniqueID 2> /dev/null) || true
|
|
|
|
|
u=''${u#UniqueID: }
|
|
|
|
|
if [ "$u" -eq ${toString v.uid} ]; then
|
|
|
|
|
echo "deleting user ${v.name}..." >&2
|
|
|
|
|
dscl . -delete '/Users/${v.name}' 2> /dev/null
|
|
|
|
|
else
|
|
|
|
|
echo "[1;31mwarning: existing user '${v.name}' has unexpected uid $u, skipping...[0m" >&2
|
|
|
|
|
fi
|
|
|
|
|
''}
|
|
|
|
|
|
2018-01-13 12:41:08 +00:00
|
|
|
|
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 "[1;31mwarning: existing user '${v.name}' has unexpected uid $u, skipping...[0m" >&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 "[1;31mwarning: existing user '${name}' has unexpected uid $u, skipping...[0m" >&2
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
'') deletedUsers}
|
|
|
|
|
'';
|
|
|
|
|
|
2019-02-20 16:20:32 +00:00
|
|
|
|
environment.etc = mapAttrs' (name: { packages, ... }: {
|
|
|
|
|
name = "profiles/per-user/${name}";
|
|
|
|
|
value.source = pkgs.buildEnv {
|
|
|
|
|
name = "user-environment";
|
|
|
|
|
paths = packages;
|
|
|
|
|
inherit (config.environment) pathsToLink extraOutputsToInstall;
|
|
|
|
|
inherit (config.system.path) postBuild;
|
|
|
|
|
};
|
|
|
|
|
}) (filterAttrs (_: u: u.packages != []) cfg.users);
|
|
|
|
|
|
2019-02-21 23:57:51 +00:00
|
|
|
|
environment.profiles = mkOrder 900 [ "/etc/profiles/per-user/$USER" ];
|
2018-01-13 12:41:08 +00:00
|
|
|
|
};
|
|
|
|
|
}
|