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