269 lines
7.5 KiB
Nix
269 lines
7.5 KiB
Nix
|
{ config, lib, pkgs, ... }:
|
||
|
|
||
|
with lib;
|
||
|
|
||
|
let
|
||
|
|
||
|
cfg = config.systemd.user;
|
||
|
|
||
|
enabled = cfg.services != {}
|
||
|
|| cfg.sockets != {}
|
||
|
|| cfg.targets != {}
|
||
|
|| cfg.timers != {}
|
||
|
|| cfg.paths != {}
|
||
|
|| cfg.sessionVariables != {};
|
||
|
|
||
|
toSystemdIni = generators.toINI {
|
||
|
mkKeyValue = key: value:
|
||
|
let
|
||
|
value' =
|
||
|
if isBool value then (if value then "true" else "false")
|
||
|
else toString value;
|
||
|
in
|
||
|
"${key}=${value'}";
|
||
|
};
|
||
|
|
||
|
buildService = style: name: serviceCfg:
|
||
|
let
|
||
|
filename = "${name}.${style}";
|
||
|
pathSafeName = lib.replaceChars ["@" ":" "\\" "[" "]"]
|
||
|
["-" "-" "-" "" "" ]
|
||
|
filename;
|
||
|
|
||
|
# Needed because systemd derives unit names from the ultimate
|
||
|
# link target.
|
||
|
source = pkgs.writeTextFile {
|
||
|
name = pathSafeName;
|
||
|
text = toSystemdIni serviceCfg;
|
||
|
destination = "/${filename}";
|
||
|
} + "/${filename}";
|
||
|
|
||
|
wantedBy = target:
|
||
|
{
|
||
|
name = "systemd/user/${target}.wants/${filename}";
|
||
|
value = { inherit source; };
|
||
|
};
|
||
|
in
|
||
|
singleton {
|
||
|
name = "systemd/user/${filename}";
|
||
|
value = { inherit source; };
|
||
|
}
|
||
|
++
|
||
|
map wantedBy (serviceCfg.Install.WantedBy or []);
|
||
|
|
||
|
buildServices = style: serviceCfgs:
|
||
|
concatLists (mapAttrsToList (buildService style) serviceCfgs);
|
||
|
|
||
|
servicesStartTimeoutMs = builtins.toString cfg.servicesStartTimeoutMs;
|
||
|
|
||
|
unitType = unitKind: with types;
|
||
|
let
|
||
|
primitive = either bool (either int str);
|
||
|
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.
|
||
|
</para><para>
|
||
|
Note that the attributes follow the capitalization and naming used
|
||
|
by systemd. More details can be found in
|
||
|
<citerefentry>
|
||
|
<refentrytitle>systemd.${type}</refentrytitle>
|
||
|
<manvolnum>5</manvolnum>
|
||
|
</citerefentry>.
|
||
|
'';
|
||
|
|
||
|
unitExample = type: literalExample ''
|
||
|
{
|
||
|
${toLower type}-name = {
|
||
|
Unit = {
|
||
|
Description = "Example description";
|
||
|
Documentation = [ "man:example(1)" "man:example(5)" ];
|
||
|
};
|
||
|
|
||
|
${type} = {
|
||
|
…
|
||
|
};
|
||
|
}
|
||
|
};
|
||
|
'';
|
||
|
|
||
|
sessionVariables = mkIf (cfg.sessionVariables != {}) {
|
||
|
"environment.d/10-home-manager.conf".text =
|
||
|
concatStringsSep "\n" (
|
||
|
mapAttrsToList (n: v: "${n}=${toString v}") cfg.sessionVariables
|
||
|
) + "\n";
|
||
|
};
|
||
|
|
||
|
in
|
||
|
|
||
|
{
|
||
|
meta.maintainers = [ maintainers.rycee ];
|
||
|
|
||
|
options = {
|
||
|
systemd.user = {
|
||
|
systemctlPath = mkOption {
|
||
|
default = "${pkgs.systemd}/bin/systemctl";
|
||
|
defaultText = "\${pkgs.systemd}/bin/systemctl";
|
||
|
type = types.str;
|
||
|
description = ''
|
||
|
Absolute path to the <command>systemctl</command> tool. This
|
||
|
option may need to be set if running Home Manager on a
|
||
|
non-NixOS distribution.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
services = mkOption {
|
||
|
default = {};
|
||
|
type = unitType "service";
|
||
|
description = unitDescription "service";
|
||
|
example = unitExample "Service";
|
||
|
};
|
||
|
|
||
|
sockets = mkOption {
|
||
|
default = {};
|
||
|
type = unitType "socket";
|
||
|
description = unitDescription "socket";
|
||
|
example = unitExample "Socket";
|
||
|
};
|
||
|
|
||
|
targets = mkOption {
|
||
|
default = {};
|
||
|
type = unitType "target";
|
||
|
description = unitDescription "target";
|
||
|
example = unitExample "Target";
|
||
|
};
|
||
|
|
||
|
timers = mkOption {
|
||
|
default = {};
|
||
|
type = unitType "timer";
|
||
|
description = unitDescription "timer";
|
||
|
example = unitExample "Timer";
|
||
|
};
|
||
|
|
||
|
paths = mkOption {
|
||
|
default = {};
|
||
|
type = unitType "path";
|
||
|
description = unitDescription "path";
|
||
|
example = unitExample "Path";
|
||
|
};
|
||
|
|
||
|
startServices = mkOption {
|
||
|
default = false;
|
||
|
type = types.bool;
|
||
|
description = ''
|
||
|
Start all services that are wanted by active targets.
|
||
|
Additionally, stop obsolete services from the previous
|
||
|
generation.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
servicesStartTimeoutMs = mkOption {
|
||
|
default = 0;
|
||
|
type = types.int;
|
||
|
description = ''
|
||
|
How long to wait for started services to fail until their
|
||
|
start is considered successful.
|
||
|
'';
|
||
|
};
|
||
|
|
||
|
sessionVariables = mkOption {
|
||
|
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
|
||
|
<citerefentry>
|
||
|
<refentrytitle>environment.d</refentrytitle>
|
||
|
<manvolnum>5</manvolnum>
|
||
|
</citerefentry>.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
|
||
|
config = mkMerge [
|
||
|
{
|
||
|
assertions = [
|
||
|
{
|
||
|
assertion = enabled -> pkgs.stdenv.isLinux;
|
||
|
message =
|
||
|
let
|
||
|
names = concatStringsSep ", " (
|
||
|
attrNames (
|
||
|
cfg.services // cfg.sockets // cfg.targets // cfg.timers // cfg.paths // cfg.sessionVariables
|
||
|
)
|
||
|
);
|
||
|
in
|
||
|
"Must use Linux for modules that require systemd: " + names;
|
||
|
}
|
||
|
];
|
||
|
}
|
||
|
|
||
|
# If we run under a Linux system we assume that systemd is
|
||
|
# available, in particular we assume that systemctl is in PATH.
|
||
|
(mkIf pkgs.stdenv.isLinux {
|
||
|
xdg.configFile = mkMerge [
|
||
|
(listToAttrs (
|
||
|
(buildServices "service" cfg.services)
|
||
|
++
|
||
|
(buildServices "socket" cfg.sockets)
|
||
|
++
|
||
|
(buildServices "target" cfg.targets)
|
||
|
++
|
||
|
(buildServices "timer" cfg.timers)
|
||
|
++
|
||
|
(buildServices "path" cfg.paths)
|
||
|
))
|
||
|
|
||
|
sessionVariables
|
||
|
];
|
||
|
|
||
|
# 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
|
||
|
autoReloadCmd = ''
|
||
|
${pkgs.ruby}/bin/ruby ${./systemd-activate.rb} \
|
||
|
"''${oldGenPath=}" "$newGenPath" "${servicesStartTimeoutMs}"
|
||
|
'';
|
||
|
|
||
|
legacyReloadCmd = ''
|
||
|
bash ${./systemd-activate.sh} "''${oldGenPath=}" "$newGenPath"
|
||
|
'';
|
||
|
|
||
|
ensureRuntimeDir = "XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$(id -u)}";
|
||
|
|
||
|
systemctl = "${ensureRuntimeDir} ${cfg.systemctlPath}";
|
||
|
in
|
||
|
''
|
||
|
systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)
|
||
|
|
||
|
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
|
||
|
|
||
|
${ensureRuntimeDir} \
|
||
|
PATH=${dirOf cfg.systemctlPath}:$PATH \
|
||
|
${if cfg.startServices then autoReloadCmd else legacyReloadCmd}
|
||
|
else
|
||
|
echo "User systemd daemon not running. Skipping reload."
|
||
|
fi
|
||
|
|
||
|
unset systemdStatus
|
||
|
''
|
||
|
);
|
||
|
})
|
||
|
];
|
||
|
}
|