home-manager/modules/systemd.nix

220 lines
5.8 KiB
Nix
Raw Normal View History

2017-01-07 18:16:26 +00:00
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.systemd.user;
dag = config.lib.dag;
2017-06-28 23:06:08 +00:00
enabled = cfg.services != {}
|| cfg.sockets != {}
|| cfg.targets != {}
2018-06-29 10:14:35 +00:00
|| cfg.timers != {}
|| cfg.paths != {};
2017-09-21 11:18:33 +00:00
toSystemdIni = generators.toINI {
2017-01-07 18:16:26 +00:00
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
2017-12-02 21:52:35 +00:00
filename = "${name}.${style}";
# Needed because systemd derives unit names from the ultimate
# link target.
source = pkgs.writeTextDir filename (toSystemdIni serviceCfg)
+ "/" + filename;
2017-01-07 18:16:26 +00:00
wantedBy = target:
{
2017-12-02 21:52:35 +00:00
name = "systemd/user/${target}.wants/${filename}";
value = { inherit source; };
2017-01-07 18:16:26 +00:00
};
in
singleton {
2017-12-02 21:52:35 +00:00
name = "systemd/user/${filename}";
value = { inherit source; };
2017-01-07 18:16:26 +00:00
}
++
map wantedBy (serviceCfg.Install.WantedBy or []);
buildServices = style: serviceCfgs:
concatLists (mapAttrsToList (buildService style) serviceCfgs);
servicesStartTimeoutMs = builtins.toString cfg.servicesStartTimeoutMs;
attrsRecursivelyMerged = types.attrs // {
merge = loc: foldl' (res: def: recursiveUpdate res def.value) {};
};
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 ''
{
Unit = {
Description = "Example description";
};
${type} = {
};
}
'';
2017-01-07 18:16:26 +00:00
in
{
meta.maintainers = [ maintainers.rycee ];
2017-01-07 18:16:26 +00:00
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.
'';
};
2017-01-07 18:16:26 +00:00
services = mkOption {
default = {};
type = attrsRecursivelyMerged;
description = unitDescription "service";
example = unitExample "Service";
2017-01-07 18:16:26 +00:00
};
2017-06-28 23:06:08 +00:00
sockets = mkOption {
default = {};
type = attrsRecursivelyMerged;
description = unitDescription "socket";
example = unitExample "Socket";
2017-06-28 23:06:08 +00:00
};
targets = mkOption {
default = {};
type = attrsRecursivelyMerged;
description = unitDescription "target";
example = unitExample "Target";
};
2017-01-07 18:16:26 +00:00
timers = mkOption {
default = {};
type = attrsRecursivelyMerged;
description = unitDescription "timer";
example = unitExample "Timer";
2017-01-07 18:16:26 +00:00
};
2018-06-29 10:14:35 +00:00
paths = mkOption {
default = {};
type = attrsRecursivelyMerged;
description = unitDescription "path";
example = unitExample "Path";
2018-06-29 10:14:35 +00:00
};
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.
'';
};
2017-01-07 18:16:26 +00:00
};
};
config = mkMerge [
{
assertions = [
{
assertion = enabled -> pkgs.stdenv.isLinux;
message =
let
names = concatStringsSep ", " (
2017-06-28 23:06:08 +00:00
attrNames (
2018-06-29 10:14:35 +00:00
cfg.services // cfg.sockets // cfg.targets // cfg.timers // cfg.paths
2017-06-28 23:06:08 +00:00
)
);
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 =
listToAttrs (
(buildServices "service" cfg.services)
++
2017-06-28 23:06:08 +00:00
(buildServices "socket" cfg.sockets)
++
(buildServices "target" cfg.targets)
++
(buildServices "timer" cfg.timers)
2018-06-29 10:14:35 +00:00
++
(buildServices "path" cfg.paths)
);
# 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 = 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)}";
in
''
if ${ensureRuntimeDir} ${cfg.systemctlPath} --quiet --user is-system-running 2> /dev/null; then
${ensureRuntimeDir} \
PATH=${dirOf cfg.systemctlPath}:$PATH \
${if cfg.startServices then autoReloadCmd else legacyReloadCmd}
else
echo "User systemd daemon not running. Skipping reload."
fi
''
);
})
];
2017-01-07 18:16:26 +00:00
}