1{
2 config,
3 lib,
4 pkgs,
5 utils,
6}:
7
8let
9 inherit (lib)
10 all
11 attrByPath
12 attrNames
13 concatLists
14 concatMap
15 concatMapStrings
16 concatStrings
17 concatStringsSep
18 const
19 elem
20 elemAt
21 filter
22 filterAttrs
23 flatten
24 flip
25 hasPrefix
26 head
27 isInt
28 isFloat
29 isList
30 isPath
31 isString
32 length
33 makeBinPath
34 makeSearchPathOutput
35 mapAttrs
36 mapAttrsToList
37 mapNullable
38 match
39 mkAfter
40 mkIf
41 optional
42 optionalAttrs
43 optionalString
44 pipe
45 range
46 replaceStrings
47 reverseList
48 splitString
49 stringLength
50 stringToCharacters
51 tail
52 toIntBase10
53 trace
54 types
55 ;
56
57 inherit (lib.strings) toJSON;
58
59 cfg = config.systemd;
60 lndir = "${pkgs.buildPackages.xorg.lndir}/bin/lndir";
61 systemd = cfg.package;
62in
63rec {
64
65 shellEscape = s: (replaceStrings [ "\\" ] [ "\\\\" ] s);
66
67 mkPathSafeName = replaceStrings [ "@" ":" "\\" "[" "]" ] [ "-" "-" "-" "" "" ];
68
69 # a type for options that take a unit name
70 unitNameType = types.strMatching "[a-zA-Z0-9@%:_.\\-]+[.](service|socket|device|mount|automount|swap|target|path|timer|scope|slice)";
71
72 makeUnit =
73 name: unit:
74 if unit.enable then
75 pkgs.runCommand "unit-${mkPathSafeName name}"
76 {
77 preferLocalBuild = true;
78 allowSubstitutes = false;
79 # unit.text can be null. But variables that are null listed in
80 # passAsFile are ignored by nix, resulting in no file being created,
81 # making the mv operation fail.
82 text = optionalString (unit.text != null) unit.text;
83 passAsFile = [ "text" ];
84 }
85 ''
86 name=${shellEscape name}
87 mkdir -p "$out/$(dirname -- "$name")"
88 mv "$textPath" "$out/$name"
89 ''
90 else
91 pkgs.runCommand "unit-${mkPathSafeName name}-disabled"
92 {
93 preferLocalBuild = true;
94 allowSubstitutes = false;
95 }
96 ''
97 name=${shellEscape name}
98 mkdir -p "$out/$(dirname "$name")"
99 ln -s /dev/null "$out/$name"
100 '';
101
102 boolValues = [
103 true
104 false
105 "yes"
106 "no"
107 ];
108
109 digits = map toString (range 0 9);
110
111 isByteFormat =
112 s:
113 let
114 l = reverseList (stringToCharacters s);
115 suffix = head l;
116 nums = tail l;
117 in
118 builtins.isInt s
119 || (
120 elem suffix (
121 [
122 "K"
123 "M"
124 "G"
125 "T"
126 ]
127 ++ digits
128 )
129 && all (num: elem num digits) nums
130 );
131
132 assertByteFormat =
133 name: group: attr:
134 optional (
135 attr ? ${name} && !isByteFormat attr.${name}
136 ) "Systemd ${group} field `${name}' must be in byte format [0-9]+[KMGT].";
137
138 toIntBaseDetected =
139 value:
140 assert (match "[0-9]+|0x[0-9a-fA-F]+" value) != null;
141 (builtins.fromTOML "v=${value}").v;
142
143 hexChars = stringToCharacters "0123456789abcdefABCDEF";
144
145 isMacAddress =
146 s:
147 stringLength s == 17
148 && flip all (splitString ":" s) (bytes: all (byte: elem byte hexChars) (stringToCharacters bytes));
149
150 assertMacAddress =
151 name: group: attr:
152 optional (
153 attr ? ${name} && !isMacAddress attr.${name}
154 ) "Systemd ${group} field `${name}' must be a valid MAC address.";
155
156 assertNetdevMacAddress =
157 name: group: attr:
158 optional (
159 attr ? ${name} && (!isMacAddress attr.${name} && attr.${name} != "none")
160 ) "Systemd ${group} field `${name}` must be a valid MAC address or the special value `none`.";
161
162 isNumberOrRangeOf =
163 check: v:
164 if isInt v then
165 check v
166 else
167 let
168 parts = splitString "-" v;
169 lower = toIntBase10 (head parts);
170 upper = if tail parts != [ ] then toIntBase10 (head (tail parts)) else lower;
171 in
172 length parts <= 2 && lower <= upper && check lower && check upper;
173 isPort = i: i >= 0 && i <= 65535;
174 isPortOrPortRange = isNumberOrRangeOf isPort;
175
176 assertPort =
177 name: group: attr:
178 optional (
179 attr ? ${name} && !isPort attr.${name}
180 ) "Error on the systemd ${group} field `${name}': ${attr.name} is not a valid port number.";
181
182 assertPortOrPortRange =
183 name: group: attr:
184 optional (attr ? ${name} && !isPortOrPortRange attr.${name})
185 "Error on the systemd ${group} field `${name}': ${attr.name} is not a valid port number or range of port numbers.";
186
187 assertValueOneOf =
188 name: values: group: attr:
189 optional (
190 attr ? ${name} && !elem attr.${name} values
191 ) "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'.";
192
193 assertValuesSomeOfOr =
194 name: values: default: group: attr:
195 optional (
196 attr ? ${name}
197 && !(all (x: elem x values) (splitString " " attr.${name}) || attr.${name} == default)
198 ) "Systemd ${group} field `${name}' cannot have value `${toString attr.${name}}'.";
199
200 assertHasField =
201 name: group: attr:
202 optional (!(attr ? ${name})) "Systemd ${group} field `${name}' must exist.";
203
204 assertRange =
205 name: min: max: group: attr:
206 optional (
207 attr ? ${name} && !(min <= attr.${name} && max >= attr.${name})
208 ) "Systemd ${group} field `${name}' is outside the range [${toString min},${toString max}]";
209
210 assertRangeOrOneOf =
211 name: min: max: values: group: attr:
212 optional
213 (
214 attr ? ${name}
215 && !(
216 ((isInt attr.${name} || isFloat attr.${name}) && min <= attr.${name} && max >= attr.${name})
217 || elem attr.${name} values
218 )
219 )
220 "Systemd ${group} field `${name}' is not a value in range [${toString min},${toString max}], or one of ${toString values}";
221
222 assertRangeWithOptionalMask =
223 name: min: max: group: attr:
224 if (attr ? ${name}) then
225 if isInt attr.${name} then
226 assertRange name min max group attr
227 else if isString attr.${name} then
228 let
229 fields = match "([0-9]+|0x[0-9a-fA-F]+)(/([0-9]+|0x[0-9a-fA-F]+))?" attr.${name};
230 in
231 if fields == null then
232 [
233 "Systemd ${group} field `${name}' must either be an integer or two integers separated by a slash (/)."
234 ]
235 else
236 let
237 value = toIntBaseDetected (elemAt fields 0);
238 mask = mapNullable toIntBaseDetected (elemAt fields 2);
239 in
240 optional (!(min <= value && max >= value))
241 "Systemd ${group} field `${name}' has main value outside the range [${toString min},${toString max}]."
242 ++ optional (
243 mask != null && !(min <= mask && max >= mask)
244 ) "Systemd ${group} field `${name}' has mask outside the range [${toString min},${toString max}]."
245 else
246 [ "Systemd ${group} field `${name}' must either be an integer or a string." ]
247 else
248 [ ];
249
250 assertMinimum =
251 name: min: group: attr:
252 optional (
253 attr ? ${name} && attr.${name} < min
254 ) "Systemd ${group} field `${name}' must be greater than or equal to ${toString min}";
255
256 assertOnlyFields =
257 fields: group: attr:
258 let
259 badFields = filter (name: !elem name fields) (attrNames attr);
260 in
261 optional (
262 badFields != [ ]
263 ) "Systemd ${group} has extra fields [${concatStringsSep " " badFields}].";
264
265 assertInt =
266 name: group: attr:
267 optional (
268 attr ? ${name} && !isInt attr.${name}
269 ) "Systemd ${group} field `${name}' is not an integer";
270
271 assertRemoved =
272 name: see: group: attr:
273 optional (attr ? ${name}) "Systemd ${group} field `${name}' has been removed. See ${see}";
274
275 assertKeyIsSystemdCredential =
276 name: group: attr:
277 optional (
278 attr ? ${name} && !(hasPrefix "@" attr.${name})
279 ) "Systemd ${group} field `${name}' is not a systemd credential";
280
281 checkUnitConfig =
282 group: checks: attrs:
283 let
284 # We're applied at the top-level type (attrsOf unitOption), so the actual
285 # unit options might contain attributes from mkOverride and mkIf that we need to
286 # convert into single values before checking them.
287 defs = mapAttrs (const (
288 v:
289 if v._type or "" == "override" then
290 v.content
291 else if v._type or "" == "if" then
292 v.content
293 else
294 v
295 )) attrs;
296 errors = concatMap (c: c group defs) checks;
297 in
298 if errors == [ ] then true else trace (concatStringsSep "\n" errors) false;
299
300 checkUnitConfigWithLegacyKey =
301 legacyKey: group: checks: attrs:
302 let
303 dump = lib.generators.toPretty { } (
304 lib.generators.withRecursion {
305 depthLimit = 2;
306 throwOnDepthLimit = false;
307 } attrs
308 );
309 attrs' =
310 if legacyKey == null then
311 attrs
312 else if !attrs ? ${legacyKey} then
313 attrs
314 else if removeAttrs attrs [ legacyKey ] == { } then
315 attrs.${legacyKey}
316 else
317 throw ''
318 The declaration
319
320 ${dump}
321
322 must not mix unit options with the legacy key '${legacyKey}'.
323
324 This can be fixed by moving all settings from within ${legacyKey}
325 one level up.
326 '';
327 in
328 checkUnitConfig group checks attrs';
329
330 toOption =
331 x:
332 if x == true then
333 "true"
334 else if x == false then
335 "false"
336 else
337 toString x;
338
339 attrsToSection =
340 as:
341 concatStrings (
342 concatLists (
343 mapAttrsToList (
344 name: value:
345 map (x: ''
346 ${name}=${toOption x}
347 '') (if isList value then value else [ value ])
348 ) as
349 )
350 );
351
352 generateUnits =
353 {
354 allowCollisions ? true,
355 type,
356 units,
357 upstreamUnits,
358 upstreamWants,
359 packages ? cfg.packages,
360 package ? cfg.package,
361 }:
362 let
363 typeDir =
364 ({
365 system = "system";
366 initrd = "system";
367 user = "user";
368 nspawn = "nspawn";
369 }).${type};
370 in
371 pkgs.runCommand "${type}-units"
372 {
373 preferLocalBuild = true;
374 allowSubstitutes = false;
375 }
376 ''
377 mkdir -p $out
378
379 # Copy the upstream systemd units we're interested in.
380 for i in ${toString upstreamUnits}; do
381 fn=${package}/example/systemd/${typeDir}/$i
382 if ! [ -e $fn ]; then echo "missing $fn"; false; fi
383 if [ -L $fn ]; then
384 target="$(readlink "$fn")"
385 if [ ''${target:0:3} = ../ ]; then
386 ln -s "$(readlink -f "$fn")" $out/
387 else
388 cp -pd $fn $out/
389 fi
390 else
391 ln -s $fn $out/
392 fi
393 done
394
395 # Copy .wants links, but only those that point to units that
396 # we're interested in.
397 for i in ${toString upstreamWants}; do
398 fn=${package}/example/systemd/${typeDir}/$i
399 if ! [ -e $fn ]; then echo "missing $fn"; false; fi
400 x=$out/$(basename $fn)
401 mkdir $x
402 for i in $fn/*; do
403 y=$x/$(basename $i)
404 cp -pd $i $y
405 if ! [ -e $y ]; then rm $y; fi
406 done
407 done
408
409 # Symlink all units provided listed in systemd.packages.
410 packages="${toString packages}"
411
412 # Filter duplicate directories
413 declare -A unique_packages
414 for k in $packages ; do unique_packages[$k]=1 ; done
415
416 for i in ''${!unique_packages[@]}; do
417 for fn in $i/etc/systemd/${typeDir}/* $i/lib/systemd/${typeDir}/*; do
418 if ! [[ "$fn" =~ .wants$ ]]; then
419 if [[ -d "$fn" ]]; then
420 targetDir="$out/$(basename "$fn")"
421 mkdir -p "$targetDir"
422 ${lndir} "$fn" "$targetDir"
423 else
424 ln -s $fn $out/
425 fi
426 fi
427 done
428 done
429
430 # Symlink units defined by systemd.units where override strategy
431 # shall be automatically detected. If these are also provided by
432 # systemd or systemd.packages, then add them as
433 # <unit-name>.d/overrides.conf, which makes them extend the
434 # upstream unit.
435 for i in ${
436 toString (
437 mapAttrsToList (n: v: v.unit) (
438 filterAttrs (
439 n: v: (attrByPath [ "overrideStrategy" ] "asDropinIfExists" v) == "asDropinIfExists"
440 ) units
441 )
442 )
443 }; do
444 fn=$(basename $i/*)
445 if [ -e $out/$fn ]; then
446 if [ "$(readlink -f $i/$fn)" = /dev/null ]; then
447 ln -sfn /dev/null $out/$fn
448 else
449 ${
450 if allowCollisions then
451 ''
452 mkdir -p $out/$fn.d
453 ln -s $i/$fn $out/$fn.d/overrides.conf
454 ''
455 else
456 ''
457 echo "Found multiple derivations configuring $fn!"
458 exit 1
459 ''
460 }
461 fi
462 else
463 ln -fs $i/$fn $out/
464 fi
465 done
466
467 # Symlink units defined by systemd.units which shall be
468 # treated as drop-in file.
469 for i in ${
470 toString (
471 mapAttrsToList (n: v: v.unit) (
472 filterAttrs (n: v: v ? overrideStrategy && v.overrideStrategy == "asDropin") units
473 )
474 )
475 }; do
476 fn=$(basename $i/*)
477 mkdir -p $out/$fn.d
478 ln -s $i/$fn $out/$fn.d/overrides.conf
479 done
480
481 # Create service aliases from aliases option.
482 ${concatStrings (
483 mapAttrsToList (
484 name: unit:
485 concatMapStrings (name2: ''
486 ln -sfn '${name}' $out/'${name2}'
487 '') (unit.aliases or [ ])
488 ) units
489 )}
490
491 # Create .wants, .upholds and .requires symlinks from the wantedBy, upheldBy and
492 # requiredBy options.
493 ${concatStrings (
494 mapAttrsToList (
495 name: unit:
496 concatMapStrings (name2: ''
497 mkdir -p $out/'${name2}.wants'
498 ln -sfn '../${name}' $out/'${name2}.wants'/
499 '') (unit.wantedBy or [ ])
500 ) units
501 )}
502
503 ${concatStrings (
504 mapAttrsToList (
505 name: unit:
506 concatMapStrings (name2: ''
507 mkdir -p $out/'${name2}.upholds'
508 ln -sfn '../${name}' $out/'${name2}.upholds'/
509 '') (unit.upheldBy or [ ])
510 ) units
511 )}
512
513 ${concatStrings (
514 mapAttrsToList (
515 name: unit:
516 concatMapStrings (name2: ''
517 mkdir -p $out/'${name2}.requires'
518 ln -sfn '../${name}' $out/'${name2}.requires'/
519 '') (unit.requiredBy or [ ])
520 ) units
521 )}
522
523 ${optionalString (type == "system") ''
524 # Stupid misc. symlinks.
525 ln -s ${cfg.defaultUnit} $out/default.target
526 ln -s ${cfg.ctrlAltDelUnit} $out/ctrl-alt-del.target
527 ln -s rescue.target $out/kbrequest.target
528
529 ln -s ../remote-fs.target $out/multi-user.target.wants/
530 ''}
531 ''; # */
532
533 makeJobScript =
534 {
535 name,
536 text,
537 enableStrictShellChecks,
538 }:
539 let
540 scriptName = replaceStrings [ "\\" "@" ] [ "-" "_" ] (shellEscape name);
541 out =
542 (
543 if !enableStrictShellChecks then
544 pkgs.writeShellScriptBin scriptName ''
545 set -e
546
547 ${text}
548 ''
549 else
550 pkgs.writeShellApplication {
551 name = scriptName;
552 inherit text;
553 }
554 ).overrideAttrs
555 (_: {
556 # The derivation name is different from the script file name
557 # to keep the script file name short to avoid cluttering logs.
558 name = "unit-script-${scriptName}";
559 });
560 in
561 lib.getExe out;
562
563 unitConfig =
564 {
565 config,
566 name,
567 options,
568 ...
569 }:
570 {
571 config = {
572 unitConfig =
573 optionalAttrs (config.requires != [ ]) { Requires = toString config.requires; }
574 // optionalAttrs (config.wants != [ ]) { Wants = toString config.wants; }
575 // optionalAttrs (config.upholds != [ ]) { Upholds = toString config.upholds; }
576 // optionalAttrs (config.after != [ ]) { After = toString config.after; }
577 // optionalAttrs (config.before != [ ]) { Before = toString config.before; }
578 // optionalAttrs (config.bindsTo != [ ]) { BindsTo = toString config.bindsTo; }
579 // optionalAttrs (config.partOf != [ ]) { PartOf = toString config.partOf; }
580 // optionalAttrs (config.conflicts != [ ]) { Conflicts = toString config.conflicts; }
581 // optionalAttrs (config.requisite != [ ]) { Requisite = toString config.requisite; }
582 // optionalAttrs (config ? restartTriggers && config.restartTriggers != [ ]) {
583 X-Restart-Triggers = "${pkgs.writeText "X-Restart-Triggers-${name}" (
584 pipe config.restartTriggers [
585 flatten
586 (map (x: if isPath x then "${x}" else x))
587 toString
588 ]
589 )}";
590 }
591 // optionalAttrs (config ? reloadTriggers && config.reloadTriggers != [ ]) {
592 X-Reload-Triggers = "${pkgs.writeText "X-Reload-Triggers-${name}" (
593 pipe config.reloadTriggers [
594 flatten
595 (map (x: if isPath x then "${x}" else x))
596 toString
597 ]
598 )}";
599 }
600 // optionalAttrs (config.description != "") {
601 Description = config.description;
602 }
603 // optionalAttrs (config.documentation != [ ]) {
604 Documentation = toString config.documentation;
605 }
606 // optionalAttrs (config.onFailure != [ ]) {
607 OnFailure = toString config.onFailure;
608 }
609 // optionalAttrs (config.onSuccess != [ ]) {
610 OnSuccess = toString config.onSuccess;
611 }
612 // optionalAttrs (options.startLimitIntervalSec.isDefined) {
613 StartLimitIntervalSec = toString config.startLimitIntervalSec;
614 }
615 // optionalAttrs (options.startLimitBurst.isDefined) {
616 StartLimitBurst = toString config.startLimitBurst;
617 };
618 };
619 };
620
621 serviceConfig =
622 let
623 nixosConfig = config;
624 in
625 {
626 name,
627 lib,
628 config,
629 ...
630 }:
631 {
632 config = {
633 name = "${name}.service";
634 environment.PATH =
635 mkIf (config.path != [ ])
636 "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
637
638 enableStrictShellChecks = lib.mkOptionDefault nixosConfig.systemd.enableStrictShellChecks;
639 };
640 };
641
642 pathConfig =
643 { name, config, ... }:
644 {
645 config = {
646 name = "${name}.path";
647 };
648 };
649
650 socketConfig =
651 { name, config, ... }:
652 {
653 config = {
654 name = "${name}.socket";
655 };
656 };
657
658 sliceConfig =
659 { name, config, ... }:
660 {
661 config = {
662 name = "${name}.slice";
663 };
664 };
665
666 targetConfig =
667 { name, config, ... }:
668 {
669 config = {
670 name = "${name}.target";
671 };
672 };
673
674 timerConfig =
675 { name, config, ... }:
676 {
677 config = {
678 name = "${name}.timer";
679 };
680 };
681
682 stage2ServiceConfig = {
683 imports = [ serviceConfig ];
684 # Default path for systemd services. Should be quite minimal.
685 config.path = mkAfter [
686 pkgs.coreutils
687 pkgs.findutils
688 pkgs.gnugrep
689 pkgs.gnused
690 systemd
691 ];
692 };
693
694 stage1ServiceConfig = serviceConfig;
695
696 mountConfig =
697 { config, ... }:
698 {
699 config = {
700 name = "${utils.escapeSystemdPath config.where}.mount";
701 mountConfig = {
702 What = config.what;
703 Where = config.where;
704 }
705 // optionalAttrs (config.type != "") {
706 Type = config.type;
707 }
708 // optionalAttrs (config.options != "") {
709 Options = config.options;
710 };
711 };
712 };
713
714 automountConfig =
715 { config, ... }:
716 {
717 config = {
718 name = "${utils.escapeSystemdPath config.where}.automount";
719 automountConfig = {
720 Where = config.where;
721 };
722 };
723 };
724
725 commonUnitText =
726 def: lines:
727 ''
728 [Unit]
729 ${attrsToSection def.unitConfig}
730 ''
731 + lines
732 + optionalString (def.wantedBy != [ ]) ''
733
734 [Install]
735 WantedBy=${concatStringsSep " " def.wantedBy}
736 '';
737
738 targetToUnit = def: {
739 inherit (def)
740 name
741 aliases
742 wantedBy
743 requiredBy
744 upheldBy
745 enable
746 overrideStrategy
747 ;
748 text = ''
749 [Unit]
750 ${attrsToSection def.unitConfig}
751 '';
752 };
753
754 serviceToUnit = def: {
755 inherit (def)
756 name
757 aliases
758 wantedBy
759 requiredBy
760 upheldBy
761 enable
762 overrideStrategy
763 ;
764 text = commonUnitText def (
765 ''
766 [Service]
767 ''
768 + (
769 let
770 env = cfg.globalEnvironment // def.environment;
771 in
772 concatMapStrings (
773 n:
774 let
775 s = optionalString (env.${n} != null) "Environment=${toJSON "${n}=${env.${n}}"}\n";
776 # systemd max line length is now 1MiB
777 # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
778 in
779 if stringLength s >= 1048576 then
780 throw "The value of the environment variable ‘${n}’ in systemd service ‘${def.name}.service’ is too long."
781 else
782 s
783 ) (attrNames env)
784 )
785 + (
786 if def ? reloadIfChanged && def.reloadIfChanged then
787 ''
788 X-ReloadIfChanged=true
789 ''
790 else if (def ? restartIfChanged && !def.restartIfChanged) then
791 ''
792 X-RestartIfChanged=false
793 ''
794 else
795 ""
796 )
797 + optionalString (def ? stopIfChanged && !def.stopIfChanged) ''
798 X-StopIfChanged=false
799 ''
800 + optionalString (def ? notSocketActivated && def.notSocketActivated) ''
801 X-NotSocketActivated=true
802 ''
803 + attrsToSection def.serviceConfig
804 );
805 };
806
807 socketToUnit = def: {
808 inherit (def)
809 name
810 aliases
811 wantedBy
812 requiredBy
813 upheldBy
814 enable
815 overrideStrategy
816 ;
817 text = commonUnitText def ''
818 [Socket]
819 ${attrsToSection def.socketConfig}
820 ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
821 ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)}
822 '';
823 };
824
825 timerToUnit = def: {
826 inherit (def)
827 name
828 aliases
829 wantedBy
830 requiredBy
831 upheldBy
832 enable
833 overrideStrategy
834 ;
835 text = commonUnitText def ''
836 [Timer]
837 ${attrsToSection def.timerConfig}
838 '';
839 };
840
841 pathToUnit = def: {
842 inherit (def)
843 name
844 aliases
845 wantedBy
846 requiredBy
847 upheldBy
848 enable
849 overrideStrategy
850 ;
851 text = commonUnitText def ''
852 [Path]
853 ${attrsToSection def.pathConfig}
854 '';
855 };
856
857 mountToUnit = def: {
858 inherit (def)
859 name
860 aliases
861 wantedBy
862 requiredBy
863 upheldBy
864 enable
865 overrideStrategy
866 ;
867 text = commonUnitText def ''
868 [Mount]
869 ${attrsToSection def.mountConfig}
870 '';
871 };
872
873 automountToUnit = def: {
874 inherit (def)
875 name
876 aliases
877 wantedBy
878 requiredBy
879 upheldBy
880 enable
881 overrideStrategy
882 ;
883 text = commonUnitText def ''
884 [Automount]
885 ${attrsToSection def.automountConfig}
886 '';
887 };
888
889 sliceToUnit = def: {
890 inherit (def)
891 name
892 aliases
893 wantedBy
894 requiredBy
895 upheldBy
896 enable
897 overrideStrategy
898 ;
899 text = commonUnitText def ''
900 [Slice]
901 ${attrsToSection def.sliceConfig}
902 '';
903 };
904
905 # Create a directory that contains systemd definition files from an attrset
906 # that contains the file names as keys and the content as values. The values
907 # in that attrset are determined by the supplied format.
908 definitions =
909 directoryName: format: definitionAttrs:
910 let
911 listOfDefinitions = mapAttrsToList (name: format.generate "${name}.conf") definitionAttrs;
912 in
913 pkgs.runCommand directoryName { } ''
914 mkdir -p $out
915 ${(concatStringsSep "\n" (map (pkg: "cp ${pkg} $out/${pkg.name}") listOfDefinitions))}
916 '';
917
918 # The maximum number of characters allowed in a GPT partition label. This
919 # limit is specified by UEFI and enforced by systemd-repart.
920 # Corresponds to GPT_LABEL_MAX from systemd's gpt.h.
921 GPTMaxLabelLength = 36;
922
923}