main
1{
2 den,
3 lib,
4 ...
5}: let
6 # Walk den.aspects (at pipeline time) for .themes sub-aspects
7 # and include the ones selected in user.system.themes.
8 # user.system.themes.theme = global default; per-aspect keys override it.
9 themeIncludes = {user, ...}: let
10 themesCfg = user.system.themes or {};
11 globalTheme = themesCfg.theme or null;
12
13 inherit (den.lib.aspects.fx.keyClassification) structuralKeysSet;
14 classKeys = den.classes or {};
15 quirkKeys = den.quirks or {};
16 skipKey = k: lib.hasPrefix "__" k || structuralKeysSet ? ${k} || classKeys ? ${k} || quirkKeys ? ${k} || k == "settings" || k == "provides";
17
18 # Collect paths of aspects that have .themes (non-recursive flat walk).
19 collectThemePaths = let
20 go = prefix: node:
21 if !builtins.isAttrs node
22 then []
23 else let
24 hasThemes = node ? themes && builtins.isAttrs node.themes;
25 pathStr = lib.concatStringsSep "." prefix;
26 here =
27 if hasThemes
28 then [pathStr]
29 else [];
30 children = lib.concatMap (
31 k:
32 if skipKey k
33 then []
34 else go (prefix ++ [k]) node.${k}
35 ) (builtins.attrNames node);
36 in
37 here ++ children;
38 in
39 go [] (den.aspects or {});
40
41 # Resolve effective theme for an aspect path — walks the tree structure.
42 effectiveTheme = path: let
43 segs = lib.splitString "." path;
44 explicit = lib.attrByPath segs null themesCfg;
45 in
46 if explicit != null
47 then explicit
48 else globalTheme;
49
50 includeThemeAspect = path: theme: let
51 segs = lib.splitString "." path;
52 parentAspect = lib.attrByPath segs null den.aspects;
53 themeAspect = lib.attrByPath (segs ++ ["themes" theme]) null den.aspects;
54 # some den.aspects.<path> node carries no name/meta.provider
55 parentRef = {
56 name = lib.last segs;
57 meta.provider = lib.init segs;
58 };
59 in
60 lib.optionals (themeAspect != null && parentAspect != null) (
61 den.lib.policy.when
62 ({user, ...}: user.hasAspect parentRef)
63 [themeAspect]
64 );
65 in {
66 name = "users/themes";
67 includes =
68 lib.concatMap (
69 path: let
70 t = effectiveTheme path;
71 in
72 if t != null
73 then includeThemeAspect path t
74 else []
75 )
76 collectThemePaths;
77 };
78in {
79 den.default.includes = [
80 den.batteries.define-user
81 ({user, ...}: {
82 name = "users/description";
83 user.description = user.identity.displayName;
84 })
85 ({user, ...}: {
86 name = "users/avatar";
87 nixos = lib.optionalAttrs (user.identity.avatar != null) {
88 systemd.tmpfiles.rules = [
89 "d /var/lib/AccountsService/icons 0755 root root -"
90 "C /var/lib/AccountsService/icons/${user.userName}.png - - - - ${user.identity.avatar}"
91 ];
92 };
93 })
94 ({user, ...}: {
95 name = "users/password";
96 nixos = {config, ...}: let
97 secretName = "hashed-password-user-${user.userName}";
98 in {
99 users.users.${user.userName}.hashedPasswordFile = config.vaultix.secrets.${secretName}.path;
100 vaultix.secrets.${secretName}.file = user.system.hashedPasswordAged;
101 vaultix.beforeUserborn = [secretName];
102 };
103 })
104 themeIncludes
105 ];
106}