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