From da614d98e9540002bb1a1a9165177c3a6719f07f Mon Sep 17 00:00:00 2001 From: linsui Date: Wed, 14 Jun 2023 16:14:23 +0800 Subject: [PATCH 1/5] lib/gvariant: init --- doc/default.nix | 1 + lib/default.nix | 1 + lib/gvariant.nix | 290 +++++++++++++++++++++++++++++++++ lib/tests/modules/gvariant.nix | 93 +++++++++++ 4 files changed, 385 insertions(+) create mode 100644 lib/gvariant.nix create mode 100644 lib/tests/modules/gvariant.nix diff --git a/doc/default.nix b/doc/default.nix index f4270ae856d5f..58b945c692fc3 100644 --- a/doc/default.nix +++ b/doc/default.nix @@ -21,6 +21,7 @@ let { name = "filesystem"; description = "filesystem functions"; } { name = "sources"; description = "source filtering functions"; } { name = "cli"; description = "command-line serialization functions"; } + { name = "gvariant"; description = "GVariant formatted string serialization functions"; } ]; }; diff --git a/lib/default.nix b/lib/default.nix index 73b8ad8715444..56af195d7c95b 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -41,6 +41,7 @@ let # serialization cli = callLibs ./cli.nix; + gvariant = callLibs ./gvariant.nix; generators = callLibs ./generators.nix; # misc diff --git a/lib/gvariant.nix b/lib/gvariant.nix new file mode 100644 index 0000000000000..3142ffc5f1494 --- /dev/null +++ b/lib/gvariant.nix @@ -0,0 +1,290 @@ +# This file is based on https://github.com/nix-community/home-manager +# Copyright (c) 2017-2022 Home Manager contributors +# + + +{ lib }: + +/* A partial and basic implementation of GVariant formatted strings. + See https://docs.gtk.org/glib/gvariant-format-strings.html for detauls. + + Note, this API is not considered fully stable and it might therefore + change in backwards incompatible ways without prior notice. +*/ +let + inherit (lib) + concatMapStringsSep concatStrings escape head replaceStrings; + + mkPrimitive = t: v: { + _type = "gvariant"; + type = t; + value = v; + __toString = self: "@${self.type} ${toString self.value}"; # https://docs.gtk.org/glib/gvariant-text.html + }; + + type = { + arrayOf = t: "a${t}"; + maybeOf = t: "m${t}"; + tupleOf = ts: "(${concatStrings ts})"; + dictionaryEntryOf = nameType: valueType: "{${nameType}${valueType}}"; + string = "s"; + boolean = "b"; + uchar = "y"; + int16 = "n"; + uint16 = "q"; + int32 = "i"; + uint32 = "u"; + int64 = "x"; + uint64 = "t"; + double = "d"; + variant = "v"; + }; + + /* Check if a value is a GVariant value + + Type: + isGVariant :: Any -> Bool + */ + isGVariant = v: v._type or "" == "gvariant"; + +in +rec { + + inherit type isGVariant; + + /* Returns the GVariant value that most closely matches the given Nix value. + If no GVariant value can be found unambiguously then error is thrown. + + Type: + mkValue :: Any -> gvariant + */ + mkValue = v: + if builtins.isBool v then + mkBoolean v + else if builtins.isFloat v then + mkDouble v + else if builtins.isString v then + mkString v + else if builtins.isList v then + mkArray v + else if isGVariant v then + v + else + throw "The GVariant type of ${v} can't be inferred."; + + /* Returns the GVariant array from the given type of the elements and a Nix list. + + Type: + mkArray :: [Any] -> gvariant + + Example: + # Creating a string array + lib.gvariant.mkArray [ "a" "b" "c" ] + */ + mkArray = elems: + let + vs = map mkValue (lib.throwIf (elems == [ ]) "Please create empty array with mkEmptyArray." elems); + elemType = lib.throwIfNot (lib.all (t: (head vs).type == t) (map (v: v.type) vs)) + "Elements in a list should have same type." + (head vs).type; + in + mkPrimitive (type.arrayOf elemType) vs // { + __toString = self: + "@${self.type} [${concatMapStringsSep "," toString self.value}]"; + }; + + /* Returns the GVariant array from the given empty Nix list. + + Type: + mkEmptyArray :: gvariant.type -> gvariant + + Example: + # Creating an empty string array + lib.gvariant.mkEmptyArray (lib.gvariant.type.string) + */ + mkEmptyArray = elemType: mkPrimitive (type.arrayOf elemType) [ ] // { + __toString = self: "@${self.type} []"; + }; + + + /* Returns the GVariant variant from the given Nix value. Variants are containers + of different GVariant type. + + Type: + mkVariant :: Any -> gvariant + + Example: + lib.gvariant.mkArray [ + (lib.gvariant.mkVariant "a string") + (lib.gvariant.mkVariant (lib.gvariant.mkInt32 1)) + ] + */ + mkVariant = elem: + let gvarElem = mkValue elem; + in mkPrimitive type.variant gvarElem // { + __toString = self: "<${toString self.value}>"; + }; + + /* Returns the GVariant dictionary entry from the given key and value. + + Type: + mkDictionaryEntry :: String -> Any -> gvariant + + Example: + # A dictionary describing an Epiphany’s search provider + [ + (lib.gvariant.mkDictionaryEntry "url" (lib.gvariant.mkVariant "https://duckduckgo.com/?q=%s&t=epiphany")) + (lib.gvariant.mkDictionaryEntry "bang" (lib.gvariant.mkVariant "!d")) + (lib.gvariant.mkDictionaryEntry "name" (lib.gvariant.mkVariant "DuckDuckGo")) + ] + */ + mkDictionaryEntry = + # The key of the entry + name: + # The value of the entry + value: + let + name' = mkValue name; + value' = mkValue value; + dictionaryType = type.dictionaryEntryOf name'.type value'.type; + in + mkPrimitive dictionaryType { inherit name value; } // { + __toString = self: "@${self.type} {${name'},${value'}}"; + }; + + /* Returns the GVariant maybe from the given element type. + + Type: + mkMaybe :: gvariant.type -> Any -> gvariant + */ + mkMaybe = elemType: elem: + mkPrimitive (type.maybeOf elemType) elem // { + __toString = self: + if self.value == null then + "@${self.type} nothing" + else + "just ${toString self.value}"; + }; + + /* Returns the GVariant nothing from the given element type. + + Type: + mkNothing :: gvariant.type -> gvariant + */ + mkNothing = elemType: mkMaybe elemType null; + + /* Returns the GVariant just from the given Nix value. + + Type: + mkJust :: Any -> gvariant + */ + mkJust = elem: let gvarElem = mkValue elem; in mkMaybe gvarElem.type gvarElem; + + /* Returns the GVariant tuple from the given Nix list. + + Type: + mkTuple :: [Any] -> gvariant + */ + mkTuple = elems: + let + gvarElems = map mkValue elems; + tupleType = type.tupleOf (map (e: e.type) gvarElems); + in + mkPrimitive tupleType gvarElems // { + __toString = self: + "@${self.type} (${concatMapStringsSep "," toString self.value})"; + }; + + /* Returns the GVariant boolean from the given Nix bool value. + + Type: + mkBoolean :: Bool -> gvariant + */ + mkBoolean = v: + mkPrimitive type.boolean v // { + __toString = self: if self.value then "true" else "false"; + }; + + /* Returns the GVariant string from the given Nix string value. + + Type: + mkString :: String -> gvariant + */ + mkString = v: + let sanitize = s: replaceStrings [ "\n" ] [ "\\n" ] (escape [ "'" "\\" ] s); + in mkPrimitive type.string v // { + __toString = self: "'${sanitize self.value}'"; + }; + + /* Returns the GVariant object path from the given Nix string value. + + Type: + mkObjectpath :: String -> gvariant + */ + mkObjectpath = v: + mkPrimitive type.string v // { + __toString = self: "objectpath '${escape [ "'" ] self.value}'"; + }; + + /* Returns the GVariant uchar from the given Nix int value. + + Type: + mkUchar :: Int -> gvariant + */ + mkUchar = mkPrimitive type.uchar; + + /* Returns the GVariant int16 from the given Nix int value. + + Type: + mkInt16 :: Int -> gvariant + */ + mkInt16 = mkPrimitive type.int16; + + /* Returns the GVariant uint16 from the given Nix int value. + + Type: + mkUint16 :: Int -> gvariant + */ + mkUint16 = mkPrimitive type.uint16; + + /* Returns the GVariant int32 from the given Nix int value. + + Type: + mkInt32 :: Int -> gvariant + */ + mkInt32 = v: + mkPrimitive type.int32 v // { + __toString = self: toString self.value; + }; + + /* Returns the GVariant uint32 from the given Nix int value. + + Type: + mkUint32 :: Int -> gvariant + */ + mkUint32 = mkPrimitive type.uint32; + + /* Returns the GVariant int64 from the given Nix int value. + + Type: + mkInt64 :: Int -> gvariant + */ + mkInt64 = mkPrimitive type.int64; + + /* Returns the GVariant uint64 from the given Nix int value. + + Type: + mkUint64 :: Int -> gvariant + */ + mkUint64 = mkPrimitive type.uint64; + + /* Returns the GVariant double from the given Nix float value. + + Type: + mkDouble :: Float -> gvariant + */ + mkDouble = v: + mkPrimitive type.double v // { + __toString = self: toString self.value; + }; +} diff --git a/lib/tests/modules/gvariant.nix b/lib/tests/modules/gvariant.nix new file mode 100644 index 0000000000000..a792ebf85b743 --- /dev/null +++ b/lib/tests/modules/gvariant.nix @@ -0,0 +1,93 @@ +{ config, lib, ... }: + +let inherit (lib) concatStringsSep mapAttrsToList mkMerge mkOption types gvariant; +in { + options.examples = mkOption { type = types.attrsOf gvariant; }; + + config = { + examples = with gvariant; + mkMerge [ + { bool = true; } + { bool = true; } + + { float = 3.14; } + + { int32 = mkInt32 (- 42); } + { int32 = mkInt32 (- 42); } + + { uint32 = mkUint32 42; } + { uint32 = mkUint32 42; } + + { int16 = mkInt16 (-42); } + { int16 = mkInt16 (-42); } + + { uint16 = mkUint16 42; } + { uint16 = mkUint16 42; } + + { int64 = mkInt64 (-42); } + { int64 = mkInt64 (-42); } + + { uint64 = mkUint64 42; } + { uint64 = mkUint64 42; } + + { array1 = [ "one" ]; } + { array1 = mkArray [ "two" ]; } + { array2 = mkArray [ (mkInt32 1) ]; } + { array2 = mkArray [ (nkUint32 2) ]; } + + { emptyArray1 = [ ]; } + { emptyArray2 = mkEmptyArray type.uint32; } + + { string = "foo"; } + { string = "foo"; } + { + escapedString = '' + '\ + ''; + } + + { tuple = mkTuple [ (mkInt32 1) [ "foo" ] ]; } + + { maybe1 = mkNothing type.string; } + { maybe2 = mkJust (mkUint32 4); } + + { variant1 = mkVariant "foo"; } + { variant2 = mkVariant 42; } + + { dictionaryEntry = mkDictionaryEntry (mkInt32 1) [ "foo" ]; } + ]; + + assertions = [ + { + assertion = ( + let + mkLine = n: v: "${n} = ${toString (gvariant.mkValue v)}"; + result = concatStringsSep "\n" (mapAttrsToList mkLine config.examples); + in + result + "\n" + ) == '' + array1 = @as ['one','two'] + array2 = @au [1,2] + bool = true + dictionaryEntry = @{ias} {1,@as ['foo']} + emptyArray1 = @as [] + emptyArray2 = @au [] + escapedString = '\'\\\n' + float = 3.140000 + int = -42 + int16 = @n -42 + int64 = @x -42 + maybe1 = @ms nothing + maybe2 = just @u 4 + string = 'foo' + tuple = @(ias) (1,@as ['foo']) + uint16 = @q 42 + uint32 = @u 42 + uint64 = @t 42 + variant1 = @v <'foo'> + variant2 = @v <42> + ''; + } + ]; + }; +} From cce75fa51e1ef5d6f1657b9c0b9ca1963f8f3b03 Mon Sep 17 00:00:00 2001 From: linsui Date: Wed, 14 Jun 2023 16:22:17 +0800 Subject: [PATCH 2/5] nixos/dconf: refractor remove `with lib;` profiles option now accepts packages in addition to paths. profiles option is no longer internal. cfgDir definition has been inlined. pulled GIO_EXTRA_MODULES inside mkif. removed pointless comments with section headings. defined profiles are now turned into package, allowing to simplify the db update logic. --- nixos/modules/programs/dconf.nix | 74 +++++++++++++++----------------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix index 7261a143528ff..b5bfb79be200c 100644 --- a/nixos/modules/programs/dconf.nix +++ b/nixos/modules/programs/dconf.nix @@ -1,55 +1,50 @@ { 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 + + # Generate dconf profile + mkDconfProfile = name: value: + pkgs.runCommand "dconf-profile" { } '' + mkdir -p $out/etc/dconf/profile/ + cp ${value} $out/etc/dconf/profile/${name} ''; - }; in { - ###### interface - options = { programs.dconf = { - enable = 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; + enable = lib.mkEnableOption (lib.mdDoc "dconf"); + + profiles = lib.mkOption { + type = with lib.types; attrsOf (oneOf [ + path + package + ]); + description = lib.mdDoc "Attrset of dconf profiles."; }; - 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 = mkIf (cfg.profiles != {} || cfg.enable) { - environment.etc.dconf = mkIf (cfg.profiles != {} || cfg.packages != []) { - source = cfgDir; + 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 ]; @@ -59,8 +54,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" ]; + }; }; - } From fb52d5df8669aa7ca64f3cb83c42c118cd244603 Mon Sep 17 00:00:00 2001 From: linsui Date: Tue, 15 Aug 2023 17:58:02 +0800 Subject: [PATCH 3/5] nixos/dconf: add settings support --- lib/generators.nix | 8 ++ nixos/modules/programs/dconf.nix | 155 ++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 5 deletions(-) diff --git a/lib/generators.nix b/lib/generators.nix index c37be1942d82f..0368577d5a512 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 b5bfb79be200c..567d42a4a41ba 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 { From 038d78d4ce9fdef9f271891953d946233fe5ed42 Mon Sep 17 00:00:00 2001 From: linsui Date: Wed, 14 Jun 2023 16:27:19 +0800 Subject: [PATCH 4/5] nixos/dconf: add locks support --- nixos/modules/programs/dconf.nix | 22 +++++++++++++++++++++ nixos/tests/all-tests.nix | 1 + nixos/tests/dconf.nix | 34 ++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 nixos/tests/dconf.nix diff --git a/nixos/modules/programs/dconf.nix b/nixos/modules/programs/dconf.nix index 567d42a4a41ba..cf53658c4fada 100644 --- a/nixos/modules/programs/dconf.nix +++ b/nixos/modules/programs/dconf.nix @@ -26,11 +26,17 @@ let 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); }); @@ -103,6 +109,22 @@ let } ''; }; + 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`."; + }; }; }; diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 530447b997863..d867e4549cf92 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -210,6 +210,7 @@ in { custom-ca = handleTest ./custom-ca.nix {}; croc = handleTest ./croc.nix {}; darling = handleTest ./darling.nix {}; + dconf = handleTest ./dconf.nix {}; deepin = handleTest ./deepin.nix {}; deluge = handleTest ./deluge.nix {}; dendrite = handleTest ./matrix/dendrite.nix {}; diff --git a/nixos/tests/dconf.nix b/nixos/tests/dconf.nix new file mode 100644 index 0000000000000..86f703e3b98e8 --- /dev/null +++ b/nixos/tests/dconf.nix @@ -0,0 +1,34 @@ +import ./make-test-python.nix + ({ lib, ... }: + { + name = "dconf"; + + meta.maintainers = with lib.maintainers; [ + linsui + ]; + + nodes.machine = { config, pkgs, lib, ... }: { + users.extraUsers.alice = { isNormalUser = true; }; + programs.dconf = with lib.gvariant; { + enable = true; + profiles.user.databases = [ + { + settings = { + "test/not/locked" = mkInt32 1; + "test/is/locked" = "locked"; + }; + locks = [ + "/test/is/locked" + ]; + } + ]; + }; + }; + + testScript = '' + machine.succeed("test $(dconf read -d /test/not/locked) == 1") + machine.succeed("test $(dconf read -d /test/is/locked) == \"'locked'\"") + machine.fail("sudo -u alice dbus-run-session -- dconf write /test/is/locked \"@s 'unlocked'\"") + machine.succeed("sudo -u alice dbus-run-session -- dconf write /test/not/locked \"@i 2\"") + ''; + }) From 0e6827ed9c09e517d47575e1b9e19f52e0961225 Mon Sep 17 00:00:00 2001 From: linsui Date: Wed, 14 Jun 2023 16:28:04 +0800 Subject: [PATCH 5/5] nixos/gdm: switch to dconf settings --- .../services/x11/display-managers/gdm.nix | 40 ++++--------------- pkgs/desktops/gnome/core/gdm/default.nix | 38 ++++++++++++------ 2 files changed, 32 insertions(+), 46 deletions(-) diff --git a/nixos/modules/services/x11/display-managers/gdm.nix b/nixos/modules/services/x11/display-managers/gdm.nix index 676d08b93e2c4..e6923bcbb56c0 100644 --- a/nixos/modules/services/x11/display-managers/gdm.nix +++ b/nixos/modules/services/x11/display-managers/gdm.nix @@ -231,40 +231,14 @@ in systemd.user.services.dbus.wantedBy = [ "default.target" ]; - programs.dconf.profiles.gdm = - let - customDconf = pkgs.writeTextFile { - name = "gdm-dconf"; - destination = "/dconf/gdm-custom"; - text = '' - ${optionalString (!cfg.gdm.autoSuspend) '' - [org/gnome/settings-daemon/plugins/power] - sleep-inactive-ac-type='nothing' - sleep-inactive-battery-type='nothing' - sleep-inactive-ac-timeout=0 - sleep-inactive-battery-timeout=0 - ''} - ''; - }; - - customDconfDb = pkgs.stdenv.mkDerivation { - name = "gdm-dconf-db"; - buildCommand = '' - ${pkgs.dconf}/bin/dconf compile $out ${customDconf}/dconf - ''; + programs.dconf.profiles.gdm.databases = lib.optionals (!cfg.gdm.autoSuspend) [{ + settings."org/gnome/settings-daemon/plugins/power" = { + sleep-inactive-ac-type = "nothing"; + sleep-inactive-battery-type = "nothing"; + sleep-inactive-ac-timeout = lib.gvariant.mkInt32 0; + sleep-inactive-battery-timeout = lib.gvariant.mkInt32 0; }; - in pkgs.stdenv.mkDerivation { - name = "dconf-gdm-profile"; - buildCommand = '' - # Check that the GDM profile starts with what we expect. - if [ $(head -n 1 ${gdm}/share/dconf/profile/gdm) != "user-db:user" ]; then - echo "GDM dconf profile changed, please update gdm.nix" - exit 1 - fi - # Insert our custom DB behind it. - sed '2ifile-db:${customDconfDb}' ${gdm}/share/dconf/profile/gdm > $out - ''; - }; + }] ++ [ "${gdm}/share/gdm/greeter-dconf-defaults" ]; # Use AutomaticLogin if delay is zero, because it's immediate. # Otherwise with TimedLogin with zero seconds the prompt is still diff --git a/pkgs/desktops/gnome/core/gdm/default.nix b/pkgs/desktops/gnome/core/gdm/default.nix index 8faa1615dc07d..cfdde43ae776b 100644 --- a/pkgs/desktops/gnome/core/gdm/default.nix +++ b/pkgs/desktops/gnome/core/gdm/default.nix @@ -1,4 +1,5 @@ -{ lib, stdenv +{ lib +, stdenv , fetchurl , fetchpatch , substituteAll @@ -8,7 +9,6 @@ , pkg-config , glib , itstool -, libxml2 , xorg , accountsservice , libX11 @@ -24,12 +24,12 @@ , audit , gobject-introspection , plymouth -, librsvg , coreutils , xorgserver , xwayland , dbus , nixos-icons +, runCommand }: let @@ -41,21 +41,21 @@ let in -stdenv.mkDerivation rec { +stdenv.mkDerivation (finalAttrs: { pname = "gdm"; version = "44.1"; outputs = [ "out" "dev" ]; src = fetchurl { - url = "mirror://gnome/sources/gdm/${lib.versions.major version}/${pname}-${version}.tar.xz"; + url = "mirror://gnome/sources/gdm/${lib.versions.major finalAttrs.version}/${finalAttrs.pname}-${finalAttrs.version}.tar.xz"; sha256 = "aCZrOr59KPxGnQBnqsnF2rsMp5UswffCOKBJUfPcWw0="; }; mesonFlags = [ "-Dgdm-xsession=true" # TODO: Setup a default-path? https://gitlab.gnome.org/GNOME/gdm/-/blob/6fc40ac6aa37c8ad87c32f0b1a5d813d34bf7770/meson_options.txt#L6 - "-Dinitial-vt=${passthru.initialVT}" + "-Dinitial-vt=${finalAttrs.passthru.initialVT}" "-Dudev-dir=${placeholder "out"}/lib/udev/rules.d" "-Dsystemdsystemunitdir=${placeholder "out"}/lib/systemd/system" "-Dsystemduserunitdir=${placeholder "out"}/lib/systemd/user" @@ -131,21 +131,21 @@ stdenv.mkDerivation rec { ''; preInstall = '' - install -D ${override} ${DESTDIR}/$out/share/glib-2.0/schemas/org.gnome.login-screen.gschema.override + install -D ${override} $DESTDIR/$out/share/glib-2.0/schemas/org.gnome.login-screen.gschema.override ''; postInstall = '' # Move stuff from DESTDIR to proper location. # We use rsync to merge the directories. - rsync --archive "${DESTDIR}/etc" "$out" - rm --recursive "${DESTDIR}/etc" + rsync --archive "$DESTDIR/etc" "$out" + rm --recursive "$DESTDIR/etc" for o in $(getAllOutputNames); do if [[ "$o" = "debug" ]]; then continue; fi - rsync --archive "${DESTDIR}/''${!o}" "$(dirname "''${!o}")" - rm --recursive "${DESTDIR}/''${!o}" + rsync --archive "$DESTDIR/''${!o}" "$(dirname "''${!o}")" + rm --recursive "$DESTDIR/''${!o}" done # Ensure the DESTDIR is removed. - rmdir "${DESTDIR}/nix/store" "${DESTDIR}/nix" "${DESTDIR}" + rmdir "$DESTDIR/nix/store" "$DESTDIR/nix" "$DESTDIR" # We are setting DESTDIR so the post-install script does not compile the schemas. glib-compile-schemas "$out/share/glib-2.0/schemas" @@ -170,6 +170,18 @@ stdenv.mkDerivation rec { # Used in GDM NixOS module # Don't remove. initialVT = "7"; + dconfDb = "${finalAttrs.finalPackage}/share/gdm/greeter-dconf-defaults"; + dconfProfile = "user-db:user\nfile-db:${finalAttrs.passthru.dconfDb}"; + + tests = { + profile = runCommand "gdm-profile-test" { } '' + if test "${finalAttrs.passthru.dconfProfile}" != "$(cat ${finalAttrs.finalPackage}/share/dconf/profile/gdm)"; then + echo "GDM dconf profile changed, please update gdm.nix" + exit 1 + fi + touch $out + ''; + }; }; meta = with lib; { @@ -179,4 +191,4 @@ stdenv.mkDerivation rec { maintainers = teams.gnome.members; platforms = platforms.linux; }; -} +})