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