nix: add assumeXdg option

The use-xdg-base-directories Nix setting can be set globally in
/etc/nix/nix.conf, in which case Home Manager doesn't know about it.
Users could fix that by also setting it, redundantly, in `nix.settings`,
but then Nix issues a lot of spurious warnings about
use-xdg-base-directories being a restricted setting that untrusted users
can't pass on to the daemon.

As an alternative, users can now set `nix.assumeXdg`, which makes Home
Manager assume that use-xdg-base-directories is in effect without adding
it to the user's nix.conf file.
This commit is contained in:
Ryan Hendrickson 2026-01-12 20:54:04 -05:00 committed by Austin Horstman
parent b3258dece8
commit 26dfad95d9
7 changed files with 217 additions and 28 deletions

View file

@ -618,7 +618,7 @@ in
home.profileDirectory =
if config.submoduleSupport.enable && config.submoduleSupport.externalPackageInstall then
"/etc/profiles/per-user/${cfg.username}"
else if config.nix.enable && (config.nix.settings.use-xdg-base-directories or false) then
else if config.nix.useXdg then
"${config.xdg.stateHome}/nix/profile"
else
cfg.homeDirectory + "/.nix-profile";

View file

@ -1,6 +1,7 @@
{
config,
lib,
osConfig,
pkgs,
...
}:
@ -21,6 +22,7 @@ let
isList
isString
literalExpression
literalMD
mapAttrsToList
mkDefault
mkEnableOption
@ -41,7 +43,7 @@ let
nixPath = concatStringsSep ":" cfg.nixPath;
useXdg = config.nix.enable && (config.nix.settings.use-xdg-base-directories or false);
inherit (config.nix) useXdg;
defexprDir =
if useXdg then
"${config.xdg.stateHome}/nix/defexpr"
@ -296,6 +298,30 @@ in
'';
};
assumeXdg = mkOption {
type = types.bool;
default = false;
description = ''
Whether Home Manager should assume that Nix is configured to use XDG
base directories. Note that this doesn't change the behavior of Nix. To
do that, set nix.settings.use-xdg-base-directories instead. This option
is intended for settings in which use-xdg-base-directories is set
globally or nix.conf is unmanaged by Home Manager.
'';
};
useXdg = mkOption {
type = types.bool;
visible = false;
readOnly = true;
defaultText = literalMD ''
In descending priority:
* `true` if `nix.assumeXdg` is `true`
* `nix.settings.use-xdg-base-directories` if it is set and `nix.enable` is `true`
* `osConfig.nix.settings.use-xdg-base-directories` if it is set and `osConfig.nix.enable` is `true`
'';
};
extraOptions = mkOption {
type = types.lines;
default = "";
@ -326,39 +352,69 @@ in
};
};
config = mkIf cfg.enable (mkMerge [
(mkIf (cfg.nixPath != [ ] && !cfg.keepOldNixPath) {
home.sessionVariables.NIX_PATH = "${nixPath}";
})
config = mkMerge [
{
nix.useXdg =
let
# Helper that checks if use-xdg-base-directories is in effect for the
# given Nix configuration, falling back to a given default value if
# it is undefined or the configuration is not enabled.
checkNixXdg = def: cfg: if cfg.enable then cfg.settings.use-xdg-base-directories or def else def;
(mkIf (cfg.nixPath != [ ] && cfg.keepOldNixPath) {
home.sessionVariables.NIX_PATH = "${nixPath}\${NIX_PATH:+:$NIX_PATH}";
})
# Whether the OS configuration indicates that XDG directories should
# be used.
osUseXdg = checkNixXdg false osConfig.nix or { enable = false; };
(lib.mkIf (cfg.channels != { }) {
nix.nixPath = [ channelPath ];
home.file."${channelPath}".source = channelsDrv;
})
# Whether the user configuration indicates that XDG directories
# should be used, falling back to the OS configuration if not
# specified.
hmUseXdg = checkNixXdg osUseXdg cfg;
in
cfg.assumeXdg || hmUseXdg;
(mkIf (cfg.registry != { }) {
xdg.configFile."nix/registry.json".source = jsonFormat.generate "registry.json" {
version = cfg.registryVersion;
flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
};
})
(mkIf (cfg.settings != { } || cfg.extraOptions != "") {
assertions = [
{
assertion = cfg.package != null;
assertion = !(cfg.assumeXdg && cfg.enable && cfg.settings ? use-xdg-base-directories);
message = ''
A corresponding Nix package must be specified via `nix.package` for generating
nix.conf.
`nix.assumeXdg` should not be set if `nix.settings.use-xdg-base-directories` is.
'';
}
];
}
(mkIf cfg.enable (mkMerge [
(mkIf (cfg.nixPath != [ ] && !cfg.keepOldNixPath) {
home.sessionVariables.NIX_PATH = "${nixPath}";
})
xdg.configFile."nix/nix.conf".source = nixConf;
})
]);
(mkIf (cfg.nixPath != [ ] && cfg.keepOldNixPath) {
home.sessionVariables.NIX_PATH = "${nixPath}\${NIX_PATH:+:$NIX_PATH}";
})
(lib.mkIf (cfg.channels != { }) {
nix.nixPath = [ channelPath ];
home.file."${channelPath}".source = channelsDrv;
})
(mkIf (cfg.registry != { }) {
xdg.configFile."nix/registry.json".source = jsonFormat.generate "registry.json" {
version = cfg.registryVersion;
flakes = mapAttrsToList (n: v: { inherit (v) from to exact; }) cfg.registry;
};
})
(mkIf (cfg.settings != { } || cfg.extraOptions != "") {
assertions = [
{
assertion = cfg.package != null;
message = ''
A corresponding Nix package must be specified via `nix.package` for generating
nix.conf.
'';
}
];
xdg.configFile."nix/nix.conf".source = nixConf;
})
]))
];
}

View file

@ -112,7 +112,7 @@ in
in
{
NIX_PATH =
if config.nix.enable && (config.nix.settings.use-xdg-base-directories or false) then
if config.nix.useXdg then
"${config.xdg.stateHome}/nix/defexpr/channels\${NIX_PATH:+:}$NIX_PATH"
else
"$HOME/.nix-defexpr/channels\${NIX_PATH:+:}$NIX_PATH";

View file

@ -5,4 +5,5 @@
nix-keep-old-nix-path = ./keep-old-nix-path.nix;
nix-example-channels = ./example-channels.nix;
nix-example-channels-xdg = ./example-channels-xdg.nix;
nix-use-xdg = ./use-xdg.nix;
}

View file

@ -0,0 +1,92 @@
{ lib, pkgs, ... }:
let
nixosLib = import /${pkgs.path}/nixos/lib { inherit lib; };
getUseXdg =
osNix: userNix:
(nixosLib.evalTest {
hostPkgs = pkgs;
nodes.machine.imports = [
../../../../nixos
{
nix = osNix;
home-manager.users.user = {
home.stateVersion = "26.05";
nix = userNix;
};
}
];
}).config.nodes.machine.home-manager.users.user.nix.useXdg;
in
{
nmt.script =
# Defaults to false
assert !getUseXdg { } { };
# Test OS config
assert !getUseXdg { enable = true; } { };
assert
!getUseXdg {
enable = false;
settings.use-xdg-base-directories = true;
} { };
assert getUseXdg {
enable = true;
settings.use-xdg-base-directories = true;
} { };
# Test user config
assert !getUseXdg { } { enable = true; };
assert
!getUseXdg { } {
enable = false;
settings.use-xdg-base-directories = true;
};
assert getUseXdg { } {
enable = true;
settings.use-xdg-base-directories = true;
};
# Fallback to OS config if user config is unset
assert getUseXdg
{
enable = true;
settings.use-xdg-base-directories = true;
}
{
enable = true;
};
# But user config takes precedence
assert
!getUseXdg
{
enable = true;
settings.use-xdg-base-directories = true;
}
{
enable = true;
settings.use-xdg-base-directories = false;
};
assert getUseXdg
{
enable = true;
settings.use-xdg-base-directories = false;
}
{
enable = true;
settings.use-xdg-base-directories = true;
};
# assumeXdg also takes precedence
assert getUseXdg { } { assumeXdg = true; };
assert getUseXdg
{
enable = true;
settings.use-xdg-base-directories = false;
}
{
enable = false;
assumeXdg = true;
};
"";
}

View file

@ -3,4 +3,5 @@
targets-generic-linux-gpu-basic = ./generic-linux-gpu/basic-enable.nix;
targets-generic-linux-gpu-setup-contents = ./generic-linux-gpu/setup-package-contents.nix;
targets-generic-linux-gpu-nvidia = ./generic-linux-gpu/nvidia-enabled.nix;
targets-generic-linux-xdg = ./generic-linux-xdg.nix;
}

View file

@ -0,0 +1,39 @@
{ lib, pkgs, ... }:
let
expectedXdgDataDirs = lib.concatStringsSep ":" [
"\${NIX_STATE_DIR:-/nix/var/nix}/profiles/default/share"
"/home/hm-user/.local/state/nix/profile/share"
"/usr/share/ubuntu"
"/usr/local/share"
"/usr/share"
"/var/lib/snapd/desktop"
"/foo"
];
in
{
config = {
targets.genericLinux.enable = true;
xdg.systemDirs.data = [ "/foo" ];
nix.assumeXdg = true;
nmt.script = ''
envFile=home-files/.config/environment.d/10-home-manager.conf
assertFileExists $envFile
assertFileContains $envFile \
'XDG_DATA_DIRS=${expectedXdgDataDirs}''${XDG_DATA_DIRS:+:$XDG_DATA_DIRS}'
assertFileContains $envFile \
'TERMINFO_DIRS=/home/hm-user/.local/state/nix/profile/share/terminfo:$TERMINFO_DIRS''${TERMINFO_DIRS:+:}/etc/terminfo:/lib/terminfo:/usr/share/terminfo'
sessionVarsFile=home-path/etc/profile.d/hm-session-vars.sh
assertFileExists $sessionVarsFile
assertFileContains $sessionVarsFile \
'. "${pkgs.nix}/etc/profile.d/nix.sh"'
assertFileContains \
home-path/etc/profile.d/hm-session-vars.sh \
'export TERM="$TERM"'
'';
};
}