summary refs log tree commit diff
diff options
context:
space:
mode:
authorlinsui <linsui@inbox.lv>2023-08-15 17:58:02 +0800
committerlinsui <linsui@inbox.lv>2023-08-15 19:20:39 +0800
commitfb52d5df8669aa7ca64f3cb83c42c118cd244603 (patch)
tree1e107ffe78a43859c517b10f82026f3e06d9d42b
parentcce75fa51e1ef5d6f1657b9c0b9ca1963f8f3b03 (diff)
downloadnixpkgs-fb52d5df8669aa7ca64f3cb83c42c118cd244603.tar
nixpkgs-fb52d5df8669aa7ca64f3cb83c42c118cd244603.tar.gz
nixpkgs-fb52d5df8669aa7ca64f3cb83c42c118cd244603.tar.bz2
nixpkgs-fb52d5df8669aa7ca64f3cb83c42c118cd244603.tar.lz
nixpkgs-fb52d5df8669aa7ca64f3cb83c42c118cd244603.tar.xz
nixpkgs-fb52d5df8669aa7ca64f3cb83c42c118cd244603.tar.zst
nixpkgs-fb52d5df8669aa7ca64f3cb83c42c118cd244603.zip
nixos/dconf: add settings support
-rw-r--r--lib/generators.nix8
-rw-r--r--nixos/modules/programs/dconf.nix155
2 files changed, 158 insertions, 5 deletions
diff --git a/lib/generators.nix b/lib/generators.nix
index c37be1942d8..0368577d5a5 100644
--- a/lib/generators.nix
+++ b/lib/generators.nix
@@ -230,6 +230,14 @@ rec {
     in
       toINI_ (gitFlattenAttrs attrs);
 
+  # mkKeyValueDefault wrapper that handles dconf INI quirks.
+  # The main differences of the format is that it requires strings to be quoted.
+  mkDconfKeyValue = mkKeyValueDefault { mkValueString = v: toString (lib.gvariant.mkValue v); } "=";
+
+  # Generates INI in dconf keyfile style. See https://help.gnome.org/admin/system-admin-guide/stable/dconf-keyfiles.html.en
+  # for details.
+  toDconfINI = toINI { mkKeyValue = mkDconfKeyValue; };
+
   /* Generates JSON from an arbitrary (non-function) value.
     * For more information see the documentation of the builtin.
     */
diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix
index b5bfb79be20..567d42a4a41 100644
--- a/nixos/modules/programs/dconf.nix
+++ b/nixos/modules/programs/dconf.nix
@@ -3,12 +3,138 @@
 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
+  '';
+
+  # 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))
+    ] ++ (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:
-    pkgs.runCommand "dconf-profile" { } ''
-      mkdir -p $out/etc/dconf/profile/
-      cp ${value} $out/etc/dconf/profile/${name}
-    '';
+    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";
+            };
+          }
+        '';
+      };
+    };
+  };
+
+  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 = {
@@ -19,8 +145,27 @@ in
         type = with lib.types; attrsOf (oneOf [
           path
           package
+          dconfProfile
         ]);
-        description = lib.mdDoc "Attrset of dconf profiles.";
+        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 {