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