Clone of https://github.com/NixOS/nixpkgs.git (to stress-test knotserver)
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}