2.home-manager/nixos/default.nix
Vidhan Bhatt e8e0f53079
nixos: flip && to prevent recursion errors
The recent change to add the ability to use systemd units for users made it so configs with home-manager.users dependent on config.users.users failed with recursion errors. Simply flipping the &&s so it avoids running the check if the feature is disabled fixes this.
2026-01-14 16:35:15 -06:00

184 lines
6 KiB
Nix

{
config,
lib,
pkgs,
utils,
...
}:
let
inherit (lib) mkIf;
cfg = config.home-manager;
baseService = username: {
Type = "oneshot";
RemainAfterExit = "yes";
TimeoutStartSec = "5m";
SyslogIdentifier = "hm-activate-${username}";
};
baseUnit = username: {
description = "Home Manager environment for ${username}";
stopIfChanged = false;
serviceConfig = baseService username;
environment = lib.mkMerge [
{
# needed to run qt programs like kwriteconfig
QT_QPA_PLATFORM = "offscreen";
}
(mkIf cfg.verbose { VERBOSE = "1"; })
(mkIf (cfg.backupCommand != null) {
HOME_MANAGER_BACKUP_COMMAND = cfg.backupCommand;
})
(mkIf (cfg.backupFileExtension != null) {
HOME_MANAGER_BACKUP_EXT = cfg.backupFileExtension;
})
(mkIf cfg.overwriteBackup {
HOME_MANAGER_BACKUP_OVERWRITE = "1";
})
];
};
# we use a service separated from nixos-activation
# to keep the logs separate
hmDropIn = "/share/systemd/user/home-manager.service.d";
in
{
imports = [ ./common.nix ];
options.home-manager.startAsUserService = lib.mkOption {
type = lib.types.bool;
default = false;
description = ''
Whether to activate each user's environment on demand, when
they log in, using a systemd user service. If this option is
off, all configured users' environments are instead activated
during boot-up.
This option needs to be turned on in any situation where users'
home directories are not available until they log in; for
example, when using pam_mount.
Other usage scenarios are still experimental. It may speed up
boot when there are many users; this has not yet been confirmed.
It could break configurations where the configured users do not
(or do not always) run their processes within a complete
systemd-managed user context.
'';
};
config = lib.mkMerge [
{
home-manager = {
extraSpecialArgs.nixosConfig = config;
sharedModules = [
{
key = "home-manager#nixos-shared-module";
config = {
# The per-user directory inside /etc/profiles is not known by
# fontconfig by default.
fonts.fontconfig.enable = lib.mkDefault (cfg.useUserPackages && config.fonts.fontconfig.enable);
# Inherit glibcLocales setting from NixOS.
i18n.glibcLocales = lib.mkDefault config.i18n.glibcLocales;
};
}
];
};
}
(mkIf (!cfg.startAsUserService && cfg.users != { }) {
systemd.services = lib.mapAttrs' (
_: usercfg:
let
inherit (usercfg.home) username homeDirectory activationPackage;
driverVersion = if cfg.enableLegacyProfileManagement then "0" else "1";
in
lib.nameValuePair "home-manager-${utils.escapeSystemdPath username}" (
lib.attrsets.recursiveUpdate (baseUnit username) {
wantedBy = [ "multi-user.target" ];
wants = [ "nix-daemon.socket" ];
after = [ "nix-daemon.socket" ];
before = [ "systemd-user-sessions.service" ];
unitConfig.RequiresMountsFor = homeDirectory;
serviceConfig.User = username;
serviceConfig.ExecStart =
let
systemctl = "XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$UID} systemctl";
sed = "${pkgs.gnused}/bin/sed";
exportedSystemdVariables = lib.concatStringsSep "|" [
"DBUS_SESSION_BUS_ADDRESS"
"DISPLAY"
"WAYLAND_DISPLAY"
"XAUTHORITY"
"XDG_RUNTIME_DIR"
];
setupEnv = pkgs.writeScript "hm-setup-env" ''
#! ${pkgs.runtimeShell} -el
# The activation script is run by a login shell to make sure
# that the user is given a sane environment.
# If the user is logged in, import variables from their current
# session environment.
eval "$(
${systemctl} --user show-environment 2> /dev/null \
| ${sed} -En '/^(${exportedSystemdVariables})=/s/^/export /p'
)"
exec "$1/activate" --driver-version ${driverVersion}
'';
in
"${setupEnv} ${activationPackage}";
}
)
) cfg.users;
})
(mkIf (cfg.startAsUserService && cfg.users != { }) {
systemd.user.services.home-manager = baseUnit "%u" // {
# this _should_ depend on nix-daemon.socket, as the system-service
# version of this unit does, but systemd doesn't allow user units
# to depend on system units
unitConfig.RequiresMountsFor = "%h";
# no ExecStart= is defined for any user that has not defined
# config.home-manager.users.${username}
# this will be overridden by the below drop-in
};
users.users = lib.mapAttrs (
_:
{ home, ... }:
{
# unit files are taken from $XDG_DATA_DIRS too, but are
# loaded after units from /etc. We write a drop in so that
# it will take precedence over the above unit declaration.
# Because this unit will be run in the user context, it does
# not need the wrapper script that's used when activation is
# done by system units.
packages = [
(pkgs.writeTextDir "${hmDropIn}/10-user-activation.conf" ''
[Service]
ExecStart=${home.activationPackage}/activate
'')
];
}
) cfg.users;
environment.pathsToLink = [ hmDropIn ];
# Without this will not reload home conf
# of logged user on system activation
# it will also start the unit on startup
system.userActivationScripts.home-manager = {
text = "${pkgs.systemd}/bin/systemctl --user restart home-manager";
deps = [ ];
};
})
];
}