1{ config, lib, pkgs, utils, ... }:
2
3with utils;
4with systemdUtils.unitOptions;
5with lib;
6
7let
8
9 cfg = config.systemd;
10
11 inherit (systemdUtils.lib)
12 generateUnits
13 targetToUnit
14 serviceToUnit
15 socketToUnit
16 timerToUnit
17 pathToUnit
18 mountToUnit
19 automountToUnit
20 sliceToUnit;
21
22 upstreamSystemUnits =
23 [ # Targets.
24 "basic.target"
25 "sysinit.target"
26 "sockets.target"
27 "exit.target"
28 "graphical.target"
29 "multi-user.target"
30 "network.target"
31 "network-pre.target"
32 "network-online.target"
33 "nss-lookup.target"
34 "nss-user-lookup.target"
35 "time-sync.target"
36 ] ++ optionals cfg.package.withCryptsetup [
37 "cryptsetup.target"
38 "cryptsetup-pre.target"
39 "remote-cryptsetup.target"
40 ] ++ [
41 "sigpwr.target"
42 "timers.target"
43 "paths.target"
44 "rpcbind.target"
45
46 # Rescue mode.
47 "rescue.target"
48 "rescue.service"
49
50 # Udev.
51 "systemd-udevd-control.socket"
52 "systemd-udevd-kernel.socket"
53 "systemd-udevd.service"
54 "systemd-udev-settle.service"
55 ] ++ (optional (!config.boot.isContainer) "systemd-udev-trigger.service") ++ [
56 # hwdb.bin is managed by NixOS
57 # "systemd-hwdb-update.service"
58
59 # Consoles.
60 "getty.target"
61 "getty-pre.target"
62 "getty@.service"
63 "serial-getty@.service"
64 "console-getty.service"
65 "container-getty@.service"
66 "systemd-vconsole-setup.service"
67
68 # Hardware (started by udev when a relevant device is plugged in).
69 "sound.target"
70 "bluetooth.target"
71 "printer.target"
72 "smartcard.target"
73
74 # Kernel module loading.
75 "systemd-modules-load.service"
76 "kmod-static-nodes.service"
77 "modprobe@.service"
78
79 # Filesystems.
80 "systemd-fsck@.service"
81 "systemd-fsck-root.service"
82 "systemd-growfs@.service"
83 "systemd-growfs-root.service"
84 "systemd-remount-fs.service"
85 "systemd-pstore.service"
86 "local-fs.target"
87 "local-fs-pre.target"
88 "remote-fs.target"
89 "remote-fs-pre.target"
90 "swap.target"
91 "dev-hugepages.mount"
92 "dev-mqueue.mount"
93 "sys-fs-fuse-connections.mount"
94 ] ++ (optional (!config.boot.isContainer) "sys-kernel-config.mount") ++ [
95 "sys-kernel-debug.mount"
96
97 # Maintaining state across reboots.
98 "systemd-random-seed.service"
99 "systemd-backlight@.service"
100 "systemd-rfkill.service"
101 "systemd-rfkill.socket"
102
103 # Hibernate / suspend.
104 "hibernate.target"
105 "suspend.target"
106 "suspend-then-hibernate.target"
107 "sleep.target"
108 "hybrid-sleep.target"
109 "systemd-hibernate.service"
110 "systemd-hybrid-sleep.service"
111 "systemd-suspend.service"
112 "systemd-suspend-then-hibernate.service"
113
114 # Reboot stuff.
115 "reboot.target"
116 "systemd-reboot.service"
117 "poweroff.target"
118 "systemd-poweroff.service"
119 "halt.target"
120 "systemd-halt.service"
121 "shutdown.target"
122 "umount.target"
123 "final.target"
124 "kexec.target"
125 "systemd-kexec.service"
126 ] ++ lib.optional cfg.package.withUtmp "systemd-update-utmp.service" ++ [
127
128 # Password entry.
129 "systemd-ask-password-console.path"
130 "systemd-ask-password-console.service"
131 "systemd-ask-password-wall.path"
132 "systemd-ask-password-wall.service"
133
134 # Slices / containers.
135 "slices.target"
136 ] ++ optionals cfg.package.withImportd [
137 "systemd-importd.service"
138 ] ++ optionals cfg.package.withMachined [
139 "machine.slice"
140 "machines.target"
141 "systemd-machined.service"
142 ] ++ [
143 "systemd-nspawn@.service"
144
145 # Misc.
146 "systemd-sysctl.service"
147 ] ++ optionals cfg.package.withTimedated [
148 "dbus-org.freedesktop.timedate1.service"
149 "systemd-timedated.service"
150 ] ++ optionals cfg.package.withLocaled [
151 "dbus-org.freedesktop.locale1.service"
152 "systemd-localed.service"
153 ] ++ optionals cfg.package.withHostnamed [
154 "dbus-org.freedesktop.hostname1.service"
155 "systemd-hostnamed.service"
156 ] ++ optionals cfg.package.withPortabled [
157 "dbus-org.freedesktop.portable1.service"
158 "systemd-portabled.service"
159 ] ++ [
160 "systemd-exit.service"
161 "systemd-update-done.service"
162 ] ++ cfg.additionalUpstreamSystemUnits;
163
164 upstreamSystemWants =
165 [ "sysinit.target.wants"
166 "sockets.target.wants"
167 "local-fs.target.wants"
168 "multi-user.target.wants"
169 "timers.target.wants"
170 ];
171
172 proxy_env = config.networking.proxy.envVars;
173
174in
175
176{
177 ###### interface
178
179 options = {
180
181 systemd.package = mkOption {
182 default = pkgs.systemd;
183 defaultText = literalExpression "pkgs.systemd";
184 type = types.package;
185 description = lib.mdDoc "The systemd package.";
186 };
187
188 systemd.units = mkOption {
189 description = lib.mdDoc "Definition of systemd units.";
190 default = {};
191 type = systemdUtils.types.units;
192 };
193
194 systemd.packages = mkOption {
195 default = [];
196 type = types.listOf types.package;
197 example = literalExpression "[ pkgs.systemd-cryptsetup-generator ]";
198 description = lib.mdDoc "Packages providing systemd units and hooks.";
199 };
200
201 systemd.targets = mkOption {
202 default = {};
203 type = systemdUtils.types.targets;
204 description = lib.mdDoc "Definition of systemd target units.";
205 };
206
207 systemd.services = mkOption {
208 default = {};
209 type = systemdUtils.types.services;
210 description = lib.mdDoc "Definition of systemd service units.";
211 };
212
213 systemd.sockets = mkOption {
214 default = {};
215 type = systemdUtils.types.sockets;
216 description = lib.mdDoc "Definition of systemd socket units.";
217 };
218
219 systemd.timers = mkOption {
220 default = {};
221 type = systemdUtils.types.timers;
222 description = lib.mdDoc "Definition of systemd timer units.";
223 };
224
225 systemd.paths = mkOption {
226 default = {};
227 type = systemdUtils.types.paths;
228 description = lib.mdDoc "Definition of systemd path units.";
229 };
230
231 systemd.mounts = mkOption {
232 default = [];
233 type = systemdUtils.types.mounts;
234 description = lib.mdDoc ''
235 Definition of systemd mount units.
236 This is a list instead of an attrSet, because systemd mandates the names to be derived from
237 the 'where' attribute.
238 '';
239 };
240
241 systemd.automounts = mkOption {
242 default = [];
243 type = systemdUtils.types.automounts;
244 description = lib.mdDoc ''
245 Definition of systemd automount units.
246 This is a list instead of an attrSet, because systemd mandates the names to be derived from
247 the 'where' attribute.
248 '';
249 };
250
251 systemd.slices = mkOption {
252 default = {};
253 type = systemdUtils.types.slices;
254 description = lib.mdDoc "Definition of slice configurations.";
255 };
256
257 systemd.generators = mkOption {
258 type = types.attrsOf types.path;
259 default = {};
260 example = { systemd-gpt-auto-generator = "/dev/null"; };
261 description = lib.mdDoc ''
262 Definition of systemd generators.
263 For each `NAME = VALUE` pair of the attrSet, a link is generated from
264 `/etc/systemd/system-generators/NAME` to `VALUE`.
265 '';
266 };
267
268 systemd.shutdown = mkOption {
269 type = types.attrsOf types.path;
270 default = {};
271 description = lib.mdDoc ''
272 Definition of systemd shutdown executables.
273 For each `NAME = VALUE` pair of the attrSet, a link is generated from
274 `/etc/systemd/system-shutdown/NAME` to `VALUE`.
275 '';
276 };
277
278 systemd.defaultUnit = mkOption {
279 default = "multi-user.target";
280 type = types.str;
281 description = lib.mdDoc "Default unit started when the system boots.";
282 };
283
284 systemd.ctrlAltDelUnit = mkOption {
285 default = "reboot.target";
286 type = types.str;
287 example = "poweroff.target";
288 description = lib.mdDoc ''
289 Target that should be started when Ctrl-Alt-Delete is pressed.
290 '';
291 };
292
293 systemd.globalEnvironment = mkOption {
294 type = with types; attrsOf (nullOr (oneOf [ str path package ]));
295 default = {};
296 example = { TZ = "CET"; };
297 description = lib.mdDoc ''
298 Environment variables passed to *all* systemd units.
299 '';
300 };
301
302 systemd.managerEnvironment = mkOption {
303 type = with types; attrsOf (nullOr (oneOf [ str path package ]));
304 default = {};
305 example = { SYSTEMD_LOG_LEVEL = "debug"; };
306 description = lib.mdDoc ''
307 Environment variables of PID 1. These variables are
308 *not* passed to started units.
309 '';
310 };
311
312 systemd.enableCgroupAccounting = mkOption {
313 default = true;
314 type = types.bool;
315 description = lib.mdDoc ''
316 Whether to enable cgroup accounting.
317 '';
318 };
319
320 systemd.enableUnifiedCgroupHierarchy = mkOption {
321 default = true;
322 type = types.bool;
323 description = lib.mdDoc ''
324 Whether to enable the unified cgroup hierarchy (cgroupsv2).
325 '';
326 };
327
328 systemd.extraConfig = mkOption {
329 default = "";
330 type = types.lines;
331 example = "DefaultLimitCORE=infinity";
332 description = lib.mdDoc ''
333 Extra config options for systemd. See systemd-system.conf(5) man page
334 for available options.
335 '';
336 };
337
338 systemd.sleep.extraConfig = mkOption {
339 default = "";
340 type = types.lines;
341 example = "HibernateDelaySec=1h";
342 description = lib.mdDoc ''
343 Extra config options for systemd sleep state logic.
344 See sleep.conf.d(5) man page for available options.
345 '';
346 };
347
348 systemd.additionalUpstreamSystemUnits = mkOption {
349 default = [ ];
350 type = types.listOf types.str;
351 example = [ "debug-shell.service" "systemd-quotacheck.service" ];
352 description = lib.mdDoc ''
353 Additional units shipped with systemd that shall be enabled.
354 '';
355 };
356
357 systemd.suppressedSystemUnits = mkOption {
358 default = [ ];
359 type = types.listOf types.str;
360 example = [ "systemd-backlight@.service" ];
361 description = lib.mdDoc ''
362 A list of units to skip when generating system systemd configuration directory. This has
363 priority over upstream units, {option}`systemd.units`, and
364 {option}`systemd.additionalUpstreamSystemUnits`. The main purpose of this is to
365 prevent a upstream systemd unit from being added to the initrd with any modifications made to it
366 by other NixOS modules.
367 '';
368 };
369
370 systemd.watchdog.device = mkOption {
371 type = types.nullOr types.path;
372 default = null;
373 example = "/dev/watchdog";
374 description = lib.mdDoc ''
375 The path to a hardware watchdog device which will be managed by systemd.
376 If not specified, systemd will default to /dev/watchdog.
377 '';
378 };
379
380 systemd.watchdog.runtimeTime = mkOption {
381 type = types.nullOr types.str;
382 default = null;
383 example = "30s";
384 description = lib.mdDoc ''
385 The amount of time which can elapse before a watchdog hardware device
386 will automatically reboot the system. Valid time units include "ms",
387 "s", "min", "h", "d", and "w".
388 '';
389 };
390
391 systemd.watchdog.rebootTime = mkOption {
392 type = types.nullOr types.str;
393 default = null;
394 example = "10m";
395 description = lib.mdDoc ''
396 The amount of time which can elapse after a reboot has been triggered
397 before a watchdog hardware device will automatically reboot the system.
398 Valid time units include "ms", "s", "min", "h", "d", and "w".
399 '';
400 };
401
402 systemd.watchdog.kexecTime = mkOption {
403 type = types.nullOr types.str;
404 default = null;
405 example = "10m";
406 description = lib.mdDoc ''
407 The amount of time which can elapse when kexec is being executed before
408 a watchdog hardware device will automatically reboot the system. This
409 option should only be enabled if reloadTime is also enabled. Valid
410 time units include "ms", "s", "min", "h", "d", and "w".
411 '';
412 };
413 };
414
415
416 ###### implementation
417
418 config = {
419
420 warnings = concatLists (
421 mapAttrsToList
422 (name: service:
423 let
424 type = service.serviceConfig.Type or "";
425 restart = service.serviceConfig.Restart or "no";
426 hasDeprecated = builtins.hasAttr "StartLimitInterval" service.serviceConfig;
427 in
428 concatLists [
429 (optional (type == "oneshot" && (restart == "always" || restart == "on-success"))
430 "Service '${name}.service' with 'Type=oneshot' cannot have 'Restart=always' or 'Restart=on-success'"
431 )
432 (optional hasDeprecated
433 "Service '${name}.service' uses the attribute 'StartLimitInterval' in the Service section, which is deprecated. See https://github.com/NixOS/nixpkgs/issues/45786."
434 )
435 (optional (service.reloadIfChanged && service.reloadTriggers != [])
436 "Service '${name}.service' has both 'reloadIfChanged' and 'reloadTriggers' set. This is probably not what you want, because 'reloadTriggers' behave the same whay as 'restartTriggers' if 'reloadIfChanged' is set."
437 )
438 ]
439 )
440 cfg.services
441 );
442
443 system.build.units = cfg.units;
444
445 system.nssModules = [ cfg.package.out ];
446 system.nssDatabases = {
447 hosts = (mkMerge [
448 (mkOrder 400 ["mymachines"]) # 400 to ensure it comes before resolve (which is mkBefore'd)
449 (mkOrder 999 ["myhostname"]) # after files (which is 998), but before regular nss modules
450 ]);
451 passwd = (mkMerge [
452 (mkAfter [ "systemd" ])
453 ]);
454 group = (mkMerge [
455 (mkAfter [ "[success=merge] systemd" ]) # need merge so that NSS won't stop at file-based groups
456 ]);
457 };
458
459 environment.systemPackages = [ cfg.package ];
460
461 environment.etc = let
462 # generate contents for /etc/systemd/system-${type} from attrset of links and packages
463 hooks = type: links: pkgs.runCommand "system-${type}" {
464 preferLocalBuild = true;
465 packages = cfg.packages;
466 } ''
467 set -e
468 mkdir -p $out
469 for package in $packages
470 do
471 for hook in $package/lib/systemd/system-${type}/*
472 do
473 ln -s $hook $out/
474 done
475 done
476 ${concatStrings (mapAttrsToList (exec: target: "ln -s ${target} $out/${exec};\n") links)}
477 '';
478
479 enabledUpstreamSystemUnits = filter (n: ! elem n cfg.suppressedSystemUnits) upstreamSystemUnits;
480 enabledUnits = filterAttrs (n: v: ! elem n cfg.suppressedSystemUnits) cfg.units;
481
482 in ({
483 "systemd/system".source = generateUnits {
484 type = "system";
485 units = enabledUnits;
486 upstreamUnits = enabledUpstreamSystemUnits;
487 upstreamWants = upstreamSystemWants;
488 };
489
490 "systemd/system.conf".text = ''
491 [Manager]
492 ManagerEnvironment=${lib.concatStringsSep " " (lib.mapAttrsToList (n: v: "${n}=${lib.escapeShellArg v}") cfg.managerEnvironment)}
493 ${optionalString config.systemd.enableCgroupAccounting ''
494 DefaultCPUAccounting=yes
495 DefaultIOAccounting=yes
496 DefaultBlockIOAccounting=yes
497 DefaultIPAccounting=yes
498 ''}
499 DefaultLimitCORE=infinity
500 ${optionalString (config.systemd.watchdog.device != null) ''
501 WatchdogDevice=${config.systemd.watchdog.device}
502 ''}
503 ${optionalString (config.systemd.watchdog.runtimeTime != null) ''
504 RuntimeWatchdogSec=${config.systemd.watchdog.runtimeTime}
505 ''}
506 ${optionalString (config.systemd.watchdog.rebootTime != null) ''
507 RebootWatchdogSec=${config.systemd.watchdog.rebootTime}
508 ''}
509 ${optionalString (config.systemd.watchdog.kexecTime != null) ''
510 KExecWatchdogSec=${config.systemd.watchdog.kexecTime}
511 ''}
512
513 ${config.systemd.extraConfig}
514 '';
515
516 "systemd/sleep.conf".text = ''
517 [Sleep]
518 ${config.systemd.sleep.extraConfig}
519 '';
520
521 "systemd/system-generators" = { source = hooks "generators" cfg.generators; };
522 "systemd/system-shutdown" = { source = hooks "shutdown" cfg.shutdown; };
523 });
524
525 services.dbus.enable = true;
526
527 users.users.systemd-network = {
528 uid = config.ids.uids.systemd-network;
529 group = "systemd-network";
530 };
531 users.groups.systemd-network.gid = config.ids.gids.systemd-network;
532 users.users.systemd-resolve = {
533 uid = config.ids.uids.systemd-resolve;
534 group = "systemd-resolve";
535 };
536 users.groups.systemd-resolve.gid = config.ids.gids.systemd-resolve;
537
538 # Target for ‘charon send-keys’ to hook into.
539 users.groups.keys.gid = config.ids.gids.keys;
540
541 systemd.targets.keys =
542 { description = "Security Keys";
543 unitConfig.X-StopOnReconfiguration = true;
544 };
545
546 systemd.units =
547 mapAttrs' (n: v: nameValuePair "${n}.path" (pathToUnit n v)) cfg.paths
548 // mapAttrs' (n: v: nameValuePair "${n}.service" (serviceToUnit n v)) cfg.services
549 // mapAttrs' (n: v: nameValuePair "${n}.slice" (sliceToUnit n v)) cfg.slices
550 // mapAttrs' (n: v: nameValuePair "${n}.socket" (socketToUnit n v)) cfg.sockets
551 // mapAttrs' (n: v: nameValuePair "${n}.target" (targetToUnit n v)) cfg.targets
552 // mapAttrs' (n: v: nameValuePair "${n}.timer" (timerToUnit n v)) cfg.timers
553 // listToAttrs (map
554 (v: let n = escapeSystemdPath v.where;
555 in nameValuePair "${n}.mount" (mountToUnit n v)) cfg.mounts)
556 // listToAttrs (map
557 (v: let n = escapeSystemdPath v.where;
558 in nameValuePair "${n}.automount" (automountToUnit n v)) cfg.automounts);
559
560 # Environment of PID 1
561 systemd.managerEnvironment = {
562 # Doesn't contain systemd itself - everything works so it seems to use the compiled-in value for its tools
563 # util-linux is needed for the main fsck utility wrapping the fs-specific ones
564 PATH = lib.makeBinPath (config.system.fsPackages ++ [cfg.package.util-linux]);
565 LOCALE_ARCHIVE = "/run/current-system/sw/lib/locale/locale-archive";
566 TZDIR = "/etc/zoneinfo";
567 # If SYSTEMD_UNIT_PATH ends with an empty component (":"), the usual unit load path will be appended to the contents of the variable
568 SYSTEMD_UNIT_PATH = lib.mkIf (config.boot.extraSystemdUnitPaths != []) "${builtins.concatStringsSep ":" config.boot.extraSystemdUnitPaths}:";
569 };
570
571
572 system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled
573 [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET"
574 "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC"
575 "CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL"
576 "TMPFS_XATTR" "SECCOMP"
577 ];
578
579 # Generate timer units for all services that have a ‘startAt’ value.
580 systemd.timers =
581 mapAttrs (name: service:
582 { wantedBy = [ "timers.target" ];
583 timerConfig.OnCalendar = service.startAt;
584 })
585 (filterAttrs (name: service: service.enable && service.startAt != []) cfg.services);
586
587 # Some overrides to upstream units.
588 systemd.services."systemd-backlight@".restartIfChanged = false;
589 systemd.services."systemd-fsck@".restartIfChanged = false;
590 systemd.services."systemd-fsck@".path = [ config.system.path ];
591 systemd.services.systemd-random-seed.restartIfChanged = false;
592 systemd.services.systemd-remount-fs.restartIfChanged = false;
593 systemd.services.systemd-update-utmp.restartIfChanged = false;
594 systemd.services.systemd-udev-settle.restartIfChanged = false; # Causes long delays in nixos-rebuild
595 systemd.targets.local-fs.unitConfig.X-StopOnReconfiguration = true;
596 systemd.targets.remote-fs.unitConfig.X-StopOnReconfiguration = true;
597 systemd.targets.network-online.wantedBy = [ "multi-user.target" ];
598 systemd.services.systemd-importd.environment = proxy_env;
599 systemd.services.systemd-pstore.wantedBy = [ "sysinit.target" ]; # see #81138
600
601 # NixOS has kernel modules in a different location, so override that here.
602 systemd.services.kmod-static-nodes.unitConfig.ConditionFileNotEmpty = [
603 "" # required to unset the previous value!
604 "/run/booted-system/kernel-modules/lib/modules/%v/modules.devname"
605 ];
606
607 # Don't bother with certain units in containers.
608 systemd.services.systemd-remount-fs.unitConfig.ConditionVirtualization = "!container";
609 systemd.services.systemd-random-seed.unitConfig.ConditionVirtualization = "!container";
610
611 # Increase numeric PID range (set directly instead of copying a one-line file from systemd)
612 # https://github.com/systemd/systemd/pull/12226
613 boot.kernel.sysctl."kernel.pid_max" = mkIf pkgs.stdenv.is64bit (lib.mkDefault 4194304);
614
615 boot.kernelParams = optional (!cfg.enableUnifiedCgroupHierarchy) "systemd.unified_cgroup_hierarchy=0";
616
617 # Avoid potentially degraded system state due to
618 # "Userspace Out-Of-Memory (OOM) Killer was skipped because of a failed condition check (ConditionControlGroupController=v2)."
619 systemd.oomd.enable = mkIf (!cfg.enableUnifiedCgroupHierarchy) false;
620
621 services.logrotate.settings = {
622 "/var/log/btmp" = mapAttrs (_: mkDefault) {
623 frequency = "monthly";
624 rotate = 1;
625 create = "0660 root ${config.users.groups.utmp.name}";
626 minsize = "1M";
627 };
628 "/var/log/wtmp" = mapAttrs (_: mkDefault) {
629 frequency = "monthly";
630 rotate = 1;
631 create = "0664 root ${config.users.groups.utmp.name}";
632 minsize = "1M";
633 };
634 };
635 };
636
637 # FIXME: Remove these eventually.
638 imports =
639 [ (mkRenamedOptionModule [ "boot" "systemd" "sockets" ] [ "systemd" "sockets" ])
640 (mkRenamedOptionModule [ "boot" "systemd" "targets" ] [ "systemd" "targets" ])
641 (mkRenamedOptionModule [ "boot" "systemd" "services" ] [ "systemd" "services" ])
642 (mkRenamedOptionModule [ "jobs" ] [ "systemd" "services" ])
643 (mkRemovedOptionModule [ "systemd" "generator-packages" ] "Use systemd.packages instead.")
644 ];
645}