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
528 ln -s ../remote-fs.target $out/multi-user.target.wants/
529 ''}
530 ''; # */
531
532 makeJobScript =
533 {
534 name,
535 text,
536 enableStrictShellChecks,
537 }:
538 let
539 scriptName = replaceStrings [ "\\" "@" ] [ "-" "_" ] (shellEscape name);
540 out =
541 (
542 if !enableStrictShellChecks then
543 pkgs.writeShellScriptBin scriptName ''
544 set -e
545
546 ${text}
547 ''
548 else
549 pkgs.writeShellApplication {
550 name = scriptName;
551 inherit text;
552 }
553 ).overrideAttrs
554 (_: {
555 # The derivation name is different from the script file name
556 # to keep the script file name short to avoid cluttering logs.
557 name = "unit-script-${scriptName}";
558 });
559 in
560 lib.getExe out;
561
562 unitConfig =
563 {
564 config,
565 name,
566 options,
567 ...
568 }:
569 {
570 config = {
571 unitConfig =
572 optionalAttrs (config.requires != [ ]) { Requires = toString config.requires; }
573 // optionalAttrs (config.wants != [ ]) { Wants = toString config.wants; }
574 // optionalAttrs (config.upholds != [ ]) { Upholds = toString config.upholds; }
575 // optionalAttrs (config.after != [ ]) { After = toString config.after; }
576 // optionalAttrs (config.before != [ ]) { Before = toString config.before; }
577 // optionalAttrs (config.bindsTo != [ ]) { BindsTo = toString config.bindsTo; }
578 // optionalAttrs (config.partOf != [ ]) { PartOf = toString config.partOf; }
579 // optionalAttrs (config.conflicts != [ ]) { Conflicts = toString config.conflicts; }
580 // optionalAttrs (config.requisite != [ ]) { Requisite = toString config.requisite; }
581 // optionalAttrs (config ? restartTriggers && config.restartTriggers != [ ]) {
582 X-Restart-Triggers = "${pkgs.writeText "X-Restart-Triggers-${name}" (
583 pipe config.restartTriggers [
584 flatten
585 (map (x: if isPath x then "${x}" else x))
586 toString
587 ]
588 )}";
589 }
590 // optionalAttrs (config ? reloadTriggers && config.reloadTriggers != [ ]) {
591 X-Reload-Triggers = "${pkgs.writeText "X-Reload-Triggers-${name}" (
592 pipe config.reloadTriggers [
593 flatten
594 (map (x: if isPath x then "${x}" else x))
595 toString
596 ]
597 )}";
598 }
599 // optionalAttrs (config.description != "") {
600 Description = config.description;
601 }
602 // optionalAttrs (config.documentation != [ ]) {
603 Documentation = toString config.documentation;
604 }
605 // optionalAttrs (config.onFailure != [ ]) {
606 OnFailure = toString config.onFailure;
607 }
608 // optionalAttrs (config.onSuccess != [ ]) {
609 OnSuccess = toString config.onSuccess;
610 }
611 // optionalAttrs (options.startLimitIntervalSec.isDefined) {
612 StartLimitIntervalSec = toString config.startLimitIntervalSec;
613 }
614 // optionalAttrs (options.startLimitBurst.isDefined) {
615 StartLimitBurst = toString config.startLimitBurst;
616 };
617 };
618 };
619
620 serviceConfig =
621 let
622 nixosConfig = config;
623 in
624 {
625 name,
626 lib,
627 config,
628 ...
629 }:
630 {
631 config = {
632 name = "${name}.service";
633 environment.PATH =
634 mkIf (config.path != [ ])
635 "${makeBinPath config.path}:${makeSearchPathOutput "bin" "sbin" config.path}";
636
637 enableStrictShellChecks = lib.mkOptionDefault nixosConfig.systemd.enableStrictShellChecks;
638 };
639 };
640
641 pathConfig =
642 { name, config, ... }:
643 {
644 config = {
645 name = "${name}.path";
646 };
647 };
648
649 socketConfig =
650 { name, config, ... }:
651 {
652 config = {
653 name = "${name}.socket";
654 };
655 };
656
657 sliceConfig =
658 { name, config, ... }:
659 {
660 config = {
661 name = "${name}.slice";
662 };
663 };
664
665 targetConfig =
666 { name, config, ... }:
667 {
668 config = {
669 name = "${name}.target";
670 };
671 };
672
673 timerConfig =
674 { name, config, ... }:
675 {
676 config = {
677 name = "${name}.timer";
678 };
679 };
680
681 stage2ServiceConfig = {
682 imports = [ serviceConfig ];
683 # Default path for systemd services. Should be quite minimal.
684 config.path = mkAfter [
685 pkgs.coreutils
686 pkgs.findutils
687 pkgs.gnugrep
688 pkgs.gnused
689 systemd
690 ];
691 };
692
693 stage1ServiceConfig = serviceConfig;
694
695 mountConfig =
696 { config, ... }:
697 {
698 config = {
699 name = "${utils.escapeSystemdPath config.where}.mount";
700 mountConfig = {
701 What = config.what;
702 Where = config.where;
703 }
704 // optionalAttrs (config.type != "") {
705 Type = config.type;
706 }
707 // optionalAttrs (config.options != "") {
708 Options = config.options;
709 };
710 };
711 };
712
713 automountConfig =
714 { config, ... }:
715 {
716 config = {
717 name = "${utils.escapeSystemdPath config.where}.automount";
718 automountConfig = {
719 Where = config.where;
720 };
721 };
722 };
723
724 commonUnitText =
725 def: lines:
726 ''
727 [Unit]
728 ${attrsToSection def.unitConfig}
729 ''
730 + lines
731 + optionalString (def.wantedBy != [ ]) ''
732
733 [Install]
734 WantedBy=${concatStringsSep " " def.wantedBy}
735 '';
736
737 targetToUnit = def: {
738 inherit (def)
739 name
740 aliases
741 wantedBy
742 requiredBy
743 upheldBy
744 enable
745 overrideStrategy
746 ;
747 text = ''
748 [Unit]
749 ${attrsToSection def.unitConfig}
750 '';
751 };
752
753 serviceToUnit = def: {
754 inherit (def)
755 name
756 aliases
757 wantedBy
758 requiredBy
759 upheldBy
760 enable
761 overrideStrategy
762 ;
763 text = commonUnitText def (
764 ''
765 [Service]
766 ''
767 + (
768 let
769 env = cfg.globalEnvironment // def.environment;
770 in
771 concatMapStrings (
772 n:
773 let
774 s = optionalString (env.${n} != null) "Environment=${toJSON "${n}=${env.${n}}"}\n";
775 # systemd max line length is now 1MiB
776 # https://github.com/systemd/systemd/commit/e6dde451a51dc5aaa7f4d98d39b8fe735f73d2af
777 in
778 if stringLength s >= 1048576 then
779 throw "The value of the environment variable ‘${n}’ in systemd service ‘${def.name}.service’ is too long."
780 else
781 s
782 ) (attrNames env)
783 )
784 + (
785 if def ? reloadIfChanged && def.reloadIfChanged then
786 ''
787 X-ReloadIfChanged=true
788 ''
789 else if (def ? restartIfChanged && !def.restartIfChanged) then
790 ''
791 X-RestartIfChanged=false
792 ''
793 else
794 ""
795 )
796 + optionalString (def ? stopIfChanged && !def.stopIfChanged) ''
797 X-StopIfChanged=false
798 ''
799 + optionalString (def ? notSocketActivated && def.notSocketActivated) ''
800 X-NotSocketActivated=true
801 ''
802 + attrsToSection def.serviceConfig
803 );
804 };
805
806 socketToUnit = def: {
807 inherit (def)
808 name
809 aliases
810 wantedBy
811 requiredBy
812 upheldBy
813 enable
814 overrideStrategy
815 ;
816 text = commonUnitText def ''
817 [Socket]
818 ${attrsToSection def.socketConfig}
819 ${concatStringsSep "\n" (map (s: "ListenStream=${s}") def.listenStreams)}
820 ${concatStringsSep "\n" (map (s: "ListenDatagram=${s}") def.listenDatagrams)}
821 '';
822 };
823
824 timerToUnit = def: {
825 inherit (def)
826 name
827 aliases
828 wantedBy
829 requiredBy
830 upheldBy
831 enable
832 overrideStrategy
833 ;
834 text = commonUnitText def ''
835 [Timer]
836 ${attrsToSection def.timerConfig}
837 '';
838 };
839
840 pathToUnit = def: {
841 inherit (def)
842 name
843 aliases
844 wantedBy
845 requiredBy
846 upheldBy
847 enable
848 overrideStrategy
849 ;
850 text = commonUnitText def ''
851 [Path]
852 ${attrsToSection def.pathConfig}
853 '';
854 };
855
856 mountToUnit = def: {
857 inherit (def)
858 name
859 aliases
860 wantedBy
861 requiredBy
862 upheldBy
863 enable
864 overrideStrategy
865 ;
866 text = commonUnitText def ''
867 [Mount]
868 ${attrsToSection def.mountConfig}
869 '';
870 };
871
872 automountToUnit = def: {
873 inherit (def)
874 name
875 aliases
876 wantedBy
877 requiredBy
878 upheldBy
879 enable
880 overrideStrategy
881 ;
882 text = commonUnitText def ''
883 [Automount]
884 ${attrsToSection def.automountConfig}
885 '';
886 };
887
888 sliceToUnit = def: {
889 inherit (def)
890 name
891 aliases
892 wantedBy
893 requiredBy
894 upheldBy
895 enable
896 overrideStrategy
897 ;
898 text = commonUnitText def ''
899 [Slice]
900 ${attrsToSection def.sliceConfig}
901 '';
902 };
903
904 # Create a directory that contains systemd definition files from an attrset
905 # that contains the file names as keys and the content as values. The values
906 # in that attrset are determined by the supplied format.
907 definitions =
908 directoryName: format: definitionAttrs:
909 let
910 listOfDefinitions = mapAttrsToList (name: format.generate "${name}.conf") definitionAttrs;
911 in
912 pkgs.runCommand directoryName { } ''
913 mkdir -p $out
914 ${(concatStringsSep "\n" (map (pkg: "cp ${pkg} $out/${pkg.name}") listOfDefinitions))}
915 '';
916
917 # The maximum number of characters allowed in a GPT partition label. This
918 # limit is specified by UEFI and enforced by systemd-repart.
919 # Corresponds to GPT_LABEL_MAX from systemd's gpt.h.
920 GPTMaxLabelLength = 36;
921
922}