about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/programs/dconf.nix
diff options
context:
space:
mode:
Diffstat (limited to 'nixpkgs/nixos/modules/programs/dconf.nix')
-rw-r--r--nixpkgs/nixos/modules/programs/dconf.nix237
1 files changed, 200 insertions, 37 deletions
diff --git a/nixpkgs/nixos/modules/programs/dconf.nix b/nixpkgs/nixos/modules/programs/dconf.nix
index 7261a143528f..cf53658c4fad 100644
--- a/nixpkgs/nixos/modules/programs/dconf.nix
+++ b/nixpkgs/nixos/modules/programs/dconf.nix
@@ -1,55 +1,217 @@
 { config, lib, pkgs, ... }:
 
-with lib;
-
 let
   cfg = config.programs.dconf;
-  cfgDir = pkgs.symlinkJoin {
-    name = "dconf-system-config";
-    paths = map (x: "${x}/etc/dconf") cfg.packages;
-    postBuild = ''
-      mkdir -p $out/profile
-      mkdir -p $out/db
-    '' + (
-      concatStringsSep "\n" (
-        mapAttrsToList (
-          name: path: ''
-            ln -s ${path} $out/profile/${name}
-          ''
-        ) cfg.profiles
-      )
-    ) + ''
-      ${pkgs.dconf}/bin/dconf update $out/db
-    '';
+
+  # 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
 {
-  ###### interface
-
   options = {
     programs.dconf = {
-      enable = mkEnableOption (lib.mdDoc "dconf");
+      enable = lib.mkEnableOption (lib.mdDoc "dconf");
 
-      profiles = mkOption {
-        type = types.attrsOf types.path;
-        default = {};
-        description = lib.mdDoc "Set of dconf profile files, installed at {file}`/etc/dconf/profiles/«name»`.";
-        internal = true;
+      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 = mkOption {
-        type = types.listOf types.package;
-        default = [];
+      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`.";
       };
     };
   };
 
-  ###### implementation
+  config = lib.mkIf (cfg.profiles != { } || cfg.enable) {
+    programs.dconf.packages = lib.mapAttrsToList mkDconfProfile cfg.profiles;
 
-  config = mkIf (cfg.profiles != {} || cfg.enable) {
-    environment.etc.dconf = mkIf (cfg.profiles != {} || cfg.packages != []) {
-      source = cfgDir;
+    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 ];
@@ -59,8 +221,9 @@ in
     # For dconf executable
     environment.systemPackages = [ pkgs.dconf ];
 
-    # Needed for unwrapped applications
-    environment.sessionVariables.GIO_EXTRA_MODULES = mkIf cfg.enable [ "${pkgs.dconf.lib}/lib/gio/modules" ];
+    environment.sessionVariables = lib.mkIf cfg.enable {
+      # Needed for unwrapped applications
+      GIO_EXTRA_MODULES = [ "${pkgs.dconf.lib}/lib/gio/modules" ];
+    };
   };
-
 }