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 systemd.unit(1).
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 in systemd.unit(5) manpage described `[Install]` section 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 description = lib.mdDoc "Arguments passed to the main process script.";
328 };
329
330 preStart = mkOption {
331 type = types.lines;
332 default = "";
333 description = lib.mdDoc ''
334 Shell commands executed before the service's main process
335 is started.
336 '';
337 };
338
339 postStart = mkOption {
340 type = types.lines;
341 default = "";
342 description = lib.mdDoc ''
343 Shell commands executed after the service's main process
344 is started.
345 '';
346 };
347
348 reload = mkOption {
349 type = types.lines;
350 default = "";
351 description = lib.mdDoc ''
352 Shell commands executed when the service's main process
353 is reloaded.
354 '';
355 };
356
357 preStop = mkOption {
358 type = types.lines;
359 default = "";
360 description = lib.mdDoc ''
361 Shell commands executed to stop the service.
362 '';
363 };
364
365 postStop = mkOption {
366 type = types.lines;
367 default = "";
368 description = lib.mdDoc ''
369 Shell commands executed after the service's main process
370 has exited.
371 '';
372 };
373
374 jobScripts = mkOption {
375 type = with types; coercedTo path singleton (listOf path);
376 internal = true;
377 description = lib.mdDoc "A list of all job script derivations of this unit.";
378 default = [];
379 };
380
381 };
382
383 config = mkMerge [
384 (mkIf (config.preStart != "") rec {
385 jobScripts = makeJobScript "${name}-pre-start" config.preStart;
386 serviceConfig.ExecStartPre = [ jobScripts ];
387 })
388 (mkIf (config.script != "") rec {
389 jobScripts = makeJobScript "${name}-start" config.script;
390 serviceConfig.ExecStart = jobScripts + " " + config.scriptArgs;
391 })
392 (mkIf (config.postStart != "") rec {
393 jobScripts = (makeJobScript "${name}-post-start" config.postStart);
394 serviceConfig.ExecStartPost = [ jobScripts ];
395 })
396 (mkIf (config.reload != "") rec {
397 jobScripts = makeJobScript "${name}-reload" config.reload;
398 serviceConfig.ExecReload = jobScripts;
399 })
400 (mkIf (config.preStop != "") rec {
401 jobScripts = makeJobScript "${name}-pre-stop" config.preStop;
402 serviceConfig.ExecStop = jobScripts;
403 })
404 (mkIf (config.postStop != "") rec {
405 jobScripts = makeJobScript "${name}-post-stop" config.postStop;
406 serviceConfig.ExecStopPost = jobScripts;
407 })
408 ];
409
410 };
411
412 stage2ServiceOptions = {
413 imports = [
414 stage2CommonUnitOptions
415 serviceOptions
416 ];
417
418 options = {
419 restartIfChanged = mkOption {
420 type = types.bool;
421 default = true;
422 description = lib.mdDoc ''
423 Whether the service should be restarted during a NixOS
424 configuration switch if its definition has changed.
425 '';
426 };
427
428 reloadIfChanged = mkOption {
429 type = types.bool;
430 default = false;
431 description = lib.mdDoc ''
432 Whether the service should be reloaded during a NixOS
433 configuration switch if its definition has changed. If
434 enabled, the value of {option}`restartIfChanged` is
435 ignored.
436
437 This option should not be used anymore in favor of
438 {option}`reloadTriggers` which allows more granular
439 control of when a service is reloaded and when a service
440 is restarted.
441 '';
442 };
443
444 stopIfChanged = mkOption {
445 type = types.bool;
446 default = true;
447 description = lib.mdDoc ''
448 If set, a changed unit is restarted by calling
449 {command}`systemctl stop` in the old configuration,
450 then {command}`systemctl start` in the new one.
451 Otherwise, it is restarted in a single step using
452 {command}`systemctl restart` in the new configuration.
453 The latter is less correct because it runs the
454 `ExecStop` commands from the new
455 configuration.
456 '';
457 };
458
459 startAt = mkOption {
460 type = with types; either str (listOf str);
461 default = [];
462 example = "Sun 14:00:00";
463 description = lib.mdDoc ''
464 Automatically start this unit at the given date/time, which
465 must be in the format described in
466 {manpage}`systemd.time(7)`. This is equivalent
467 to adding a corresponding timer unit with
468 {option}`OnCalendar` set to the value given here.
469 '';
470 apply = v: if isList v then v else [ v ];
471 };
472 };
473 };
474
475 stage1ServiceOptions = {
476 imports = [
477 stage1CommonUnitOptions
478 serviceOptions
479 ];
480 };
481
482
483 socketOptions = {
484 options = {
485
486 listenStreams = mkOption {
487 default = [];
488 type = types.listOf types.str;
489 example = [ "0.0.0.0:993" "/run/my-socket" ];
490 description = lib.mdDoc ''
491 For each item in this list, a `ListenStream`
492 option in the `[Socket]` section will be created.
493 '';
494 };
495
496 listenDatagrams = mkOption {
497 default = [];
498 type = types.listOf types.str;
499 example = [ "0.0.0.0:993" "/run/my-socket" ];
500 description = lib.mdDoc ''
501 For each item in this list, a `ListenDatagram`
502 option in the `[Socket]` section will be created.
503 '';
504 };
505
506 socketConfig = mkOption {
507 default = {};
508 example = { ListenStream = "/run/my-socket"; };
509 type = types.attrsOf unitOption;
510 description = lib.mdDoc ''
511 Each attribute in this set specifies an option in the
512 `[Socket]` section of the unit. See
513 {manpage}`systemd.socket(5)` for details.
514 '';
515 };
516 };
517
518 };
519
520 stage2SocketOptions = {
521 imports = [
522 stage2CommonUnitOptions
523 socketOptions
524 ];
525 };
526
527 stage1SocketOptions = {
528 imports = [
529 stage1CommonUnitOptions
530 socketOptions
531 ];
532 };
533
534
535 timerOptions = {
536 options = {
537
538 timerConfig = mkOption {
539 default = {};
540 example = { OnCalendar = "Sun 14:00:00"; Unit = "foo.service"; };
541 type = types.attrsOf unitOption;
542 description = lib.mdDoc ''
543 Each attribute in this set specifies an option in the
544 `[Timer]` section of the unit. See
545 {manpage}`systemd.timer(5)` and
546 {manpage}`systemd.time(7)` for details.
547 '';
548 };
549
550 };
551 };
552
553 stage2TimerOptions = {
554 imports = [
555 stage2CommonUnitOptions
556 timerOptions
557 ];
558 };
559
560 stage1TimerOptions = {
561 imports = [
562 stage1CommonUnitOptions
563 timerOptions
564 ];
565 };
566
567
568 pathOptions = {
569 options = {
570
571 pathConfig = mkOption {
572 default = {};
573 example = { PathChanged = "/some/path"; Unit = "changedpath.service"; };
574 type = types.attrsOf unitOption;
575 description = lib.mdDoc ''
576 Each attribute in this set specifies an option in the
577 `[Path]` section of the unit. See
578 {manpage}`systemd.path(5)` for details.
579 '';
580 };
581
582 };
583 };
584
585 stage2PathOptions = {
586 imports = [
587 stage2CommonUnitOptions
588 pathOptions
589 ];
590 };
591
592 stage1PathOptions = {
593 imports = [
594 stage1CommonUnitOptions
595 pathOptions
596 ];
597 };
598
599
600 mountOptions = {
601 options = {
602
603 what = mkOption {
604 example = "/dev/sda1";
605 type = types.str;
606 description = lib.mdDoc "Absolute path of device node, file or other resource. (Mandatory)";
607 };
608
609 where = mkOption {
610 example = "/mnt";
611 type = types.str;
612 description = lib.mdDoc ''
613 Absolute path of a directory of the mount point.
614 Will be created if it doesn't exist. (Mandatory)
615 '';
616 };
617
618 type = mkOption {
619 default = "";
620 example = "ext4";
621 type = types.str;
622 description = lib.mdDoc "File system type.";
623 };
624
625 options = mkOption {
626 default = "";
627 example = "noatime";
628 type = types.commas;
629 description = lib.mdDoc "Options used to mount the file system.";
630 };
631
632 mountConfig = mkOption {
633 default = {};
634 example = { DirectoryMode = "0775"; };
635 type = types.attrsOf unitOption;
636 description = lib.mdDoc ''
637 Each attribute in this set specifies an option in the
638 `[Mount]` section of the unit. See
639 {manpage}`systemd.mount(5)` for details.
640 '';
641 };
642
643 };
644 };
645
646 stage2MountOptions = {
647 imports = [
648 stage2CommonUnitOptions
649 mountOptions
650 ];
651 };
652
653 stage1MountOptions = {
654 imports = [
655 stage1CommonUnitOptions
656 mountOptions
657 ];
658 };
659
660 automountOptions = {
661 options = {
662
663 where = mkOption {
664 example = "/mnt";
665 type = types.str;
666 description = lib.mdDoc ''
667 Absolute path of a directory of the mount point.
668 Will be created if it doesn't exist. (Mandatory)
669 '';
670 };
671
672 automountConfig = mkOption {
673 default = {};
674 example = { DirectoryMode = "0775"; };
675 type = types.attrsOf unitOption;
676 description = lib.mdDoc ''
677 Each attribute in this set specifies an option in the
678 `[Automount]` section of the unit. See
679 {manpage}`systemd.automount(5)` for details.
680 '';
681 };
682
683 };
684 };
685
686 stage2AutomountOptions = {
687 imports = [
688 stage2CommonUnitOptions
689 automountOptions
690 ];
691 };
692
693 stage1AutomountOptions = {
694 imports = [
695 stage1CommonUnitOptions
696 automountOptions
697 ];
698 };
699
700 sliceOptions = {
701 options = {
702
703 sliceConfig = mkOption {
704 default = {};
705 example = { MemoryMax = "2G"; };
706 type = types.attrsOf unitOption;
707 description = lib.mdDoc ''
708 Each attribute in this set specifies an option in the
709 `[Slice]` section of the unit. See
710 {manpage}`systemd.slice(5)` for details.
711 '';
712 };
713
714 };
715 };
716
717 stage2SliceOptions = {
718 imports = [
719 stage2CommonUnitOptions
720 sliceOptions
721 ];
722 };
723
724 stage1SliceOptions = {
725 imports = [
726 stage1CommonUnitOptions
727 sliceOptions
728 ];
729 };
730
731}