1{ config, lib, pkgs, utils, ... }:
2
3with utils;
4with lib;
5with import ./systemd-unit-options.nix { inherit config lib; };
6with import ./systemd-lib.nix { inherit config lib pkgs; };
7
8let
9
10 cfg = config.systemd;
11
12 systemd = cfg.package;
13
14 upstreamSystemUnits =
15 [ # Targets.
16 "basic.target"
17 "sysinit.target"
18 "sockets.target"
19 "exit.target"
20 "graphical.target"
21 "multi-user.target"
22 "network.target"
23 "network-pre.target"
24 "network-online.target"
25 "nss-lookup.target"
26 "nss-user-lookup.target"
27 "time-sync.target"
28 "cryptsetup.target"
29 "cryptsetup-pre.target"
30 "remote-cryptsetup.target"
31 "sigpwr.target"
32 "timers.target"
33 "paths.target"
34 "rpcbind.target"
35
36 # Rescue mode.
37 "rescue.target"
38 "rescue.service"
39
40 # Udev.
41 "systemd-udevd-control.socket"
42 "systemd-udevd-kernel.socket"
43 "systemd-udevd.service"
44 "systemd-udev-settle.service"
45 ] ++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service") ++ [
46 # hwdb.bin is managed by NixOS
47 # "systemd-hwdb-update.service"
48
49 # Consoles.
50 "getty.target"
51 "getty-pre.target"
52 "getty@.service"
53 "serial-getty@.service"
54 "console-getty.service"
55 "container-getty@.service"
56 "systemd-vconsole-setup.service"
57
58 # Hardware (started by udev when a relevant device is plugged in).
59 "sound.target"
60 "bluetooth.target"
61 "printer.target"
62 "smartcard.target"
63
64 # Login stuff.
65 "systemd-logind.service"
66 "autovt@.service"
67 "systemd-user-sessions.service"
68 "dbus-org.freedesktop.import1.service"
69 "dbus-org.freedesktop.machine1.service"
70 "dbus-org.freedesktop.login1.service"
71 "user@.service"
72 "user-runtime-dir@.service"
73
74 # Journal.
75 "systemd-journald.socket"
76 "systemd-journald@.socket"
77 "systemd-journald-varlink@.socket"
78 "systemd-journald.service"
79 "systemd-journald@.service"
80 "systemd-journal-flush.service"
81 "systemd-journal-catalog-update.service"
82 ] ++ (optional (!config.boot.isContainer) "systemd-journald-audit.socket") ++ [
83 "systemd-journald-dev-log.socket"
84 "syslog.socket"
85
86 # Coredumps.
87 "systemd-coredump.socket"
88 "systemd-coredump@.service"
89
90 # Kernel module loading.
91 "systemd-modules-load.service"
92 "kmod-static-nodes.service"
93 "modprobe@.service"
94
95 # Filesystems.
96 "systemd-fsck@.service"
97 "systemd-fsck-root.service"
98 "systemd-remount-fs.service"
99 "systemd-pstore.service"
100 "local-fs.target"
101 "local-fs-pre.target"
102 "remote-fs.target"
103 "remote-fs-pre.target"
104 "swap.target"
105 "dev-hugepages.mount"
106 "dev-mqueue.mount"
107 "sys-fs-fuse-connections.mount"
108 ] ++ (optional (!config.boot.isContainer) "sys-kernel-config.mount") ++ [
109 "sys-kernel-debug.mount"
110
111 # Maintaining state across reboots.
112 "systemd-random-seed.service"
113 "systemd-backlight@.service"
114 "systemd-rfkill.service"
115 "systemd-rfkill.socket"
116
117 # Hibernate / suspend.
118 "hibernate.target"
119 "suspend.target"
120 "suspend-then-hibernate.target"
121 "sleep.target"
122 "hybrid-sleep.target"
123 "systemd-hibernate.service"
124 "systemd-hybrid-sleep.service"
125 "systemd-suspend.service"
126 "systemd-suspend-then-hibernate.service"
127
128 # Reboot stuff.
129 "reboot.target"
130 "systemd-reboot.service"
131 "poweroff.target"
132 "systemd-poweroff.service"
133 "halt.target"
134 "systemd-halt.service"
135 "shutdown.target"
136 "umount.target"
137 "final.target"
138 "kexec.target"
139 "systemd-kexec.service"
140 "systemd-update-utmp.service"
141
142 # Password entry.
143 "systemd-ask-password-console.path"
144 "systemd-ask-password-console.service"
145 "systemd-ask-password-wall.path"
146 "systemd-ask-password-wall.service"
147
148 # Slices / containers.
149 "slices.target"
150 "user.slice"
151 "machine.slice"
152 "machines.target"
153 "systemd-importd.service"
154 "systemd-machined.service"
155 "systemd-nspawn@.service"
156
157 # Temporary file creation / cleanup.
158 "systemd-tmpfiles-clean.service"
159 "systemd-tmpfiles-clean.timer"
160 "systemd-tmpfiles-setup.service"
161 "systemd-tmpfiles-setup-dev.service"
162
163 # Misc.
164 "systemd-sysctl.service"
165 "dbus-org.freedesktop.timedate1.service"
166 "dbus-org.freedesktop.locale1.service"
167 "dbus-org.freedesktop.hostname1.service"
168 "systemd-timedated.service"
169 "systemd-localed.service"
170 "systemd-hostnamed.service"
171 "systemd-exit.service"
172 "systemd-update-done.service"
173 ] ++ optionals config.services.journald.enableHttpGateway [
174 "systemd-journal-gatewayd.socket"
175 "systemd-journal-gatewayd.service"
176 ] ++ cfg.additionalUpstreamSystemUnits;
177
178 upstreamSystemWants =
179 [ "sysinit.target.wants"
180 "sockets.target.wants"
181 "local-fs.target.wants"
182 "multi-user.target.wants"
183 "timers.target.wants"
184 ];
185
186 upstreamUserUnits = [
187 "app.slice"
188 "background.slice"
189 "basic.target"
190 "bluetooth.target"
191 "default.target"
192 "exit.target"
193 "graphical-session-pre.target"
194 "graphical-session.target"
195 "paths.target"
196 "printer.target"
197 "session.slice"
198 "shutdown.target"
199 "smartcard.target"
200 "sockets.target"
201 "sound.target"
202 "systemd-exit.service"
203 "systemd-tmpfiles-clean.service"
204 "systemd-tmpfiles-clean.timer"
205 "systemd-tmpfiles-setup.service"
206 "timers.target"
207 "xdg-desktop-autostart.target"
208 ];
209
210 makeJobScript = name: text:
211 let
212 scriptName = replaceChars [ "\\" "@" ] [ "-" "_" ] (shellEscape name);
213 out = pkgs.writeTextFile {
214 # The derivation name is different from the script file name
215 # to keep the script file name short to avoid cluttering logs.
216 name = "unit-script-${scriptName}";
217 executable = true;
218 destination = "/bin/${scriptName}";
219 text = ''
220 #!${pkgs.runtimeShell} -e
221 ${text}
222 '';
223 checkPhase = ''
224 ${pkgs.stdenv.shell} -n "$out/bin/${scriptName}"
225 '';
226 };
227 in "${out}/bin/${scriptName}";
228
229 unitConfig = { config, options, ... }: {
230 config = {
231 unitConfig =
232 optionalAttrs (config.requires != [])
233 { Requires = toString config.requires; }
234 // optionalAttrs (config.wants != [])
235 { Wants = toString config.wants; }
236 // optionalAttrs (config.after != [])
237 { After = toString config.after; }
238 // optionalAttrs (config.before != [])
239 { Before = toString config.before; }
240 // optionalAttrs (config.bindsTo != [])
241 { BindsTo = toString config.bindsTo; }
242 // optionalAttrs (config.partOf != [])
243 { PartOf = toString config.partOf; }
244 // optionalAttrs (config.conflicts != [])
245 { Conflicts = toString config.conflicts; }
246 // optionalAttrs (config.requisite != [])
247 { Requisite = toString config.requisite; }
248 // optionalAttrs (config.restartTriggers != [])
249 { X-Restart-Triggers = toString config.restartTriggers; }
250 // optionalAttrs (config.description != "") {
251 Description = config.description; }
252 // optionalAttrs (config.documentation != []) {
253 Documentation = toString config.documentation; }
254 // optionalAttrs (config.onFailure != []) {
255 OnFailure = toString config.onFailure; }
256 // optionalAttrs (options.startLimitIntervalSec.isDefined) {
257 StartLimitIntervalSec = toString config.startLimitIntervalSec;
258 } // optionalAttrs (options.startLimitBurst.isDefined) {
259 StartLimitBurst = toString config.startLimitBurst;
260 };
261 };
262 };
263
264 serviceConfig = { name, config, ... }: {
265 config = mkMerge
266 [ { # Default path for systemd services. Should be quite minimal.
267 path = mkAfter
268 [ pkgs.coreutils
269 pkgs.findutils
270 pkgs.gnugrep
271 pkgs.gnused
272 systemd
273 ];
274 environment.PATH = "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
275 }
276 (mkIf (config.preStart != "")
277 { serviceConfig.ExecStartPre =
278 [ (makeJobScript "${name}-pre-start" config.preStart) ];
279 })
280 (mkIf (config.script != "")
281 { serviceConfig.ExecStart =
282 makeJobScript "${name}-start" config.script + " " + config.scriptArgs;
283 })
284 (mkIf (config.postStart != "")
285 { serviceConfig.ExecStartPost =
286 [ (makeJobScript "${name}-post-start" config.postStart) ];
287 })
288 (mkIf (config.reload != "")
289 { serviceConfig.ExecReload =
290 makeJobScript "${name}-reload" config.reload;
291 })
292 (mkIf (config.preStop != "")
293 { serviceConfig.ExecStop =
294 makeJobScript "${name}-pre-stop" config.preStop;
295 })
296 (mkIf (config.postStop != "")
297 { serviceConfig.ExecStopPost =
298 makeJobScript "${name}-post-stop" config.postStop;
299 })
300 ];
301 };
302
303 mountConfig = { config, ... }: {
304 config = {
305 mountConfig =
306 { What = config.what;
307 Where = config.where;
308 } // optionalAttrs (config.type != "") {
309 Type = config.type;
310 } // optionalAttrs (config.options != "") {
311 Options = config.options;
312 };
313 };
314 };
315
316 automountConfig = { config, ... }: {
317 config = {
318 automountConfig =
319 { Where = config.where;
320 };
321 };
322 };
323
324 commonUnitText = def: ''
325 [Unit]
326 ${attrsToSection def.unitConfig}
327 '';
328
329 targetToUnit = name: def:
330 { inherit (def) aliases wantedBy requiredBy enable;
331 text =
332 ''
333 [Unit]
334 ${attrsToSection def.unitConfig}
335 '';
336 };
337
338 serviceToUnit = name: def:
339 { inherit (def) aliases wantedBy requiredBy enable;
340 text = commonUnitText def +
341 ''
342 [Service]
343 ${let env = cfg.globalEnvironment // def.environment;
344 in concatMapStrings (n:
345 let s = optionalString (env.${n} != null)
346 "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
347 # systemd max line length is now 1MiB
348 # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
349 in if stringLength s >= 1048576 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
350 ${if def.reloadIfChanged then ''
351 X-ReloadIfChanged=true
352 '' else if !def.restartIfChanged then ''
353 X-RestartIfChanged=false
354 '' else ""}
355 ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"}
356 ${attrsToSection def.serviceConfig}
357 '';
358 };
359
360 socketToUnit = name: def:
361 { inherit (def) aliases wantedBy requiredBy enable;
362 text = commonUnitText def +
363 ''
364 [Socket]
365 ${attrsToSection def.socketConfig}
366 ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
367 ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)}
368 '';
369 };
370
371 timerToUnit = name: def:
372 { inherit (def) aliases wantedBy requiredBy enable;
373 text = commonUnitText def +
374 ''
375 [Timer]
376 ${attrsToSection def.timerConfig}
377 '';
378 };
379
380 pathToUnit = name: def:
381 { inherit (def) aliases wantedBy requiredBy enable;
382 text = commonUnitText def +
383 ''
384 [Path]
385 ${attrsToSection def.pathConfig}
386 '';
387 };
388
389 mountToUnit = name: def:
390 { inherit (def) aliases wantedBy requiredBy enable;
391 text = commonUnitText def +
392 ''
393 [Mount]
394 ${attrsToSection def.mountConfig}
395 '';
396 };
397
398 automountToUnit = name: def:
399 { inherit (def) aliases wantedBy requiredBy enable;
400 text = commonUnitText def +
401 ''
402 [Automount]
403 ${attrsToSection def.automountConfig}
404 '';
405 };
406
407 sliceToUnit = name: def:
408 { inherit (def) aliases wantedBy requiredBy enable;
409 text = commonUnitText def +
410 ''
411 [Slice]
412 ${attrsToSection def.sliceConfig}
413 '';
414 };
415
416 logindHandlerType = types.enum [
417 "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
418 "hibernate" "hybrid-sleep" "suspend-then-hibernate" "lock"
419 ];
420
421 proxy_env = config.networking.proxy.envVars;
422
423in
424
425{
426 ###### interface
427
428 options = {
429
430 systemd.package = mkOption {
431 default = pkgs.systemd;
432 defaultText = literalExpression "pkgs.systemd";
433 type = types.package;
434 description = "The systemd package.";
435 };
436
437 systemd.units = mkOption {
438 description = "Definition of systemd units.";
439 default = {};
440 type = with types; attrsOf (submodule (
441 { name, config, ... }:
442 { options = concreteUnitOptions;
443 config = {
444 unit = mkDefault (makeUnit name config);
445 };
446 }));
447 };
448
449 systemd.packages = mkOption {
450 default = [];
451 type = types.listOf types.package;
452 example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
453 description = "Packages providing systemd units and hooks.";
454 };
455
456 systemd.targets = mkOption {
457 default = {};
458 type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
459 description = "Definition of systemd target units.";
460 };
461
462 systemd.services = mkOption {
463 default = {};
464 type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]);
465 description = "Definition of systemd service units.";
466 };
467
468 systemd.sockets = mkOption {
469 default = {};
470 type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ]);
471 description = "Definition of systemd socket units.";
472 };
473
474 systemd.timers = mkOption {
475 default = {};
476 type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ]);
477 description = "Definition of systemd timer units.";
478 };
479
480 systemd.paths = mkOption {
481 default = {};
482 type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
483 description = "Definition of systemd path units.";
484 };
485
486 systemd.mounts = mkOption {
487 default = [];
488 type = with types; listOf (submodule [ { options = mountOptions; } unitConfig mountConfig ]);
489 description = ''
490 Definition of systemd mount units.
491 This is a list instead of an attrSet, because systemd mandates the names to be derived from
492 the 'where' attribute.
493 '';
494 };
495
496 systemd.automounts = mkOption {
497 default = [];
498 type = with types; listOf (submodule [ { options = automountOptions; } unitConfig automountConfig ]);
499 description = ''
500 Definition of systemd automount units.
501 This is a list instead of an attrSet, because systemd mandates the names to be derived from
502 the 'where' attribute.
503 '';
504 };
505
506 systemd.slices = mkOption {
507 default = {};
508 type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig] );
509 description = "Definition of slice configurations.";
510 };
511
512 systemd.generators = mkOption {
513 type = types.attrsOf types.path;
514 default = {};
515 example = { systemd-gpt-auto-generator = "/dev/null"; };
516 description = ''
517 Definition of systemd generators.
518 For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
519 <literal>/etc/systemd/system-generators/NAME</literal> to <literal>VALUE</literal>.
520 '';
521 };
522
523 systemd.shutdown = mkOption {
524 type = types.attrsOf types.path;
525 default = {};
526 description = ''
527 Definition of systemd shutdown executables.
528 For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
529 <literal>/etc/systemd/system-shutdown/NAME</literal> to <literal>VALUE</literal>.
530 '';
531 };
532
533 systemd.defaultUnit = mkOption {
534 default = "multi-user.target";
535 type = types.str;
536 description = "Default unit started when the system boots.";
537 };
538
539 systemd.ctrlAltDelUnit = mkOption {
540 default = "reboot.target";
541 type = types.str;
542 example = "poweroff.target";
543 description = ''
544 Target that should be started when Ctrl-Alt-Delete is pressed.
545 '';
546 };
547
548 systemd.globalEnvironment = mkOption {
549 type = with types; attrsOf (nullOr (oneOf [ str path package ]));
550 default = {};
551 example = { TZ = "CET"; };
552 description = ''
553 Environment variables passed to <emphasis>all</emphasis> systemd units.
554 '';
555 };
556
557 systemd.enableCgroupAccounting = mkOption {
558 default = true;
559 type = types.bool;
560 description = ''
561 Whether to enable cgroup accounting.
562 '';
563 };
564
565 systemd.enableUnifiedCgroupHierarchy = mkOption {
566 default = true;
567 type = types.bool;
568 description = ''
569 Whether to enable the unified cgroup hierarchy (cgroupsv2).
570 '';
571 };
572
573 systemd.coredump.enable = mkOption {
574 default = true;
575 type = types.bool;
576 description = ''
577 Whether core dumps should be processed by
578 <command>systemd-coredump</command>. If disabled, core dumps
579 appear in the current directory of the crashing process.
580 '';
581 };
582
583 systemd.coredump.extraConfig = mkOption {
584 default = "";
585 type = types.lines;
586 example = "Storage=journal";
587 description = ''
588 Extra config options for systemd-coredump. See coredump.conf(5) man page
589 for available options.
590 '';
591 };
592
593 systemd.extraConfig = mkOption {
594 default = "";
595 type = types.lines;
596 example = "DefaultLimitCORE=infinity";
597 description = ''
598 Extra config options for systemd. See man systemd-system.conf for
599 available options.
600 '';
601 };
602
603 services.journald.console = mkOption {
604 default = "";
605 type = types.str;
606 description = "If non-empty, write log messages to the specified TTY device.";
607 };
608
609 services.journald.rateLimitInterval = mkOption {
610 default = "30s";
611 type = types.str;
612 description = ''
613 Configures the rate limiting interval that is applied to all
614 messages generated on the system. This rate limiting is applied
615 per-service, so that two services which log do not interfere with
616 each other's limit. The value may be specified in the following
617 units: s, min, h, ms, us. To turn off any kind of rate limiting,
618 set either value to 0.
619
620 See <option>services.journald.rateLimitBurst</option> for important
621 considerations when setting this value.
622 '';
623 };
624
625 services.journald.rateLimitBurst = mkOption {
626 default = 10000;
627 type = types.int;
628 description = ''
629 Configures the rate limiting burst limit (number of messages per
630 interval) that is applied to all messages generated on the system.
631 This rate limiting is applied per-service, so that two services
632 which log do not interfere with each other's limit.
633
634 Note that the effective rate limit is multiplied by a factor derived
635 from the available free disk space for the journal as described on
636 <link xlink:href="https://www.freedesktop.org/software/systemd/man/journald.conf.html">
637 journald.conf(5)</link>.
638
639 Note that the total amount of logs stored is limited by journald settings
640 such as <literal>SystemMaxUse</literal>, which defaults to a 4 GB cap.
641
642 It is thus recommended to compute what period of time that you will be
643 able to store logs for when an application logs at full burst rate.
644 With default settings for log lines that are 100 Bytes long, this can
645 amount to just a few hours.
646 '';
647 };
648
649 services.journald.extraConfig = mkOption {
650 default = "";
651 type = types.lines;
652 example = "Storage=volatile";
653 description = ''
654 Extra config options for systemd-journald. See man journald.conf
655 for available options.
656 '';
657 };
658
659 services.journald.enableHttpGateway = mkOption {
660 default = false;
661 type = types.bool;
662 description = ''
663 Whether to enable the HTTP gateway to the journal.
664 '';
665 };
666
667 services.journald.forwardToSyslog = mkOption {
668 default = config.services.rsyslogd.enable || config.services.syslog-ng.enable;
669 defaultText = literalExpression "services.rsyslogd.enable || services.syslog-ng.enable";
670 type = types.bool;
671 description = ''
672 Whether to forward log messages to syslog.
673 '';
674 };
675
676 services.logind.extraConfig = mkOption {
677 default = "";
678 type = types.lines;
679 example = "IdleAction=lock";
680 description = ''
681 Extra config options for systemd-logind. See
682 <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html">
683 logind.conf(5)</link> for available options.
684 '';
685 };
686
687 services.logind.killUserProcesses = mkOption {
688 default = false;
689 type = types.bool;
690 description = ''
691 Specifies whether the processes of a user should be killed
692 when the user logs out. If true, the scope unit corresponding
693 to the session and all processes inside that scope will be
694 terminated. If false, the scope is "abandoned" (see
695 <link xlink:href="https://www.freedesktop.org/software/systemd/man/systemd.scope.html#">
696 systemd.scope(5)</link>), and processes are not killed.
697 </para>
698
699 <para>
700 See <link xlink:href="https://www.freedesktop.org/software/systemd/man/logind.conf.html#KillUserProcesses=">logind.conf(5)</link>
701 for more details.
702 '';
703 };
704
705 services.logind.lidSwitch = mkOption {
706 default = "suspend";
707 example = "ignore";
708 type = logindHandlerType;
709
710 description = ''
711 Specifies what to be done when the laptop lid is closed.
712 '';
713 };
714
715 services.logind.lidSwitchDocked = mkOption {
716 default = "ignore";
717 example = "suspend";
718 type = logindHandlerType;
719
720 description = ''
721 Specifies what to be done when the laptop lid is closed
722 and another screen is added.
723 '';
724 };
725
726 services.logind.lidSwitchExternalPower = mkOption {
727 default = config.services.logind.lidSwitch;
728 defaultText = literalExpression "services.logind.lidSwitch";
729 example = "ignore";
730 type = logindHandlerType;
731
732 description = ''
733 Specifies what to do when the laptop lid is closed and the system is
734 on external power. By default use the same action as specified in
735 services.logind.lidSwitch.
736 '';
737 };
738
739 systemd.sleep.extraConfig = mkOption {
740 default = "";
741 type = types.lines;
742 example = "HibernateDelaySec=1h";
743 description = ''
744 Extra config options for systemd sleep state logic.
745 See sleep.conf.d(5) man page for available options.
746 '';
747 };
748
749 systemd.user.extraConfig = mkOption {
750 default = "";
751 type = types.lines;
752 example = "DefaultCPUAccounting=yes";
753 description = ''
754 Extra config options for systemd user instances. See man systemd-user.conf for
755 available options.
756 '';
757 };
758
759 systemd.tmpfiles.rules = mkOption {
760 type = types.listOf types.str;
761 default = [];
762 example = [ "d /tmp 1777 root root 10d" ];
763 description = ''
764 Rules for creation, deletion and cleaning of volatile and temporary files
765 automatically. See
766 <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
767 for the exact format.
768 '';
769 };
770
771 systemd.tmpfiles.packages = mkOption {
772 type = types.listOf types.package;
773 default = [];
774 example = literalExpression "[ pkgs.lvm2 ]";
775 apply = map getLib;
776 description = ''
777 List of packages containing <command>systemd-tmpfiles</command> rules.
778
779 All files ending in .conf found in
780 <filename><replaceable>pkg</replaceable>/lib/tmpfiles.d</filename>
781 will be included.
782 If this folder does not exist or does not contain any files an error will be returned instead.
783
784 If a <filename>lib</filename> output is available, rules are searched there and only there.
785 If there is no <filename>lib</filename> output it will fall back to <filename>out</filename>
786 and if that does not exist either, the default output will be used.
787 '';
788 };
789
790 systemd.user.units = mkOption {
791 description = "Definition of systemd per-user units.";
792 default = {};
793 type = with types; attrsOf (submodule (
794 { name, config, ... }:
795 { options = concreteUnitOptions;
796 config = {
797 unit = mkDefault (makeUnit name config);
798 };
799 }));
800 };
801
802 systemd.user.paths = mkOption {
803 default = {};
804 type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
805 description = "Definition of systemd per-user path units.";
806 };
807
808 systemd.user.services = mkOption {
809 default = {};
810 type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] );
811 description = "Definition of systemd per-user service units.";
812 };
813
814 systemd.user.slices = mkOption {
815 default = {};
816 type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] );
817 description = "Definition of systemd per-user slice units.";
818 };
819
820 systemd.user.sockets = mkOption {
821 default = {};
822 type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] );
823 description = "Definition of systemd per-user socket units.";
824 };
825
826 systemd.user.targets = mkOption {
827 default = {};
828 type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
829 description = "Definition of systemd per-user target units.";
830 };
831
832 systemd.user.timers = mkOption {
833 default = {};
834 type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] );
835 description = "Definition of systemd per-user timer units.";
836 };
837
838 systemd.additionalUpstreamSystemUnits = mkOption {
839 default = [ ];
840 type = types.listOf types.str;
841 example = [ "debug-shell.service" "systemd-quotacheck.service" ];
842 description = ''
843 Additional units shipped with systemd that shall be enabled.
844 '';
845 };
846
847 systemd.suppressedSystemUnits = mkOption {
848 default = [ ];
849 type = types.listOf types.str;
850 example = [ "systemd-backlight@.service" ];
851 description = ''
852 A list of units to suppress when generating system systemd configuration directory. This has
853 priority over upstream units, <option>systemd.units</option>, and
854 <option>systemd.additionalUpstreamSystemUnits</option>. The main purpose of this is to
855 suppress a upstream systemd unit with any modifications made to it by other NixOS modules.
856 '';
857 };
858
859 systemd.watchdog.device = mkOption {
860 type = types.nullOr types.path;
861 default = null;
862 example = "/dev/watchdog";
863 description = ''
864 The path to a hardware watchdog device which will be managed by systemd.
865 If not specified, systemd will default to /dev/watchdog.
866 '';
867 };
868
869 systemd.watchdog.runtimeTime = mkOption {
870 type = types.nullOr types.str;
871 default = null;
872 example = "30s";
873 description = ''
874 The amount of time which can elapse before a watchdog hardware device
875 will automatically reboot the system. Valid time units include "ms",
876 "s", "min", "h", "d", and "w".
877 '';
878 };
879
880 systemd.watchdog.rebootTime = mkOption {
881 type = types.nullOr types.str;
882 default = null;
883 example = "10m";
884 description = ''
885 The amount of time which can elapse after a reboot has been triggered
886 before a watchdog hardware device will automatically reboot the system.
887 Valid time units include "ms", "s", "min", "h", "d", and "w".
888 '';
889 };
890
891 systemd.watchdog.kexecTime = mkOption {
892 type = types.nullOr types.str;
893 default = null;
894 example = "10m";
895 description = ''
896 The amount of time which can elapse when kexec is being executed before
897 a watchdog hardware device will automatically reboot the system. This
898 option should only be enabled if reloadTime is also enabled. Valid
899 time units include "ms", "s", "min", "h", "d", and "w".
900 '';
901 };
902 };
903
904
905 ###### implementation
906
907 config = {
908
909 warnings = concatLists (
910 mapAttrsToList
911 (name: service:
912 let
913 type = service.serviceConfig.Type or "";
914 restart = service.serviceConfig.Restart or "no";
915 hasDeprecated = builtins.hasAttr "StartLimitInterval" service.serviceConfig;
916 in
917 concatLists [
918 (optional (type == "oneshot" && (restart == "always" || restart == "on-success"))
919 "Service '${name}.service' with 'Type=oneshot' cannot have 'Restart=always' or 'Restart=on-success'"
920 )
921 (optional hasDeprecated
922 "Service '${name}.service' uses the attribute 'StartLimitInterval' in the Service section, which is deprecated. See https://github.com/NixOS/nixpkgs/issues/45786."
923 )
924 ]
925 )
926 cfg.services
927 );
928
929 system.build.units = cfg.units;
930
931 system.nssModules = [ systemd.out ];
932 system.nssDatabases = {
933 hosts = (mkMerge [
934 (mkOrder 400 ["mymachines"]) # 400 to ensure it comes before resolve (which is mkBefore'd)
935 (mkOrder 999 ["myhostname"]) # after files (which is 998), but before regular nss modules
936 ]);
937 passwd = (mkMerge [
938 (mkAfter [ "systemd" ])
939 ]);
940 group = (mkMerge [
941 (mkAfter [ "systemd" ])
942 ]);
943 };
944
945 environment.systemPackages = [ systemd ];
946
947 environment.etc = let
948 # generate contents for /etc/systemd/system-${type} from attrset of links and packages
949 hooks = type: links: pkgs.runCommand "system-${type}" {
950 preferLocalBuild = true;
951 packages = cfg.packages;
952 } ''
953 set -e
954 mkdir -p $out
955 for package in $packages
956 do
957 for hook in $package/lib/systemd/system-${type}/*
958 do
959 ln -s $hook $out/
960 done
961 done
962 ${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)}
963 '';
964
965 enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits;
966 enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units;
967 in ({
968 "systemd/system".source = generateUnits "system" enabledUnits enabledUpstreamSystemUnits upstreamSystemWants;
969
970 "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits [];
971
972 "systemd/system.conf".text = ''
973 [Manager]
974 ${optionalString config.systemd.enableCgroupAccounting ''
975 DefaultCPUAccounting=yes
976 DefaultIOAccounting=yes
977 DefaultBlockIOAccounting=yes
978 DefaultIPAccounting=yes
979 ''}
980 DefaultLimitCORE=infinity
981 ${optionalString (config.systemd.watchdog.device != null) ''
982 WatchdogDevice=${config.systemd.watchdog.device}
983 ''}
984 ${optionalString (config.systemd.watchdog.runtimeTime != null) ''
985 RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime}
986 ''}
987 ${optionalString (config.systemd.watchdog.rebootTime != null) ''
988 RebootWatchdogSec=${config.systemd.watchdog.rebootTime}
989 ''}
990 ${optionalString (config.systemd.watchdog.kexecTime != null) ''
991 KExecWatchdogSec=${config.systemd.watchdog.kexecTime}
992 ''}
993
994 ${config.systemd.extraConfig}
995 '';
996
997 "systemd/user.conf".text = ''
998 [Manager]
999 ${config.systemd.user.extraConfig}
1000 '';
1001
1002 "systemd/journald.conf".text = ''
1003 [Journal]
1004 Storage=persistent
1005 RateLimitInterval=${config.services.journald.rateLimitInterval}
1006 RateLimitBurst=${toString config.services.journald.rateLimitBurst}
1007 ${optionalString (config.services.journald.console != "") ''
1008 ForwardToConsole=yes
1009 TTYPath=${config.services.journald.console}
1010 ''}
1011 ${optionalString (config.services.journald.forwardToSyslog) ''
1012 ForwardToSyslog=yes
1013 ''}
1014 ${config.services.journald.extraConfig}
1015 '';
1016
1017 "systemd/coredump.conf".text =
1018 ''
1019 [Coredump]
1020 ${config.systemd.coredump.extraConfig}
1021 '';
1022
1023 "systemd/logind.conf".text = ''
1024 [Login]
1025 KillUserProcesses=${if config.services.logind.killUserProcesses then "yes" else "no"}
1026 HandleLidSwitch=${config.services.logind.lidSwitch}
1027 HandleLidSwitchDocked=${config.services.logind.lidSwitchDocked}
1028 HandleLidSwitchExternalPower=${config.services.logind.lidSwitchExternalPower}
1029 ${config.services.logind.extraConfig}
1030 '';
1031
1032 "systemd/sleep.conf".text = ''
1033 [Sleep]
1034 ${config.systemd.sleep.extraConfig}
1035 '';
1036
1037 # install provided sysctl snippets
1038 "sysctl.d/50-coredump.conf".source = "${systemd}/example/sysctl.d/50-coredump.conf";
1039 "sysctl.d/50-default.conf".source = "${systemd}/example/sysctl.d/50-default.conf";
1040
1041 "tmpfiles.d".source = (pkgs.symlinkJoin {
1042 name = "tmpfiles.d";
1043 paths = map (p: p + "/lib/tmpfiles.d") cfg.tmpfiles.packages;
1044 postBuild = ''
1045 for i in $(cat $pathsPath); do
1046 (test -d "$i" && test $(ls "$i"/*.conf | wc -l) -ge 1) || (
1047 echo "ERROR: The path '$i' from systemd.tmpfiles.packages contains no *.conf files."
1048 exit 1
1049 )
1050 done
1051 '' + concatMapStrings (name: optionalString (hasPrefix "tmpfiles.d/" name) ''
1052 rm -f $out/${removePrefix "tmpfiles.d/" name}
1053 '') config.system.build.etc.passthru.targets;
1054 }) + "/*";
1055
1056 "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
1057 "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
1058 });
1059
1060 services.dbus.enable = true;
1061
1062 users.users.systemd-coredump = {
1063 uid = config.ids.uids.systemd-coredump;
1064 group = "systemd-coredump";
1065 };
1066 users.groups.systemd-coredump = {};
1067 users.users.systemd-network = {
1068 uid = config.ids.uids.systemd-network;
1069 group = "systemd-network";
1070 };
1071 users.groups.systemd-network.gid = config.ids.gids.systemd-network;
1072 users.users.systemd-resolve = {
1073 uid = config.ids.uids.systemd-resolve;
1074 group = "systemd-resolve";
1075 };
1076 users.groups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
1077
1078 # Target for ‘charon send-keys’ to hook into.
1079 users.groups.keys.gid = config.ids.gids.keys;
1080
1081 systemd.targets.keys =
1082 { description = "Security Keys";
1083 unitConfig.X-StopOnReconfiguration = true;
1084 };
1085
1086 systemd.tmpfiles.packages = [
1087 # Default tmpfiles rules provided by systemd
1088 (pkgs.runCommand "systemd-default-tmpfiles" {} ''
1089 mkdir -p $out/lib/tmpfiles.d
1090 cd $out/lib/tmpfiles.d
1091
1092 ln -s "${systemd}/example/tmpfiles.d/home.conf"
1093 ln -s "${systemd}/example/tmpfiles.d/journal-nocow.conf"
1094 ln -s "${systemd}/example/tmpfiles.d/static-nodes-permissions.conf"
1095 ln -s "${systemd}/example/tmpfiles.d/systemd.conf"
1096 ln -s "${systemd}/example/tmpfiles.d/systemd-nologin.conf"
1097 ln -s "${systemd}/example/tmpfiles.d/systemd-nspawn.conf"
1098 ln -s "${systemd}/example/tmpfiles.d/systemd-tmp.conf"
1099 ln -s "${systemd}/example/tmpfiles.d/tmp.conf"
1100 ln -s "${systemd}/example/tmpfiles.d/var.conf"
1101 ln -s "${systemd}/example/tmpfiles.d/x11.conf"
1102 '')
1103 # User-specified tmpfiles rules
1104 (pkgs.writeTextFile {
1105 name = "nixos-tmpfiles.d";
1106 destination = "/lib/tmpfiles.d/00-nixos.conf";
1107 text = ''
1108 # This file is created automatically and should not be modified.
1109 # Please change the option ‘systemd.tmpfiles.rules’ instead.
1110
1111 ${concatStringsSep "\n" cfg.tmpfiles.rules}
1112 '';
1113 })
1114 ];
1115
1116 systemd.units =
1117 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
1118 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
1119 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices
1120 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
1121 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
1122 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
1123 // listToAttrs (map
1124 (v: let n = escapeSystemdPath v.where;
1125 in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
1126 // listToAttrs (map
1127 (v: let n = escapeSystemdPath v.where;
1128 in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
1129
1130 systemd.user.units =
1131 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.user.paths
1132 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services
1133 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.user.slices
1134 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.user.sockets
1135 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.user.targets
1136 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.user.timers;
1137
1138 system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
1139 [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
1140 "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC"
1141 "CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
1142 "TMPFS_XATTR" "SECCOMP"
1143 ];
1144
1145 users.groups.systemd-journal.gid = config.ids.gids.systemd-journal;
1146 users.users.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
1147 users.users.systemd-journal-gateway.group = "systemd-journal-gateway";
1148 users.groups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
1149
1150 # Generate timer units for all services that have a ‘startAt’ value.
1151 systemd.timers =
1152 mapAttrs (name: service:
1153 { wantedBy = [ "timers.target" ];
1154 timerConfig.OnCalendar = service.startAt;
1155 })
1156 (filterAttrs (name: service: service.enable && service.startAt != []) cfg.services);
1157
1158 # Generate timer units for all services that have a ‘startAt’ value.
1159 systemd.user.timers =
1160 mapAttrs (name: service:
1161 { wantedBy = [ "timers.target" ];
1162 timerConfig.OnCalendar = service.startAt;
1163 })
1164 (filterAttrs (name: service: service.startAt != []) cfg.user.services);
1165
1166 systemd.sockets.systemd-journal-gatewayd.wantedBy =
1167 optional config.services.journald.enableHttpGateway "sockets.target";
1168
1169 # Provide the systemd-user PAM service, required to run systemd
1170 # user instances.
1171 security.pam.services.systemd-user =
1172 { # Ensure that pam_systemd gets included. This is special-cased
1173 # in systemd to provide XDG_RUNTIME_DIR.
1174 startSession = true;
1175 };
1176
1177 # Some overrides to upstream units.
1178 systemd.services."systemd-backlight@".restartIfChanged = false;
1179 systemd.services."systemd-fsck@".restartIfChanged = false;
1180 systemd.services."systemd-fsck@".path = [ config.system.path ];
1181 systemd.services."user@".restartIfChanged = false;
1182 systemd.services.systemd-journal-flush.restartIfChanged = false;
1183 systemd.services.systemd-random-seed.restartIfChanged = false;
1184 systemd.services.systemd-remount-fs.restartIfChanged = false;
1185 systemd.services.systemd-update-utmp.restartIfChanged = false;
1186 systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
1187 systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild
1188 # Restarting systemd-logind breaks X11
1189 # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101
1190 # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112
1191 # - this might be addressed in the future by xorg
1192 #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
1193 systemd.services.systemd-logind.restartIfChanged = false;
1194 systemd.services.systemd-logind.stopIfChanged = false;
1195 # The user-runtime-dir@ service is managed by systemd-logind we should not touch it or else we break the users' sessions.
1196 systemd.services."user-runtime-dir@".stopIfChanged = false;
1197 systemd.services."user-runtime-dir@".restartIfChanged = false;
1198 systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
1199 systemd.services.systemd-journald.stopIfChanged = false;
1200 systemd.services."systemd-journald@".restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
1201 systemd.services."systemd-journald@".stopIfChanged = false;
1202 systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
1203 systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
1204 systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
1205 systemd.services.systemd-importd.environment = proxy_env;
1206 systemd.services.systemd-pstore.wantedBy = [ "sysinit.target" ]; # see #81138
1207
1208 # Don't bother with certain units in containers.
1209 systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
1210 systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
1211
1212 boot.kernel.sysctl."kernel.core_pattern" = mkIf (!cfg.coredump.enable) "core";
1213
1214 # Increase numeric PID range (set directly instead of copying a one-line file from systemd)
1215 # https://github.com/systemd/systemd/pull/12226
1216 boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304);
1217
1218 boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
1219 };
1220
1221 # FIXME: Remove these eventually.
1222 imports =
1223 [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ])
1224 (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
1225 (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
1226 (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
1227 (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
1228 ];
1229}