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