{ lib, systemdUtils }: with systemdUtils.lib; with lib; let checkService = checkUnitConfig "Service" [ (assertValueOneOf "Type" [ "exec" "simple" "forking" "oneshot" "dbus" "notify" "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 isList (head defs').value then concatMap (def: if builtins.typeOf def.value == "list" then def.value else throw "The definitions for systemd unit options should be either all lists, representing repeatable options, or all non-lists, but for the option ${showOption loc}, the definitions are a mix of list and non-list ${lib.options.showDefs defs'}" ) 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. ''; }; requiredBy = mkOption { default = []; type = types.listOf unitNameType; description = '' Units that require (i.e. depend on and need to go down with) this unit. The discussion under wantedBy applies here as well: inverse .requires symlinks are established. ''; }; wantedBy = mkOption { default = []; type = types.listOf unitNameType; description = '' Units that want (i.e. depend on) this unit. The standard way to make a unit start by default at boot is to set this option to [ "multi-user.target" ]. That's despite the fact that the systemd.unit(5) manpage says this option goes in the [Install] section that controls the behaviour of systemctl enable. Since such a process is stateful and thus contrary to the design of NixOS, setting this option instead causes the equivalent inverse .wants symlink to be present, establishing the same desired relationship in a stateless way. ''; }; 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. ''; }; 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 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 PATH environment variable. Both the bin and 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 systemd.service 5 for details. ''; }; script = mkOption { type = types.lines; default = ""; description = "Shell commands executed as the service's main process."; }; scriptArgs = mkOption { type = types.str; default = ""; description = "Arguments passed to the main process script."; }; 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}-pre-start" config.preStart; serviceConfig.ExecStartPre = [ jobScripts ]; }) (mkIf (config.script != "") rec { jobScripts = makeJobScript "${name}-start" config.script; serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs; }) (mkIf (config.postStart != "") rec { jobScripts = (makeJobScript "${name}-post-start" config.postStart); serviceConfig.ExecStartPost = [ jobScripts ]; }) (mkIf (config.reload != "") rec { jobScripts = makeJobScript "${name}-reload" config.reload; serviceConfig.ExecReload = jobScripts; }) (mkIf (config.preStop != "") rec { jobScripts = makeJobScript "${name}-pre-stop" config.preStop; serviceConfig.ExecStop = jobScripts; }) (mkIf (config.postStop != "") rec { jobScripts = makeJobScript "${name}-post-stop" config.postStop; 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 is ignored. This option should not be used anymore in favor of 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 systemctl stop in the old configuration, then systemctl start in the new one. Otherwise, it is restarted in a single step using 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 systemd.time 7. This is equivalent to adding a corresponding timer unit with 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 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 systemd.timer 5 and 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 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 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 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 systemd.slice 5 for details. ''; }; }; }; stage2SliceOptions = { imports = [ stage2CommonUnitOptions sliceOptions ]; }; stage1SliceOptions = { imports = [ stage1CommonUnitOptions sliceOptions ]; }; }