user activation

This commit is contained in:
Grimmauld 2025-01-15 12:45:49 +01:00
parent 0d7908bd09
commit 1faf0d76a9
No known key found for this signature in database
2 changed files with 132 additions and 52 deletions

View file

@ -493,6 +493,23 @@ in {
for more information.
'';
};
configPath = mkOption {
type = types.path;
internal = true;
description = ''
Path to the ssh configuration.
'';
};
internallyManaged = mkOption {
type = types.bool;
default = true;
internal = true;
description = ''
Whether to link .ssh/config to programs.ssh.configPath
'';
};
};
config = mkIf cfg.enable {
@ -518,14 +535,14 @@ in {
home.packages = optional (cfg.package != null) cfg.package;
home.file.".ssh/config".text = let
home.file.".ssh/config".source = mkIf cfg.internallyManaged cfg.configPath;
programs.ssh.configPath = let
sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks;
sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks;
matchBlocks = if sortedMatchBlocks ? result then
sortedMatchBlocks.result
else
abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}";
in ''
matchBlocks = sortedMatchBlocks.result or (abort
"Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}");
in pkgs.writeText "ssh_config" ''
${concatStringsSep "\n"
((mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)
++ (optional (cfg.includes != [ ]) ''

View file

@ -5,14 +5,28 @@ with lib;
let
cfg = config.home-manager;
serviceEnvironment = optionalAttrs (cfg.backupFileExtension != null) {
HOME_MANAGER_BACKUP_EXT = cfg.backupFileExtension;
} // optionalAttrs cfg.verbose { VERBOSE = "1"; };
baseService = username: {
Type = "oneshot";
RemainAfterExit = "yes";
TimeoutStartSec = "5m";
SyslogIdentifier = "hm-activate-${username}";
};
baseUnit = username: {
description = "Home Manager environment for ${username}";
stopIfChanged = false;
environment = optionalAttrs (cfg.backupFileExtension != null) {
HOME_MANAGER_BACKUP_EXT = cfg.backupFileExtension;
} // optionalAttrs cfg.verbose { VERBOSE = "1"; };
serviceConfig = baseService username;
};
# 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.useUserService = mkEnableOption
"activation on each user login instead of every user together on system boot";
config = mkMerge [
{
home-manager = {
@ -26,66 +40,115 @@ in {
# 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;
# .ssh/config needs to exists before login to let ssh login as that user
programs.ssh.internallyManaged =
lib.mkDefault (!cfg.useUserService);
};
}];
};
systemd.services = mapAttrs' (_:
{ home, programs, ... }:
let inherit (home) username homeDirectory;
in nameValuePair "ssh_config-${utils.escapeSystemdPath username}" {
enable = with programs.ssh; enable && !internallyManaged;
description = "Linking ${username}' ssh config";
wantedBy = [ "multi-user.target" ];
before = [ "systemd-user-sessions.service" ];
unitConfig.RequiresMountsFor = homeDirectory;
stopIfChanged = false;
serviceConfig = (baseService username) // {
User = username;
ExecStart = [
"${pkgs.coreutils}/bin/mkdir -p ${homeDirectory}/.ssh"
"${pkgs.coreutils}/bin/ln -s ${programs.ssh.configPath} ${homeDirectory}/.ssh/config"
];
};
}) cfg.users;
}
(mkIf (cfg.users != { }) {
(mkIf (cfg.users != { } && !cfg.useUserService) {
systemd.services = mapAttrs' (_: usercfg:
let username = usercfg.home.username;
in nameValuePair ("home-manager-${utils.escapeSystemdPath username}") {
description = "Home Manager environment for ${username}";
let inherit (usercfg.home) username homeDirectory activationPackage;
in nameValuePair "home-manager-${utils.escapeSystemdPath username}"
(attrsets.recursiveUpdate (baseUnit username) {
wantedBy = [ "multi-user.target" ];
wants = [ "nix-daemon.socket" ];
after = [ "nix-daemon.socket" ];
before = [ "systemd-user-sessions.service" ];
environment = serviceEnvironment;
unitConfig.RequiresMountsFor = homeDirectory;
unitConfig = { RequiresMountsFor = usercfg.home.homeDirectory; };
serviceConfig.User = username;
serviceConfig.ExecStart = let
systemctl =
"XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$UID} systemctl";
stopIfChanged = false;
sed = "${pkgs.gnused}/bin/sed";
exportedSystemdVariables = concatStringsSep "|" [
"DBUS_SESSION_BUS_ADDRESS"
"DISPLAY"
"WAYLAND_DISPLAY"
"XAUTHORITY"
"XDG_RUNTIME_DIR"
];
setupEnv = pkgs.writeScript "hm-setup-env" ''
#! ${pkgs.runtimeShell} -el
serviceConfig = {
User = usercfg.home.username;
Type = "oneshot";
TimeoutStartSec = "5m";
SyslogIdentifier = "hm-activate-${username}";
# 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'
)"
ExecStart = let
systemctl =
"XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$UID} systemctl";
exec "$1/activate"
'';
in "${setupEnv} ${activationPackage}";
})) cfg.users;
})
(mkIf (cfg.users != { } && cfg.useUserService) {
systemd.user.services.home-manager = (baseUnit "%u") // {
# user units cannot depend on system units
# TODO: Insert in the script logic for waiting on the nix socket via dbus
# like https://github.com/mogorman/systemd-lock-handler
# wants = [ "nix-daemon.socket" ];
# after = [ "nix-daemon.socket" ];
sed = "${pkgs.gnused}/bin/sed";
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
};
exportedSystemdVariables = 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"
'';
in "${setupEnv} ${usercfg.home.activationPackage}";
};
users.users = 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
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 = [ ];
};
})
];
}