main
1{
2 lib,
3 config,
4 ...
5}: {
6 # Share a portion of the user schema across every host that declares
7 # the same user. Hosts without the user stay unaffected.
8 options.den.users = lib.mkOption {
9 type = lib.types.attrsOf (
10 lib.types.submodule ({name, ...}: {
11 freeformType = lib.types.attrsOf lib.types.anything;
12 options = {
13 admin = lib.mkEnableOption "Whether to add user to admin user";
14 identity = lib.mkOption {
15 type = lib.types.submodule {
16 options = {
17 displayName = lib.mkOption {
18 type = lib.types.str;
19 default = name;
20 description = "Display name for the user";
21 };
22 email = lib.mkOption {
23 type = lib.types.nullOr lib.types.str;
24 default = null;
25 description = "Email address for the user";
26 };
27 avatar = lib.mkOption {
28 type = lib.types.nullOr lib.types.path;
29 default = null;
30 description = "Avatar for the user";
31 };
32 sshKeys = lib.mkOption {
33 type = lib.types.listOf lib.types.str;
34 default = [];
35 };
36 gpgKeys = lib.mkOption {
37 type = lib.types.listOf (lib.types.either lib.types.path lib.types.str);
38 default = [];
39 description = "OpenPGP public keys (paths or armored key text) for this user";
40 };
41 };
42 };
43 default = {};
44 };
45 system = lib.mkOption {
46 type = lib.types.submodule {
47 options = {
48 hashedPasswordAged = lib.mkOption {
49 type = lib.types.path;
50 description = "Age encryped hashed password file for user";
51 };
52 settings = lib.mkOption {
53 type = lib.types.anything;
54 };
55 };
56 };
57 default = {};
58 };
59 };
60 })
61 );
62 default = {};
63 description = ''
64 Shared user definitions, keyed by user name.
65
66 Merged into the user schema of every host that declares `users.<name>`
67 in its `den.hosts` entry; host-specific values override shared ones.
68 '';
69 };
70
71 config.den.schema.user = {
72 # `config` referencing the instance's own args are forced while the
73 # fixpoint is still being built and recurse, so the per-user name can only
74 # be recovered from the freeform merge's option path.
75 options._module.freeformType = lib.mkOption {
76 apply = t:
77 if t == null
78 then null
79 else
80 lib.mkOptionType {
81 name = "den-shared-user-freeform";
82 check = builtins.isAttrs;
83 # If without any freeform definitions, the module system will skips
84 # the freeform merge entirely, so the shared registry would never
85 # reach hosts that declare `users.<name> = {}`.
86 merge = loc: defs:
87 lib.removeAttrs (
88 lib.recursiveUpdate (config.den.users.${lib.last loc} or {}) (t.merge loc defs)
89 ) ["ixSharedSentinel"];
90 };
91 };
92 config.ixSharedSentinel = true;
93 };
94}