Merge pull request #234615 from linsui/dconf

nixos/dconf: support generating from attrs

Changed files
+660 -83
doc
lib
nixos
modules
programs
services
x11
display-managers
tests
pkgs
desktops
gnome
core
+1
doc/default.nix
···
{ 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"; }
];
};
+1
lib/default.nix
···
# serialization
cli = callLibs ./cli.nix;
+
gvariant = callLibs ./gvariant.nix;
generators = callLibs ./generators.nix;
# misc
+8
lib/generators.nix
···
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.
*/
+290
lib/gvariant.nix
···
+
# 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;
+
};
+
}
+93
lib/tests/modules/gvariant.nix
···
+
{ 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>
+
'';
+
}
+
];
+
};
+
}
+200 -37
nixos/modules/programs/dconf.nix
···
{ 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 ];
···
# 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" ];
+
};
};
-
}
+7 -33
nixos/modules/services/x11/display-managers/gdm.nix
···
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
+1
nixos/tests/all-tests.nix
···
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 {};
+34
nixos/tests/dconf.nix
···
+
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\"")
+
'';
+
})
+25 -13
pkgs/desktops/gnome/core/gdm/default.nix
···
-
{ lib, stdenv
+
{ lib
+
, stdenv
, fetchurl
, fetchpatch
, substituteAll
···
, pkg-config
, glib
, itstool
-
, libxml2
, xorg
, accountsservice
, libX11
···
, audit
, gobject-introspection
, plymouth
-
, librsvg
, coreutils
, xorgserver
, xwayland
, dbus
, nixos-icons
+
, runCommand
}:
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"
···
'';
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"
···
# 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; {
···
maintainers = teams.gnome.members;
platforms = platforms.linux;
};
-
}
+
})