1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 cfg = config.programs.dconf;
10
11 # Compile keyfiles to dconf DB
12 compileDconfDb =
13 dir:
14 pkgs.runCommand "dconf-db" {
15 nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
16 } "dconf compile $out ${dir}";
17
18 # Check if dconf keyfiles are valid
19 checkDconfKeyfiles =
20 dir:
21 pkgs.runCommand "check-dconf-keyfiles"
22 {
23 nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
24 }
25 ''
26 if [[ -f ${dir} ]]; then
27 echo "dconf keyfiles should be a directory but a file is provided: ${dir}"
28 exit 1
29 fi
30
31 dconf compile db ${dir} || (
32 echo "The dconf keyfiles are invalid: ${dir}"
33 exit 1
34 )
35 cp -R ${dir} $out
36 '';
37
38 mkAllLocks =
39 settings:
40 lib.flatten (lib.mapAttrsToList (k: v: lib.mapAttrsToList (k': _: "/${k}/${k'}") v) settings);
41
42 # Generate dconf DB from dconfDatabase and keyfiles
43 mkDconfDb =
44 val:
45 compileDconfDb (
46 pkgs.symlinkJoin {
47 name = "nixos-generated-dconf-keyfiles";
48 paths = [
49 (pkgs.writeTextDir "nixos-generated-dconf-keyfiles" (lib.generators.toDconfINI val.settings))
50 (pkgs.writeTextDir "locks/nixos-generated-dconf-locks" (
51 lib.concatStringsSep "\n" (if val.lockAll then mkAllLocks val.settings else val.locks)
52 ))
53 ]
54 ++ (map checkDconfKeyfiles val.keyfiles);
55 }
56 );
57
58 # Check if a dconf DB file is valid. The dconf cli doesn't return 1 when it can't
59 # open the database file so we have to check if the output is empty.
60 checkDconfDb =
61 file:
62 pkgs.runCommand "check-dconf-db"
63 {
64 nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
65 }
66 ''
67 if [[ -d ${file} ]]; then
68 echo "dconf DB should be a file but a directory is provided: ${file}"
69 exit 1
70 fi
71
72 echo "file-db:${file}" > profile
73 DCONF_PROFILE=$(pwd)/profile dconf dump / > output 2> error
74 if [[ ! -s output ]] && [[ -s error ]]; then
75 cat error
76 echo "The dconf DB file is invalid: ${file}"
77 exit 1
78 fi
79
80 cp ${file} $out
81 '';
82
83 # Generate dconf profile
84 mkDconfProfile =
85 name: value:
86 if lib.isDerivation value || lib.isPath value then
87 pkgs.runCommand "dconf-profile" { } ''
88 if [[ -d ${value} ]]; then
89 echo "Dconf profile should be a file but a directory is provided."
90 exit 1
91 fi
92 mkdir -p $out/etc/dconf/profile/
93 cp ${value} $out/etc/dconf/profile/${name}
94 ''
95 else
96 pkgs.writeTextDir "etc/dconf/profile/${name}" (
97 lib.concatMapStrings (x: "${x}\n") (
98 (lib.optional value.enableUserDb "user-db:user")
99 ++ (map (
100 value:
101 let
102 db = if lib.isAttrs value && !lib.isDerivation value then mkDconfDb value else checkDconfDb value;
103 in
104 "file-db:${db}"
105 ) value.databases)
106 )
107 );
108
109 dconfDatabase =
110 with lib.types;
111 submodule {
112 options = {
113 keyfiles = lib.mkOption {
114 type = listOf (oneOf [
115 path
116 package
117 ]);
118 default = [ ];
119 description = "A list of dconf keyfile directories.";
120 };
121 settings = lib.mkOption {
122 type = attrs;
123 default = { };
124 description = "An attrset used to generate dconf keyfile.";
125 example = literalExpression ''
126 with lib.gvariant;
127 {
128 "com/raggesilver/BlackBox" = {
129 scrollback-lines = mkUint32 10000;
130 theme-dark = "Tommorow Night";
131 };
132 }
133 '';
134 };
135 locks = lib.mkOption {
136 type = with lib.types; listOf str;
137 default = [ ];
138 description = ''
139 A list of dconf keys to be lockdown. This doesn't take effect if `lockAll`
140 is set.
141 '';
142 example = literalExpression ''
143 [ "/org/gnome/desktop/background/picture-uri" ]
144 '';
145 };
146 lockAll = lib.mkOption {
147 type = lib.types.bool;
148 default = false;
149 description = "Lockdown all dconf keys in `settings`.";
150 };
151 };
152 };
153
154 dconfProfile =
155 with lib.types;
156 submodule {
157 options = {
158 enableUserDb = lib.mkOption {
159 type = bool;
160 default = true;
161 description = "Add `user-db:user` at the beginning of the profile.";
162 };
163
164 databases = lib.mkOption {
165 type =
166 with lib.types;
167 listOf (oneOf [
168 path
169 package
170 dconfDatabase
171 ]);
172 default = [ ];
173 description = ''
174 List of data sources for the profile. An element can be an attrset,
175 or the path of an already compiled database. Each element is converted
176 to a file-db.
177
178 A key is searched from up to down and the first result takes the
179 priority. If a lock for a particular key is installed then the value from
180 the last database in the profile where the key is locked will be used.
181 This can be used to enforce mandatory settings.
182 '';
183 };
184 };
185 };
186
187in
188{
189 options = {
190 programs.dconf = {
191 enable = lib.mkEnableOption "dconf";
192
193 profiles = lib.mkOption {
194 type =
195 with lib.types;
196 attrsOf (oneOf [
197 path
198 package
199 dconfProfile
200 ]);
201 default = { };
202 description = ''
203 Attrset of dconf profiles. By default the `user` profile is used which
204 ends up in `/etc/dconf/profile/user`.
205 '';
206 example = lib.literalExpression ''
207 {
208 # A "user" profile with a database
209 user.databases = [
210 {
211 settings = { };
212 }
213 ];
214 # A "bar" profile from a package
215 bar = pkgs.bar-dconf-profile;
216 # A "foo" profile from a path
217 foo = ''${./foo}
218 };
219 '';
220 };
221
222 packages = lib.mkOption {
223 type = lib.types.listOf lib.types.package;
224 default = [ ];
225 description = "A list of packages which provide dconf profiles and databases in {file}`/etc/dconf`.";
226 };
227 };
228 };
229
230 config = lib.mkIf (cfg.profiles != { } || cfg.enable) {
231 programs.dconf.packages = lib.mapAttrsToList mkDconfProfile cfg.profiles;
232
233 environment.etc.dconf = lib.mkIf (cfg.packages != [ ]) {
234 source = pkgs.symlinkJoin {
235 name = "dconf-system-config";
236 paths = map (x: "${x}/etc/dconf") cfg.packages;
237 nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
238 postBuild = ''
239 if test -d $out/db; then
240 dconf update $out/db
241 fi
242 '';
243 };
244 };
245
246 services.dbus.packages = [ pkgs.dconf ];
247
248 systemd.packages = [ pkgs.dconf ];
249
250 # For dconf executable
251 environment.systemPackages = [ pkgs.dconf ];
252
253 environment.sessionVariables = lib.mkIf cfg.enable {
254 # Needed for unwrapped applications
255 GIO_EXTRA_MODULES = [ "${pkgs.dconf.lib}/lib/gio/modules" ];
256 };
257 };
258}