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