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