mirror of
https://github.com/nix-community/home-manager.git
synced 2026-01-11 09:29:41 +08:00
dconf: support configuring specific user databases (#6301)
By default, dconf uses $XDG_CONFIG_HOME/dconf/user as the user database, but this can be changed by specifying user-db:<name> in a profile file and setting the DCONF_PROFILE environment variable to that profile. One may want to use different user databases for different DE/WMs to avoid collision. Currently the module invokes dconf without touching DCONF_PROFILE, which means that 1) it is unable to configure multiple different user databases, and 2) the behavior of activation script will be affected by the DCONF_PROFILE environment variable when it is invoked, possibly leading to undesired results. This PR adds a dconf.databases option, so that settings under dconf.databases.<name> will be written to $XDG_CONFIG_HOME/dconf/<name>. The old dconf.settings option is left as-is to avoid breaking compatibility.
This commit is contained in:
@@ -17,15 +17,29 @@ let
|
||||
# The dconf keys managed by this configuration. We store this as part of the
|
||||
# generation state to be able to reset keys that become unmanaged during
|
||||
# switch.
|
||||
stateDconfKeys = pkgs.writeText "dconf-keys.json" (
|
||||
builtins.toJSON (
|
||||
lib.concatLists (
|
||||
lib.mapAttrsToList (
|
||||
dir: entries: lib.mapAttrsToList (key: _: "/${dir}/${key}") entries
|
||||
) cfg.settings
|
||||
mkStateDconfKeys =
|
||||
nameSuffix: settings:
|
||||
pkgs.writeText "dconf-keys${nameSuffix}.json" (
|
||||
builtins.toJSON (
|
||||
lib.concatLists (
|
||||
lib.mapAttrsToList (dir: entries: lib.mapAttrsToList (key: _: "/${dir}/${key}") entries) settings
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
databases =
|
||||
lib.optional (cfg.settings != { }) {
|
||||
dconfProfile = null;
|
||||
stateDconfKeys = mkStateDconfKeys "" cfg.settings;
|
||||
inherit (cfg) settings;
|
||||
}
|
||||
++ lib.mapAttrsToList (name: value: {
|
||||
dconfProfile = pkgs.writeText "dconf-profile-${name}" ''
|
||||
user-db:${name}
|
||||
'';
|
||||
stateDconfKeys = mkStateDconfKeys "-${name}" value;
|
||||
settings = value;
|
||||
}) cfg.databases;
|
||||
|
||||
in
|
||||
{
|
||||
@@ -81,73 +95,87 @@ in
|
||||
to convert dconf database dumps into compatible Nix expression.
|
||||
'';
|
||||
};
|
||||
databases = lib.mkOption {
|
||||
type = with types; attrsOf (attrsOf (attrsOf lib.hm.types.gvariant));
|
||||
default = { };
|
||||
description = ''
|
||||
Settings to write to specific dconf user databases.
|
||||
See [](#opt-dconf.settings) for details.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.enable && cfg.settings != { }) {
|
||||
config = lib.mkIf (cfg.enable && databases != [ ]) {
|
||||
# Make sure the dconf directory exists.
|
||||
xdg.configFile."dconf/.keep".source = builtins.toFile "keep" "";
|
||||
|
||||
home.extraBuilderCommands = ''
|
||||
mkdir -p $out/state/
|
||||
ln -s ${stateDconfKeys} $out/state/${stateDconfKeys.name}
|
||||
'';
|
||||
''
|
||||
+ lib.concatMapStrings (db: ''
|
||||
ln -s ${db.stateDconfKeys} $out/state/${db.stateDconfKeys.name}
|
||||
'') databases;
|
||||
|
||||
home.activation.dconfSettings = lib.hm.dag.entryAfter [ "installPackages" ] (
|
||||
let
|
||||
iniFile = pkgs.writeText "hm-dconf.ini" (toDconfIni cfg.settings);
|
||||
lib.concatMapStrings (
|
||||
db:
|
||||
let
|
||||
iniFile = pkgs.writeText "hm-dconf.ini" (toDconfIni db.settings);
|
||||
|
||||
statePath = "state/${stateDconfKeys.name}";
|
||||
statePath = "state/${db.stateDconfKeys.name}";
|
||||
|
||||
cleanup = pkgs.writeShellScript "dconf-cleanup" ''
|
||||
set -euo pipefail
|
||||
cleanup = pkgs.writeShellScript "dconf-cleanup" ''
|
||||
set -euo pipefail
|
||||
|
||||
${config.lib.bash.initHomeManagerLib}
|
||||
${config.lib.bash.initHomeManagerLib}
|
||||
|
||||
PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.dconf
|
||||
pkgs.jq
|
||||
]
|
||||
}''${PATH:+:}$PATH
|
||||
PATH=${
|
||||
lib.makeBinPath [
|
||||
pkgs.dconf
|
||||
pkgs.jq
|
||||
]
|
||||
}''${PATH:+:}$PATH
|
||||
|
||||
oldState="$1"
|
||||
newState="$2"
|
||||
oldState="$1"
|
||||
newState="$2"
|
||||
|
||||
# Can't do cleanup if we don't know the old state.
|
||||
if [[ ! -f $oldState ]]; then
|
||||
exit 0
|
||||
# Can't do cleanup if we don't know the old state.
|
||||
if [[ ! -f $oldState ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Reset all keys that are present in the old generation but not the new
|
||||
# one.
|
||||
jq -r -n \
|
||||
--slurpfile old "$oldState" \
|
||||
--slurpfile new "$newState" \
|
||||
'($old[] - $new[])[]' \
|
||||
| while read -r key; do
|
||||
verboseEcho "Resetting dconf key \"$key\""
|
||||
run $DCONF_DBUS_RUN_SESSION dconf reset "$key"
|
||||
done
|
||||
'';
|
||||
envCommand = lib.optionalString (db.dconfProfile != null) "env DCONF_PROFILE=${db.dconfProfile}";
|
||||
in
|
||||
''
|
||||
if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then
|
||||
export DCONF_DBUS_RUN_SESSION="${envCommand}"
|
||||
else
|
||||
export DCONF_DBUS_RUN_SESSION="${pkgs.dbus}/bin/dbus-run-session --dbus-daemon=${pkgs.dbus}/bin/dbus-daemon ${envCommand}"
|
||||
fi
|
||||
|
||||
# Reset all keys that are present in the old generation but not the new
|
||||
# one.
|
||||
jq -r -n \
|
||||
--slurpfile old "$oldState" \
|
||||
--slurpfile new "$newState" \
|
||||
'($old[] - $new[])[]' \
|
||||
| while read -r key; do
|
||||
verboseEcho "Resetting dconf key \"$key\""
|
||||
run $DCONF_DBUS_RUN_SESSION dconf reset "$key"
|
||||
done
|
||||
'';
|
||||
in
|
||||
''
|
||||
if [[ -v DBUS_SESSION_BUS_ADDRESS ]]; then
|
||||
export DCONF_DBUS_RUN_SESSION=""
|
||||
else
|
||||
export DCONF_DBUS_RUN_SESSION="${pkgs.dbus}/bin/dbus-run-session --dbus-daemon=${pkgs.dbus}/bin/dbus-daemon"
|
||||
fi
|
||||
if [[ -v oldGenPath ]]; then
|
||||
${cleanup} \
|
||||
"$oldGenPath/${statePath}" \
|
||||
"$newGenPath/${statePath}"
|
||||
fi
|
||||
|
||||
if [[ -v oldGenPath ]]; then
|
||||
${cleanup} \
|
||||
"$oldGenPath/${statePath}" \
|
||||
"$newGenPath/${statePath}"
|
||||
fi
|
||||
run $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / < ${iniFile}
|
||||
|
||||
run $DCONF_DBUS_RUN_SESSION ${pkgs.dconf}/bin/dconf load / < ${iniFile}
|
||||
|
||||
unset DCONF_DBUS_RUN_SESSION
|
||||
''
|
||||
unset DCONF_DBUS_RUN_SESSION
|
||||
''
|
||||
) databases
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ let
|
||||
standalone-flake-basics = runTest ./standalone/flake-basics.nix;
|
||||
standalone-specialisation = runTest ./standalone/specialisation.nix;
|
||||
standalone-standard-basics = runTest ./standalone/standard-basics.nix;
|
||||
dconf = runTest ./standalone/dconf.nix;
|
||||
};
|
||||
in
|
||||
tests
|
||||
|
||||
22
tests/integration/standalone/dconf-home.nix
Normal file
22
tests/integration/standalone/dconf-home.nix
Normal file
@@ -0,0 +1,22 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
home.username = "alice";
|
||||
home.homeDirectory = "/home/alice";
|
||||
|
||||
home.stateVersion = "25.11"; # Please read the comment before changing.
|
||||
|
||||
# Let Home Manager install and manage itself.
|
||||
programs.home-manager.enable = true;
|
||||
|
||||
dconf.settings = {
|
||||
foo = {
|
||||
bar = 42;
|
||||
};
|
||||
};
|
||||
|
||||
dconf.databases.custom = {
|
||||
foo1 = {
|
||||
bar1 = 42;
|
||||
};
|
||||
};
|
||||
}
|
||||
77
tests/integration/standalone/dconf.nix
Normal file
77
tests/integration/standalone/dconf.nix
Normal file
@@ -0,0 +1,77 @@
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
name = "dconf";
|
||||
meta.maintainers = [ pkgs.lib.maintainers.rycee ];
|
||||
|
||||
nodes.machine = {
|
||||
imports = [ "${pkgs.path}/nixos/modules/installer/cd-dvd/channel.nix" ];
|
||||
virtualisation.memorySize = 2048;
|
||||
users.users.alice = {
|
||||
isNormalUser = true;
|
||||
description = "Alice Foobar";
|
||||
password = "foobar";
|
||||
uid = 1000;
|
||||
};
|
||||
programs.dconf = {
|
||||
enable = true;
|
||||
profiles.custom = pkgs.writeText "dconf-profile-custom" ''
|
||||
user-db:custom
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("network.target")
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
|
||||
home_manager = "${../../..}"
|
||||
|
||||
def login_as_alice():
|
||||
machine.wait_until_tty_matches("1", "login: ")
|
||||
machine.send_chars("alice\n")
|
||||
machine.wait_until_tty_matches("1", "Password: ")
|
||||
machine.send_chars("foobar\n")
|
||||
machine.wait_until_tty_matches("1", "alice\\@machine")
|
||||
|
||||
def logout_alice():
|
||||
machine.send_chars("exit\n")
|
||||
|
||||
def alice_cmd(cmd):
|
||||
return f"su -l alice --shell /bin/sh -c $'export XDG_RUNTIME_DIR=/run/user/$UID ; {cmd}'"
|
||||
|
||||
def succeed_as_alice(cmd):
|
||||
return machine.succeed(alice_cmd(cmd))
|
||||
|
||||
def fail_as_alice(cmd):
|
||||
return machine.fail(alice_cmd(cmd))
|
||||
|
||||
# Create a persistent login so that Alice has a systemd session.
|
||||
login_as_alice()
|
||||
|
||||
# Set up a home-manager channel.
|
||||
succeed_as_alice(" ; ".join([
|
||||
"mkdir -p /home/alice/.nix-defexpr/channels",
|
||||
f"ln -s {home_manager} /home/alice/.nix-defexpr/channels/home-manager"
|
||||
]))
|
||||
|
||||
succeed_as_alice("nix-shell \"<home-manager>\" -A install")
|
||||
|
||||
succeed_as_alice("cp ${./dconf-home.nix} /home/alice/.config/home-manager/home.nix")
|
||||
succeed_as_alice("home-manager switch")
|
||||
|
||||
succeed_as_alice("test -e /home/alice/.config/dconf/user")
|
||||
actual = succeed_as_alice("dconf dump /")
|
||||
expected = """[foo]
|
||||
bar=42
|
||||
"""
|
||||
assert actual == expected, "invalid content in dconf database \"user\""
|
||||
|
||||
succeed_as_alice("test -e /home/alice/.config/dconf/custom")
|
||||
actual = succeed_as_alice("DCONF_PROFILE=custom dconf dump /")
|
||||
expected = """[foo1]
|
||||
bar1=42
|
||||
"""
|
||||
assert actual == expected, "invalid content in dconf database \"custom\""
|
||||
'';
|
||||
}
|
||||
Reference in New Issue
Block a user