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 "sigpwr.target"
30 "timers.target"
31 "paths.target"
32 "rpcbind.target"
33
34 # Rescue mode.
35 "rescue.target"
36 "rescue.service"
37
38 # Udev.
39 "systemd-udevd-control.socket"
40 "systemd-udevd-kernel.socket"
41 "systemd-udevd.service"
42 "systemd-udev-settle.service"
43 "systemd-udev-trigger.service"
44 # hwdb.bin is managed by NixOS
45 # "systemd-hwdb-update.service"
46
47 # Consoles.
48 "getty.target"
49 "getty-pre.target"
50 "getty@.service"
51 "serial-getty@.service"
52 "console-getty.service"
53 "container-getty@.service"
54 "systemd-vconsole-setup.service"
55
56 # Hardware (started by udev when a relevant device is plugged in).
57 "sound.target"
58 "bluetooth.target"
59 "printer.target"
60 "smartcard.target"
61
62 # Login stuff.
63 "systemd-logind.service"
64 "autovt@.service"
65 "systemd-user-sessions.service"
66 "dbus-org.freedesktop.machine1.service"
67 "user@.service"
68
69 # Journal.
70 "systemd-journald.socket"
71 "systemd-journald.service"
72 "systemd-journal-flush.service"
73 "systemd-journal-gatewayd.socket"
74 "systemd-journal-gatewayd.service"
75 "systemd-journal-catalog-update.service"
76 "systemd-journald-audit.socket"
77 "systemd-journald-dev-log.socket"
78 "syslog.socket"
79
80 # SysV init compatibility.
81 "systemd-initctl.socket"
82 "systemd-initctl.service"
83
84 # Kernel module loading.
85 "systemd-modules-load.service"
86 "kmod-static-nodes.service"
87
88 # Filesystems.
89 "systemd-fsck@.service"
90 "systemd-fsck-root.service"
91 "systemd-remount-fs.service"
92 "local-fs.target"
93 "local-fs-pre.target"
94 "remote-fs.target"
95 "remote-fs-pre.target"
96 "swap.target"
97 "dev-hugepages.mount"
98 "dev-mqueue.mount"
99 "sys-fs-fuse-connections.mount"
100 "sys-kernel-config.mount"
101 "sys-kernel-debug.mount"
102
103 # Maintaining state across reboots.
104 "systemd-random-seed.service"
105 "systemd-backlight@.service"
106 "systemd-rfkill.service"
107 "systemd-rfkill.socket"
108
109 # Hibernate / suspend.
110 "hibernate.target"
111 "suspend.target"
112 "sleep.target"
113 "hybrid-sleep.target"
114 "systemd-hibernate.service"
115 "systemd-hybrid-sleep.service"
116 "systemd-suspend.service"
117
118 # Reboot stuff.
119 "reboot.target"
120 "systemd-reboot.service"
121 "poweroff.target"
122 "systemd-poweroff.service"
123 "halt.target"
124 "systemd-halt.service"
125 "shutdown.target"
126 "umount.target"
127 "final.target"
128 "kexec.target"
129 "systemd-kexec.service"
130 "systemd-update-utmp.service"
131
132 # Password entry.
133 "systemd-ask-password-console.path"
134 "systemd-ask-password-console.service"
135 "systemd-ask-password-wall.path"
136 "systemd-ask-password-wall.service"
137
138 # Slices / containers.
139 "slices.target"
140 "system.slice"
141 "user.slice"
142 "machine.slice"
143 "machines.target"
144 "systemd-machined.service"
145 "systemd-nspawn@.service"
146
147 # Temporary file creation / cleanup.
148 "systemd-tmpfiles-clean.service"
149 "systemd-tmpfiles-clean.timer"
150 "systemd-tmpfiles-setup.service"
151 "systemd-tmpfiles-setup-dev.service"
152
153 # Misc.
154 "systemd-sysctl.service"
155 "dbus-org.freedesktop.timedate1.service"
156 "dbus-org.freedesktop.locale1.service"
157 "dbus-org.freedesktop.hostname1.service"
158 "systemd-timedated.service"
159 "systemd-localed.service"
160 "systemd-hostnamed.service"
161 "systemd-binfmt.service"
162 "systemd-exit.service"
163 "systemd-update-done.service"
164 ]
165 ++ cfg.additionalUpstreamSystemUnits;
166
167 upstreamSystemWants =
168 [ "sysinit.target.wants"
169 "sockets.target.wants"
170 "local-fs.target.wants"
171 "multi-user.target.wants"
172 "timers.target.wants"
173 ];
174
175 upstreamUserUnits =
176 [ "basic.target"
177 "bluetooth.target"
178 "default.target"
179 "exit.target"
180 "graphical-session-pre.target"
181 "graphical-session.target"
182 "paths.target"
183 "printer.target"
184 "shutdown.target"
185 "smartcard.target"
186 "sockets.target"
187 "sound.target"
188 "systemd-exit.service"
189 "timers.target"
190 ];
191
192 boolToString = value: if value then "yes" else "no";
193
194 makeJobScript = name: text:
195 let mkScriptName = s: (replaceChars [ "\\" ] [ "-" ] (shellEscape s) );
196 x = pkgs.writeTextFile { name = "unit-script"; executable = true; destination = "/bin/${mkScriptName name}"; inherit text; };
197 in "${x}/bin/${mkScriptName name}";
198
199 unitConfig = { name, config, ... }: {
200 config = {
201 unitConfig =
202 optionalAttrs (config.requires != [])
203 { Requires = toString config.requires; }
204 // optionalAttrs (config.wants != [])
205 { Wants = toString config.wants; }
206 // optionalAttrs (config.after != [])
207 { After = toString config.after; }
208 // optionalAttrs (config.before != [])
209 { Before = toString config.before; }
210 // optionalAttrs (config.bindsTo != [])
211 { BindsTo = toString config.bindsTo; }
212 // optionalAttrs (config.partOf != [])
213 { PartOf = toString config.partOf; }
214 // optionalAttrs (config.conflicts != [])
215 { Conflicts = toString config.conflicts; }
216 // optionalAttrs (config.requisite != [])
217 { Requisite = toString config.requisite; }
218 // optionalAttrs (config.restartTriggers != [])
219 { X-Restart-Triggers = toString config.restartTriggers; }
220 // optionalAttrs (config.description != "") {
221 Description = config.description; }
222 // optionalAttrs (config.documentation != []) {
223 Documentation = toString config.documentation; }
224 // optionalAttrs (config.onFailure != []) {
225 OnFailure = toString config.onFailure;
226 };
227 };
228 };
229
230 serviceConfig = { name, config, ... }: {
231 config = mkMerge
232 [ { # Default path for systemd services. Should be quite minimal.
233 path =
234 [ pkgs.coreutils
235 pkgs.findutils
236 pkgs.gnugrep
237 pkgs.gnused
238 systemd
239 ];
240 environment.PATH = config.path;
241 }
242 (mkIf (config.preStart != "")
243 { serviceConfig.ExecStartPre = makeJobScript "${name}-pre-start" ''
244 #! ${pkgs.stdenv.shell} -e
245 ${config.preStart}
246 '';
247 })
248 (mkIf (config.script != "")
249 { serviceConfig.ExecStart = makeJobScript "${name}-start" ''
250 #! ${pkgs.stdenv.shell} -e
251 ${config.script}
252 '' + " " + config.scriptArgs;
253 })
254 (mkIf (config.postStart != "")
255 { serviceConfig.ExecStartPost = makeJobScript "${name}-post-start" ''
256 #! ${pkgs.stdenv.shell} -e
257 ${config.postStart}
258 '';
259 })
260 (mkIf (config.reload != "")
261 { serviceConfig.ExecReload = makeJobScript "${name}-reload" ''
262 #! ${pkgs.stdenv.shell} -e
263 ${config.reload}
264 '';
265 })
266 (mkIf (config.preStop != "")
267 { serviceConfig.ExecStop = makeJobScript "${name}-pre-stop" ''
268 #! ${pkgs.stdenv.shell} -e
269 ${config.preStop}
270 '';
271 })
272 (mkIf (config.postStop != "")
273 { serviceConfig.ExecStopPost = makeJobScript "${name}-post-stop" ''
274 #! ${pkgs.stdenv.shell} -e
275 ${config.postStop}
276 '';
277 })
278 ];
279 };
280
281 mountConfig = { name, config, ... }: {
282 config = {
283 mountConfig =
284 { What = config.what;
285 Where = config.where;
286 } // optionalAttrs (config.type != "") {
287 Type = config.type;
288 } // optionalAttrs (config.options != "") {
289 Options = config.options;
290 };
291 };
292 };
293
294 automountConfig = { name, config, ... }: {
295 config = {
296 automountConfig =
297 { Where = config.where;
298 };
299 };
300 };
301
302 commonUnitText = def: ''
303 [Unit]
304 ${attrsToSection def.unitConfig}
305 '';
306
307 targetToUnit = name: def:
308 { inherit (def) aliases wantedBy requiredBy enable;
309 text =
310 ''
311 [Unit]
312 ${attrsToSection def.unitConfig}
313 '';
314 };
315
316 serviceToUnit = name: def:
317 { inherit (def) aliases wantedBy requiredBy enable;
318 text = commonUnitText def +
319 ''
320 [Service]
321 ${let env = cfg.globalEnvironment // def.environment;
322 in concatMapStrings (n:
323 let s = optionalString (env."${n}" != null)
324 "Environment=${builtins.toJSON "${n}=${env.${n}}"}\n";
325 in if stringLength s >= 2048 then throw "The value of the environment variable ‘${n}’ in systemd service ‘${name}.service’ is too long." else s) (attrNames env)}
326 ${if def.reloadIfChanged then ''
327 X-ReloadIfChanged=true
328 '' else if !def.restartIfChanged then ''
329 X-RestartIfChanged=false
330 '' else ""}
331 ${optionalString (!def.stopIfChanged) "X-StopIfChanged=false"}
332 ${attrsToSection def.serviceConfig}
333 '';
334 };
335
336 socketToUnit = name: def:
337 { inherit (def) aliases wantedBy requiredBy enable;
338 text = commonUnitText def +
339 ''
340 [Socket]
341 ${attrsToSection def.socketConfig}
342 ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
343 '';
344 };
345
346 timerToUnit = name: def:
347 { inherit (def) aliases wantedBy requiredBy enable;
348 text = commonUnitText def +
349 ''
350 [Timer]
351 ${attrsToSection def.timerConfig}
352 '';
353 };
354
355 pathToUnit = name: def:
356 { inherit (def) aliases wantedBy requiredBy enable;
357 text = commonUnitText def +
358 ''
359 [Path]
360 ${attrsToSection def.pathConfig}
361 '';
362 };
363
364 mountToUnit = name: def:
365 { inherit (def) aliases wantedBy requiredBy enable;
366 text = commonUnitText def +
367 ''
368 [Mount]
369 ${attrsToSection def.mountConfig}
370 '';
371 };
372
373 automountToUnit = name: def:
374 { inherit (def) aliases wantedBy requiredBy enable;
375 text = commonUnitText def +
376 ''
377 [Automount]
378 ${attrsToSection def.automountConfig}
379 '';
380 };
381
382 sliceToUnit = name: def:
383 { inherit (def) aliases wantedBy requiredBy enable;
384 text = commonUnitText def +
385 ''
386 [Slice]
387 ${attrsToSection def.sliceConfig}
388 '';
389 };
390
391 logindHandlerType = types.enum [
392 "ignore" "poweroff" "reboot" "halt" "kexec" "suspend"
393 "hibernate" "hybrid-sleep" "lock"
394 ];
395
396in
397
398{
399
400 ###### interface
401
402 options = {
403
404 systemd.package = mkOption {
405 default = pkgs.systemd;
406 defaultText = "pkgs.systemd";
407 type = types.package;
408 description = "The systemd package.";
409 };
410
411 systemd.units = mkOption {
412 description = "Definition of systemd units.";
413 default = {};
414 type = with types; attrsOf (submodule (
415 { name, config, ... }:
416 { options = concreteUnitOptions;
417 config = {
418 unit = mkDefault (makeUnit name config);
419 };
420 }));
421 };
422
423 systemd.packages = mkOption {
424 default = [];
425 type = types.listOf types.package;
426 description = "Packages providing systemd units.";
427 };
428
429 systemd.targets = mkOption {
430 default = {};
431 type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
432 description = "Definition of systemd target units.";
433 };
434
435 systemd.services = mkOption {
436 default = {};
437 type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ]);
438 description = "Definition of systemd service units.";
439 };
440
441 systemd.sockets = mkOption {
442 default = {};
443 type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ]);
444 description = "Definition of systemd socket units.";
445 };
446
447 systemd.timers = mkOption {
448 default = {};
449 type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ]);
450 description = "Definition of systemd timer units.";
451 };
452
453 systemd.paths = mkOption {
454 default = {};
455 type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
456 description = "Definition of systemd path units.";
457 };
458
459 systemd.mounts = mkOption {
460 default = [];
461 type = with types; listOf (submodule [ { options = mountOptions; } unitConfig mountConfig ]);
462 description = ''
463 Definition of systemd mount units.
464 This is a list instead of an attrSet, because systemd mandates the names to be derived from
465 the 'where' attribute.
466 '';
467 };
468
469 systemd.automounts = mkOption {
470 default = [];
471 type = with types; listOf (submodule [ { options = automountOptions; } unitConfig automountConfig ]);
472 description = ''
473 Definition of systemd automount units.
474 This is a list instead of an attrSet, because systemd mandates the names to be derived from
475 the 'where' attribute.
476 '';
477 };
478
479 systemd.slices = mkOption {
480 default = {};
481 type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig] );
482 description = "Definition of slice configurations.";
483 };
484
485 systemd.generators = mkOption {
486 type = types.attrsOf types.path;
487 default = {};
488 example = { "systemd-gpt-auto-generator" = "/dev/null"; };
489 description = ''
490 Definition of systemd generators.
491 For each <literal>NAME = VALUE</literal> pair of the attrSet, a link is generated from
492 <literal>/etc/systemd/system-generators/NAME</literal> to <literal>VALUE</literal>.
493 '';
494 };
495
496 systemd.generator-packages = mkOption {
497 default = [];
498 type = types.listOf types.package;
499 example = literalExample "[ pkgs.systemd-cryptsetup-generator ]";
500 description = "Packages providing systemd generators.";
501 };
502
503 systemd.defaultUnit = mkOption {
504 default = "multi-user.target";
505 type = types.str;
506 description = "Default unit started when the system boots.";
507 };
508
509 systemd.ctrlAltDelUnit = mkOption {
510 default = "reboot.target";
511 type = types.str;
512 example = "poweroff.target";
513 description = ''
514 Target that should be started when Ctrl-Alt-Delete is pressed.
515 '';
516 };
517
518 systemd.globalEnvironment = mkOption {
519 type = types.attrs;
520 default = {};
521 example = { TZ = "CET"; };
522 description = ''
523 Environment variables passed to <emphasis>all</emphasis> systemd units.
524 '';
525 };
526
527 systemd.extraConfig = mkOption {
528 default = "";
529 type = types.lines;
530 example = "DefaultLimitCORE=infinity";
531 description = ''
532 Extra config options for systemd. See man systemd-system.conf for
533 available options.
534 '';
535 };
536
537 services.journald.console = mkOption {
538 default = "";
539 type = types.str;
540 description = "If non-empty, write log messages to the specified TTY device.";
541 };
542
543 services.journald.rateLimitInterval = mkOption {
544 default = "10s";
545 type = types.str;
546 description = ''
547 Configures the rate limiting interval that is applied to all
548 messages generated on the system. This rate limiting is applied
549 per-service, so that two services which log do not interfere with
550 each other's limit. The value may be specified in the following
551 units: s, min, h, ms, us. To turn off any kind of rate limiting,
552 set either value to 0.
553 '';
554 };
555
556 services.journald.rateLimitBurst = mkOption {
557 default = 100;
558 type = types.int;
559 description = ''
560 Configures the rate limiting burst limit (number of messages per
561 interval) that is applied to all messages generated on the system.
562 This rate limiting is applied per-service, so that two services
563 which log do not interfere with each other's limit.
564 '';
565 };
566
567 services.journald.extraConfig = mkOption {
568 default = "";
569 type = types.lines;
570 example = "Storage=volatile";
571 description = ''
572 Extra config options for systemd-journald. See man journald.conf
573 for available options.
574 '';
575 };
576
577 services.journald.enableHttpGateway = mkOption {
578 default = false;
579 type = types.bool;
580 description = ''
581 Whether to enable the HTTP gateway to the journal.
582 '';
583 };
584
585 services.logind.extraConfig = mkOption {
586 default = "";
587 type = types.lines;
588 example = "IdleAction=lock";
589 description = ''
590 Extra config options for systemd-logind. See man logind.conf for
591 available options.
592 '';
593 };
594
595 services.logind.lidSwitch = mkOption {
596 default = "suspend";
597 example = "ignore";
598 type = logindHandlerType;
599
600 description = ''
601 Specifies what to be done when the laptop lid is closed.
602 '';
603 };
604
605 services.logind.lidSwitchDocked = mkOption {
606 default = "ignore";
607 example = "suspend";
608 type = logindHandlerType;
609
610 description = ''
611 Specifies what to be done when the laptop lid is closed
612 and another screen is added.
613 '';
614 };
615
616 systemd.user.extraConfig = mkOption {
617 default = "";
618 type = types.lines;
619 example = "DefaultCPUAccounting=yes";
620 description = ''
621 Extra config options for systemd user instances. See man systemd-user.conf for
622 available options.
623 '';
624 };
625
626 systemd.tmpfiles.rules = mkOption {
627 type = types.listOf types.str;
628 default = [];
629 example = [ "d /tmp 1777 root root 10d" ];
630 description = ''
631 Rules for creating and cleaning up temporary files
632 automatically. See
633 <citerefentry><refentrytitle>tmpfiles.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>
634 for the exact format.
635 '';
636 };
637
638 systemd.user.units = mkOption {
639 description = "Definition of systemd per-user units.";
640 default = {};
641 type = with types; attrsOf (submodule (
642 { name, config, ... }:
643 { options = concreteUnitOptions;
644 config = {
645 unit = mkDefault (makeUnit name config);
646 };
647 }));
648 };
649
650 systemd.user.paths = mkOption {
651 default = {};
652 type = with types; attrsOf (submodule [ { options = pathOptions; } unitConfig ]);
653 description = "Definition of systemd per-user path units.";
654 };
655
656 systemd.user.services = mkOption {
657 default = {};
658 type = with types; attrsOf (submodule [ { options = serviceOptions; } unitConfig serviceConfig ] );
659 description = "Definition of systemd per-user service units.";
660 };
661
662 systemd.user.slices = mkOption {
663 default = {};
664 type = with types; attrsOf (submodule [ { options = sliceOptions; } unitConfig ] );
665 description = "Definition of systemd per-user slice units.";
666 };
667
668 systemd.user.sockets = mkOption {
669 default = {};
670 type = with types; attrsOf (submodule [ { options = socketOptions; } unitConfig ] );
671 description = "Definition of systemd per-user socket units.";
672 };
673
674 systemd.user.targets = mkOption {
675 default = {};
676 type = with types; attrsOf (submodule [ { options = targetOptions; } unitConfig] );
677 description = "Definition of systemd per-user target units.";
678 };
679
680 systemd.user.timers = mkOption {
681 default = {};
682 type = with types; attrsOf (submodule [ { options = timerOptions; } unitConfig ] );
683 description = "Definition of systemd per-user timer units.";
684 };
685
686 systemd.additionalUpstreamSystemUnits = mkOption {
687 default = [ ];
688 type = types.listOf types.str;
689 example = [ "debug-shell.service" "systemd-quotacheck.service" ];
690 description = ''
691 Additional units shipped with systemd that shall be enabled.
692 '';
693 };
694
695 };
696
697
698 ###### implementation
699
700 config = {
701
702 warnings = concatLists (mapAttrsToList (name: service:
703 optional (service.serviceConfig.Type or "" == "oneshot" && service.serviceConfig.Restart or "no" != "no")
704 "Service ‘${name}.service’ with ‘Type=oneshot’ must have ‘Restart=no’") cfg.services);
705
706 system.build.units = cfg.units;
707
708 environment.systemPackages = [ systemd ];
709
710 environment.etc = let
711 # generate contents for /etc/systemd/system-generators from
712 # systemd.generators and systemd.generator-packages
713 generators = pkgs.runCommand "system-generators" { packages = cfg.generator-packages; } ''
714 mkdir -p $out
715 for package in $packages
716 do
717 ln -s $package/lib/systemd/system-generators/* $out/
718 done;
719 ${concatStrings (mapAttrsToList (generator: target: "ln -s ${target} $out/${generator};\n") cfg.generators)}
720 '';
721 in ({
722 "systemd/system".source = generateUnits "system" cfg.units upstreamSystemUnits upstreamSystemWants;
723
724 "systemd/user".source = generateUnits "user" cfg.user.units upstreamUserUnits [];
725
726 "systemd/system.conf".text = ''
727 [Manager]
728 ${config.systemd.extraConfig}
729 '';
730
731 "systemd/user.conf".text = ''
732 [Manager]
733 ${config.systemd.user.extraConfig}
734 '';
735
736 "systemd/journald.conf".text = ''
737 [Journal]
738 RateLimitInterval=${config.services.journald.rateLimitInterval}
739 RateLimitBurst=${toString config.services.journald.rateLimitBurst}
740 ${optionalString (config.services.journald.console != "") ''
741 ForwardToConsole=yes
742 TTYPath=${config.services.journald.console}
743 ''}
744 ${config.services.journald.extraConfig}
745 '';
746
747 "systemd/logind.conf".text = ''
748 [Login]
749 KillUserProcesses=no
750 HandleLidSwitch=${config.services.logind.lidSwitch}
751 HandleLidSwitchDocked=${config.services.logind.lidSwitchDocked}
752 ${config.services.logind.extraConfig}
753 '';
754
755 "systemd/sleep.conf".text = ''
756 [Sleep]
757 '';
758
759 "tmpfiles.d/systemd.conf".source = "${systemd}/example/tmpfiles.d/systemd.conf";
760 "tmpfiles.d/x11.conf".source = "${systemd}/example/tmpfiles.d/x11.conf";
761
762 "tmpfiles.d/nixos.conf".text = ''
763 # This file is created automatically and should not be modified.
764 # Please change the option ‘systemd.tmpfiles.rules’ instead.
765
766 ${concatStringsSep "\n" cfg.tmpfiles.rules}
767 '';
768
769 "systemd/system-generators" = { source = generators; };
770 });
771
772 services.dbus.enable = true;
773
774 system.activationScripts.systemd = stringAfter [ "groups" ]
775 ''
776 mkdir -m 0755 -p /var/lib/udev
777
778 if ! [ -e /etc/machine-id ]; then
779 ${systemd}/bin/systemd-machine-id-setup
780 fi
781
782 # Keep a persistent journal. Note that systemd-tmpfiles will
783 # set proper ownership/permissions.
784 mkdir -m 0700 -p /var/log/journal
785 '';
786
787 users.extraUsers.systemd-network.uid = config.ids.uids.systemd-network;
788 users.extraGroups.systemd-network.gid = config.ids.gids.systemd-network;
789 users.extraUsers.systemd-resolve.uid = config.ids.uids.systemd-resolve;
790 users.extraGroups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
791
792 # Target for ‘charon send-keys’ to hook into.
793 users.extraGroups.keys.gid = config.ids.gids.keys;
794
795 systemd.targets.keys =
796 { description = "Security Keys";
797 unitConfig.X-StopOnReconfiguration = true;
798 };
799
800 systemd.units =
801 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
802 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
803 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices
804 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
805 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
806 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
807 // listToAttrs (map
808 (v: let n = escapeSystemdPath v.where;
809 in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
810 // listToAttrs (map
811 (v: let n = escapeSystemdPath v.where;
812 in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
813
814 systemd.user.units =
815 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.user.paths
816 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.user.services
817 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.user.slices
818 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.user.sockets
819 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.user.targets
820 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.user.timers;
821
822 system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
823 [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
824 "SYSFS" "PROC_FS" "FHANDLE" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
825 "TMPFS_XATTR" "SECCOMP"
826 ];
827
828 users.extraGroups.systemd-journal.gid = config.ids.gids.systemd-journal;
829 users.extraUsers.systemd-journal-gateway.uid = config.ids.uids.systemd-journal-gateway;
830 users.extraGroups.systemd-journal-gateway.gid = config.ids.gids.systemd-journal-gateway;
831
832 # Generate timer units for all services that have a ‘startAt’ value.
833 systemd.timers =
834 mapAttrs (name: service:
835 { wantedBy = [ "timers.target" ];
836 timerConfig.OnCalendar = service.startAt;
837 })
838 (filterAttrs (name: service: service.enable && service.startAt != []) cfg.services);
839
840 # Generate timer units for all services that have a ‘startAt’ value.
841 systemd.user.timers =
842 mapAttrs (name: service:
843 { wantedBy = [ "timers.target" ];
844 timerConfig.OnCalendar = service.startAt;
845 })
846 (filterAttrs (name: service: service.startAt != []) cfg.user.services);
847
848 systemd.sockets.systemd-journal-gatewayd.wantedBy =
849 optional config.services.journald.enableHttpGateway "sockets.target";
850
851 # Provide the systemd-user PAM service, required to run systemd
852 # user instances.
853 security.pam.services.systemd-user =
854 { # Ensure that pam_systemd gets included. This is special-cased
855 # in systemd to provide XDG_RUNTIME_DIR.
856 startSession = true;
857 };
858
859 # Some overrides to upstream units.
860 systemd.services."systemd-backlight@".restartIfChanged = false;
861 systemd.services."systemd-fsck@".restartIfChanged = false;
862 systemd.services."systemd-fsck@".path = [ config.system.path ];
863 systemd.services."user@".restartIfChanged = false;
864 systemd.services.systemd-journal-flush.restartIfChanged = false;
865 systemd.services.systemd-random-seed.restartIfChanged = false;
866 systemd.services.systemd-remount-fs.restartIfChanged = false;
867 systemd.services.systemd-update-utmp.restartIfChanged = false;
868 systemd.services.systemd-user-sessions.restartIfChanged = false; # Restart kills all active sessions.
869 # Restarting systemd-logind breaks X11
870 # - upstream commit: https://cgit.freedesktop.org/xorg/xserver/commit/?id=dc48bd653c7e101
871 # - systemd announcement: https://github.com/systemd/systemd/blob/22043e4317ecd2bc7834b48a6d364de76bb26d91/NEWS#L103-L112
872 # - this might be addressed in the future by xorg
873 #systemd.services.systemd-logind.restartTriggers = [ config.environment.etc."systemd/logind.conf".source ];
874 systemd.services.systemd-logind.restartIfChanged = false;
875 systemd.services.systemd-logind.stopIfChanged = false;
876 systemd.services.systemd-journald.restartTriggers = [ config.environment.etc."systemd/journald.conf".source ];
877 systemd.services.systemd-journald.stopIfChanged = false;
878 systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
879 systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
880 systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
881 systemd.services.systemd-binfmt.wants = [ "proc-sys-fs-binfmt_misc.mount" ];
882
883 # Don't bother with certain units in containers.
884 systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
885 systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
886
887 };
888
889 # FIXME: Remove these eventually.
890 imports =
891 [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ])
892 (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
893 (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
894 ];
895
896}