2017-01-07 18:16:26 +00:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
|
|
|
|
let
|
|
|
|
|
2017-05-15 21:53:49 +00:00
|
|
|
cfg = config.systemd.user;
|
|
|
|
|
2021-10-09 09:14:08 +00:00
|
|
|
inherit (lib) getAttr hm isBool literalExpression mkIf mkMerge mkOption types;
|
2019-10-29 02:29:12 +00:00
|
|
|
|
|
|
|
# From <nixpkgs/nixos/modules/system/boot/systemd-lib.nix>
|
2021-07-18 21:34:50 +00:00
|
|
|
mkPathSafeName =
|
2022-12-16 04:45:46 +00:00
|
|
|
lib.replaceStrings [ "@" ":" "\\" "[" "]" ] [ "-" "-" "-" "" "" ];
|
2021-07-18 21:34:50 +00:00
|
|
|
|
2019-10-29 02:29:12 +00:00
|
|
|
toSystemdIni = lib.generators.toINI {
|
2020-10-25 21:39:02 +00:00
|
|
|
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}";
|
2019-10-29 02:29:12 +00:00
|
|
|
pathSafeName = mkPathSafeName filename;
|
2017-12-02 21:52:35 +00:00
|
|
|
|
|
|
|
# Needed because systemd derives unit names from the ultimate
|
|
|
|
# link target.
|
2019-02-19 07:10:04 +00:00
|
|
|
source = pkgs.writeTextFile {
|
|
|
|
name = pathSafeName;
|
|
|
|
text = toSystemdIni serviceCfg;
|
2022-04-07 06:38:20 +00:00
|
|
|
destination = "/${filename}";
|
2019-02-19 07:10:04 +00:00
|
|
|
} + "/${filename}";
|
2017-01-07 18:16:26 +00:00
|
|
|
|
2022-09-07 17:09:17 +00:00
|
|
|
install = variant: target: {
|
|
|
|
name = "systemd/user/${target}.${variant}/${filename}";
|
2017-11-11 23:49:36 +00:00
|
|
|
value = { inherit source; };
|
2021-07-18 21:34:50 +00:00
|
|
|
};
|
|
|
|
in lib.singleton {
|
|
|
|
name = "systemd/user/${filename}";
|
|
|
|
value = { inherit source; };
|
2022-09-07 17:09:17 +00:00
|
|
|
} ++ 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:
|
2019-10-29 02:29:12 +00:00
|
|
|
lib.concatLists (lib.mapAttrsToList (buildService style) serviceCfgs);
|
2017-01-07 18:16:26 +00:00
|
|
|
|
2020-08-04 17:38:14 +00:00
|
|
|
servicesStartTimeoutMs = builtins.toString cfg.servicesStartTimeoutMs;
|
|
|
|
|
2021-07-18 21:34:50 +00:00
|
|
|
unitType = unitKind:
|
|
|
|
with types;
|
2023-05-11 13:50:37 +00:00
|
|
|
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";
|
|
|
|
};
|
2018-05-09 13:22:34 +00:00
|
|
|
|
2018-10-18 04:30:36 +00:00
|
|
|
unitDescription = type: ''
|
|
|
|
Definition of systemd per-user ${type} units. Attributes are
|
|
|
|
merged recursively.
|
2023-06-30 05:22:38 +00:00
|
|
|
|
2018-10-18 04:30:36 +00:00
|
|
|
Note that the attributes follow the capitalization and naming used
|
|
|
|
by systemd. More details can be found in
|
2023-06-30 05:22:38 +00:00
|
|
|
{manpage}`systemd.${type}(5)`.
|
2018-10-18 04:30:36 +00:00
|
|
|
'';
|
|
|
|
|
2021-07-18 21:34:50 +00:00
|
|
|
unitExample = type:
|
2021-10-09 09:14:08 +00:00
|
|
|
literalExpression ''
|
2021-07-18 21:34:50 +00:00
|
|
|
{
|
|
|
|
${lib.toLower type}-name = {
|
|
|
|
Unit = {
|
|
|
|
Description = "Example description";
|
|
|
|
Documentation = [ "man:example(1)" "man:example(5)" ];
|
|
|
|
};
|
2018-10-18 04:30:36 +00:00
|
|
|
|
2021-07-18 21:34:50 +00:00
|
|
|
${type} = {
|
|
|
|
…
|
|
|
|
};
|
2019-08-29 17:12:39 +00:00
|
|
|
};
|
2020-12-04 17:39:05 +00:00
|
|
|
};
|
2021-07-18 21:34:50 +00:00
|
|
|
'';
|
2018-10-18 04:30:36 +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
|
|
|
|
2021-07-18 21:34:50 +00:00
|
|
|
in {
|
2019-10-29 02:29:12 +00:00
|
|
|
meta.maintainers = [ lib.maintainers.rycee ];
|
2017-09-26 21:40:31 +00:00
|
|
|
|
2017-01-07 18:16:26 +00:00
|
|
|
options = {
|
|
|
|
systemd.user = {
|
2017-10-20 12:02:05 +00:00
|
|
|
systemctlPath = mkOption {
|
|
|
|
default = "${pkgs.systemd}/bin/systemctl";
|
2022-11-03 22:13:29 +00:00
|
|
|
defaultText = literalExpression ''"''${pkgs.systemd}/bin/systemctl"'';
|
2017-10-20 12:02:05 +00:00
|
|
|
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 {
|
2021-07-18 21:34:50 +00:00
|
|
|
default = { };
|
2019-04-09 22:40:45 +00:00
|
|
|
type = unitType "service";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (unitDescription "service");
|
2018-10-18 04:30:36 +00:00
|
|
|
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";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (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 = { };
|
2019-04-09 22:40:45 +00:00
|
|
|
type = unitType "socket";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (unitDescription "socket");
|
2018-10-18 04:30:36 +00:00
|
|
|
example = unitExample "Socket";
|
2017-06-28 23:06:08 +00:00
|
|
|
};
|
|
|
|
|
2017-01-09 21:48:53 +00:00
|
|
|
targets = mkOption {
|
2021-07-18 21:34:50 +00:00
|
|
|
default = { };
|
2019-04-09 22:40:45 +00:00
|
|
|
type = unitType "target";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (unitDescription "target");
|
2018-10-18 04:30:36 +00:00
|
|
|
example = unitExample "Target";
|
2017-01-09 21:48:53 +00:00
|
|
|
};
|
|
|
|
|
2017-01-07 18:16:26 +00:00
|
|
|
timers = mkOption {
|
2021-07-18 21:34:50 +00:00
|
|
|
default = { };
|
2019-04-09 22:40:45 +00:00
|
|
|
type = unitType "timer";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (unitDescription "timer");
|
2018-10-18 04:30:36 +00:00
|
|
|
example = unitExample "Timer";
|
2017-01-07 18:16:26 +00:00
|
|
|
};
|
2017-12-04 10:17:46 +00:00
|
|
|
|
2018-06-29 10:14:35 +00:00
|
|
|
paths = mkOption {
|
2021-07-18 21:34:50 +00:00
|
|
|
default = { };
|
2019-04-09 22:40:45 +00:00
|
|
|
type = unitType "path";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (unitDescription "path");
|
2018-10-18 04:30:36 +00:00
|
|
|
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";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (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";
|
2023-06-30 05:22:38 +00:00
|
|
|
description = lib.mdDoc (unitDescription "automount");
|
2019-10-29 02:29:12 +00:00
|
|
|
example = unitExample "Automount";
|
|
|
|
};
|
|
|
|
|
2017-12-04 10:17:46 +00:00
|
|
|
startServices = mkOption {
|
2020-12-13 20:53:49 +00:00
|
|
|
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 "legacy" else "suggest" else p;
|
2023-07-01 00:40:42 +00:00
|
|
|
description = lib.mdDoc ''
|
2020-12-13 20:53:49 +00:00
|
|
|
Whether new or changed services that are wanted by active targets
|
|
|
|
should be started. Additionally, stop obsolete services from the
|
|
|
|
previous generation.
|
2023-07-01 00:40:42 +00:00
|
|
|
|
2020-12-13 20:53:49 +00:00
|
|
|
The alternatives are
|
2023-07-01 00:40:42 +00:00
|
|
|
|
|
|
|
`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` (or `true`)
|
|
|
|
: Use a Ruby script to, in a more robust fashion, determine the
|
|
|
|
necessary changes and automatically run the
|
|
|
|
{command}`systemctl` commands.
|
|
|
|
|
|
|
|
`sd-switch`
|
|
|
|
: Use sd-switch, a third party application, to perform the service
|
|
|
|
updates. This tool offers more features while having a small
|
|
|
|
closure size. Note, it requires a fully functional user D-Bus
|
|
|
|
session. Once tested and deemed sufficiently robust, this will
|
|
|
|
become the default.
|
2017-12-04 10:17:46 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
servicesStartTimeoutMs = mkOption {
|
|
|
|
default = 0;
|
2020-12-13 20:53:49 +00:00
|
|
|
type = types.ints.unsigned;
|
2017-12-04 10:17:46 +00:00
|
|
|
description = ''
|
2020-12-13 20:53:49 +00:00
|
|
|
How long to wait for started services to fail until their start is
|
|
|
|
considered successful. The value 0 indicates no timeout.
|
2017-12-04 10:17:46 +00:00
|
|
|
'';
|
|
|
|
};
|
2019-03-22 11:47:49 +00:00
|
|
|
|
|
|
|
sessionVariables = mkOption {
|
2021-07-18 21:34:50 +00:00
|
|
|
default = { };
|
2019-03-22 11:47:49 +00:00
|
|
|
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>.
|
|
|
|
'';
|
|
|
|
};
|
2017-01-07 18:16:26 +00:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2022-04-24 14:26:25 +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.
|
|
|
|
config = mkIf (pkgs.stdenv.isLinux && config.home.username != "root") {
|
|
|
|
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
|
|
|
|
];
|
|
|
|
|
|
|
|
# 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 = ''
|
|
|
|
PATH=${dirOf cfg.systemctlPath}:$PATH \
|
|
|
|
bash ${./systemd-activate.sh} "''${oldGenPath=}" "$newGenPath"
|
|
|
|
'';
|
|
|
|
legacy = ''
|
|
|
|
PATH=${dirOf cfg.systemctlPath}:$PATH \
|
|
|
|
${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} \
|
|
|
|
''${oldGenPath:+--old-units $oldGenPath/home-files/.config/systemd/user} \
|
|
|
|
--new-units $newGenPath/home-files/.config/systemd/user
|
|
|
|
'';
|
|
|
|
};
|
2018-02-18 15:39:28 +00:00
|
|
|
|
2022-04-24 14:26:25 +00:00
|
|
|
ensureRuntimeDir =
|
|
|
|
"XDG_RUNTIME_DIR=\${XDG_RUNTIME_DIR:-/run/user/$(id -u)}";
|
2019-11-17 20:16:28 +00:00
|
|
|
|
2022-04-24 14:26:25 +00:00
|
|
|
systemctl = "${ensureRuntimeDir} ${cfg.systemctlPath}";
|
|
|
|
in ''
|
|
|
|
systemdStatus=$(${systemctl} --user is-system-running 2>&1 || true)
|
2021-07-18 21:34:50 +00:00
|
|
|
|
2022-04-24 14:26:25 +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
|
2019-11-17 20:16:28 +00:00
|
|
|
|
2022-04-24 14:26:25 +00:00
|
|
|
${ensureRuntimeDir} \
|
|
|
|
${getAttr cfg.startServices cmd}
|
|
|
|
else
|
|
|
|
echo "User systemd daemon not running. Skipping reload."
|
|
|
|
fi
|
2021-07-18 21:34:50 +00:00
|
|
|
|
2022-04-24 14:26:25 +00:00
|
|
|
unset systemdStatus
|
|
|
|
'');
|
|
|
|
};
|
2017-01-07 18:16:26 +00:00
|
|
|
}
|