810 lines
21 KiB
Nix
810 lines
21 KiB
Nix
{ lib, systemdUtils }:
|
||
|
||
let
|
||
inherit (systemdUtils.lib)
|
||
assertValueOneOf
|
||
automountConfig
|
||
checkUnitConfig
|
||
makeJobScript
|
||
mountConfig
|
||
serviceConfig
|
||
unitConfig
|
||
unitNameType
|
||
;
|
||
|
||
inherit (lib)
|
||
any
|
||
concatMap
|
||
filterOverrides
|
||
isList
|
||
literalExpression
|
||
mergeEqualOption
|
||
mkIf
|
||
mkMerge
|
||
mkOption
|
||
mkOptionType
|
||
singleton
|
||
toList
|
||
types
|
||
;
|
||
|
||
checkService = checkUnitConfig "Service" [
|
||
(assertValueOneOf "Type" [
|
||
"exec" "simple" "forking" "oneshot" "dbus" "notify" "notify-reload" "idle"
|
||
])
|
||
(assertValueOneOf "Restart" [
|
||
"no" "on-success" "on-failure" "on-abnormal" "on-abort" "always"
|
||
])
|
||
];
|
||
|
||
in rec {
|
||
|
||
unitOption = mkOptionType {
|
||
name = "systemd option";
|
||
merge = loc: defs:
|
||
let
|
||
defs' = filterOverrides defs;
|
||
in
|
||
if any (def: isList def.value) defs'
|
||
then concatMap (def: toList def.value) defs'
|
||
else mergeEqualOption loc defs';
|
||
};
|
||
|
||
sharedOptions = {
|
||
|
||
enable = mkOption {
|
||
default = true;
|
||
type = types.bool;
|
||
description = ''
|
||
If set to false, this unit will be a symlink to
|
||
/dev/null. This is primarily useful to prevent specific
|
||
template instances
|
||
(e.g. `serial-getty@ttyS0`) from being
|
||
started. Note that `enable=true` does not
|
||
make a unit start by default at boot; if you want that, see
|
||
`wantedBy`.
|
||
'';
|
||
};
|
||
|
||
name = lib.mkOption {
|
||
type = lib.types.str;
|
||
description = ''
|
||
The name of this systemd unit, including its extension.
|
||
This can be used to refer to this unit from other systemd units.
|
||
'';
|
||
};
|
||
|
||
overrideStrategy = mkOption {
|
||
default = "asDropinIfExists";
|
||
type = types.enum [ "asDropinIfExists" "asDropin" ];
|
||
description = ''
|
||
Defines how unit configuration is provided for systemd:
|
||
|
||
`asDropinIfExists` creates a unit file when no unit file is provided by the package
|
||
otherwise a drop-in file name `overrides.conf`.
|
||
|
||
`asDropin` creates a drop-in file named `overrides.conf`.
|
||
Mainly needed to define instances for systemd template units (e.g. `systemd-nspawn@mycontainer.service`).
|
||
|
||
See also {manpage}`systemd.unit(5)`.
|
||
'';
|
||
};
|
||
|
||
requiredBy = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Units that require (i.e. depend on and need to go down with) this unit.
|
||
As discussed in the `wantedBy` option description this also creates
|
||
`.requires` symlinks automatically.
|
||
'';
|
||
};
|
||
|
||
upheldBy = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Keep this unit running as long as the listed units are running. This is a continuously
|
||
enforced version of wantedBy.
|
||
'';
|
||
};
|
||
|
||
wantedBy = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Units that want (i.e. depend on) this unit. The default method for
|
||
starting a unit by default at boot time is to set this option to
|
||
`["multi-user.target"]` for system services. Likewise for user units
|
||
(`systemd.user.<name>.*`) set it to `["default.target"]` to make a unit
|
||
start by default when the user `<name>` logs on.
|
||
|
||
This option creates a `.wants` symlink in the given target that exists
|
||
statelessly without the need for running `systemctl enable`.
|
||
The `[Install]` section described in {manpage}`systemd.unit(5)` however is
|
||
not supported because it is a stateful process that does not fit well
|
||
into the NixOS design.
|
||
'';
|
||
};
|
||
|
||
aliases = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = "Aliases of that unit.";
|
||
};
|
||
|
||
};
|
||
|
||
concreteUnitOptions = sharedOptions // {
|
||
|
||
text = mkOption {
|
||
type = types.nullOr types.str;
|
||
default = null;
|
||
description = "Text of this systemd unit.";
|
||
};
|
||
|
||
unit = mkOption {
|
||
internal = true;
|
||
description = "The generated unit.";
|
||
};
|
||
|
||
};
|
||
|
||
commonUnitOptions = {
|
||
options = sharedOptions // {
|
||
|
||
description = mkOption {
|
||
default = "";
|
||
type = types.singleLineStr;
|
||
description = "Description of this unit used in systemd messages and progress indicators.";
|
||
};
|
||
|
||
documentation = mkOption {
|
||
default = [];
|
||
type = types.listOf types.str;
|
||
description = "A list of URIs referencing documentation for this unit or its configuration.";
|
||
};
|
||
|
||
requires = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Start the specified units when this unit is started, and stop
|
||
this unit when the specified units are stopped or fail.
|
||
'';
|
||
};
|
||
|
||
wants = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Start the specified units when this unit is started.
|
||
'';
|
||
};
|
||
|
||
upholds = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Keeps the specified running while this unit is running. A continuous version of `wants`.
|
||
'';
|
||
};
|
||
|
||
after = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
If the specified units are started at the same time as
|
||
this unit, delay this unit until they have started.
|
||
'';
|
||
};
|
||
|
||
before = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
If the specified units are started at the same time as
|
||
this unit, delay them until this unit has started.
|
||
'';
|
||
};
|
||
|
||
bindsTo = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Like ‘requires’, but in addition, if the specified units
|
||
unexpectedly disappear, this unit will be stopped as well.
|
||
'';
|
||
};
|
||
|
||
partOf = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
If the specified units are stopped or restarted, then this
|
||
unit is stopped or restarted as well.
|
||
'';
|
||
};
|
||
|
||
conflicts = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
If the specified units are started, then this unit is stopped
|
||
and vice versa.
|
||
'';
|
||
};
|
||
|
||
requisite = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
Similar to requires. However if the units listed are not started,
|
||
they will not be started and the transaction will fail.
|
||
'';
|
||
};
|
||
|
||
unitConfig = mkOption {
|
||
default = {};
|
||
example = { RequiresMountsFor = "/data"; };
|
||
type = types.attrsOf unitOption;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Unit]` section of the unit. See
|
||
{manpage}`systemd.unit(5)` for details.
|
||
'';
|
||
};
|
||
|
||
onFailure = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
A list of one or more units that are activated when
|
||
this unit enters the "failed" state.
|
||
'';
|
||
};
|
||
|
||
onSuccess = mkOption {
|
||
default = [];
|
||
type = types.listOf unitNameType;
|
||
description = ''
|
||
A list of one or more units that are activated when
|
||
this unit enters the "inactive" state.
|
||
'';
|
||
};
|
||
|
||
startLimitBurst = mkOption {
|
||
type = types.int;
|
||
description = ''
|
||
Configure unit start rate limiting. Units which are started
|
||
more than startLimitBurst times within an interval time
|
||
interval are not permitted to start any more.
|
||
'';
|
||
};
|
||
|
||
startLimitIntervalSec = mkOption {
|
||
type = types.int;
|
||
description = ''
|
||
Configure unit start rate limiting. Units which are started
|
||
more than startLimitBurst times within an interval time
|
||
interval are not permitted to start any more.
|
||
'';
|
||
};
|
||
|
||
};
|
||
};
|
||
|
||
stage2CommonUnitOptions = {
|
||
imports = [
|
||
commonUnitOptions
|
||
];
|
||
|
||
options = {
|
||
restartTriggers = mkOption {
|
||
default = [];
|
||
type = types.listOf types.unspecified;
|
||
description = ''
|
||
An arbitrary list of items such as derivations. If any item
|
||
in the list changes between reconfigurations, the service will
|
||
be restarted.
|
||
'';
|
||
};
|
||
|
||
reloadTriggers = mkOption {
|
||
default = [];
|
||
type = types.listOf unitOption;
|
||
description = ''
|
||
An arbitrary list of items such as derivations. If any item
|
||
in the list changes between reconfigurations, the service will
|
||
be reloaded. If anything but a reload trigger changes in the
|
||
unit file, the unit will be restarted instead.
|
||
'';
|
||
};
|
||
};
|
||
};
|
||
stage1CommonUnitOptions = commonUnitOptions;
|
||
|
||
serviceOptions = { name, config, ... }: {
|
||
options = {
|
||
|
||
environment = mkOption {
|
||
default = {};
|
||
type = with types; attrsOf (nullOr (oneOf [ str path package ]));
|
||
example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
|
||
description = "Environment variables passed to the service's processes.";
|
||
};
|
||
|
||
path = mkOption {
|
||
default = [];
|
||
type = with types; listOf (oneOf [ package str ]);
|
||
description = ''
|
||
Packages added to the service's {env}`PATH`
|
||
environment variable. Both the {file}`bin`
|
||
and {file}`sbin` subdirectories of each
|
||
package are added.
|
||
'';
|
||
};
|
||
|
||
serviceConfig = mkOption {
|
||
default = {};
|
||
example =
|
||
{ RestartSec = 5;
|
||
};
|
||
type = types.addCheck (types.attrsOf unitOption) checkService;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Service]` section of the unit. See
|
||
{manpage}`systemd.service(5)` for details.
|
||
'';
|
||
};
|
||
|
||
enableStrictShellChecks = mkOption {
|
||
type = types.bool;
|
||
description = "Enable running shellcheck on the generated scripts for this unit.";
|
||
# The default gets set in systemd-lib.nix because we don't have access to
|
||
# the full NixOS config here.
|
||
defaultText = literalExpression "config.systemd.enableStrictShellChecks";
|
||
};
|
||
|
||
script = mkOption {
|
||
type = types.lines;
|
||
default = "";
|
||
description = "Shell commands executed as the service's main process.";
|
||
};
|
||
|
||
scriptArgs = mkOption {
|
||
type = types.str;
|
||
default = "";
|
||
example = "%i";
|
||
description = ''
|
||
Arguments passed to the main process script.
|
||
Can contain specifiers (`%` placeholders expanded by systemd, see {manpage}`systemd.unit(5)`).
|
||
'';
|
||
};
|
||
|
||
preStart = mkOption {
|
||
type = types.lines;
|
||
default = "";
|
||
description = ''
|
||
Shell commands executed before the service's main process
|
||
is started.
|
||
'';
|
||
};
|
||
|
||
postStart = mkOption {
|
||
type = types.lines;
|
||
default = "";
|
||
description = ''
|
||
Shell commands executed after the service's main process
|
||
is started.
|
||
'';
|
||
};
|
||
|
||
reload = mkOption {
|
||
type = types.lines;
|
||
default = "";
|
||
description = ''
|
||
Shell commands executed when the service's main process
|
||
is reloaded.
|
||
'';
|
||
};
|
||
|
||
preStop = mkOption {
|
||
type = types.lines;
|
||
default = "";
|
||
description = ''
|
||
Shell commands executed to stop the service.
|
||
'';
|
||
};
|
||
|
||
postStop = mkOption {
|
||
type = types.lines;
|
||
default = "";
|
||
description = ''
|
||
Shell commands executed after the service's main process
|
||
has exited.
|
||
'';
|
||
};
|
||
|
||
jobScripts = mkOption {
|
||
type = with types; coercedTo path singleton (listOf path);
|
||
internal = true;
|
||
description = "A list of all job script derivations of this unit.";
|
||
default = [];
|
||
};
|
||
|
||
};
|
||
|
||
config = mkMerge [
|
||
(mkIf (config.preStart != "") rec {
|
||
jobScripts = makeJobScript {
|
||
name = "${name}-pre-start";
|
||
text = config.preStart;
|
||
inherit (config) enableStrictShellChecks;
|
||
};
|
||
serviceConfig.ExecStartPre = [ jobScripts ];
|
||
})
|
||
(mkIf (config.script != "") rec {
|
||
jobScripts = makeJobScript {
|
||
name = "${name}-start";
|
||
text = config.script;
|
||
inherit (config) enableStrictShellChecks;
|
||
};
|
||
serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs;
|
||
})
|
||
(mkIf (config.postStart != "") rec {
|
||
jobScripts = makeJobScript {
|
||
name = "${name}-post-start";
|
||
text = config.postStart;
|
||
inherit (config) enableStrictShellChecks;
|
||
};
|
||
serviceConfig.ExecStartPost = [ jobScripts ];
|
||
})
|
||
(mkIf (config.reload != "") rec {
|
||
jobScripts = makeJobScript {
|
||
name = "${name}-reload";
|
||
text = config.reload;
|
||
inherit (config) enableStrictShellChecks;
|
||
};
|
||
serviceConfig.ExecReload = jobScripts;
|
||
})
|
||
(mkIf (config.preStop != "") rec {
|
||
jobScripts = makeJobScript {
|
||
name = "${name}-pre-stop";
|
||
text = config.preStop;
|
||
inherit (config) enableStrictShellChecks;
|
||
};
|
||
serviceConfig.ExecStop = jobScripts;
|
||
})
|
||
(mkIf (config.postStop != "") rec {
|
||
jobScripts = makeJobScript {
|
||
name = "${name}-post-stop";
|
||
text = config.postStop;
|
||
inherit (config) enableStrictShellChecks;
|
||
};
|
||
serviceConfig.ExecStopPost = jobScripts;
|
||
})
|
||
];
|
||
|
||
};
|
||
|
||
stage2ServiceOptions = {
|
||
imports = [
|
||
stage2CommonUnitOptions
|
||
serviceOptions
|
||
];
|
||
|
||
options = {
|
||
restartIfChanged = mkOption {
|
||
type = types.bool;
|
||
default = true;
|
||
description = ''
|
||
Whether the service should be restarted during a NixOS
|
||
configuration switch if its definition has changed.
|
||
'';
|
||
};
|
||
|
||
reloadIfChanged = mkOption {
|
||
type = types.bool;
|
||
default = false;
|
||
description = ''
|
||
Whether the service should be reloaded during a NixOS
|
||
configuration switch if its definition has changed. If
|
||
enabled, the value of {option}`restartIfChanged` is
|
||
ignored.
|
||
|
||
This option should not be used anymore in favor of
|
||
{option}`reloadTriggers` which allows more granular
|
||
control of when a service is reloaded and when a service
|
||
is restarted.
|
||
'';
|
||
};
|
||
|
||
stopIfChanged = mkOption {
|
||
type = types.bool;
|
||
default = true;
|
||
description = ''
|
||
If set, a changed unit is restarted by calling
|
||
{command}`systemctl stop` in the old configuration,
|
||
then {command}`systemctl start` in the new one.
|
||
Otherwise, it is restarted in a single step using
|
||
{command}`systemctl restart` in the new configuration.
|
||
The latter is less correct because it runs the
|
||
`ExecStop` commands from the new
|
||
configuration.
|
||
'';
|
||
};
|
||
|
||
startAt = mkOption {
|
||
type = with types; either str (listOf str);
|
||
default = [];
|
||
example = "Sun 14:00:00";
|
||
description = ''
|
||
Automatically start this unit at the given date/time, which
|
||
must be in the format described in
|
||
{manpage}`systemd.time(7)`. This is equivalent
|
||
to adding a corresponding timer unit with
|
||
{option}`OnCalendar` set to the value given here.
|
||
'';
|
||
apply = v: if isList v then v else [ v ];
|
||
};
|
||
};
|
||
};
|
||
|
||
stage1ServiceOptions = {
|
||
imports = [
|
||
stage1CommonUnitOptions
|
||
serviceOptions
|
||
];
|
||
};
|
||
|
||
|
||
socketOptions = {
|
||
options = {
|
||
|
||
listenStreams = mkOption {
|
||
default = [];
|
||
type = types.listOf types.str;
|
||
example = [ "0.0.0.0:993" "/run/my-socket" ];
|
||
description = ''
|
||
For each item in this list, a `ListenStream`
|
||
option in the `[Socket]` section will be created.
|
||
'';
|
||
};
|
||
|
||
listenDatagrams = mkOption {
|
||
default = [];
|
||
type = types.listOf types.str;
|
||
example = [ "0.0.0.0:993" "/run/my-socket" ];
|
||
description = ''
|
||
For each item in this list, a `ListenDatagram`
|
||
option in the `[Socket]` section will be created.
|
||
'';
|
||
};
|
||
|
||
socketConfig = mkOption {
|
||
default = {};
|
||
example = { ListenStream = "/run/my-socket"; };
|
||
type = types.attrsOf unitOption;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Socket]` section of the unit. See
|
||
{manpage}`systemd.socket(5)` for details.
|
||
'';
|
||
};
|
||
};
|
||
|
||
};
|
||
|
||
stage2SocketOptions = {
|
||
imports = [
|
||
stage2CommonUnitOptions
|
||
socketOptions
|
||
];
|
||
};
|
||
|
||
stage1SocketOptions = {
|
||
imports = [
|
||
stage1CommonUnitOptions
|
||
socketOptions
|
||
];
|
||
};
|
||
|
||
|
||
timerOptions = {
|
||
options = {
|
||
|
||
timerConfig = mkOption {
|
||
default = {};
|
||
example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; };
|
||
type = types.attrsOf unitOption;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Timer]` section of the unit. See
|
||
{manpage}`systemd.timer(5)` and
|
||
{manpage}`systemd.time(7)` for details.
|
||
'';
|
||
};
|
||
|
||
};
|
||
};
|
||
|
||
stage2TimerOptions = {
|
||
imports = [
|
||
stage2CommonUnitOptions
|
||
timerOptions
|
||
];
|
||
};
|
||
|
||
stage1TimerOptions = {
|
||
imports = [
|
||
stage1CommonUnitOptions
|
||
timerOptions
|
||
];
|
||
};
|
||
|
||
|
||
pathOptions = {
|
||
options = {
|
||
|
||
pathConfig = mkOption {
|
||
default = {};
|
||
example = { PathChanged = "/some/path"; Unit = "changedpath.service"; };
|
||
type = types.attrsOf unitOption;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Path]` section of the unit. See
|
||
{manpage}`systemd.path(5)` for details.
|
||
'';
|
||
};
|
||
|
||
};
|
||
};
|
||
|
||
stage2PathOptions = {
|
||
imports = [
|
||
stage2CommonUnitOptions
|
||
pathOptions
|
||
];
|
||
};
|
||
|
||
stage1PathOptions = {
|
||
imports = [
|
||
stage1CommonUnitOptions
|
||
pathOptions
|
||
];
|
||
};
|
||
|
||
|
||
mountOptions = {
|
||
options = {
|
||
|
||
what = mkOption {
|
||
example = "/dev/sda1";
|
||
type = types.str;
|
||
description = "Absolute path of device node, file or other resource. (Mandatory)";
|
||
};
|
||
|
||
where = mkOption {
|
||
example = "/mnt";
|
||
type = types.str;
|
||
description = ''
|
||
Absolute path of a directory of the mount point.
|
||
Will be created if it doesn't exist. (Mandatory)
|
||
'';
|
||
};
|
||
|
||
type = mkOption {
|
||
default = "";
|
||
example = "ext4";
|
||
type = types.str;
|
||
description = "File system type.";
|
||
};
|
||
|
||
options = mkOption {
|
||
default = "";
|
||
example = "noatime";
|
||
type = types.commas;
|
||
description = "Options used to mount the file system.";
|
||
};
|
||
|
||
mountConfig = mkOption {
|
||
default = {};
|
||
example = { DirectoryMode = "0775"; };
|
||
type = types.attrsOf unitOption;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Mount]` section of the unit. See
|
||
{manpage}`systemd.mount(5)` for details.
|
||
'';
|
||
};
|
||
|
||
};
|
||
};
|
||
|
||
stage2MountOptions = {
|
||
imports = [
|
||
stage2CommonUnitOptions
|
||
mountOptions
|
||
];
|
||
};
|
||
|
||
stage1MountOptions = {
|
||
imports = [
|
||
stage1CommonUnitOptions
|
||
mountOptions
|
||
];
|
||
};
|
||
|
||
automountOptions = {
|
||
options = {
|
||
|
||
where = mkOption {
|
||
example = "/mnt";
|
||
type = types.str;
|
||
description = ''
|
||
Absolute path of a directory of the mount point.
|
||
Will be created if it doesn't exist. (Mandatory)
|
||
'';
|
||
};
|
||
|
||
automountConfig = mkOption {
|
||
default = {};
|
||
example = { DirectoryMode = "0775"; };
|
||
type = types.attrsOf unitOption;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Automount]` section of the unit. See
|
||
{manpage}`systemd.automount(5)` for details.
|
||
'';
|
||
};
|
||
|
||
};
|
||
};
|
||
|
||
stage2AutomountOptions = {
|
||
imports = [
|
||
stage2CommonUnitOptions
|
||
automountOptions
|
||
];
|
||
};
|
||
|
||
stage1AutomountOptions = {
|
||
imports = [
|
||
stage1CommonUnitOptions
|
||
automountOptions
|
||
];
|
||
};
|
||
|
||
sliceOptions = {
|
||
options = {
|
||
|
||
sliceConfig = mkOption {
|
||
default = {};
|
||
example = { MemoryMax = "2G"; };
|
||
type = types.attrsOf unitOption;
|
||
description = ''
|
||
Each attribute in this set specifies an option in the
|
||
`[Slice]` section of the unit. See
|
||
{manpage}`systemd.slice(5)` for details.
|
||
'';
|
||
};
|
||
|
||
};
|
||
};
|
||
|
||
stage2SliceOptions = {
|
||
imports = [
|
||
stage2CommonUnitOptions
|
||
sliceOptions
|
||
];
|
||
};
|
||
|
||
stage1SliceOptions = {
|
||
imports = [
|
||
stage1CommonUnitOptions
|
||
sliceOptions
|
||
];
|
||
};
|
||
|
||
}
|