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