about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/programs/dconf.nix
blob: cf53658c4fadaed5b43f601692fac2f06e1a2b9d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
{ config, lib, pkgs, ... }:

let
  cfg = config.programs.dconf;

  # Compile keyfiles to dconf DB
  compileDconfDb = dir: pkgs.runCommand "dconf-db"
    {
      nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
    } "dconf compile $out ${dir}";

  # Check if dconf keyfiles are valid
  checkDconfKeyfiles = dir: pkgs.runCommand "check-dconf-keyfiles"
    {
      nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
    } ''
    if [[ -f ${dir} ]]; then
      echo "dconf keyfiles should be a directory but a file is provided: ${dir}"
      exit 1
    fi

    dconf compile db ${dir} || (
      echo "The dconf keyfiles are invalid: ${dir}"
      exit 1
    )
    cp -R ${dir} $out
  '';

  mkAllLocks = settings: lib.flatten (
    lib.mapAttrsToList (k: v: lib.mapAttrsToList (k': _: "/${k}/${k'}") v) settings);

  # Generate dconf DB from dconfDatabase and keyfiles
  mkDconfDb = val: compileDconfDb (pkgs.symlinkJoin {
    name = "nixos-generated-dconf-keyfiles";
    paths = [
      (pkgs.writeTextDir "nixos-generated-dconf-keyfiles" (lib.generators.toDconfINI val.settings))
      (pkgs.writeTextDir "locks/nixos-generated-dconf-locks" (lib.concatStringsSep "\n"
        (if val.lockAll then mkAllLocks val.settings else val.locks)
      ))
    ] ++ (map checkDconfKeyfiles val.keyfiles);
  });

  # Check if a dconf DB file is valid. The dconf cli doesn't return 1 when it can't
  # open the database file so we have to check if the output is empty.
  checkDconfDb = file: pkgs.runCommand "check-dconf-db"
    {
      nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
    } ''
    if [[ -d ${file} ]]; then
      echo "dconf DB should be a file but a directory is provided: ${file}"
      exit 1
    fi

    echo "file-db:${file}" > profile
    DCONF_PROFILE=$(pwd)/profile dconf dump / > output 2> error
    if [[ ! -s output ]] && [[ -s error ]]; then
      cat error
      echo "The dconf DB file is invalid: ${file}"
      exit 1
    fi

    cp ${file} $out
  '';

  # Generate dconf profile
  mkDconfProfile = name: value:
    if lib.isDerivation value || lib.isPath value then
      pkgs.runCommand "dconf-profile" { } ''
        if [[ -d ${value} ]]; then
          echo "Dconf profile should be a file but a directory is provided."
          exit 1
        fi
        mkdir -p $out/etc/dconf/profile/
        cp ${value} $out/etc/dconf/profile/${name}
      ''
    else
      pkgs.writeTextDir "etc/dconf/profile/${name}" (
        lib.concatMapStrings (x: "${x}\n") ((
          lib.optional value.enableUserDb "user-db:user"
        ) ++ (
          map
            (value:
              let
                db = if lib.isAttrs value && !lib.isDerivation value then mkDconfDb value else checkDconfDb value;
              in
              "file-db:${db}")
            value.databases
        ))
      );

  dconfDatabase = with lib.types; submodule {
    options = {
      keyfiles = lib.mkOption {
        type = listOf (oneOf [ path package ]);
        default = [ ];
        description = lib.mdDoc "A list of dconf keyfile directories.";
      };
      settings = lib.mkOption {
        type = attrs;
        default = { };
        description = lib.mdDoc "An attrset used to generate dconf keyfile.";
        example = literalExpression ''
          with lib.gvariant;
          {
            "com/raggesilver/BlackBox" = {
              scrollback-lines = mkUint32 10000;
              theme-dark = "Tommorow Night";
            };
          }
        '';
      };
      locks = lib.mkOption {
        type = with lib.types; listOf str;
        default = [ ];
        description = lib.mdDoc ''
          A list of dconf keys to be lockdown. This doesn't take effect if `lockAll`
          is set.
        '';
        example = literalExpression ''
          [ "/org/gnome/desktop/background/picture-uri" ]
        '';
      };
      lockAll = lib.mkOption {
        type = lib.types.bool;
        default = false;
        description = lib.mdDoc "Lockdown all dconf keys in `settings`.";
      };
    };
  };

  dconfProfile = with lib.types; submodule {
    options = {
      enableUserDb = lib.mkOption {
        type = bool;
        default = true;
        description = lib.mdDoc "Add `user-db:user` at the beginning of the profile.";
      };

      databases = lib.mkOption {
        type = with lib.types; listOf (oneOf [
          path
          package
          dconfDatabase
        ]);
        default = [ ];
        description = lib.mdDoc ''
          List of data sources for the profile. An element can be an attrset,
          or the path of an already compiled database. Each element is converted
          to a file-db.

          A key is searched from up to down and the first result takes the
          priority. If a lock for a particular key is installed then the value from
          the last database in the profile where the key is locked will be used.
          This can be used to enforce mandatory settings.
        '';
      };
    };
  };

in
{
  options = {
    programs.dconf = {
      enable = lib.mkEnableOption (lib.mdDoc "dconf");

      profiles = lib.mkOption {
        type = with lib.types; attrsOf (oneOf [
          path
          package
          dconfProfile
        ]);
        default = { };
        description = lib.mdDoc ''
          Attrset of dconf profiles. By default the `user` profile is used which
          ends up in `/etc/dconf/profile/user`.
        '';
        example = lib.literalExpression ''
          {
            # A "user" profile with a database
            user.databases = [
              {
                settings = { };
              }
            ];
            # A "bar" profile from a package
            bar = pkgs.bar-dconf-profile;
            # A "foo" profile from a path
            foo = ''${./foo}
          };
        '';
      };

      packages = lib.mkOption {
        type = lib.types.listOf lib.types.package;
        default = [ ];
        description = lib.mdDoc "A list of packages which provide dconf profiles and databases in {file}`/etc/dconf`.";
      };
    };
  };

  config = lib.mkIf (cfg.profiles != { } || cfg.enable) {
    programs.dconf.packages = lib.mapAttrsToList mkDconfProfile cfg.profiles;

    environment.etc.dconf = lib.mkIf (cfg.packages != [ ]) {
      source = pkgs.symlinkJoin {
        name = "dconf-system-config";
        paths = map (x: "${x}/etc/dconf") cfg.packages;
        nativeBuildInputs = [ (lib.getBin pkgs.dconf) ];
        postBuild = ''
          if test -d $out/db; then
            dconf update $out/db
          fi
        '';
      };
    };

    services.dbus.packages = [ pkgs.dconf ];

    systemd.packages = [ pkgs.dconf ];

    # For dconf executable
    environment.systemPackages = [ pkgs.dconf ];

    environment.sessionVariables = lib.mkIf cfg.enable {
      # Needed for unwrapped applications
      GIO_EXTRA_MODULES = [ "${pkgs.dconf.lib}/lib/gio/modules" ];
    };
  };
}