1
0
Fork 0
mirror of https://github.com/nix-community/home-manager.git synced 2024-12-14 11:57:55 +00:00
home-manager/modules/systemd.nix

388 lines
12 KiB
Nix
Raw Normal View History

2017-01-07 18:16:26 +00:00
{ config, lib, pkgs, ... }:
let
cfg = config.systemd.user;
inherit (lib)
2024-04-15 17:57:48 +00:00
any attrValues getAttr hm isBool literalExpression mkIf mkMerge
mkEnableOption mkOption types;
settingsFormat = pkgs.formats.ini { listsAsDuplicateKeys = true; };
# From <nixpkgs/nixos/modules/system/boot/systemd-lib.nix>
2021-07-18 21:34:50 +00:00
mkPathSafeName =
lib.replaceStrings [ "@" ":" "\\" "[" "]" ] [ "-" "-" "-" "" "" ];
2021-07-18 21:34:50 +00:00
removeIfEmpty = attrs: names:
lib.filterAttrs (name: value: !(builtins.elem name names) || value != "")
attrs;
toSystemdIni = lib.generators.toINI {
listsAsDuplicateKeys = true;
2017-01-07 18:16:26 +00:00
mkKeyValue = key: value:
let
2021-07-18 21:34:50 +00:00
value' = if isBool value then
(if value then "true" else "false")
else
toString value;
in "${key}=${value'}";
2017-01-07 18:16:26 +00:00
};
buildService = style: name: serviceCfg:
let
2017-12-02 21:52:35 +00:00
filename = "${name}.${style}";
pathSafeName = mkPathSafeName filename;
2017-12-02 21:52:35 +00:00
# Needed because systemd derives unit names from the ultimate
# link target.
source = pkgs.writeTextFile {
name = pathSafeName;
text = toSystemdIni serviceCfg;
destination = "/${filename}";
} + "/${filename}";
2017-01-07 18:16:26 +00:00
install = variant: target: {
name = "systemd/user/${target}.${variant}/${filename}";
value = { inherit source; };
2021-07-18 21:34:50 +00:00
};
in lib.singleton {
name = "systemd/user/${filename}";
value = { inherit source; };
} ++ map (install "wants") (serviceCfg.Install.WantedBy or [ ])
++ map (install "requires") (serviceCfg.Install.RequiredBy or [ ]);
2017-01-07 18:16:26 +00:00
buildServices = style: serviceCfgs:
lib.concatLists (lib.mapAttrsToList (buildService style) serviceCfgs);
2017-01-07 18:16:26 +00:00
servicesStartTimeoutMs = builtins.toString cfg.servicesStartTimeoutMs;
2021-07-18 21:34:50 +00:00
unitType = unitKind:
with types;
let primitive = oneOf [ bool int str path ];
2021-07-18 21:34:50 +00:00
in attrsOf (attrsOf (attrsOf (either primitive (listOf primitive)))) // {
description = "systemd ${unitKind} unit configuration";
};
unitDescription = type: ''
Definition of systemd per-user ${type} units. Attributes are
merged recursively.
Note that the attributes follow the capitalization and naming used
by systemd. More details can be found in
{manpage}`systemd.${type}(5)`.
'';
2021-07-18 21:34:50 +00:00
unitExample = type:
literalExpression ''
2021-07-18 21:34:50 +00:00
{
${lib.toLower type}-name = {
Unit = {
Description = "Example description";
Documentation = [ "man:example(1)" "man:example(5)" ];
};
2021-07-18 21:34:50 +00:00
${type} = {
};
2019-08-29 17:12:39 +00:00
};
};
2021-07-18 21:34:50 +00:00
'';
2021-07-18 21:34:50 +00:00
sessionVariables = mkIf (cfg.sessionVariables != { }) {
"environment.d/10-home-manager.conf".text = lib.concatStringsSep "\n"
(lib.mapAttrsToList (n: v: "${n}=${toString v}") cfg.sessionVariables)
+ "\n";
};
2017-01-07 18:16:26 +00:00
settings = mkIf (any (v: v != { }) (attrValues cfg.settings)) {
"systemd/user.conf".source =
settingsFormat.generate "user.conf" cfg.settings;
};
configHome = lib.removePrefix config.home.homeDirectory config.xdg.configHome;
2021-07-18 21:34:50 +00:00
in {
meta.maintainers = [ lib.maintainers.rycee ];
2017-01-07 18:16:26 +00:00
options = {
systemd.user = {
2024-04-15 17:57:48 +00:00
enable = mkEnableOption "the user systemd service manager" // {
default = pkgs.stdenv.isLinux;
defaultText = literalExpression "pkgs.stdenv.isLinux";
};
systemctlPath = mkOption {
default = "${pkgs.systemd}/bin/systemctl";
defaultText = literalExpression ''"''${pkgs.systemd}/bin/systemctl"'';
type = types.str;
description = ''
Absolute path to the {command}`systemctl` tool. This
option may need to be set if running Home Manager on a
non-NixOS distribution.
'';
};
2017-01-07 18:16:26 +00:00
services = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
type = unitType "service";
description = (unitDescription "service");
example = unitExample "Service";
2017-01-07 18:16:26 +00:00
};
2020-12-04 13:14:24 +00:00
slices = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
2022-10-07 04:49:39 +00:00
type = unitType "slice";
description = (unitDescription "slice");
2022-10-07 04:49:39 +00:00
example = unitExample "Slice";
2020-12-04 13:14:24 +00:00
};
2017-06-28 23:06:08 +00:00
sockets = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
type = unitType "socket";
description = (unitDescription "socket");
example = unitExample "Socket";
2017-06-28 23:06:08 +00:00
};
targets = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
type = unitType "target";
description = (unitDescription "target");
example = unitExample "Target";
};
2017-01-07 18:16:26 +00:00
timers = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
type = unitType "timer";
description = (unitDescription "timer");
example = unitExample "Timer";
2017-01-07 18:16:26 +00:00
};
2018-06-29 10:14:35 +00:00
paths = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
type = unitType "path";
description = (unitDescription "path");
example = unitExample "Path";
2018-06-29 10:14:35 +00:00
};
2020-11-27 20:29:44 +00:00
mounts = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
2020-11-27 20:29:44 +00:00
type = unitType "mount";
description = (unitDescription "mount");
2020-11-27 20:29:44 +00:00
example = unitExample "Mount";
};
2019-10-29 02:29:12 +00:00
automounts = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
2019-10-29 02:29:12 +00:00
type = unitType "automount";
description = (unitDescription "automount");
2019-10-29 02:29:12 +00:00
example = unitExample "Automount";
};
startServices = mkOption {
default = "suggest";
2021-07-18 21:34:50 +00:00
type = with types;
either bool (enum [ "suggest" "legacy" "sd-switch" ]);
apply = p: if isBool p then if p then "sd-switch" else "suggest" else p;
description = ''
Whether new or changed services that are wanted by active targets
should be started. Additionally, stop obsolete services from the
previous generation.
The alternatives are
`suggest` (or `false`)
: Use a very simple shell script to print suggested
{command}`systemctl` commands to run. You will have to
manually run those commands after the switch.
`legacy`
: Use a Ruby script to, in a more robust fashion, determine the
necessary changes and automatically run the
{command}`systemctl` commands. Note, this alternative will soon
be removed.
`sd-switch` (or `true`)
: Use sd-switch, a tool that determines the necessary changes and
automatically apply them.
'';
};
servicesStartTimeoutMs = mkOption {
default = 0;
type = types.ints.unsigned;
description = ''
How long to wait for started services to fail until their start is
considered successful. The value 0 indicates no timeout.
'';
};
sessionVariables = mkOption {
2021-07-18 21:34:50 +00:00
default = { };
type = with types; attrsOf (either int str);
example = { EDITOR = "vim"; };
description = ''
Environment variables that will be set for the user session.
The variable values must be as described in
{manpage}`environment.d(5)`.
'';
};
settings = mkOption {
apply = sections:
sections // {
# Setting one of these to an empty value would reset any
# previous settings, so well remove them instead if they
# are not explicitly set.
Manager = removeIfEmpty sections.Manager [
"ManagerEnvironment"
"DefaultEnvironment"
];
};
type = types.submodule {
freeformType = settingsFormat.type;
options = let
inherit (lib) concatStringsSep escapeShellArg mapAttrsToList;
environmentOption = args:
mkOption {
type = with types;
attrsOf (nullOr (oneOf [ str path package ]));
default = { };
example = literalExpression ''
{
PATH = "%u/bin:%u/.cargo/bin";
}
'';
apply = value:
concatStringsSep " "
(mapAttrsToList (n: v: "${n}=${escapeShellArg v}") value);
} // args;
in {
Manager = {
DefaultEnvironment = environmentOption {
description = ''
Configures environment variables passed to all executed processes.
'';
};
ManagerEnvironment = environmentOption {
description = ''
Sets environment variables just for the manager process itself.
'';
};
};
};
};
default = { };
example = literalExpression ''
{
Manager.DefaultCPUAccounting = true;
}
'';
description = ''
Extra config options for user session service manager. See {manpage}`systemd-user.conf(5)` for
available options.
'';
};
2017-01-07 18:16:26 +00:00
};
};
# If we run under a Linux system we assume that systemd is
# available, in particular we assume that systemctl is in PATH.
# Do not install any user services if username is root.
2024-04-15 17:57:48 +00:00
config = mkIf (cfg.enable && config.home.username != "root") {
assertions = [{
assertion = pkgs.stdenv.isLinux;
message = "This module is only available on Linux.";
}];
warnings = lib.optional (cfg.startServices == "legacy") ''
Having 'systemd.user.startServices = "legacy"' is deprecated and will soon be removed.
Please change to 'systemd.user.startServices = true' to use the new systemd unit switcher (sd-switch).
'';
xdg.configFile = mkMerge [
(lib.listToAttrs ((buildServices "service" cfg.services)
++ (buildServices "slice" cfg.slices)
++ (buildServices "socket" cfg.sockets)
++ (buildServices "target" cfg.targets)
++ (buildServices "timer" cfg.timers)
++ (buildServices "path" cfg.paths)
++ (buildServices "mount" cfg.mounts)
++ (buildServices "automount" cfg.automounts)))
sessionVariables
settings
];
# Run systemd service reload if user is logged in. If we're
# running this from the NixOS module then XDG_RUNTIME_DIR is not
# set and systemd commands will fail. We'll therefore have to
# set it ourselves in that case.
home.activation.reloadSystemd = hm.dag.entryAfter [ "linkGeneration" ] (let
cmd = {
suggest = ''
bash ${./systemd-activate.sh} "''${oldGenPath=}" "$newGenPath"
'';
legacy = ''
${pkgs.ruby}/bin/ruby ${./systemd-activate.rb} \
"''${oldGenPath=}" "$newGenPath" "${servicesStartTimeoutMs}"
'';
sd-switch = let
timeoutArg = if cfg.servicesStartTimeoutMs != 0 then
"--timeout " + servicesStartTimeoutMs
else
"";
in ''
${pkgs.sd-switch}/bin/sd-switch \
''${DRY_RUN:+--dry-run} $VERBOSE_ARG ${timeoutArg} \
''${oldUnitsDir:+--old-units $oldUnitsDir} \
--new-units "$newUnitsDir"
'';
};
# Make sure that we have an environment where we are likely to
# successfully talk with systemd.
ensureSystemd = ''
env XDG_RUNTIME_DIR="''${XDG_RUNTIME_DIR:-/run/user/$(id -u)}" \
PATH="${dirOf cfg.systemctlPath}:$PATH" \
'';
systemctl = "${ensureSystemd} systemctl";
in ''
systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)
2021-07-18 21:34:50 +00:00
if [[ $systemdStatus == 'running' || $systemdStatus == 'degraded' ]]; then
if [[ $systemdStatus == 'degraded' ]]; then
warnEcho "The user systemd session is degraded:"
${systemctl} --user --no-pager --state=failed
warnEcho "Attempting to reload services anyway..."
fi
if [[ -v oldGenPath ]]; then
oldUnitsDir="$oldGenPath/home-files${configHome}/systemd/user"
if [[ ! -e $oldUnitsDir ]]; then
oldUnitsDir=
fi
fi
newUnitsDir="$newGenPath/home-files${configHome}/systemd/user"
if [[ ! -e $newUnitsDir ]]; then
newUnitsDir=${pkgs.emptyDirectory}
fi
${ensureSystemd} ${getAttr cfg.startServices cmd}
unset newUnitsDir oldUnitsDir
else
echo "User systemd daemon not running. Skipping reload."
fi
2021-07-18 21:34:50 +00:00
unset systemdStatus
'');
};
2017-01-07 18:16:26 +00:00
}