at 25.11-pre 25 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 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}