1{ lib, systemdUtils }:
2
3with systemdUtils.lib;
4with lib;
5
6let
7 checkService = checkUnitConfig "Service" [
8 (assertValueOneOf "Type" [
9 "exec" "simple" "forking" "oneshot" "dbus" "notify" "idle"
10 ])
11 (assertValueOneOf "Restart" [
12 "no" "on-success" "on-failure" "on-abnormal" "on-abort" "always"
13 ])
14 ];
15
16in rec {
17
18 unitOption = mkOptionType {
19 name = "systemd option";
20 merge = loc: defs:
21 let
22 defs' = filterOverrides defs;
23 in
24 if isList (head defs').value
25 then concatMap (def:
26 if builtins.typeOf def.value == "list"
27 then def.value
28 else
29 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'}"
30 ) defs'
31
32 else mergeEqualOption loc defs';
33 };
34
35 sharedOptions = {
36
37 enable = mkOption {
38 default = true;
39 type = types.bool;
40 description = lib.mdDoc ''
41 If set to false, this unit will be a symlink to
42 /dev/null. This is primarily useful to prevent specific
43 template instances
44 (e.g. `serial-getty@ttyS0`) from being
45 started. Note that `enable=true` does not
46 make a unit start by default at boot; if you want that, see
47 `wantedBy`.
48 '';
49 };
50
51 overrideStrategy = mkOption {
52 default = "asDropinIfExists";
53 type = types.enum [ "asDropinIfExists" "asDropin" ];
54 description = lib.mdDoc ''
55 Defines how unit configuration is provided for systemd:
56
57 `asDropinIfExists` creates a unit file when no unit file is provided by the package
58 otherwise a drop-in file name `overrides.conf`.
59
60 `asDropin` creates a drop-in file named `overrides.conf`.
61 Mainly needed to define instances for systemd template units (e.g. `systemd-nspawn@mycontainer.service`).
62
63 See also {manpage}`systemd.unit(5)`.
64 '';
65 };
66
67 requiredBy = mkOption {
68 default = [];
69 type = types.listOf unitNameType;
70 description = lib.mdDoc ''
71 Units that require (i.e. depend on and need to go down with) this unit.
72 As discussed in the `wantedBy` option description this also creates
73 `.requires` symlinks automatically.
74 '';
75 };
76
77 wantedBy = mkOption {
78 default = [];
79 type = types.listOf unitNameType;
80 description = lib.mdDoc ''
81 Units that want (i.e. depend on) this unit. The default method for
82 starting a unit by default at boot time is to set this option to
83 `["multi-user.target"]` for system services. Likewise for user units
84 (`systemd.user.<name>.*`) set it to `["default.target"]` to make a unit
85 start by default when the user `<name>` logs on.
86
87 This option creates a `.wants` symlink in the given target that exists
88 statelessly without the need for running `systemctl enable`.
89 The `[Install]` section described in {manpage}`systemd.unit(5)` however is
90 not supported because it is a stateful process that does not fit well
91 into the NixOS design.
92 '';
93 };
94
95 aliases = mkOption {
96 default = [];
97 type = types.listOf unitNameType;
98 description = lib.mdDoc "Aliases of that unit.";
99 };
100
101 };
102
103 concreteUnitOptions = sharedOptions // {
104
105 text = mkOption {
106 type = types.nullOr types.str;
107 default = null;
108 description = lib.mdDoc "Text of this systemd unit.";
109 };
110
111 unit = mkOption {
112 internal = true;
113 description = lib.mdDoc "The generated unit.";
114 };
115
116 };
117
118 commonUnitOptions = {
119 options = sharedOptions // {
120
121 description = mkOption {
122 default = "";
123 type = types.singleLineStr;
124 description = lib.mdDoc "Description of this unit used in systemd messages and progress indicators.";
125 };
126
127 documentation = mkOption {
128 default = [];
129 type = types.listOf types.str;
130 description = lib.mdDoc "A list of URIs referencing documentation for this unit or its configuration.";
131 };
132
133 requires = mkOption {
134 default = [];
135 type = types.listOf unitNameType;
136 description = lib.mdDoc ''
137 Start the specified units when this unit is started, and stop
138 this unit when the specified units are stopped or fail.
139 '';
140 };
141
142 wants = mkOption {
143 default = [];
144 type = types.listOf unitNameType;
145 description = lib.mdDoc ''
146 Start the specified units when this unit is started.
147 '';
148 };
149
150 after = mkOption {
151 default = [];
152 type = types.listOf unitNameType;
153 description = lib.mdDoc ''
154 If the specified units are started at the same time as
155 this unit, delay this unit until they have started.
156 '';
157 };
158
159 before = mkOption {
160 default = [];
161 type = types.listOf unitNameType;
162 description = lib.mdDoc ''
163 If the specified units are started at the same time as
164 this unit, delay them until this unit has started.
165 '';
166 };
167
168 bindsTo = mkOption {
169 default = [];
170 type = types.listOf unitNameType;
171 description = lib.mdDoc ''
172 Like ‘requires’, but in addition, if the specified units
173 unexpectedly disappear, this unit will be stopped as well.
174 '';
175 };
176
177 partOf = mkOption {
178 default = [];
179 type = types.listOf unitNameType;
180 description = lib.mdDoc ''
181 If the specified units are stopped or restarted, then this
182 unit is stopped or restarted as well.
183 '';
184 };
185
186 conflicts = mkOption {
187 default = [];
188 type = types.listOf unitNameType;
189 description = lib.mdDoc ''
190 If the specified units are started, then this unit is stopped
191 and vice versa.
192 '';
193 };
194
195 requisite = mkOption {
196 default = [];
197 type = types.listOf unitNameType;
198 description = lib.mdDoc ''
199 Similar to requires. However if the units listed are not started,
200 they will not be started and the transaction will fail.
201 '';
202 };
203
204 unitConfig = mkOption {
205 default = {};
206 example = { RequiresMountsFor = "/data"; };
207 type = types.attrsOf unitOption;
208 description = lib.mdDoc ''
209 Each attribute in this set specifies an option in the
210 `[Unit]` section of the unit. See
211 {manpage}`systemd.unit(5)` for details.
212 '';
213 };
214
215 onFailure = mkOption {
216 default = [];
217 type = types.listOf unitNameType;
218 description = lib.mdDoc ''
219 A list of one or more units that are activated when
220 this unit enters the "failed" state.
221 '';
222 };
223
224 onSuccess = mkOption {
225 default = [];
226 type = types.listOf unitNameType;
227 description = lib.mdDoc ''
228 A list of one or more units that are activated when
229 this unit enters the "inactive" state.
230 '';
231 };
232
233 startLimitBurst = mkOption {
234 type = types.int;
235 description = lib.mdDoc ''
236 Configure unit start rate limiting. Units which are started
237 more than startLimitBurst times within an interval time
238 interval are not permitted to start any more.
239 '';
240 };
241
242 startLimitIntervalSec = mkOption {
243 type = types.int;
244 description = lib.mdDoc ''
245 Configure unit start rate limiting. Units which are started
246 more than startLimitBurst times within an interval time
247 interval are not permitted to start any more.
248 '';
249 };
250
251 };
252 };
253
254 stage2CommonUnitOptions = {
255 imports = [
256 commonUnitOptions
257 ];
258
259 options = {
260 restartTriggers = mkOption {
261 default = [];
262 type = types.listOf types.unspecified;
263 description = lib.mdDoc ''
264 An arbitrary list of items such as derivations. If any item
265 in the list changes between reconfigurations, the service will
266 be restarted.
267 '';
268 };
269
270 reloadTriggers = mkOption {
271 default = [];
272 type = types.listOf unitOption;
273 description = lib.mdDoc ''
274 An arbitrary list of items such as derivations. If any item
275 in the list changes between reconfigurations, the service will
276 be reloaded. If anything but a reload trigger changes in the
277 unit file, the unit will be restarted instead.
278 '';
279 };
280 };
281 };
282 stage1CommonUnitOptions = commonUnitOptions;
283
284 serviceOptions = { name, config, ... }: {
285 options = {
286
287 environment = mkOption {
288 default = {};
289 type = with types; attrsOf (nullOr (oneOf [ str path package ]));
290 example = { PATH = "/foo/bar/bin"; LANG = "nl_NL.UTF-8"; };
291 description = lib.mdDoc "Environment variables passed to the service's processes.";
292 };
293
294 path = mkOption {
295 default = [];
296 type = with types; listOf (oneOf [ package str ]);
297 description = lib.mdDoc ''
298 Packages added to the service's {env}`PATH`
299 environment variable. Both the {file}`bin`
300 and {file}`sbin` subdirectories of each
301 package are added.
302 '';
303 };
304
305 serviceConfig = mkOption {
306 default = {};
307 example =
308 { RestartSec = 5;
309 };
310 type = types.addCheck (types.attrsOf unitOption) checkService;
311 description = lib.mdDoc ''
312 Each attribute in this set specifies an option in the
313 `[Service]` section of the unit. See
314 {manpage}`systemd.service(5)` for details.
315 '';
316 };
317
318 script = mkOption {
319 type = types.lines;
320 default = "";
321 description = lib.mdDoc "Shell commands executed as the service's main process.";
322 };
323
324 scriptArgs = mkOption {
325 type = types.str;
326 default = "";
327 example = "%i";
328 description = lib.mdDoc ''
329 Arguments passed to the main process script.
330 Can contain specifiers (`%` placeholders expanded by systemd, see {manpage}`systemd.unit(5)`).
331 '';
332 };
333
334 preStart = mkOption {
335 type = types.lines;
336 default = "";
337 description = lib.mdDoc ''
338 Shell commands executed before the service's main process
339 is started.
340 '';
341 };
342
343 postStart = mkOption {
344 type = types.lines;
345 default = "";
346 description = lib.mdDoc ''
347 Shell commands executed after the service's main process
348 is started.
349 '';
350 };
351
352 reload = mkOption {
353 type = types.lines;
354 default = "";
355 description = lib.mdDoc ''
356 Shell commands executed when the service's main process
357 is reloaded.
358 '';
359 };
360
361 preStop = mkOption {
362 type = types.lines;
363 default = "";
364 description = lib.mdDoc ''
365 Shell commands executed to stop the service.
366 '';
367 };
368
369 postStop = mkOption {
370 type = types.lines;
371 default = "";
372 description = lib.mdDoc ''
373 Shell commands executed after the service's main process
374 has exited.
375 '';
376 };
377
378 jobScripts = mkOption {
379 type = with types; coercedTo path singleton (listOf path);
380 internal = true;
381 description = lib.mdDoc "A list of all job script derivations of this unit.";
382 default = [];
383 };
384
385 };
386
387 config = mkMerge [
388 (mkIf (config.preStart != "") rec {
389 jobScripts = makeJobScript "${name}-pre-start" config.preStart;
390 serviceConfig.ExecStartPre = [ jobScripts ];
391 })
392 (mkIf (config.script != "") rec {
393 jobScripts = makeJobScript "${name}-start" config.script;
394 serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs;
395 })
396 (mkIf (config.postStart != "") rec {
397 jobScripts = (makeJobScript "${name}-post-start" config.postStart);
398 serviceConfig.ExecStartPost = [ jobScripts ];
399 })
400 (mkIf (config.reload != "") rec {
401 jobScripts = makeJobScript "${name}-reload" config.reload;
402 serviceConfig.ExecReload = jobScripts;
403 })
404 (mkIf (config.preStop != "") rec {
405 jobScripts = makeJobScript "${name}-pre-stop" config.preStop;
406 serviceConfig.ExecStop = jobScripts;
407 })
408 (mkIf (config.postStop != "") rec {
409 jobScripts = makeJobScript "${name}-post-stop" config.postStop;
410 serviceConfig.ExecStopPost = jobScripts;
411 })
412 ];
413
414 };
415
416 stage2ServiceOptions = {
417 imports = [
418 stage2CommonUnitOptions
419 serviceOptions
420 ];
421
422 options = {
423 restartIfChanged = mkOption {
424 type = types.bool;
425 default = true;
426 description = lib.mdDoc ''
427 Whether the service should be restarted during a NixOS
428 configuration switch if its definition has changed.
429 '';
430 };
431
432 reloadIfChanged = mkOption {
433 type = types.bool;
434 default = false;
435 description = lib.mdDoc ''
436 Whether the service should be reloaded during a NixOS
437 configuration switch if its definition has changed. If
438 enabled, the value of {option}`restartIfChanged` is
439 ignored.
440
441 This option should not be used anymore in favor of
442 {option}`reloadTriggers` which allows more granular
443 control of when a service is reloaded and when a service
444 is restarted.
445 '';
446 };
447
448 stopIfChanged = mkOption {
449 type = types.bool;
450 default = true;
451 description = lib.mdDoc ''
452 If set, a changed unit is restarted by calling
453 {command}`systemctl stop` in the old configuration,
454 then {command}`systemctl start` in the new one.
455 Otherwise, it is restarted in a single step using
456 {command}`systemctl restart` in the new configuration.
457 The latter is less correct because it runs the
458 `ExecStop` commands from the new
459 configuration.
460 '';
461 };
462
463 startAt = mkOption {
464 type = with types; either str (listOf str);
465 default = [];
466 example = "Sun 14:00:00";
467 description = lib.mdDoc ''
468 Automatically start this unit at the given date/time, which
469 must be in the format described in
470 {manpage}`systemd.time(7)`. This is equivalent
471 to adding a corresponding timer unit with
472 {option}`OnCalendar` set to the value given here.
473 '';
474 apply = v: if isList v then v else [ v ];
475 };
476 };
477 };
478
479 stage1ServiceOptions = {
480 imports = [
481 stage1CommonUnitOptions
482 serviceOptions
483 ];
484 };
485
486
487 socketOptions = {
488 options = {
489
490 listenStreams = mkOption {
491 default = [];
492 type = types.listOf types.str;
493 example = [ "0.0.0.0:993" "/run/my-socket" ];
494 description = lib.mdDoc ''
495 For each item in this list, a `ListenStream`
496 option in the `[Socket]` section will be created.
497 '';
498 };
499
500 listenDatagrams = mkOption {
501 default = [];
502 type = types.listOf types.str;
503 example = [ "0.0.0.0:993" "/run/my-socket" ];
504 description = lib.mdDoc ''
505 For each item in this list, a `ListenDatagram`
506 option in the `[Socket]` section will be created.
507 '';
508 };
509
510 socketConfig = mkOption {
511 default = {};
512 example = { ListenStream = "/run/my-socket"; };
513 type = types.attrsOf unitOption;
514 description = lib.mdDoc ''
515 Each attribute in this set specifies an option in the
516 `[Socket]` section of the unit. See
517 {manpage}`systemd.socket(5)` for details.
518 '';
519 };
520 };
521
522 };
523
524 stage2SocketOptions = {
525 imports = [
526 stage2CommonUnitOptions
527 socketOptions
528 ];
529 };
530
531 stage1SocketOptions = {
532 imports = [
533 stage1CommonUnitOptions
534 socketOptions
535 ];
536 };
537
538
539 timerOptions = {
540 options = {
541
542 timerConfig = mkOption {
543 default = {};
544 example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; };
545 type = types.attrsOf unitOption;
546 description = lib.mdDoc ''
547 Each attribute in this set specifies an option in the
548 `[Timer]` section of the unit. See
549 {manpage}`systemd.timer(5)` and
550 {manpage}`systemd.time(7)` for details.
551 '';
552 };
553
554 };
555 };
556
557 stage2TimerOptions = {
558 imports = [
559 stage2CommonUnitOptions
560 timerOptions
561 ];
562 };
563
564 stage1TimerOptions = {
565 imports = [
566 stage1CommonUnitOptions
567 timerOptions
568 ];
569 };
570
571
572 pathOptions = {
573 options = {
574
575 pathConfig = mkOption {
576 default = {};
577 example = { PathChanged = "/some/path"; Unit = "changedpath.service"; };
578 type = types.attrsOf unitOption;
579 description = lib.mdDoc ''
580 Each attribute in this set specifies an option in the
581 `[Path]` section of the unit. See
582 {manpage}`systemd.path(5)` for details.
583 '';
584 };
585
586 };
587 };
588
589 stage2PathOptions = {
590 imports = [
591 stage2CommonUnitOptions
592 pathOptions
593 ];
594 };
595
596 stage1PathOptions = {
597 imports = [
598 stage1CommonUnitOptions
599 pathOptions
600 ];
601 };
602
603
604 mountOptions = {
605 options = {
606
607 what = mkOption {
608 example = "/dev/sda1";
609 type = types.str;
610 description = lib.mdDoc "Absolute path of device node, file or other resource. (Mandatory)";
611 };
612
613 where = mkOption {
614 example = "/mnt";
615 type = types.str;
616 description = lib.mdDoc ''
617 Absolute path of a directory of the mount point.
618 Will be created if it doesn't exist. (Mandatory)
619 '';
620 };
621
622 type = mkOption {
623 default = "";
624 example = "ext4";
625 type = types.str;
626 description = lib.mdDoc "File system type.";
627 };
628
629 options = mkOption {
630 default = "";
631 example = "noatime";
632 type = types.commas;
633 description = lib.mdDoc "Options used to mount the file system.";
634 };
635
636 mountConfig = mkOption {
637 default = {};
638 example = { DirectoryMode = "0775"; };
639 type = types.attrsOf unitOption;
640 description = lib.mdDoc ''
641 Each attribute in this set specifies an option in the
642 `[Mount]` section of the unit. See
643 {manpage}`systemd.mount(5)` for details.
644 '';
645 };
646
647 };
648 };
649
650 stage2MountOptions = {
651 imports = [
652 stage2CommonUnitOptions
653 mountOptions
654 ];
655 };
656
657 stage1MountOptions = {
658 imports = [
659 stage1CommonUnitOptions
660 mountOptions
661 ];
662 };
663
664 automountOptions = {
665 options = {
666
667 where = mkOption {
668 example = "/mnt";
669 type = types.str;
670 description = lib.mdDoc ''
671 Absolute path of a directory of the mount point.
672 Will be created if it doesn't exist. (Mandatory)
673 '';
674 };
675
676 automountConfig = mkOption {
677 default = {};
678 example = { DirectoryMode = "0775"; };
679 type = types.attrsOf unitOption;
680 description = lib.mdDoc ''
681 Each attribute in this set specifies an option in the
682 `[Automount]` section of the unit. See
683 {manpage}`systemd.automount(5)` for details.
684 '';
685 };
686
687 };
688 };
689
690 stage2AutomountOptions = {
691 imports = [
692 stage2CommonUnitOptions
693 automountOptions
694 ];
695 };
696
697 stage1AutomountOptions = {
698 imports = [
699 stage1CommonUnitOptions
700 automountOptions
701 ];
702 };
703
704 sliceOptions = {
705 options = {
706
707 sliceConfig = mkOption {
708 default = {};
709 example = { MemoryMax = "2G"; };
710 type = types.attrsOf unitOption;
711 description = lib.mdDoc ''
712 Each attribute in this set specifies an option in the
713 `[Slice]` section of the unit. See
714 {manpage}`systemd.slice(5)` for details.
715 '';
716 };
717
718 };
719 };
720
721 stage2SliceOptions = {
722 imports = [
723 stage2CommonUnitOptions
724 sliceOptions
725 ];
726 };
727
728 stage1SliceOptions = {
729 imports = [
730 stage1CommonUnitOptions
731 sliceOptions
732 ];
733 };
734
735}