at master 24 kB view raw
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}