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. 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 { config = mkIf cfg.enable {
@ -518,14 +535,14 @@ in {
home.packages = optional (cfg.package != null) cfg.package; 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; sortedMatchBlocks = hm.dag.topoSort cfg.matchBlocks;
sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks; sortedMatchBlocksStr = builtins.toJSON sortedMatchBlocks;
matchBlocks = if sortedMatchBlocks ? result then matchBlocks = sortedMatchBlocks.result or (abort
sortedMatchBlocks.result "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}");
else in pkgs.writeText "ssh_config" ''
abort "Dependency cycle in SSH match blocks: ${sortedMatchBlocksStr}";
in ''
${concatStringsSep "\n" ${concatStringsSep "\n"
((mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides) ((mapAttrsToList (n: v: "${n} ${v}") cfg.extraOptionOverrides)
++ (optional (cfg.includes != [ ]) '' ++ (optional (cfg.includes != [ ]) ''

View file

@ -5,14 +5,28 @@ with lib;
let let
cfg = config.home-manager; cfg = config.home-manager;
baseService = username: {
serviceEnvironment = optionalAttrs (cfg.backupFileExtension != null) { Type = "oneshot";
HOME_MANAGER_BACKUP_EXT = cfg.backupFileExtension; RemainAfterExit = "yes";
} // optionalAttrs cfg.verbose { VERBOSE = "1"; }; 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 { in {
imports = [ ./common.nix ]; imports = [ ./common.nix ];
options.home-manager.useUserService = mkEnableOption
"activation on each user login instead of every user together on system boot";
config = mkMerge [ config = mkMerge [
{ {
home-manager = { home-manager = {
@ -26,66 +40,115 @@ in {
# fontconfig by default. # fontconfig by default.
fonts.fontconfig.enable = lib.mkDefault fonts.fontconfig.enable = lib.mkDefault
(cfg.useUserPackages && config.fonts.fontconfig.enable); (cfg.useUserPackages && config.fonts.fontconfig.enable);
# Inherit glibcLocales setting from NixOS. # Inherit glibcLocales setting from NixOS.
i18n.glibcLocales = lib.mkDefault config.i18n.glibcLocales; 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: systemd.services = mapAttrs' (_: usercfg:
let username = usercfg.home.username; let inherit (usercfg.home) username homeDirectory activationPackage;
in nameValuePair ("home-manager-${utils.escapeSystemdPath username}") { in nameValuePair "home-manager-${utils.escapeSystemdPath username}"
description = "Home Manager environment for ${username}"; (attrsets.recursiveUpdate (baseUnit username) {
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
wants = [ "nix-daemon.socket" ]; wants = [ "nix-daemon.socket" ];
after = [ "nix-daemon.socket" ]; after = [ "nix-daemon.socket" ];
before = [ "systemd-user-sessions.service" ]; 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 = { # The activation script is run by a login shell to make sure
User = usercfg.home.username; # that the user is given a sane environment.
Type = "oneshot"; # If the user is logged in, import variables from their current
TimeoutStartSec = "5m"; # session environment.
SyslogIdentifier = "hm-activate-${username}"; eval "$(
${systemctl} --user show-environment 2> /dev/null \
| ${sed} -En '/^(${exportedSystemdVariables})=/s/^/export /p'
)"
ExecStart = let exec "$1/activate"
systemctl = '';
"XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$UID} systemctl"; 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 "|" [ users.users = mapAttrs (_:
"DBUS_SESSION_BUS_ADDRESS" { home, ... }: {
"DISPLAY" # unit files are taken from $XDG_DATA_DIRS too
"WAYLAND_DISPLAY" # but are loaded after units from /etc
"XAUTHORITY" # we write a drop in so that it will take precedence
"XDG_RUNTIME_DIR" # over the above unit declaration
]; packages = [
(pkgs.writeTextDir "${hmDropIn}/10-user-activation.conf" ''
setupEnv = pkgs.writeScript "hm-setup-env" '' [Service]
#! ${pkgs.runtimeShell} -el ExecStart=${home.activationPackage}/activate
'')
# 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}";
};
}) cfg.users; }) 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 = [ ];
};
}) })
]; ];
} }